diff --git a/deps/curl/COPYING b/deps/curl/COPYING index d1eab3eb932..d9e7e0bef3e 100644 --- a/deps/curl/COPYING +++ b/deps/curl/COPYING @@ -1,6 +1,6 @@ COPYRIGHT AND PERMISSION NOTICE -Copyright (c) 1996 - 2023, Daniel Stenberg, , and many +Copyright (c) 1996 - 2024, Daniel Stenberg, , and many contributors, see the THANKS file. All rights reserved. diff --git a/deps/curl/curl.gyp b/deps/curl/curl.gyp index 9bc4649dd66..9b4326f2a6a 100644 --- a/deps/curl/curl.gyp +++ b/deps/curl/curl.gyp @@ -29,14 +29,14 @@ 'lib/curl_memrchr.c', 'lib/curl_multibyte.c', 'lib/curl_ntlm_core.c', - 'lib/curl_ntlm_wb.c', - 'lib/curl_path.c', 'lib/curl_range.c', 'lib/curl_rtmp.c', 'lib/curl_sasl.c', + 'lib/curl_sha512_256.c', 'lib/curl_sspi.c', 'lib/curl_trc.c', 'lib/curl_threads.c', + 'lib/cw-out.c', 'lib/dict.c', 'lib/doh.c', 'lib/dynbuf.c', @@ -97,6 +97,7 @@ 'lib/psl.c', 'lib/rand.c', 'lib/rename.c', + 'lib/request.c', 'lib/rtsp.c', 'lib/select.c', 'lib/sendf.c', @@ -192,6 +193,7 @@ 'CURL_DISABLE_TELNET', 'CURL_DISABLE_TFTP', 'CURL_DISABLE_VERBOSE_STRINGS', + 'CURL_DISABLE_WEBSOCKETS', "USE_OPENSSL=1", ], 'dependencies': [ @@ -362,7 +364,7 @@ "HAVE_WRITABLE_ARGV=1", "HAVE_WRITEV=1", "HAVE_ZLIB_H=1", - "OS=\"\"", + "CURL_OS=\"\"", "RANDOM_FILE=\"/dev/urandom\"", "RECV_TYPE_ARG1=int", "RECV_TYPE_ARG2=void *", @@ -523,7 +525,7 @@ "HAVE_WRITABLE_ARGV=1", "HAVE_WRITEV=1", "HAVE_ZLIB_H=1", - "OS=\"\"", + "CURL_OS=\"\"", "RECV_TYPE_ARG1=int", "RECV_TYPE_ARG2=void *", "RECV_TYPE_ARG3=size_t", diff --git a/deps/curl/include/Makefile.in b/deps/curl/include/Makefile.in index e7d2255ee0e..f02b8109cba 100644 --- a/deps/curl/include/Makefile.in +++ b/deps/curl/include/Makefile.in @@ -109,7 +109,6 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/curl-amissl.m4 \ $(top_srcdir)/m4/xc-am-iface.m4 \ $(top_srcdir)/m4/xc-cc-check.m4 \ $(top_srcdir)/m4/xc-lt-iface.m4 \ - $(top_srcdir)/m4/xc-translit.m4 \ $(top_srcdir)/m4/xc-val-flgs.m4 \ $(top_srcdir)/m4/zz40-xc-ovr.m4 \ $(top_srcdir)/m4/zz50-xc-ovr.m4 \ @@ -129,11 +128,11 @@ am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; -am__v_GEN_1 = +am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ -am__v_at_1 = +am__v_at_1 = depcomp = am__maybe_remake_depfiles = SOURCES = @@ -225,18 +224,20 @@ CFLAG_CURL_SYMBOL_HIDING = @CFLAG_CURL_SYMBOL_HIDING@ CONFIGURE_OPTIONS = @CONFIGURE_OPTIONS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ -CPPFLAG_CURL_STATICLIB = @CPPFLAG_CURL_STATICLIB@ CSCOPE = @CSCOPE@ CTAGS = @CTAGS@ CURLVERSION = @CURLVERSION@ CURL_CA_BUNDLE = @CURL_CA_BUNDLE@ +CURL_CA_EMBED = @CURL_CA_EMBED@ CURL_CFLAG_EXTRAS = @CURL_CFLAG_EXTRAS@ +CURL_CPP = @CURL_CPP@ CURL_DISABLE_DICT = @CURL_DISABLE_DICT@ CURL_DISABLE_FILE = @CURL_DISABLE_FILE@ CURL_DISABLE_FTP = @CURL_DISABLE_FTP@ CURL_DISABLE_GOPHER = @CURL_DISABLE_GOPHER@ CURL_DISABLE_HTTP = @CURL_DISABLE_HTTP@ CURL_DISABLE_IMAP = @CURL_DISABLE_IMAP@ +CURL_DISABLE_IPFS = @CURL_DISABLE_IPFS@ CURL_DISABLE_LDAP = @CURL_DISABLE_LDAP@ CURL_DISABLE_LDAPS = @CURL_DISABLE_LDAPS@ CURL_DISABLE_MQTT = @CURL_DISABLE_MQTT@ @@ -247,10 +248,11 @@ CURL_DISABLE_SMB = @CURL_DISABLE_SMB@ CURL_DISABLE_SMTP = @CURL_DISABLE_SMTP@ CURL_DISABLE_TELNET = @CURL_DISABLE_TELNET@ CURL_DISABLE_TFTP = @CURL_DISABLE_TFTP@ -CURL_LT_SHLIB_VERSIONED_FLAVOUR = @CURL_LT_SHLIB_VERSIONED_FLAVOUR@ +CURL_DISABLE_WEBSOCKETS = @CURL_DISABLE_WEBSOCKETS@ +CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX = @CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX@ +CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME = @CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME@ CURL_NETWORK_AND_TIME_LIBS = @CURL_NETWORK_AND_TIME_LIBS@ CURL_NETWORK_LIBS = @CURL_NETWORK_LIBS@ -CURL_PLIST_VERSION = @CURL_PLIST_VERSION@ CURL_WITH_MULTI_SSL = @CURL_WITH_MULTI_SSL@ CYGPATH_W = @CYGPATH_W@ DEFAULT_SSL_BACKEND = @DEFAULT_SSL_BACKEND@ @@ -291,8 +293,12 @@ IPV6_ENABLED = @IPV6_ENABLED@ LCOV = @LCOV@ LD = @LD@ LDFLAGS = @LDFLAGS@ -LIBCURL_LIBS = @LIBCURL_LIBS@ -LIBCURL_NO_SHARED = @LIBCURL_NO_SHARED@ +LIBCURL_PC_CFLAGS = @LIBCURL_PC_CFLAGS@ +LIBCURL_PC_CFLAGS_PRIVATE = @LIBCURL_PC_CFLAGS_PRIVATE@ +LIBCURL_PC_LIBS = @LIBCURL_PC_LIBS@ +LIBCURL_PC_LIBS_PRIVATE = @LIBCURL_PC_LIBS_PRIVATE@ +LIBCURL_PC_REQUIRES = @LIBCURL_PC_REQUIRES@ +LIBCURL_PC_REQUIRES_PRIVATE = @LIBCURL_PC_REQUIRES_PRIVATE@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBTOOL = @LIBTOOL@ @@ -303,11 +309,9 @@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ -MANOPT = @MANOPT@ MKDIR_P = @MKDIR_P@ NM = @NM@ NMEDIT = @NMEDIT@ -NROFF = @NROFF@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ @@ -325,10 +329,8 @@ PKGADD_NAME = @PKGADD_NAME@ PKGADD_PKG = @PKGADD_PKG@ PKGADD_VENDOR = @PKGADD_VENDOR@ PKGCONFIG = @PKGCONFIG@ -RANDOM_FILE = @RANDOM_FILE@ RANLIB = @RANLIB@ RC = @RC@ -REQUIRE_LIB_DEPS = @REQUIRE_LIB_DEPS@ SED = @SED@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ @@ -339,22 +341,29 @@ STRIP = @STRIP@ SUPPORT_FEATURES = @SUPPORT_FEATURES@ SUPPORT_PROTOCOLS = @SUPPORT_PROTOCOLS@ TEST_NGHTTPX = @TEST_NGHTTPX@ +USE_APPLE_IDN = @USE_APPLE_IDN@ USE_ARES = @USE_ARES@ USE_BEARSSL = @USE_BEARSSL@ USE_GNUTLS = @USE_GNUTLS@ USE_HYPER = @USE_HYPER@ +USE_LIBPSL = @USE_LIBPSL@ USE_LIBRTMP = @USE_LIBRTMP@ USE_LIBSSH = @USE_LIBSSH@ USE_LIBSSH2 = @USE_LIBSSH2@ +USE_LIBUV = @USE_LIBUV@ USE_MBEDTLS = @USE_MBEDTLS@ USE_MSH3 = @USE_MSH3@ USE_NGHTTP2 = @USE_NGHTTP2@ USE_NGHTTP3 = @USE_NGHTTP3@ USE_NGTCP2 = @USE_NGTCP2@ +USE_NGTCP2_CRYPTO_BORINGSSL = @USE_NGTCP2_CRYPTO_BORINGSSL@ USE_NGTCP2_CRYPTO_GNUTLS = @USE_NGTCP2_CRYPTO_GNUTLS@ USE_NGTCP2_CRYPTO_QUICTLS = @USE_NGTCP2_CRYPTO_QUICTLS@ USE_NGTCP2_CRYPTO_WOLFSSL = @USE_NGTCP2_CRYPTO_WOLFSSL@ +USE_NGTCP2_H3 = @USE_NGTCP2_H3@ USE_OPENLDAP = @USE_OPENLDAP@ +USE_OPENSSL_H3 = @USE_OPENSSL_H3@ +USE_OPENSSL_QUIC = @USE_OPENSSL_QUIC@ USE_QUICHE = @USE_QUICHE@ USE_RUSTLS = @USE_RUSTLS@ USE_SCHANNEL = @USE_SCHANNEL@ @@ -368,6 +377,7 @@ USE_WOLFSSH = @USE_WOLFSSH@ USE_WOLFSSL = @USE_WOLFSSL@ VERSION = @VERSION@ VERSIONNUM = @VERSIONNUM@ +VSFTPD = @VSFTPD@ ZLIB_LIBS = @ZLIB_LIBS@ ZSH_FUNCTIONS_DIR = @ZSH_FUNCTIONS_DIR@ abs_builddir = @abs_builddir@ diff --git a/deps/curl/include/README.md b/deps/curl/include/README.md index c96593263f7..b155d8c13b9 100644 --- a/deps/curl/include/README.md +++ b/deps/curl/include/README.md @@ -8,7 +8,7 @@ SPDX-License-Identifier: curl Public include files for libcurl, external users. -They're all placed in the curl subdirectory here for better fit in any kind of +They are all placed in the curl subdirectory here for better fit in any kind of environment. You must include files from here using... #include diff --git a/deps/curl/include/curl/Makefile.am b/deps/curl/include/curl/Makefile.am index a655aff1c8a..8864d60e1f2 100644 --- a/deps/curl/include/curl/Makefile.am +++ b/deps/curl/include/curl/Makefile.am @@ -35,7 +35,7 @@ CS_ = $(CS_0) checksrc: $(CHECKSRC)@PERL@ $(top_srcdir)/scripts/checksrc.pl -D$(top_srcdir)/include/curl $(pkginclude_HEADERS) -if CURLDEBUG +if DEBUGBUILD # for debug builds, we scan the sources on all regular make invokes all-local: checksrc endif diff --git a/deps/curl/include/curl/Makefile.in b/deps/curl/include/curl/Makefile.in index b99c8ff09f5..ac2ddbf517e 100644 --- a/deps/curl/include/curl/Makefile.in +++ b/deps/curl/include/curl/Makefile.in @@ -109,7 +109,6 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/curl-amissl.m4 \ $(top_srcdir)/m4/xc-am-iface.m4 \ $(top_srcdir)/m4/xc-cc-check.m4 \ $(top_srcdir)/m4/xc-lt-iface.m4 \ - $(top_srcdir)/m4/xc-translit.m4 \ $(top_srcdir)/m4/xc-val-flgs.m4 \ $(top_srcdir)/m4/zz40-xc-ovr.m4 \ $(top_srcdir)/m4/zz50-xc-ovr.m4 \ @@ -130,11 +129,11 @@ am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; -am__v_GEN_1 = +am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ -am__v_at_1 = +am__v_at_1 = SOURCES = DIST_SOURCES = am__can_run_installinfo = \ @@ -212,18 +211,20 @@ CFLAG_CURL_SYMBOL_HIDING = @CFLAG_CURL_SYMBOL_HIDING@ CONFIGURE_OPTIONS = @CONFIGURE_OPTIONS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ -CPPFLAG_CURL_STATICLIB = @CPPFLAG_CURL_STATICLIB@ CSCOPE = @CSCOPE@ CTAGS = @CTAGS@ CURLVERSION = @CURLVERSION@ CURL_CA_BUNDLE = @CURL_CA_BUNDLE@ +CURL_CA_EMBED = @CURL_CA_EMBED@ CURL_CFLAG_EXTRAS = @CURL_CFLAG_EXTRAS@ +CURL_CPP = @CURL_CPP@ CURL_DISABLE_DICT = @CURL_DISABLE_DICT@ CURL_DISABLE_FILE = @CURL_DISABLE_FILE@ CURL_DISABLE_FTP = @CURL_DISABLE_FTP@ CURL_DISABLE_GOPHER = @CURL_DISABLE_GOPHER@ CURL_DISABLE_HTTP = @CURL_DISABLE_HTTP@ CURL_DISABLE_IMAP = @CURL_DISABLE_IMAP@ +CURL_DISABLE_IPFS = @CURL_DISABLE_IPFS@ CURL_DISABLE_LDAP = @CURL_DISABLE_LDAP@ CURL_DISABLE_LDAPS = @CURL_DISABLE_LDAPS@ CURL_DISABLE_MQTT = @CURL_DISABLE_MQTT@ @@ -234,10 +235,11 @@ CURL_DISABLE_SMB = @CURL_DISABLE_SMB@ CURL_DISABLE_SMTP = @CURL_DISABLE_SMTP@ CURL_DISABLE_TELNET = @CURL_DISABLE_TELNET@ CURL_DISABLE_TFTP = @CURL_DISABLE_TFTP@ -CURL_LT_SHLIB_VERSIONED_FLAVOUR = @CURL_LT_SHLIB_VERSIONED_FLAVOUR@ +CURL_DISABLE_WEBSOCKETS = @CURL_DISABLE_WEBSOCKETS@ +CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX = @CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX@ +CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME = @CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME@ CURL_NETWORK_AND_TIME_LIBS = @CURL_NETWORK_AND_TIME_LIBS@ CURL_NETWORK_LIBS = @CURL_NETWORK_LIBS@ -CURL_PLIST_VERSION = @CURL_PLIST_VERSION@ CURL_WITH_MULTI_SSL = @CURL_WITH_MULTI_SSL@ CYGPATH_W = @CYGPATH_W@ DEFAULT_SSL_BACKEND = @DEFAULT_SSL_BACKEND@ @@ -278,8 +280,12 @@ IPV6_ENABLED = @IPV6_ENABLED@ LCOV = @LCOV@ LD = @LD@ LDFLAGS = @LDFLAGS@ -LIBCURL_LIBS = @LIBCURL_LIBS@ -LIBCURL_NO_SHARED = @LIBCURL_NO_SHARED@ +LIBCURL_PC_CFLAGS = @LIBCURL_PC_CFLAGS@ +LIBCURL_PC_CFLAGS_PRIVATE = @LIBCURL_PC_CFLAGS_PRIVATE@ +LIBCURL_PC_LIBS = @LIBCURL_PC_LIBS@ +LIBCURL_PC_LIBS_PRIVATE = @LIBCURL_PC_LIBS_PRIVATE@ +LIBCURL_PC_REQUIRES = @LIBCURL_PC_REQUIRES@ +LIBCURL_PC_REQUIRES_PRIVATE = @LIBCURL_PC_REQUIRES_PRIVATE@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBTOOL = @LIBTOOL@ @@ -290,11 +296,9 @@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ -MANOPT = @MANOPT@ MKDIR_P = @MKDIR_P@ NM = @NM@ NMEDIT = @NMEDIT@ -NROFF = @NROFF@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ @@ -312,10 +316,8 @@ PKGADD_NAME = @PKGADD_NAME@ PKGADD_PKG = @PKGADD_PKG@ PKGADD_VENDOR = @PKGADD_VENDOR@ PKGCONFIG = @PKGCONFIG@ -RANDOM_FILE = @RANDOM_FILE@ RANLIB = @RANLIB@ RC = @RC@ -REQUIRE_LIB_DEPS = @REQUIRE_LIB_DEPS@ SED = @SED@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ @@ -326,22 +328,29 @@ STRIP = @STRIP@ SUPPORT_FEATURES = @SUPPORT_FEATURES@ SUPPORT_PROTOCOLS = @SUPPORT_PROTOCOLS@ TEST_NGHTTPX = @TEST_NGHTTPX@ +USE_APPLE_IDN = @USE_APPLE_IDN@ USE_ARES = @USE_ARES@ USE_BEARSSL = @USE_BEARSSL@ USE_GNUTLS = @USE_GNUTLS@ USE_HYPER = @USE_HYPER@ +USE_LIBPSL = @USE_LIBPSL@ USE_LIBRTMP = @USE_LIBRTMP@ USE_LIBSSH = @USE_LIBSSH@ USE_LIBSSH2 = @USE_LIBSSH2@ +USE_LIBUV = @USE_LIBUV@ USE_MBEDTLS = @USE_MBEDTLS@ USE_MSH3 = @USE_MSH3@ USE_NGHTTP2 = @USE_NGHTTP2@ USE_NGHTTP3 = @USE_NGHTTP3@ USE_NGTCP2 = @USE_NGTCP2@ +USE_NGTCP2_CRYPTO_BORINGSSL = @USE_NGTCP2_CRYPTO_BORINGSSL@ USE_NGTCP2_CRYPTO_GNUTLS = @USE_NGTCP2_CRYPTO_GNUTLS@ USE_NGTCP2_CRYPTO_QUICTLS = @USE_NGTCP2_CRYPTO_QUICTLS@ USE_NGTCP2_CRYPTO_WOLFSSL = @USE_NGTCP2_CRYPTO_WOLFSSL@ +USE_NGTCP2_H3 = @USE_NGTCP2_H3@ USE_OPENLDAP = @USE_OPENLDAP@ +USE_OPENSSL_H3 = @USE_OPENSSL_H3@ +USE_OPENSSL_QUIC = @USE_OPENSSL_QUIC@ USE_QUICHE = @USE_QUICHE@ USE_RUSTLS = @USE_RUSTLS@ USE_SCHANNEL = @USE_SCHANNEL@ @@ -355,6 +364,7 @@ USE_WOLFSSH = @USE_WOLFSSH@ USE_WOLFSSL = @USE_WOLFSSL@ VERSION = @VERSION@ VERSIONNUM = @VERSIONNUM@ +VSFTPD = @VSFTPD@ ZLIB_LIBS = @ZLIB_LIBS@ ZSH_FUNCTIONS_DIR = @ZSH_FUNCTIONS_DIR@ abs_builddir = @abs_builddir@ @@ -441,7 +451,7 @@ pkginclude_HEADERS = \ CHECKSRC = $(CS_$(V)) CS_0 = @echo " RUN " $@; -CS_1 = +CS_1 = CS_ = $(CS_0) all: all-am @@ -455,9 +465,9 @@ $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__confi exit 1;; \ esac; \ done; \ - echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign include/curl/Makefile'; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu include/curl/Makefile'; \ $(am__cd) $(top_srcdir) && \ - $(AUTOMAKE) --foreign include/curl/Makefile + $(AUTOMAKE) --gnu include/curl/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ @@ -589,7 +599,7 @@ distdir-am: $(DISTFILES) done check-am: all-am check: check-am -@CURLDEBUG_FALSE@all-local: +@DEBUGBUILD_FALSE@all-local: all-am: Makefile $(HEADERS) all-local installdirs: for dir in "$(DESTDIR)$(pkgincludedir)"; do \ @@ -714,7 +724,7 @@ checksrc: $(CHECKSRC)@PERL@ $(top_srcdir)/scripts/checksrc.pl -D$(top_srcdir)/include/curl $(pkginclude_HEADERS) # for debug builds, we scan the sources on all regular make invokes -@CURLDEBUG_TRUE@all-local: checksrc +@DEBUGBUILD_TRUE@all-local: checksrc # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. diff --git a/deps/curl/include/curl/curl.h b/deps/curl/include/curl/curl.h index 898cbda8393..21a552bc0ba 100644 --- a/deps/curl/include/curl/curl.h +++ b/deps/curl/include/curl/curl.h @@ -30,55 +30,55 @@ */ #ifdef CURL_NO_OLDIES -#define CURL_STRICTER +#define CURL_STRICTER /* not used since 8.11.0 */ #endif /* Compile-time deprecation macros. */ -#if defined(__GNUC__) && \ - ((__GNUC__ > 12) || ((__GNUC__ == 12) && (__GNUC_MINOR__ >= 1 ))) && \ - !defined(__INTEL_COMPILER) && \ +#if (defined(__GNUC__) && \ + ((__GNUC__ > 12) || ((__GNUC__ == 12) && (__GNUC_MINOR__ >= 1))) || \ + (defined(__clang__) && __clang_major__ >= 3) || \ + defined(__IAR_SYSTEMS_ICC__)) && \ + !defined(__INTEL_COMPILER) && \ !defined(CURL_DISABLE_DEPRECATION) && !defined(BUILDING_LIBCURL) #define CURL_DEPRECATED(version, message) \ __attribute__((deprecated("since " # version ". " message))) +#if defined(__IAR_SYSTEMS_ICC__) +#define CURL_IGNORE_DEPRECATION(statements) \ + _Pragma("diag_suppress=Pe1444") \ + statements \ + _Pragma("diag_default=Pe1444") +#else #define CURL_IGNORE_DEPRECATION(statements) \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") \ statements \ _Pragma("GCC diagnostic pop") +#endif #else #define CURL_DEPRECATED(version, message) #define CURL_IGNORE_DEPRECATION(statements) statements #endif #include "curlver.h" /* libcurl version defines */ -#include "system.h" /* determine things run-time */ - -/* - * Define CURL_WIN32 when build target is Win32 API - */ - -#if (defined(_WIN32) || defined(__WIN32__) || defined(WIN32)) && \ - !defined(__SYMBIAN32__) -#define CURL_WIN32 -#endif +#include "system.h" /* determine things runtime */ #include #include -#if (defined(__FreeBSD__) && (__FreeBSD__ >= 2)) || defined(__MidnightBSD__) +#if defined(__FreeBSD__) || defined(__MidnightBSD__) /* Needed for __FreeBSD_version or __MidnightBSD_version symbol definition */ -#include +#include #endif /* The include stuff here below is mainly for time_t! */ #include #include -#if defined(CURL_WIN32) && !defined(_WIN32_WCE) && !defined(__CYGWIN__) +#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__CYGWIN__) #if !(defined(_WINSOCKAPI_) || defined(_WINSOCK_H) || \ defined(__LWIP_OPT_H__) || defined(LWIP_HDR_OPT_H)) -/* The check above prevents the winsock2 inclusion if winsock.h already was - included, since they can't co-exist without problems */ +/* The check above prevents the winsock2.h inclusion if winsock.h already was + included, since they cannot co-exist without problems */ #include #include #endif @@ -88,7 +88,7 @@ libc5-based Linux systems. Only include it on systems that are known to require it! */ #if defined(_AIX) || defined(__NOVELL_LIBC__) || defined(__NetBSD__) || \ - defined(__minix) || defined(__SYMBIAN32__) || defined(__INTEGRITY) || \ + defined(__minix) || defined(__INTEGRITY) || \ defined(ANDROID) || defined(__ANDROID__) || defined(__OpenBSD__) || \ defined(__CYGWIN__) || defined(AMIGA) || defined(__NuttX__) || \ (defined(__FreeBSD_version) && (__FreeBSD_version < 800000)) || \ @@ -97,11 +97,11 @@ #include #endif -#if !defined(CURL_WIN32) && !defined(_WIN32_WCE) +#if !defined(_WIN32) && !defined(_WIN32_WCE) #include #endif -#if !defined(CURL_WIN32) +#if !defined(_WIN32) #include #endif @@ -114,13 +114,8 @@ extern "C" { #endif -#if defined(BUILDING_LIBCURL) || defined(CURL_STRICTER) -typedef struct Curl_easy CURL; -typedef struct Curl_share CURLSH; -#else typedef void CURL; typedef void CURLSH; -#endif /* * libcurl external API function linkage decorations. @@ -128,7 +123,7 @@ typedef void CURLSH; #ifdef CURL_STATICLIB # define CURL_EXTERN -#elif defined(CURL_WIN32) || defined(__SYMBIAN32__) || \ +#elif defined(_WIN32) || \ (__has_declspec_attribute(dllexport) && \ __has_declspec_attribute(dllimport)) # if defined(BUILDING_LIBCURL) @@ -144,7 +139,7 @@ typedef void CURLSH; #ifndef curl_socket_typedef /* socket typedef */ -#if defined(CURL_WIN32) && !defined(__LWIP_OPT_H__) && !defined(LWIP_HDR_OPT_H) +#if defined(_WIN32) && !defined(__LWIP_OPT_H__) && !defined(LWIP_HDR_OPT_H) typedef SOCKET curl_socket_t; #define CURL_SOCKET_BAD INVALID_SOCKET #else @@ -159,7 +154,7 @@ typedef enum { CURLSSLBACKEND_NONE = 0, CURLSSLBACKEND_OPENSSL = 1, CURLSSLBACKEND_GNUTLS = 2, - CURLSSLBACKEND_NSS = 3, + CURLSSLBACKEND_NSS CURL_DEPRECATED(8.3.0, "") = 3, CURLSSLBACKEND_OBSOLETE4 = 4, /* Was QSOSSL. */ CURLSSLBACKEND_GSKIT CURL_DEPRECATED(8.3.0, "") = 5, CURLSSLBACKEND_POLARSSL CURL_DEPRECATED(7.69.0, "") = 6, @@ -198,9 +193,9 @@ struct curl_httppost { files */ long flags; /* as defined below */ -/* specified content is a file name */ +/* specified content is a filename */ #define CURL_HTTPPOST_FILENAME (1<<0) -/* specified content is a file name */ +/* specified content is a filename */ #define CURL_HTTPPOST_READFILE (1<<1) /* name is only stored pointer do not free in formfree */ #define CURL_HTTPPOST_PTRNAME (1<<2) @@ -216,8 +211,8 @@ struct curl_httppost { /* use size in 'contentlen', added in 7.46.0 */ #define CURL_HTTPPOST_LARGE (1<<7) - char *showfilename; /* The file name to show. If not set, the - actual file name will be used (if this + char *showfilename; /* The filename to show. If not set, the + actual filename will be used (if this is a file part) */ void *userp; /* custom pointer used for HTTPPOST_CALLBACK posts */ @@ -254,12 +249,12 @@ typedef int (*curl_xferinfo_callback)(void *clientp, #endif #ifndef CURL_MAX_WRITE_SIZE - /* Tests have proven that 20K is a very bad buffer size for uploads on - Windows, while 16K for some odd reason performed a lot better. - We do the ifndef check to allow this value to easier be changed at build - time for those who feel adventurous. The practical minimum is about - 400 bytes since libcurl uses a buffer of this size as a scratch area - (unrelated to network send operations). */ + /* Tests have proven that 20K is a bad buffer size for uploads on Windows, + while 16K for some odd reason performed a lot better. We do the ifndef + check to allow this value to easier be changed at build time for those + who feel adventurous. The practical minimum is about 400 bytes since + libcurl uses a buffer of this size as a scratch area (unrelated to + network send operations). */ #define CURL_MAX_WRITE_SIZE 16384 #endif @@ -359,13 +354,13 @@ typedef long (*curl_chunk_bgn_callback)(const void *transfer_info, download of an individual chunk finished. Note! After this callback was set then it have to be called FOR ALL chunks. Even if downloading of this chunk was skipped in CHUNK_BGN_FUNC. - This is the reason why we don't need "transfer_info" parameter in this + This is the reason why we do not need "transfer_info" parameter in this callback and we are not interested in "remains" parameter too. */ typedef long (*curl_chunk_end_callback)(void *ptr); /* return codes for FNMATCHFUNCTION */ #define CURL_FNMATCHFUNC_MATCH 0 /* string corresponds to the pattern */ -#define CURL_FNMATCHFUNC_NOMATCH 1 /* pattern doesn't match the string */ +#define CURL_FNMATCHFUNC_NOMATCH 1 /* pattern does not match the string */ #define CURL_FNMATCHFUNC_FAIL 2 /* an error occurred */ /* callback type for wildcard downloading pattern matching. If the @@ -377,7 +372,7 @@ typedef int (*curl_fnmatch_callback)(void *ptr, /* These are the return codes for the seek callbacks */ #define CURL_SEEKFUNC_OK 0 #define CURL_SEEKFUNC_FAIL 1 /* fail the entire transfer */ -#define CURL_SEEKFUNC_CANTSEEK 2 /* tell libcurl seeking can't be done, so +#define CURL_SEEKFUNC_CANTSEEK 2 /* tell libcurl seeking cannot be done, so libcurl might try other means instead */ typedef int (*curl_seek_callback)(void *instream, curl_off_t offset, @@ -460,7 +455,7 @@ typedef curlioerr (*curl_ioctl_callback)(CURL *handle, #ifndef CURL_DID_MEMORY_FUNC_TYPEDEFS /* * The following typedef's are signatures of malloc, free, realloc, strdup and - * calloc respectively. Function pointers of these types can be passed to the + * calloc respectively. Function pointers of these types can be passed to the * curl_global_init_mem() function to set user defined memory management * callback routines. */ @@ -548,17 +543,17 @@ typedef enum { CURLE_WRITE_ERROR, /* 23 */ CURLE_OBSOLETE24, /* 24 - NOT USED */ CURLE_UPLOAD_FAILED, /* 25 - failed upload "command" */ - CURLE_READ_ERROR, /* 26 - couldn't open/read from file */ + CURLE_READ_ERROR, /* 26 - could not open/read from file */ CURLE_OUT_OF_MEMORY, /* 27 */ CURLE_OPERATION_TIMEDOUT, /* 28 - the timeout time was reached */ CURLE_OBSOLETE29, /* 29 - NOT USED */ CURLE_FTP_PORT_FAILED, /* 30 - FTP PORT operation failed */ CURLE_FTP_COULDNT_USE_REST, /* 31 - the REST command failed */ CURLE_OBSOLETE32, /* 32 - NOT USED */ - CURLE_RANGE_ERROR, /* 33 - RANGE "command" didn't work */ + CURLE_RANGE_ERROR, /* 33 - RANGE "command" did not work */ CURLE_HTTP_POST_ERROR, /* 34 */ CURLE_SSL_CONNECT_ERROR, /* 35 - wrong when connecting with SSL */ - CURLE_BAD_DOWNLOAD_RESUME, /* 36 - couldn't resume download */ + CURLE_BAD_DOWNLOAD_RESUME, /* 36 - could not resume download */ CURLE_FILE_COULDNT_READ_FILE, /* 37 */ CURLE_LDAP_CANNOT_BIND, /* 38 */ CURLE_LDAP_SEARCH_FAILED, /* 39 */ @@ -582,9 +577,9 @@ typedef enum { CURLE_RECV_ERROR, /* 56 - failure in receiving network data */ CURLE_OBSOLETE57, /* 57 - NOT IN USE */ CURLE_SSL_CERTPROBLEM, /* 58 - problem with the local certificate */ - CURLE_SSL_CIPHER, /* 59 - couldn't use specified cipher */ + CURLE_SSL_CIPHER, /* 59 - could not use specified cipher */ CURLE_PEER_FAILED_VERIFICATION, /* 60 - peer's certificate or fingerprint - wasn't verified fine */ + was not verified fine */ CURLE_BAD_CONTENT_ENCODING, /* 61 - Unrecognized/bad encoding */ CURLE_OBSOLETE62, /* 62 - NOT IN USE since 7.82.0 */ CURLE_FILESIZE_EXCEEDED, /* 63 - Maximum file size exceeded */ @@ -613,7 +608,7 @@ typedef enum { CURLE_SSL_SHUTDOWN_FAILED, /* 80 - Failed to shut down the SSL connection */ CURLE_AGAIN, /* 81 - socket is not ready for send/recv, - wait till it's ready and try again (Added + wait till it is ready and try again (Added in 7.18.2) */ CURLE_SSL_CRL_BADFILE, /* 82 - could not load CRL file, missing or wrong format (Added in 7.19.0) */ @@ -640,16 +635,18 @@ typedef enum { CURLE_PROXY, /* 97 - proxy handshake error */ CURLE_SSL_CLIENTCERT, /* 98 - client-side certificate required */ CURLE_UNRECOVERABLE_POLL, /* 99 - poll/select returned fatal error */ + CURLE_TOO_LARGE, /* 100 - a value/data met its maximum */ + CURLE_ECH_REQUIRED, /* 101 - ECH tried but failed */ CURL_LAST /* never use! */ } CURLcode; #ifndef CURL_NO_OLDIES /* define this to test if your app builds with all the obsolete stuff removed! */ -/* Previously obsolete error code re-used in 7.38.0 */ +/* Previously obsolete error code reused in 7.38.0 */ #define CURLE_OBSOLETE16 CURLE_HTTP2 -/* Previously obsolete error codes re-used in 7.24.0 */ +/* Previously obsolete error codes reused in 7.24.0 */ #define CURLE_OBSOLETE10 CURLE_FTP_ACCEPT_FAILED #define CURLE_OBSOLETE12 CURLE_FTP_ACCEPT_TIMEOUT @@ -720,6 +717,8 @@ typedef enum { with them. */ #define CURLOPT_WRITEINFO CURLOPT_OBSOLETE40 #define CURLOPT_CLOSEPOLICY CURLOPT_OBSOLETE72 +#define CURLOPT_OBSOLETE72 9999 +#define CURLOPT_OBSOLETE40 9999 #endif /* !CURL_NO_OLDIES */ @@ -770,7 +769,7 @@ typedef CURLcode (*curl_conv_callback)(char *buffer, size_t length); typedef CURLcode (*curl_ssl_ctx_callback)(CURL *curl, /* easy handle */ void *ssl_ctx, /* actually an OpenSSL - or WolfSSL SSL_CTX, + or wolfSSL SSL_CTX, or an mbedTLS mbedtls_ssl_config */ void *userptr); @@ -787,7 +786,7 @@ typedef enum { CURLPROXY_SOCKS5 = 5, /* added in 7.10 */ CURLPROXY_SOCKS4A = 6, /* added in 7.18.0 */ CURLPROXY_SOCKS5_HOSTNAME = 7 /* Use the SOCKS5 protocol but pass along the - host name rather than the IP address. added + hostname rather than the IP address. added in 7.18.0 */ } curl_proxytype; /* this enum was added in 7.10 */ @@ -819,7 +818,10 @@ typedef enum { #define CURLAUTH_GSSAPI CURLAUTH_NEGOTIATE #define CURLAUTH_NTLM (((unsigned long)1)<<3) #define CURLAUTH_DIGEST_IE (((unsigned long)1)<<4) +#ifndef CURL_NO_OLDIES + /* functionality removed since 8.8.0 */ #define CURLAUTH_NTLM_WB (((unsigned long)1)<<5) +#endif #define CURLAUTH_BEARER (((unsigned long)1)<<6) #define CURLAUTH_AWS_SIGV4 (((unsigned long)1)<<7) #define CURLAUTH_ONLY (((unsigned long)1)<<31) @@ -864,7 +866,7 @@ enum curl_khstat { CURLKHSTAT_FINE_ADD_TO_FILE, CURLKHSTAT_FINE, CURLKHSTAT_REJECT, /* reject the connection, return an error */ - CURLKHSTAT_DEFER, /* do not accept it, but we can't answer right now. + CURLKHSTAT_DEFER, /* do not accept it, but we cannot answer right now. Causes a CURLE_PEER_FAILED_VERIFICATION error but the connection will be left intact etc */ CURLKHSTAT_FINE_REPLACE, /* accept and replace the wrong key */ @@ -936,6 +938,9 @@ typedef enum { a client certificate for authentication. (Schannel) */ #define CURLSSLOPT_AUTO_CLIENT_CERT (1<<5) +/* If possible, send data using TLS 1.3 early data */ +#define CURLSSLOPT_EARLYDATA (1<<6) + /* The default connection attempt delay in milliseconds for happy eyeballs. CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS.3 and happy-eyeballs-timeout-ms.d document this value, keep them in sync. */ @@ -1084,7 +1089,7 @@ typedef CURLSTScode (*curl_hstswrite_callback)(CURL *easy, #define CURLOPT(na,t,nu) na = t + nu #define CURLOPTDEPRECATED(na,t,nu,v,m) na CURL_DEPRECATED(v,m) = t + nu -/* CURLOPT aliases that make no run-time difference */ +/* CURLOPT aliases that make no runtime difference */ /* 'char *' argument to a string with a trailing zero */ #define CURLOPTTYPE_STRINGPOINT CURLOPTTYPE_OBJECTPOINT @@ -1151,7 +1156,7 @@ typedef enum { * * For large file support, there is also a _LARGE version of the key * which takes an off_t type, allowing platforms with larger off_t - * sizes to handle larger files. See below for INFILESIZE_LARGE. + * sizes to handle larger files. See below for INFILESIZE_LARGE. */ CURLOPT(CURLOPT_INFILESIZE, CURLOPTTYPE_LONG, 14), @@ -1184,7 +1189,7 @@ typedef enum { * * Note there is also a _LARGE version of this key which uses * off_t types, allowing for large file offsets on platforms which - * use larger-than-32-bit off_t's. Look below for RESUME_FROM_LARGE. + * use larger-than-32-bit off_t's. Look below for RESUME_FROM_LARGE. */ CURLOPT(CURLOPT_RESUME_FROM, CURLOPTTYPE_LONG, 21), @@ -1246,8 +1251,7 @@ typedef enum { /* send linked-list of post-transfer QUOTE commands */ CURLOPT(CURLOPT_POSTQUOTE, CURLOPTTYPE_SLISTPOINT, 39), - /* OBSOLETE, do not use! */ - CURLOPT(CURLOPT_OBSOLETE40, CURLOPTTYPE_OBJECTPOINT, 40), + /* 40 is not used */ /* talk a lot */ CURLOPT(CURLOPT_VERBOSE, CURLOPTTYPE_LONG, 41), @@ -1320,9 +1324,9 @@ typedef enum { /* Set the interface string to use as outgoing network interface */ CURLOPT(CURLOPT_INTERFACE, CURLOPTTYPE_STRINGPOINT, 62), - /* Set the krb4/5 security level, this also enables krb4/5 awareness. This - * is a string, 'clear', 'safe', 'confidential' or 'private'. If the string - * is set but doesn't match one of these, 'private' will be used. */ + /* Set the krb4/5 security level, this also enables krb4/5 awareness. This + * is a string, 'clear', 'safe', 'confidential' or 'private'. If the string + * is set but does not match one of these, 'private' will be used. */ CURLOPT(CURLOPT_KRBLEVEL, CURLOPTTYPE_STRINGPOINT, 63), /* Set if we should verify the peer in ssl handshake, set 1 to verify. */ @@ -1348,22 +1352,20 @@ typedef enum { /* Max amount of cached alive connections */ CURLOPT(CURLOPT_MAXCONNECTS, CURLOPTTYPE_LONG, 71), - /* OBSOLETE, do not use! */ - CURLOPT(CURLOPT_OBSOLETE72, CURLOPTTYPE_LONG, 72), - + /* 72 = OBSOLETE */ /* 73 = OBSOLETE */ /* Set to explicitly use a new connection for the upcoming transfer. - Do not use this unless you're absolutely sure of this, as it makes the + Do not use this unless you are absolutely sure of this, as it makes the operation slower and is less friendly for the network. */ CURLOPT(CURLOPT_FRESH_CONNECT, CURLOPTTYPE_LONG, 74), - /* Set to explicitly forbid the upcoming transfer's connection to be re-used - when done. Do not use this unless you're absolutely sure of this, as it + /* Set to explicitly forbid the upcoming transfer's connection to be reused + when done. Do not use this unless you are absolutely sure of this, as it makes the operation slower and is less friendly for the network. */ CURLOPT(CURLOPT_FORBID_REUSE, CURLOPTTYPE_LONG, 75), - /* Set to a file name that contains random data for libcurl to use to + /* Set to a filename that contains random data for libcurl to use to seed the random engine when doing SSL connects. */ CURLOPTDEPRECATED(CURLOPT_RANDOM_FILE, CURLOPTTYPE_STRINGPOINT, 76, 7.84.0, "Serves no purpose anymore"), @@ -1390,11 +1392,11 @@ typedef enum { * provided hostname. */ CURLOPT(CURLOPT_SSL_VERIFYHOST, CURLOPTTYPE_LONG, 81), - /* Specify which file name to write all known cookies in after completed - operation. Set file name to "-" (dash) to make it go to stdout. */ + /* Specify which filename to write all known cookies in after completed + operation. Set filename to "-" (dash) to make it go to stdout. */ CURLOPT(CURLOPT_COOKIEJAR, CURLOPTTYPE_STRINGPOINT, 82), - /* Specify which SSL ciphers to use */ + /* Specify which TLS 1.2 (1.1, 1.0) ciphers to use */ CURLOPT(CURLOPT_SSL_CIPHER_LIST, CURLOPTTYPE_STRINGPOINT, 83), /* Specify which HTTP version to use! This must be set to one of the @@ -1490,7 +1492,7 @@ typedef enum { CURLOPT(CURLOPT_HTTPAUTH, CURLOPTTYPE_VALUES, 107), /* Set the ssl context callback function, currently only for OpenSSL or - WolfSSL ssl_ctx, or mbedTLS mbedtls_ssl_config in the second argument. + wolfSSL ssl_ctx, or mbedTLS mbedtls_ssl_config in the second argument. The function must match the curl_ssl_ctx_callback prototype. */ CURLOPT(CURLOPT_SSL_CTX_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 108), @@ -1510,7 +1512,7 @@ typedef enum { CURLOPT(CURLOPT_PROXYAUTH, CURLOPTTYPE_VALUES, 111), /* Option that changes the timeout, in seconds, associated with getting a - response. This is different from transfer timeout time and essentially + response. This is different from transfer timeout time and essentially places a demand on the server to acknowledge commands in a timely manner. For FTP, SMTP, IMAP and POP3. */ CURLOPT(CURLOPT_SERVER_RESPONSE_TIMEOUT, CURLOPTTYPE_LONG, 112), @@ -1524,7 +1526,7 @@ typedef enum { an HTTP or FTP server. Note there is also _LARGE version which adds large file support for - platforms which have larger off_t sizes. See MAXFILESIZE_LARGE below. */ + platforms which have larger off_t sizes. See MAXFILESIZE_LARGE below. */ CURLOPT(CURLOPT_MAXFILESIZE, CURLOPTTYPE_LONG, 114), /* See the comment for INFILESIZE above, but in short, specifies @@ -1532,17 +1534,17 @@ typedef enum { */ CURLOPT(CURLOPT_INFILESIZE_LARGE, CURLOPTTYPE_OFF_T, 115), - /* Sets the continuation offset. There is also a CURLOPTTYPE_LONG version + /* Sets the continuation offset. There is also a CURLOPTTYPE_LONG version * of this; look above for RESUME_FROM. */ CURLOPT(CURLOPT_RESUME_FROM_LARGE, CURLOPTTYPE_OFF_T, 116), /* Sets the maximum size of data that will be downloaded from - * an HTTP or FTP server. See MAXFILESIZE above for the LONG version. + * an HTTP or FTP server. See MAXFILESIZE above for the LONG version. */ CURLOPT(CURLOPT_MAXFILESIZE_LARGE, CURLOPTTYPE_OFF_T, 117), - /* Set this option to the file name of your .netrc file you want libcurl + /* Set this option to the filename of your .netrc file you want libcurl to parse (using the CURLOPT_NETRC option). If not set, libcurl will do a poor attempt to find the user's home directory and check for a .netrc file in there. */ @@ -1652,7 +1654,7 @@ typedef enum { CURLOPT(CURLOPT_SOCKOPTFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 148), CURLOPT(CURLOPT_SOCKOPTDATA, CURLOPTTYPE_CBPOINT, 149), - /* set to 0 to disable session ID re-use for this transfer, default is + /* set to 0 to disable session ID reuse for this transfer, default is enabled (== 1) */ CURLOPT(CURLOPT_SSL_SESSIONID_CACHE, CURLOPTTYPE_LONG, 150), @@ -1689,7 +1691,7 @@ typedef enum { /* Callback function for opening socket (instead of socket(2)). Optionally, callback is able change the address or refuse to connect returning - CURL_SOCKET_BAD. The callback should have type + CURL_SOCKET_BAD. The callback should have type curl_opensocket_callback */ CURLOPT(CURLOPT_OPENSOCKETFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 163), CURLOPT(CURLOPT_OPENSOCKETDATA, CURLOPTTYPE_CBPOINT, 164), @@ -1759,7 +1761,7 @@ typedef enum { CURLOPTDEPRECATED(CURLOPT_REDIR_PROTOCOLS, CURLOPTTYPE_LONG, 182, 7.85.0, "Use CURLOPT_REDIR_PROTOCOLS_STR"), - /* set the SSH knownhost file name to use */ + /* set the SSH knownhost filename to use */ CURLOPT(CURLOPT_SSH_KNOWNHOSTS, CURLOPTTYPE_STRINGPOINT, 183), /* set the SSH host key callback, must point to a curl_sshkeycallback @@ -1840,7 +1842,7 @@ typedef enum { future libcurl release. libcurl will ask for the compressed methods it knows of, and if that - isn't any, it will not ask for transfer-encoding at all even if this + is not any, it will not ask for transfer-encoding at all even if this option is set to 1. */ @@ -1854,7 +1856,8 @@ typedef enum { /* allow GSSAPI credential delegation */ CURLOPT(CURLOPT_GSSAPI_DELEGATION, CURLOPTTYPE_VALUES, 210), - /* Set the name servers to use for DNS resolution */ + /* Set the name servers to use for DNS resolution. + * Only supported by the c-ares DNS backend */ CURLOPT(CURLOPT_DNS_SERVERS, CURLOPTTYPE_STRINGPOINT, 211), /* Time-out accept operations (currently for FTP only) after this amount @@ -1941,7 +1944,7 @@ typedef enum { /* Service Name */ CURLOPT(CURLOPT_SERVICE_NAME, CURLOPTTYPE_STRINGPOINT, 236), - /* Wait/don't wait for pipe/mutex to clarify */ + /* Wait/do not wait for pipe/mutex to clarify */ CURLOPT(CURLOPT_PIPEWAIT, CURLOPTTYPE_LONG, 237), /* Set the protocol used when curl is given a URL without a protocol */ @@ -2017,7 +2020,7 @@ typedef enum { /* password for the SSL private key for proxy */ CURLOPT(CURLOPT_PROXY_KEYPASSWD, CURLOPTTYPE_STRINGPOINT, 258), - /* Specify which SSL ciphers to use for proxy */ + /* Specify which TLS 1.2 (1.1, 1.0) ciphers to use for proxy */ CURLOPT(CURLOPT_PROXY_SSL_CIPHER_LIST, CURLOPTTYPE_STRINGPOINT, 259), /* CRL file for proxy */ @@ -2102,7 +2105,7 @@ typedef enum { /* alt-svc control bitmask */ CURLOPT(CURLOPT_ALTSVC_CTRL, CURLOPTTYPE_LONG, 286), - /* alt-svc cache file name to possibly read from/write to */ + /* alt-svc cache filename to possibly read from/write to */ CURLOPT(CURLOPT_ALTSVC, CURLOPTTYPE_STRINGPOINT, 287), /* maximum age (idle time) of a connection to consider it for reuse @@ -2128,13 +2131,13 @@ typedef enum { /* the EC curves requested by the TLS client (RFC 8422, 5.1); * OpenSSL support via 'set_groups'/'set_curves': - * https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set1_groups.html + * https://docs.openssl.org/master/man3/SSL_CTX_set1_curves/ */ CURLOPT(CURLOPT_SSL_EC_CURVES, CURLOPTTYPE_STRINGPOINT, 298), /* HSTS bitmask */ CURLOPT(CURLOPT_HSTS_CTRL, CURLOPTTYPE_LONG, 299), - /* HSTS file name */ + /* HSTS filename */ CURLOPT(CURLOPT_HSTS, CURLOPTTYPE_STRINGPOINT, 300), /* HSTS read callback */ @@ -2198,7 +2201,7 @@ typedef enum { /* specify which protocols that libcurl is allowed to follow directs to */ CURLOPT(CURLOPT_REDIR_PROTOCOLS_STR, CURLOPTTYPE_STRINGPOINT, 319), - /* websockets options */ + /* WebSockets options */ CURLOPT(CURLOPT_WS_OPTIONS, CURLOPTTYPE_LONG, 320), /* CA cache timeout */ @@ -2210,6 +2213,15 @@ typedef enum { /* set a specific client IP for HAProxy PROXY protocol header? */ CURLOPT(CURLOPT_HAPROXY_CLIENT_IP, CURLOPTTYPE_STRINGPOINT, 323), + /* millisecond version */ + CURLOPT(CURLOPT_SERVER_RESPONSE_TIMEOUT_MS, CURLOPTTYPE_LONG, 324), + + /* set ECH configuration */ + CURLOPT(CURLOPT_ECH, CURLOPTTYPE_STRINGPOINT, 325), + + /* maximum number of keepalive probes (Linux, *BSD, macOS, etc.) */ + CURLOPT(CURLOPT_TCP_KEEPCNT, CURLOPTTYPE_LONG, 326), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; @@ -2260,9 +2272,9 @@ typedef enum { /* These enums are for use with the CURLOPT_HTTP_VERSION option. */ enum { - CURL_HTTP_VERSION_NONE, /* setting this means we don't care, and that we'd - like the library to choose the best possible - for us! */ + CURL_HTTP_VERSION_NONE, /* setting this means we do not care, and that we + would like the library to choose the best + possible for us! */ CURL_HTTP_VERSION_1_0, /* please use HTTP 1.0 in the request */ CURL_HTTP_VERSION_1_1, /* please use HTTP 1.1 in the request */ CURL_HTTP_VERSION_2_0, /* please use HTTP 2 in the request */ @@ -2315,30 +2327,26 @@ enum CURL_NETRC_OPTION { CURL_NETRC_LAST }; -enum { - CURL_SSLVERSION_DEFAULT, - CURL_SSLVERSION_TLSv1, /* TLS 1.x */ - CURL_SSLVERSION_SSLv2, - CURL_SSLVERSION_SSLv3, - CURL_SSLVERSION_TLSv1_0, - CURL_SSLVERSION_TLSv1_1, - CURL_SSLVERSION_TLSv1_2, - CURL_SSLVERSION_TLSv1_3, - - CURL_SSLVERSION_LAST /* never use, keep last */ -}; +#define CURL_SSLVERSION_DEFAULT 0 +#define CURL_SSLVERSION_TLSv1 1 /* TLS 1.x */ +#define CURL_SSLVERSION_SSLv2 2 +#define CURL_SSLVERSION_SSLv3 3 +#define CURL_SSLVERSION_TLSv1_0 4 +#define CURL_SSLVERSION_TLSv1_1 5 +#define CURL_SSLVERSION_TLSv1_2 6 +#define CURL_SSLVERSION_TLSv1_3 7 -enum { - CURL_SSLVERSION_MAX_NONE = 0, - CURL_SSLVERSION_MAX_DEFAULT = (CURL_SSLVERSION_TLSv1 << 16), - CURL_SSLVERSION_MAX_TLSv1_0 = (CURL_SSLVERSION_TLSv1_0 << 16), - CURL_SSLVERSION_MAX_TLSv1_1 = (CURL_SSLVERSION_TLSv1_1 << 16), - CURL_SSLVERSION_MAX_TLSv1_2 = (CURL_SSLVERSION_TLSv1_2 << 16), - CURL_SSLVERSION_MAX_TLSv1_3 = (CURL_SSLVERSION_TLSv1_3 << 16), +#define CURL_SSLVERSION_LAST 8 /* never use, keep last */ + +#define CURL_SSLVERSION_MAX_NONE 0 +#define CURL_SSLVERSION_MAX_DEFAULT (CURL_SSLVERSION_TLSv1 << 16) +#define CURL_SSLVERSION_MAX_TLSv1_0 (CURL_SSLVERSION_TLSv1_0 << 16) +#define CURL_SSLVERSION_MAX_TLSv1_1 (CURL_SSLVERSION_TLSv1_1 << 16) +#define CURL_SSLVERSION_MAX_TLSv1_2 (CURL_SSLVERSION_TLSv1_2 << 16) +#define CURL_SSLVERSION_MAX_TLSv1_3 (CURL_SSLVERSION_TLSv1_3 << 16) /* never use, keep last */ - CURL_SSLVERSION_MAX_LAST = (CURL_SSLVERSION_LAST << 16) -}; +#define CURL_SSLVERSION_MAX_LAST (CURL_SSLVERSION_LAST << 16) enum CURL_TLSAUTH { CURL_TLSAUTH_NONE, @@ -2426,7 +2434,7 @@ CURL_EXTERN CURLcode curl_mime_name(curl_mimepart *part, const char *name); * * DESCRIPTION * - * Set mime part remote file name. + * Set mime part remote filename. */ CURL_EXTERN CURLcode curl_mime_filename(curl_mimepart *part, const char *filename); @@ -2635,7 +2643,7 @@ CURL_EXTERN char *curl_getenv(const char *variable); * * DESCRIPTION * - * Returns a static ascii string of the libcurl version. + * Returns a static ASCII string of the libcurl version. */ CURL_EXTERN char *curl_version(void); @@ -2707,10 +2715,10 @@ CURL_EXTERN CURLcode curl_global_init(long flags); * DESCRIPTION * * curl_global_init() or curl_global_init_mem() should be invoked exactly once - * for each application that uses libcurl. This function can be used to + * for each application that uses libcurl. This function can be used to * initialize libcurl and set user defined memory management callback - * functions. Users can implement memory management routines to check for - * memory leaks, check for mis-use of the curl library etc. User registered + * functions. Users can implement memory management routines to check for + * memory leaks, check for mis-use of the curl library etc. User registered * callback routines will be invoked by this library instead of the system * memory management routines like malloc, free etc. */ @@ -2828,7 +2836,7 @@ CURL_EXTERN time_t curl_getdate(const char *p, const time_t *unused); for with CURLOPT_CERTINFO / CURLINFO_CERTINFO */ struct curl_certinfo { int num_of_certs; /* number of certificates with information */ - struct curl_slist **certinfo; /* for each index in this array, there's a + struct curl_slist **certinfo; /* for each index in this array, there is a linked list with textual information for a certificate in the format "name:content". eg "Subject:foo", "Issuer:bar", etc. */ @@ -2941,7 +2949,11 @@ typedef enum { CURLINFO_CAPATH = CURLINFO_STRING + 62, CURLINFO_XFER_ID = CURLINFO_OFF_T + 63, CURLINFO_CONN_ID = CURLINFO_OFF_T + 64, - CURLINFO_LASTONE = 64 + CURLINFO_QUEUE_TIME_T = CURLINFO_OFF_T + 65, + CURLINFO_USED_PROXY = CURLINFO_LONG + 66, + CURLINFO_POSTTRANSFER_TIME_T = CURLINFO_OFF_T + 67, + CURLINFO_EARLYDATA_SENT_T = CURLINFO_OFF_T + 68, + CURLINFO_LASTONE = 68 } CURLINFO; /* CURLINFO_RESPONSE_CODE is the new name for the option previously known as @@ -3017,7 +3029,7 @@ typedef enum { } CURLSHcode; typedef enum { - CURLSHOPT_NONE, /* don't use */ + CURLSHOPT_NONE, /* do not use */ CURLSHOPT_SHARE, /* specify a data type to share */ CURLSHOPT_UNSHARE, /* specify which data type to stop sharing */ CURLSHOPT_LOCKFUNC, /* pass in a 'curl_lock_function' pointer */ @@ -3037,17 +3049,18 @@ CURL_EXTERN CURLSHcode curl_share_cleanup(CURLSH *share); */ typedef enum { - CURLVERSION_FIRST, - CURLVERSION_SECOND, - CURLVERSION_THIRD, - CURLVERSION_FOURTH, - CURLVERSION_FIFTH, - CURLVERSION_SIXTH, - CURLVERSION_SEVENTH, - CURLVERSION_EIGHTH, - CURLVERSION_NINTH, - CURLVERSION_TENTH, - CURLVERSION_ELEVENTH, + CURLVERSION_FIRST, /* 7.10 */ + CURLVERSION_SECOND, /* 7.11.1 */ + CURLVERSION_THIRD, /* 7.12.0 */ + CURLVERSION_FOURTH, /* 7.16.1 */ + CURLVERSION_FIFTH, /* 7.57.0 */ + CURLVERSION_SIXTH, /* 7.66.0 */ + CURLVERSION_SEVENTH, /* 7.70.0 */ + CURLVERSION_EIGHTH, /* 7.72.0 */ + CURLVERSION_NINTH, /* 7.75.0 */ + CURLVERSION_TENTH, /* 7.77.0 */ + CURLVERSION_ELEVENTH, /* 7.87.0 */ + CURLVERSION_TWELFTH, /* 8.8.0 */ CURLVERSION_LAST /* never actually use this */ } CURLversion; @@ -3056,7 +3069,7 @@ typedef enum { meant to be a built-in version number for what kind of struct the caller expects. If the struct ever changes, we redefine the NOW to another enum from above. */ -#define CURLVERSION_NOW CURLVERSION_ELEVENTH +#define CURLVERSION_NOW CURLVERSION_TWELFTH struct curl_version_info_data { CURLversion age; /* age of the returned struct */ @@ -3116,6 +3129,9 @@ struct curl_version_info_data { /* These fields were added in CURLVERSION_ELEVENTH */ /* feature_names is terminated by an entry with a NULL feature name */ const char * const *feature_names; + + /* These fields were added in CURLVERSION_TWELFTH */ + const char *rtmp_version; /* human readable string. */ }; typedef struct curl_version_info_data curl_version_info_data; @@ -3156,7 +3172,7 @@ typedef struct curl_version_info_data curl_version_info_data; #define CURL_VERSION_GSASL (1<<29) /* libgsasl is supported */ #define CURL_VERSION_THREADSAFE (1<<30) /* libcurl API is thread-safe */ - /* +/* * NAME curl_version_info() * * DESCRIPTION @@ -3172,7 +3188,7 @@ CURL_EXTERN curl_version_info_data *curl_version_info(CURLversion); * DESCRIPTION * * The curl_easy_strerror function may be used to turn a CURLcode value - * into the equivalent human readable error string. This is useful + * into the equivalent human readable error string. This is useful * for printing meaningful error messages. */ CURL_EXTERN const char *curl_easy_strerror(CURLcode); @@ -3183,7 +3199,7 @@ CURL_EXTERN const char *curl_easy_strerror(CURLcode); * DESCRIPTION * * The curl_share_strerror function may be used to turn a CURLSHcode value - * into the equivalent human readable error string. This is useful + * into the equivalent human readable error string. This is useful * for printing meaningful error messages. */ CURL_EXTERN const char *curl_share_strerror(CURLSHcode); @@ -3220,8 +3236,11 @@ CURL_EXTERN CURLcode curl_easy_pause(CURL *handle, int bitmask); #include "options.h" #include "header.h" #include "websockets.h" +#ifndef CURL_SKIP_INCLUDE_MPRINTF +#include "mprintf.h" +#endif -/* the typechecker doesn't work in C++ (yet) */ +/* the typechecker does not work in C++ (yet) */ #if defined(__GNUC__) && defined(__GNUC_MINOR__) && \ ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) && \ !defined(__cplusplus) && !defined(CURL_DISABLE_TYPECHECK) diff --git a/deps/curl/include/curl/curlver.h b/deps/curl/include/curl/curlver.h index df93ef12740..bcfe3b089e3 100644 --- a/deps/curl/include/curl/curlver.h +++ b/deps/curl/include/curl/curlver.h @@ -32,12 +32,12 @@ /* This is the version number of the libcurl package from which this header file origins: */ -#define LIBCURL_VERSION "8.3.0" +#define LIBCURL_VERSION "8.11.0" /* The numeric version number is also available "in parts" by using these defines: */ #define LIBCURL_VERSION_MAJOR 8 -#define LIBCURL_VERSION_MINOR 3 +#define LIBCURL_VERSION_MINOR 11 #define LIBCURL_VERSION_PATCH 0 /* This is the numeric version of the libcurl version number, meant for easier @@ -48,7 +48,7 @@ Where XX, YY and ZZ are the main version, release and patch numbers in hexadecimal (using 8 bits each). All three numbers are always represented - using two digits. 1.2 would appear as "0x010200" while version 9.11.7 + using two digits. 1.2 would appear as "0x010200" while version 9.11.7 appears as "0x090b07". This 6-digit (24 bits) hexadecimal number does not show pre-release number, @@ -59,7 +59,7 @@ CURL_VERSION_BITS() macro since curl's own configure script greps for it and needs it to contain the full number. */ -#define LIBCURL_VERSION_NUM 0x080300 +#define LIBCURL_VERSION_NUM 0x080b00 /* * This is the date and time when the full source package was created. The @@ -70,7 +70,7 @@ * * "2007-11-23" */ -#define LIBCURL_TIMESTAMP "2023-09-13" +#define LIBCURL_TIMESTAMP "2024-11-06" #define CURL_VERSION_BITS(x,y,z) ((x)<<16|(y)<<8|(z)) #define CURL_AT_LEAST_VERSION(x,y,z) \ diff --git a/deps/curl/include/curl/easy.h b/deps/curl/include/curl/easy.h index 1285101c587..71b8dd4674e 100644 --- a/deps/curl/include/curl/easy.h +++ b/deps/curl/include/curl/easy.h @@ -50,7 +50,7 @@ CURL_EXTERN void curl_easy_cleanup(CURL *curl); * * Request internal information from the curl session with this function. * The third argument MUST be pointing to the specific type of the used option - * which is documented in each man page of the option. The data pointed to + * which is documented in each manpage of the option. The data pointed to * will be filled in accordingly and can be relied upon only if the function * returns CURLE_OK. This function is intended to get used *AFTER* a performed * transfer, all results from this function are undefined until the transfer diff --git a/deps/curl/include/curl/mprintf.h b/deps/curl/include/curl/mprintf.h index dc5664bc539..88059c851fb 100644 --- a/deps/curl/include/curl/mprintf.h +++ b/deps/curl/include/curl/mprintf.h @@ -32,21 +32,36 @@ extern "C" { #endif -#if (defined(__GNUC__) || defined(__clang__)) && \ +#ifndef CURL_TEMP_PRINTF +#if (defined(__GNUC__) || defined(__clang__) || \ + defined(__IAR_SYSTEMS_ICC__)) && \ defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ - !defined(__MINGW32__) && !defined(CURL_NO_FMT_CHECKS) -#define CURL_TEMP_PRINTF(a,b) __attribute__ ((format(printf, a, b))) + !defined(CURL_NO_FMT_CHECKS) +#if defined(__MINGW32__) && !defined(__clang__) +#if defined(__MINGW_PRINTF_FORMAT) /* mingw-w64 3.0.0+. Needs stdio.h. */ +#define CURL_TEMP_PRINTF(fmt, arg) \ + __attribute__((format(__MINGW_PRINTF_FORMAT, fmt, arg))) #else -#define CURL_TEMP_PRINTF(a,b) +#define CURL_TEMP_PRINTF(fmt, arg) +#endif +#else +#define CURL_TEMP_PRINTF(fmt, arg) \ + __attribute__((format(printf, fmt, arg))) +#endif +#else +#define CURL_TEMP_PRINTF(fmt, arg) +#endif #endif -CURL_EXTERN int curl_mprintf(const char *format, ...) CURL_TEMP_PRINTF(1, 2); +CURL_EXTERN int curl_mprintf(const char *format, ...) + CURL_TEMP_PRINTF(1, 2); CURL_EXTERN int curl_mfprintf(FILE *fd, const char *format, ...) CURL_TEMP_PRINTF(2, 3); CURL_EXTERN int curl_msprintf(char *buffer, const char *format, ...) CURL_TEMP_PRINTF(2, 3); CURL_EXTERN int curl_msnprintf(char *buffer, size_t maxlength, - const char *format, ...) CURL_TEMP_PRINTF(3, 4); + const char *format, ...) + CURL_TEMP_PRINTF(3, 4); CURL_EXTERN int curl_mvprintf(const char *format, va_list args) CURL_TEMP_PRINTF(1, 0); CURL_EXTERN int curl_mvfprintf(FILE *fd, const char *format, va_list args) diff --git a/deps/curl/include/curl/multi.h b/deps/curl/include/curl/multi.h index 13b55b78079..42469bb5657 100644 --- a/deps/curl/include/curl/multi.h +++ b/deps/curl/include/curl/multi.h @@ -24,7 +24,7 @@ * ***************************************************************************/ /* - This is an "external" header file. Don't give away any internals here! + This is an "external" header file. Do not give away any internals here! GOALS @@ -54,11 +54,7 @@ extern "C" { #endif -#if defined(BUILDING_LIBCURL) || defined(CURL_STRICTER) -typedef struct Curl_multi CURLM; -#else typedef void CURLM; -#endif typedef enum { CURLM_CALL_MULTI_PERFORM = -1, /* please call curl_multi_perform() or @@ -66,7 +62,7 @@ typedef enum { CURLM_OK, CURLM_BAD_HANDLE, /* the passed-in handle is not a valid CURLM handle */ CURLM_BAD_EASY_HANDLE, /* an easy handle was not good/valid */ - CURLM_OUT_OF_MEMORY, /* if you ever get this, you're in deep sh*t */ + CURLM_OUT_OF_MEMORY, /* if you ever get this, you are in deep sh*t */ CURLM_INTERNAL_ERROR, /* this is a libcurl bug */ CURLM_BAD_SOCKET, /* the passed in socket argument did not match */ CURLM_UNKNOWN_OPTION, /* curl_multi_setopt() with unsupported option */ @@ -109,7 +105,7 @@ struct CURLMsg { typedef struct CURLMsg CURLMsg; /* Based on poll(2) structure and values. - * We don't use pollfd and POLL* constants explicitly + * We do not use pollfd and POLL* constants explicitly * to cover platforms without poll(). */ #define CURL_WAIT_POLLIN 0x0001 #define CURL_WAIT_POLLPRI 0x0002 @@ -205,7 +201,7 @@ CURL_EXTERN CURLMcode curl_multi_wakeup(CURLM *multi_handle); /* * Name: curl_multi_perform() * - * Desc: When the app thinks there's data available for curl it calls this + * Desc: When the app thinks there is data available for curl it calls this * function to read/write whatever there is right now. This returns * as soon as the reads and writes are done. This function does not * require that there actually is data available for reading or that @@ -236,7 +232,7 @@ CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *multi_handle); /* * Name: curl_multi_info_read() * - * Desc: Ask the multi handle if there's any messages/informationals from + * Desc: Ask the multi handle if there is any messages/informationals from * the individual transfers. Messages include informationals such as * error code from the transfer or just the fact that a transfer is * completed. More details on these should be written down as well. @@ -248,13 +244,13 @@ CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *multi_handle); * The data the returned pointer points to will not survive calling * curl_multi_cleanup(). * - * The 'CURLMsg' struct is meant to be very simple and only contain - * very basic information. If more involved information is wanted, - * we will provide the particular "transfer handle" in that struct - * and that should/could/would be used in subsequent - * curl_easy_getinfo() calls (or similar). The point being that we - * must never expose complex structs to applications, as then we'll - * undoubtably get backwards compatibility problems in the future. + * The 'CURLMsg' struct is meant to be simple and only contain basic + * information. If more involved information is wanted, we will + * provide the particular "transfer handle" in that struct and that + * should/could/would be used in subsequent curl_easy_getinfo() calls + * (or similar). The point being that we must never expose complex + * structs to applications, as then we will undoubtably get backwards + * compatibility problems in the future. * * Returns: A pointer to a filled-in struct, or NULL if it failed or ran out * of structs. It also writes the number of messages left in the @@ -268,7 +264,7 @@ CURL_EXTERN CURLMsg *curl_multi_info_read(CURLM *multi_handle, * Name: curl_multi_strerror() * * Desc: The curl_multi_strerror function may be used to turn a CURLMcode - * value into the equivalent human readable error string. This is + * value into the equivalent human readable error string. This is * useful for printing meaningful error messages. * * Returns: A pointer to a null-terminated error message. @@ -282,7 +278,7 @@ CURL_EXTERN const char *curl_multi_strerror(CURLMcode); * Desc: An alternative version of curl_multi_perform() that allows the * application to pass in one of the file descriptors that have been * detected to have "action" on them and let libcurl perform. - * See man page for details. + * See manpage for details. */ #define CURL_POLL_NONE 0 #define CURL_POLL_IN 1 @@ -426,6 +422,17 @@ CURL_EXTERN CURLMcode curl_multi_setopt(CURLM *multi_handle, CURL_EXTERN CURLMcode curl_multi_assign(CURLM *multi_handle, curl_socket_t sockfd, void *sockp); +/* + * Name: curl_multi_get_handles() + * + * Desc: Returns an allocated array holding all handles currently added to + * the multi handle. Marks the final entry with a NULL pointer. If + * there is no easy handle added to the multi handle, this function + * returns an array with the first entry as a NULL pointer. + * + * Returns: NULL on failure, otherwise a CURL **array pointer + */ +CURL_EXTERN CURL **curl_multi_get_handles(CURLM *multi_handle); /* * Name: curl_push_callback @@ -453,6 +460,20 @@ typedef int (*curl_push_callback)(CURL *parent, struct curl_pushheaders *headers, void *userp); +/* + * Name: curl_multi_waitfds() + * + * Desc: Ask curl for fds for polling. The app can use these to poll on. + * We want curl_multi_perform() called as soon as one of them are + * ready. Passing zero size allows to get just a number of fds. + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_waitfds(CURLM *multi, + struct curl_waitfd *ufds, + unsigned int size, + unsigned int *fd_count); + #ifdef __cplusplus } /* end of extern "C" */ #endif diff --git a/deps/curl/include/curl/system.h b/deps/curl/include/curl/system.h index 97e0d037a9e..e5be2568455 100644 --- a/deps/curl/include/curl/system.h +++ b/deps/curl/include/curl/system.h @@ -31,7 +31,7 @@ * changed. * * In order to differentiate between platforms/compilers/architectures use - * only compiler built in predefined preprocessor symbols. + * only compiler built-in predefined preprocessor symbols. * * curl_off_t * ---------- @@ -46,7 +46,7 @@ * As a general rule, curl_off_t shall not be mapped to off_t. This rule shall * only be violated if off_t is the only 64-bit data type available and the * size of off_t is independent of large file support settings. Keep your - * build on the safe side avoiding an off_t gating. If you have a 64-bit + * build on the safe side avoiding an off_t gating. If you have a 64-bit * off_t then take for sure that another 64-bit data type exists, dig deeper * and you will find it. * @@ -141,29 +141,6 @@ # define CURL_TYPEOF_CURL_SOCKLEN_T int # endif -#elif defined(__SYMBIAN32__) -# if defined(__EABI__) /* Treat all ARM compilers equally */ -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# elif defined(__CW32__) -# pragma longlong on -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# elif defined(__VC32__) -# define CURL_TYPEOF_CURL_OFF_T __int64 -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# endif -# define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int - #elif defined(macintosh) # include # if TYPE_LONGLONG @@ -201,14 +178,14 @@ # define CURL_TYPEOF_CURL_SOCKLEN_T int #elif defined(__MINGW32__) +# include # define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "I64d" -# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_FORMAT_CURL_OFF_T PRId64 +# define CURL_FORMAT_CURL_OFF_TU PRIu64 # define CURL_SUFFIX_CURL_OFF_T LL # define CURL_SUFFIX_CURL_OFF_TU ULL -# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_TYPEOF_CURL_SOCKLEN_T int # define CURL_PULL_SYS_TYPES_H 1 -# define CURL_PULL_WS2TCPIP_H 1 #elif defined(__VMS) # if defined(__VAX) @@ -370,7 +347,14 @@ /* ===================================== */ #elif defined(_MSC_VER) -# if (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64) +# if (_MSC_VER >= 1800) +# include +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T PRId64 +# define CURL_FORMAT_CURL_OFF_TU PRIu64 +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 +# elif (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64) # define CURL_TYPEOF_CURL_OFF_T __int64 # define CURL_FORMAT_CURL_OFF_T "I64d" # define CURL_FORMAT_CURL_OFF_TU "I64u" @@ -418,7 +402,7 @@ # define CURL_PULL_SYS_SOCKET_H 1 #else -/* generic "safe guess" on old 32 bit style */ +/* generic "safe guess" on old 32-bit style */ # define CURL_TYPEOF_CURL_OFF_T long # define CURL_FORMAT_CURL_OFF_T "ld" # define CURL_FORMAT_CURL_OFF_TU "lu" @@ -432,15 +416,6 @@ #define CURL_PULL_SYS_POLL_H #endif - -/* CURL_PULL_WS2TCPIP_H is defined above when inclusion of header file */ -/* ws2tcpip.h is required here to properly make type definitions below. */ -#ifdef CURL_PULL_WS2TCPIP_H -# include -# include -# include -#endif - /* CURL_PULL_SYS_TYPES_H is defined above when inclusion of header file */ /* sys/types.h is required here to properly make type definitions below. */ #ifdef CURL_PULL_SYS_TYPES_H diff --git a/deps/curl/include/curl/typecheck-gcc.h b/deps/curl/include/curl/typecheck-gcc.h index b880f3dc605..e532e6997db 100644 --- a/deps/curl/include/curl/typecheck-gcc.h +++ b/deps/curl/include/curl/typecheck-gcc.h @@ -34,11 +34,11 @@ * _curl_easy_setopt_err_sometype below * * NOTE: We use two nested 'if' statements here instead of the && operator, in - * order to work around gcc bug #32061. It affects only gcc 4.3.x/4.4.x + * order to work around gcc bug #32061. It affects only gcc 4.3.x/4.4.x * when compiling with -Wlogical-op. * - * To add an option that uses the same type as an existing option, you'll just - * need to extend the appropriate _curl_*_option macro + * To add an option that uses the same type as an existing option, you will + * just need to extend the appropriate _curl_*_option macro */ #define curl_easy_setopt(handle, option, value) \ __extension__({ \ @@ -245,7 +245,7 @@ CURLWARNING(_curl_easy_getinfo_err_curl_off_t, /* To add a new option to one of the groups, just add * (option) == CURLOPT_SOMETHING - * to the or-expression. If the option takes a long or curl_off_t, you don't + * to the or-expression. If the option takes a long or curl_off_t, you do not * have to do anything */ @@ -275,6 +275,7 @@ CURLWARNING(_curl_easy_getinfo_err_curl_off_t, (option) == CURLOPT_DNS_LOCAL_IP6 || \ (option) == CURLOPT_DNS_SERVERS || \ (option) == CURLOPT_DOH_URL || \ + (option) == CURLOPT_ECH || \ (option) == CURLOPT_EGDSOCKET || \ (option) == CURLOPT_FTP_ACCOUNT || \ (option) == CURLOPT_FTP_ALTERNATIVE_TO_USER || \ @@ -677,7 +678,7 @@ typedef CURLcode (*_curl_ssl_ctx_callback4)(CURL *, const void *, const void *); #ifdef HEADER_SSL_H /* hack: if we included OpenSSL's ssl.h, we know about SSL_CTX - * this will of course break if we're included before OpenSSL headers... + * this will of course break if we are included before OpenSSL headers... */ typedef CURLcode (*_curl_ssl_ctx_callback5)(CURL *, SSL_CTX *, void *); typedef CURLcode (*_curl_ssl_ctx_callback6)(CURL *, SSL_CTX *, const void *); diff --git a/deps/curl/include/curl/urlapi.h b/deps/curl/include/curl/urlapi.h index 88cdeb3bca6..b4a6e5d5670 100644 --- a/deps/curl/include/curl/urlapi.h +++ b/deps/curl/include/curl/urlapi.h @@ -63,6 +63,7 @@ typedef enum { CURLUE_BAD_SLASHES, /* 28 */ CURLUE_BAD_USER, /* 29 */ CURLUE_LACKS_IDN, /* 30 */ + CURLUE_TOO_LARGE, /* 31 */ CURLUE_LAST } CURLUcode; @@ -96,8 +97,12 @@ typedef enum { #define CURLU_NO_AUTHORITY (1<<10) /* Allow empty authority when the scheme is unknown. */ #define CURLU_ALLOW_SPACE (1<<11) /* Allow spaces in the URL */ -#define CURLU_PUNYCODE (1<<12) /* get the host name in punycode */ +#define CURLU_PUNYCODE (1<<12) /* get the hostname in punycode */ #define CURLU_PUNY2IDN (1<<13) /* punycode => IDN conversion */ +#define CURLU_GET_EMPTY (1<<14) /* allow empty queries and fragments + when extracting the URL or the + components */ +#define CURLU_NO_GUESS_SCHEME (1<<15) /* for get, do not accept a guess */ typedef struct Curl_URL CURLU; @@ -138,7 +143,7 @@ CURL_EXTERN CURLUcode curl_url_set(CURLU *handle, CURLUPart what, /* * curl_url_strerror() turns a CURLUcode value into the equivalent human - * readable error string. This is useful for printing meaningful error + * readable error string. This is useful for printing meaningful error * messages. */ CURL_EXTERN const char *curl_url_strerror(CURLUcode); diff --git a/deps/curl/lib/.checksrc b/deps/curl/lib/.checksrc index 16133a44c56..9066946c890 100644 --- a/deps/curl/lib/.checksrc +++ b/deps/curl/lib/.checksrc @@ -1 +1,2 @@ enable STRERROR +enable STRNCPY diff --git a/deps/curl/lib/CMakeLists.txt b/deps/curl/lib/CMakeLists.txt index 9bb8f0beb08..895bc9165ab 100644 --- a/deps/curl/lib/CMakeLists.txt +++ b/deps/curl/lib/CMakeLists.txt @@ -21,81 +21,46 @@ # SPDX-License-Identifier: curl # ########################################################################### -set(LIB_NAME libcurl) -set(LIBCURL_OUTPUT_NAME libcurl CACHE STRING "Basename of the curl library") -add_definitions(-DBUILDING_LIBCURL) +set(LIB_NAME "libcurl") +set(LIBCURL_OUTPUT_NAME "libcurl" CACHE STRING "Basename of the curl library") +add_definitions("-DBUILDING_LIBCURL") -configure_file(curl_config.h.cmake - ${CMAKE_CURRENT_BINARY_DIR}/curl_config.h) +configure_file("curl_config.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/curl_config.h") +# Get 'CSOURCES', 'HHEADERS' variables transform_makefile_inc("Makefile.inc" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake") -include(${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake) +include("${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake") -list(APPEND HHEADERS - ${CMAKE_CURRENT_BINARY_DIR}/curl_config.h - ) +# DllMain is added later for DLL builds only. +list(REMOVE_ITEM CSOURCES "dllmain.c") -# The rest of the build +list(APPEND HHEADERS "${CMAKE_CURRENT_BINARY_DIR}/curl_config.h") -include_directories(${CMAKE_CURRENT_BINARY_DIR}/../include) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include) -include_directories(${CMAKE_CURRENT_BINARY_DIR}/..) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) -if(USE_ARES) - include_directories(${CARES_INCLUDE_DIR}) -endif() +# The rest of the build -add_library( - curlu # special libcurlu library just for unittests - STATIC - EXCLUDE_FROM_ALL - ${HHEADERS} ${CSOURCES} +include_directories( + "${PROJECT_BINARY_DIR}/lib" # for "curl_config.h" + "${PROJECT_SOURCE_DIR}/lib" # for "curl_setup.h" ) -target_compile_definitions(curlu PUBLIC UNITTESTS CURL_STATICLIB) - -if(ENABLE_CURLDEBUG) - # We must compile memdebug.c separately to avoid memdebug.h redefinitions - # being applied to memdebug.c itself. - set_source_files_properties(memdebug.c PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) +if(USE_ARES) + include_directories(SYSTEM ${CARES_INCLUDE_DIRS}) endif() -target_link_libraries(curlu PRIVATE ${CURL_LIBS}) - -transform_makefile_inc("Makefile.soname" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.soname.cmake") -include(${CMAKE_CURRENT_BINARY_DIR}/Makefile.soname.cmake) - -if(CMAKE_SYSTEM_NAME STREQUAL "AIX" OR - CMAKE_SYSTEM_NAME STREQUAL "Linux" OR - CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR - CMAKE_SYSTEM_NAME STREQUAL "SunOS" OR - CMAKE_SYSTEM_NAME STREQUAL "GNU/kFreeBSD" OR - # FreeBSD comes with the a.out and elf flavours - # but a.out was supported up to version 3.x and - # elf from 3.x. I cannot imagine someone running - # CMake on those ancient systems - CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR - - CMAKE_SYSTEM_NAME STREQUAL "Haiku") - - math(EXPR CMAKESONAME "${VERSIONCHANGE} - ${VERSIONDEL}") - set(CMAKEVERSION "${CMAKESONAME}.${VERSIONDEL}.${VERSIONADD}") -else() - unset(CMAKESONAME) +if(CURL_BUILD_TESTING) + add_library( + curlu # special libcurlu library just for unittests + STATIC + EXCLUDE_FROM_ALL + ${HHEADERS} ${CSOURCES} + ) + target_compile_definitions(curlu PUBLIC "UNITTESTS" "CURL_STATICLIB") + target_link_libraries(curlu PRIVATE ${CURL_LIBS}) endif() -if(NOT WIN32 AND NOT CMAKE_CROSSCOMPILING) - # on not-Windows and not-crosscompiling, check for writable argv[] - include(CheckCSourceRuns) - check_c_source_runs(" -int main(int argc, char **argv) -{ - (void)argc; - argv[0][0] = ' '; - return (argv[0][0] == ' ')?0:1; -}" - HAVE_WRITABLE_ARGV) +if(ENABLE_CURLDEBUG) + # We must compile these sources separately to avoid memdebug.h redefinitions + # applying to them. + set_source_files_properties("memdebug.c" "curl_multibyte.c" PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) endif() ## Library definition @@ -125,15 +90,19 @@ endif() if(SHARE_LIB_OBJECT) set(LIB_OBJECT "libcurl_object") add_library(${LIB_OBJECT} OBJECT ${HHEADERS} ${CSOURCES}) + if(WIN32) + # Define CURL_STATICLIB always, to disable __declspec(dllexport) for + # exported libcurl symbols. We handle exports via libcurl.def instead. + # Except with symbol hiding disabled or debug mode enabled, when we export + # _all_ symbols from libcurl DLL, without using libcurl.def. + set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_STATICLIB") + endif() target_link_libraries(${LIB_OBJECT} PRIVATE ${CURL_LIBS}) set_target_properties(${LIB_OBJECT} PROPERTIES - COMPILE_DEFINITIONS "BUILDING_LIBCURL" - INTERFACE_COMPILE_DEFINITIONS "CURL_STATICLIB" POSITION_INDEPENDENT_CODE ON) - if(HIDES_CURL_PRIVATE_SYMBOLS) - set_target_properties(${LIB_OBJECT} PROPERTIES - COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS" - COMPILE_FLAGS "${CURL_CFLAG_SYMBOLS_HIDE}") + if(CURL_HIDES_PRIVATE_SYMBOLS) + set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_FLAGS "${CURL_CFLAG_SYMBOLS_HIDE}") + set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS") endif() if(CURL_HAS_LTO) set_target_properties(${LIB_OBJECT} PROPERTIES @@ -142,78 +111,157 @@ if(SHARE_LIB_OBJECT) endif() target_include_directories(${LIB_OBJECT} INTERFACE - $ - $) + "$" + "$") set(LIB_SOURCE $) else() set(LIB_SOURCE ${HHEADERS} ${CSOURCES}) endif() -# we want it to be called libcurl on all platforms +# We want it to be called libcurl on all platforms if(BUILD_STATIC_LIBS) list(APPEND libcurl_export ${LIB_STATIC}) add_library(${LIB_STATIC} STATIC ${LIB_SOURCE}) add_library(${PROJECT_NAME}::${LIB_STATIC} ALIAS ${LIB_STATIC}) + if(WIN32) + set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_STATICLIB") + endif() target_link_libraries(${LIB_STATIC} PRIVATE ${CURL_LIBS}) # Remove the "lib" prefix since the library is already named "libcurl". set_target_properties(${LIB_STATIC} PROPERTIES PREFIX "" OUTPUT_NAME "${LIBCURL_OUTPUT_NAME}" SUFFIX "${STATIC_LIB_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}" - COMPILE_DEFINITIONS "BUILDING_LIBCURL" INTERFACE_COMPILE_DEFINITIONS "CURL_STATICLIB") - if(HIDES_CURL_PRIVATE_SYMBOLS) - set_target_properties(${LIB_STATIC} PROPERTIES - COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS" - COMPILE_FLAGS "${CURL_CFLAG_SYMBOLS_HIDE}") + if(CURL_HIDES_PRIVATE_SYMBOLS) + set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_FLAGS "${CURL_CFLAG_SYMBOLS_HIDE}") + set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS") endif() if(CURL_HAS_LTO) set_target_properties(${LIB_STATIC} PROPERTIES INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE) endif() - if(CMAKEVERSION AND CMAKESONAME) - set_target_properties(${LIB_STATIC} PROPERTIES - VERSION ${CMAKEVERSION} SOVERSION ${CMAKESONAME}) - endif() target_include_directories(${LIB_STATIC} INTERFACE - $ - $) + "$" + "$") endif() if(BUILD_SHARED_LIBS) list(APPEND libcurl_export ${LIB_SHARED}) add_library(${LIB_SHARED} SHARED ${LIB_SOURCE}) add_library(${PROJECT_NAME}::${LIB_SHARED} ALIAS ${LIB_SHARED}) + if(WIN32 OR CYGWIN) + if(CYGWIN) + # For Cygwin always compile dllmain.c as a separate unit since it + # includes windows.h, which should not be included in other units. + set_source_files_properties("dllmain.c" PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) + endif() + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY SOURCES "dllmain.c") + endif() if(WIN32) - set_property(TARGET ${LIB_SHARED} APPEND PROPERTY SOURCES libcurl.rc ${CURL_SOURCE_DIR}/libcurl.def) + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY SOURCES "libcurl.rc") + if(CURL_HIDES_PRIVATE_SYMBOLS) + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY SOURCES "${PROJECT_SOURCE_DIR}/lib/libcurl.def") + endif() endif() target_link_libraries(${LIB_SHARED} PRIVATE ${CURL_LIBS}) # Remove the "lib" prefix since the library is already named "libcurl". set_target_properties(${LIB_SHARED} PROPERTIES PREFIX "" OUTPUT_NAME "${LIBCURL_OUTPUT_NAME}" IMPORT_PREFIX "" IMPORT_SUFFIX "${IMPORT_LIB_SUFFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX}" - COMPILE_DEFINITIONS "BUILDING_LIBCURL" POSITION_INDEPENDENT_CODE ON) - if(HIDES_CURL_PRIVATE_SYMBOLS) - set_target_properties(${LIB_SHARED} PROPERTIES - COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS" - COMPILE_FLAGS "${CURL_CFLAG_SYMBOLS_HIDE}") + if(CURL_HIDES_PRIVATE_SYMBOLS) + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_FLAGS "${CURL_CFLAG_SYMBOLS_HIDE}") + set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS") endif() if(CURL_HAS_LTO) set_target_properties(${LIB_SHARED} PROPERTIES INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE) endif() - if(CMAKEVERSION AND CMAKESONAME) + + target_include_directories(${LIB_SHARED} INTERFACE + "$" + "$") + + if(CMAKE_DLL_NAME_WITH_SOVERSION OR + CYGWIN OR + APPLE OR + CMAKE_SYSTEM_NAME STREQUAL "AIX" OR + CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "SunOS" OR + CMAKE_SYSTEM_NAME STREQUAL "Haiku" OR + CMAKE_SYSTEM_NAME STREQUAL "GNU/kFreeBSD" OR + # FreeBSD comes with the a.out and ELF flavours but a.out was supported + # up to v3.x and ELF from v3.x. I cannot imagine someone running CMake + # on those ancient systems. + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(_soversion_default TRUE) + else() + set(_soversion_default FALSE) + endif() + + option(CURL_LIBCURL_SOVERSION "Enable libcurl SOVERSION" ${_soversion_default}) + option(CURL_LIBCURL_VERSIONED_SYMBOLS "Enable libcurl versioned symbols" OFF) + + if(CURL_LIBCURL_SOVERSION OR CURL_LIBCURL_VERSIONED_SYMBOLS) + # Get 'VERSIONCHANGE', 'VERSIONADD', 'VERSIONDEL', 'VERSIONINFO' variables + transform_makefile_inc("Makefile.soname" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.soname.cmake") + include("${CMAKE_CURRENT_BINARY_DIR}/Makefile.soname.cmake") + + math(EXPR _cmakesoname "${VERSIONCHANGE} - ${VERSIONDEL}") + set(_cmakeversion "${_cmakesoname}.${VERSIONDEL}.${VERSIONADD}") + endif() + + if(CURL_LIBCURL_SOVERSION) set_target_properties(${LIB_SHARED} PROPERTIES - VERSION ${CMAKEVERSION} SOVERSION ${CMAKESONAME}) + VERSION "${_cmakeversion}" SOVERSION "${_cmakesoname}") endif() - target_include_directories(${LIB_SHARED} INTERFACE - $ - $) + ## Versioned symbols + + if(CURL_LIBCURL_VERSIONED_SYMBOLS) + if(NOT DEFINED CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX) + # Default to prefixes used by autotools + if(CURL_WITH_MULTI_SSL) + set(CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX "MULTISSL_") + elseif(CURL_USE_OPENSSL) + set(CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX "OPENSSL_") + elseif(CURL_USE_MBEDTLS) + set(CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX "MBEDTLS_") + elseif(CURL_USE_BEARSSL) + set(CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX "BEARSSL_") + elseif(CURL_USE_WOLFSSL) + set(CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX "WOLFSSL_") + elseif(CURL_USE_GNUTLS) + set(CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX "GNUTLS_") + elseif(CURL_USE_RUSTLS) + set(CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX "RUSTLS_") + endif() + endif() + # Generate version script for the linker, for versioned symbols. + # Consumed variables: + # CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX + # CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME + set(CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME ${_cmakesoname}) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/libcurl.vers.in" + "${CMAKE_CURRENT_BINARY_DIR}/libcurl.vers" @ONLY) + include(CMakePushCheckState) + include(CheckCSourceCompiles) + cmake_push_check_state() + set(CMAKE_REQUIRED_LINK_OPTIONS "-Wl,--version-script=${CMAKE_CURRENT_BINARY_DIR}/libcurl.vers") + check_c_source_compiles("int main(void) { return 0; }" HAVE_VERSIONED_SYMBOLS) + if(HAVE_VERSIONED_SYMBOLS) + # Superseded by LINK_OPTIONS in CMake 3.13 and later. + set_target_properties(${LIB_SHARED} PROPERTIES LINK_FLAGS "${CMAKE_REQUIRED_LINK_OPTIONS}") + else() + message(WARNING "Versioned symbols requested, but not supported by the toolchain.") + endif() + cmake_pop_check_state() + endif() endif() add_library(${LIB_NAME} ALIAS ${LIB_SELECTED}) @@ -238,7 +286,7 @@ if(CURL_ENABLE_EXPORT_TARGET) endif() export(TARGETS ${libcurl_export} - FILE ${PROJECT_BINARY_DIR}/libcurl-target.cmake - NAMESPACE ${PROJECT_NAME}:: + FILE "${PROJECT_BINARY_DIR}/libcurl-target.cmake" + NAMESPACE ${PROJECT_NAME}:: ) endif() diff --git a/deps/curl/lib/Makefile.am b/deps/curl/lib/Makefile.am index 3c0a709127d..3a40280b94c 100644 --- a/deps/curl/lib/Makefile.am +++ b/deps/curl/lib/Makefile.am @@ -25,11 +25,14 @@ AUTOMAKE_OPTIONS = foreign nostdinc CMAKE_DIST = CMakeLists.txt curl_config.h.cmake -EXTRA_DIST = Makefile.mk config-win32.h config-win32ce.h config-plan9.h \ - config-riscos.h config-mac.h curl_config.h.in config-dos.h \ - libcurl.plist libcurl.rc config-amigaos.h config-win32ce.h \ - config-os400.h setup-os400.h $(CMAKE_DIST) setup-win32.h .checksrc \ - Makefile.soname +CHECKSRC_DIST = .checksrc vauth/.checksrc vquic/.checksrc vssh/.checksrc \ + vtls/.checksrc + +EXTRA_DIST = Makefile.mk config-win32.h config-win32ce.h config-plan9.h \ + config-riscos.h config-mac.h curl_config.h.in config-dos.h libcurl.rc \ + config-amigaos.h config-win32ce.h config-os400.h setup-os400.h \ + $(CMAKE_DIST) setup-win32.h Makefile.soname optiontable.pl libcurl.def \ + $(CHECKSRC_DIST) lib_LTLIBRARIES = libcurl.la @@ -67,8 +70,27 @@ AM_CFLAGS = # Makefile.inc provides the CSOURCES and HHEADERS defines include Makefile.inc +if USE_UNITY +# Keep these separate to avoid duplicate definitions when linking libtests +# in static mode. +curl_EXCLUDE = curl_threads.c timediff.c warnless.c +if DEBUGBUILD +# We must compile these sources separately to avoid memdebug.h redefinitions +# applying to them. +curl_EXCLUDE += memdebug.c curl_multibyte.c +endif +libcurl_unity.c: $(top_srcdir)/scripts/mk-unity.pl $(CSOURCES) + @PERL@ $(top_srcdir)/scripts/mk-unity.pl $(srcdir) $(CSOURCES) --exclude $(curl_EXCLUDE) > libcurl_unity.c + +nodist_libcurl_la_SOURCES = libcurl_unity.c +libcurl_la_SOURCES = $(curl_EXCLUDE) +nodist_libcurlu_la_SOURCES = libcurl_unity.c +libcurlu_la_SOURCES = $(curl_EXCLUDE) +CLEANFILES = libcurl_unity.c +else libcurl_la_SOURCES = $(CSOURCES) $(HHEADERS) libcurlu_la_SOURCES = $(CSOURCES) $(HHEADERS) +endif libcurl_la_CPPFLAGS_EXTRA = libcurl_la_LDFLAGS_EXTRA = @@ -110,11 +132,11 @@ libcurl_la_CFLAGS_EXTRA += $(CFLAG_CURL_SYMBOL_HIDING) endif libcurl_la_CPPFLAGS = $(AM_CPPFLAGS) $(libcurl_la_CPPFLAGS_EXTRA) -libcurl_la_LDFLAGS = $(AM_LDFLAGS) $(libcurl_la_LDFLAGS_EXTRA) $(LDFLAGS) $(LIBCURL_LIBS) +libcurl_la_LDFLAGS = $(AM_LDFLAGS) $(libcurl_la_LDFLAGS_EXTRA) $(CURL_LDFLAGS_LIB) $(LIBCURL_PC_LIBS_PRIVATE) libcurl_la_CFLAGS = $(AM_CFLAGS) $(libcurl_la_CFLAGS_EXTRA) libcurlu_la_CPPFLAGS = $(AM_CPPFLAGS) -DCURL_STATICLIB -DUNITTESTS -libcurlu_la_LDFLAGS = $(AM_LDFLAGS) -static $(LIBCURL_LIBS) +libcurlu_la_LDFLAGS = $(AM_LDFLAGS) -static $(LIBCURL_PC_LIBS_PRIVATE) libcurlu_la_CFLAGS = $(AM_CFLAGS) CHECKSRC = $(CS_$(V)) @@ -127,7 +149,7 @@ checksrc: -W$(srcdir)/curl_config.h $(srcdir)/*.[ch] $(srcdir)/vauth/*.[ch] \ $(srcdir)/vtls/*.[ch] $(srcdir)/vquic/*.[ch] $(srcdir)/vssh/*.[ch]) -if CURLDEBUG +if DEBUGBUILD # for debug builds, we scan the sources on all regular make invokes all-local: checksrc endif diff --git a/deps/curl/lib/Makefile.in b/deps/curl/lib/Makefile.in index 2713d3d61c7..5c858b222cc 100644 --- a/deps/curl/lib/Makefile.in +++ b/deps/curl/lib/Makefile.in @@ -136,16 +136,19 @@ PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ -@CURL_LT_SHLIB_USE_VERSION_INFO_TRUE@am__append_1 = $(VERSIONINFO) -@CURL_LT_SHLIB_USE_NO_UNDEFINED_TRUE@am__append_2 = -no-undefined -@CURL_LT_SHLIB_USE_MIMPURE_TEXT_TRUE@am__append_3 = -mimpure-text -@CURL_LT_SHLIB_USE_VERSIONED_SYMBOLS_TRUE@am__append_4 = -Wl,--version-script=libcurl.vers +# We must compile these sources separately to avoid memdebug.h redefinitions +# applying to them. +@DEBUGBUILD_TRUE@@USE_UNITY_TRUE@am__append_1 = memdebug.c curl_multibyte.c +@CURL_LT_SHLIB_USE_VERSION_INFO_TRUE@am__append_2 = $(VERSIONINFO) +@CURL_LT_SHLIB_USE_NO_UNDEFINED_TRUE@am__append_3 = -no-undefined +@CURL_LT_SHLIB_USE_MIMPURE_TEXT_TRUE@am__append_4 = -mimpure-text +@CURL_LT_SHLIB_USE_VERSIONED_SYMBOLS_TRUE@am__append_5 = -Wl,--version-script=libcurl.vers # if symbol-hiding is enabled, hide them! -@CURL_LT_SHLIB_USE_VERSIONED_SYMBOLS_FALSE@@DOING_CURL_SYMBOL_HIDING_TRUE@am__append_5 = -export-symbols-regex '^curl_.*' -@USE_CPPFLAG_CURL_STATICLIB_TRUE@am__append_6 = -DCURL_STATICLIB -@HAVE_WINDRES_TRUE@@USE_CPPFLAG_CURL_STATICLIB_FALSE@am__append_7 = $(LIB_RCFILES) -@DOING_CURL_SYMBOL_HIDING_TRUE@am__append_8 = -DCURL_HIDDEN_SYMBOLS -@DOING_CURL_SYMBOL_HIDING_TRUE@am__append_9 = $(CFLAG_CURL_SYMBOL_HIDING) +@CURL_LT_SHLIB_USE_VERSIONED_SYMBOLS_FALSE@@DOING_CURL_SYMBOL_HIDING_TRUE@am__append_6 = -export-symbols-regex '^curl_.*' +@USE_CPPFLAG_CURL_STATICLIB_TRUE@am__append_7 = -DCURL_STATICLIB +@HAVE_WINDRES_TRUE@@USE_CPPFLAG_CURL_STATICLIB_FALSE@am__append_8 = $(LIB_RCFILES) +@DOING_CURL_SYMBOL_HIDING_TRUE@am__append_9 = -DCURL_HIDDEN_SYMBOLS +@DOING_CURL_SYMBOL_HIDING_TRUE@am__append_10 = $(CFLAG_CURL_SYMBOL_HIDING) subdir = lib ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/curl-amissl.m4 \ @@ -168,7 +171,6 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/curl-amissl.m4 \ $(top_srcdir)/m4/xc-am-iface.m4 \ $(top_srcdir)/m4/xc-cc-check.m4 \ $(top_srcdir)/m4/xc-lt-iface.m4 \ - $(top_srcdir)/m4/xc-translit.m4 \ $(top_srcdir)/m4/xc-val-flgs.m4 \ $(top_srcdir)/m4/zz40-xc-ovr.m4 \ $(top_srcdir)/m4/zz50-xc-ovr.m4 \ @@ -179,7 +181,7 @@ am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = curl_config.h -CONFIG_CLEAN_FILES = libcurl.vers libcurl.plist +CONFIG_CLEAN_FILES = libcurl.vers CONFIG_CLEAN_VPATH_FILES = am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ @@ -217,35 +219,36 @@ am__libcurl_la_SOURCES_DIST = altsvc.c amigaos.c asyn-ares.c \ cfilters.c conncache.c connect.c content_encoding.c cookie.c \ curl_addrinfo.c curl_des.c curl_endian.c curl_fnmatch.c \ curl_get_line.c curl_gethostname.c curl_gssapi.c \ - curl_memrchr.c curl_multibyte.c curl_ntlm_core.c \ - curl_ntlm_wb.c curl_path.c curl_range.c curl_rtmp.c \ - curl_sasl.c curl_sspi.c curl_threads.c curl_trc.c dict.c doh.c \ + curl_memrchr.c curl_multibyte.c curl_ntlm_core.c curl_range.c \ + curl_rtmp.c curl_sasl.c curl_sha512_256.c curl_sspi.c \ + curl_threads.c curl_trc.c cw-out.c dict.c dllmain.c doh.c \ dynbuf.c dynhds.c easy.c easygetopt.c easyoptions.c escape.c \ file.c fileinfo.c fopen.c formdata.c ftp.c ftplistparser.c \ getenv.c getinfo.c gopher.c hash.c headers.c hmac.c hostasyn.c \ hostip.c hostip4.c hostip6.c hostsyn.c hsts.c http.c http1.c \ - http2.c http_chunks.c http_digest.c http_negotiate.c \ - http_ntlm.c http_proxy.c http_aws_sigv4.c idn.c if2ip.c imap.c \ + http2.c http_aws_sigv4.c http_chunks.c http_digest.c \ + http_negotiate.c http_ntlm.c http_proxy.c idn.c if2ip.c imap.c \ inet_ntop.c inet_pton.c krb5.c ldap.c llist.c macos.c md4.c \ md5.c memdebug.c mime.c mprintf.c mqtt.c multi.c netrc.c \ nonblock.c noproxy.c openldap.c parsedate.c pingpong.c pop3.c \ - progress.c psl.c rand.c rename.c rtsp.c select.c sendf.c \ - setopt.c sha256.c share.c slist.c smb.c smtp.c socketpair.c \ - socks.c socks_gssapi.c socks_sspi.c speedcheck.c splay.c \ - strcase.c strdup.c strerror.c strtok.c strtoofft.c \ + progress.c psl.c rand.c rename.c request.c rtsp.c select.c \ + sendf.c setopt.c sha256.c share.c slist.c smb.c smtp.c \ + socketpair.c socks.c socks_gssapi.c socks_sspi.c speedcheck.c \ + splay.c strcase.c strdup.c strerror.c strtok.c strtoofft.c \ system_win32.c telnet.c tftp.c timediff.c timeval.c transfer.c \ url.c urlapi.c version.c version_win32.c warnless.c ws.c \ vauth/cleartext.c vauth/cram.c vauth/digest.c \ vauth/digest_sspi.c vauth/gsasl.c vauth/krb5_gssapi.c \ vauth/krb5_sspi.c vauth/ntlm.c vauth/ntlm_sspi.c \ vauth/oauth2.c vauth/spnego_gssapi.c vauth/spnego_sspi.c \ - vauth/vauth.c vtls/bearssl.c vtls/gtls.c vtls/hostcheck.c \ - vtls/keylog.c vtls/mbedtls.c vtls/mbedtls_threadlock.c \ - vtls/openssl.c vtls/rustls.c vtls/schannel.c \ - vtls/schannel_verify.c vtls/sectransp.c vtls/vtls.c \ - vtls/wolfssl.c vtls/x509asn1.c vquic/curl_msh3.c \ - vquic/curl_ngtcp2.c vquic/curl_quiche.c vquic/vquic.c \ - vssh/libssh.c vssh/libssh2.c vssh/wolfssh.c altsvc.h amigaos.h \ + vauth/vauth.c vtls/bearssl.c vtls/cipher_suite.c vtls/gtls.c \ + vtls/hostcheck.c vtls/keylog.c vtls/mbedtls.c \ + vtls/mbedtls_threadlock.c vtls/openssl.c vtls/rustls.c \ + vtls/schannel.c vtls/schannel_verify.c vtls/sectransp.c \ + vtls/vtls.c vtls/wolfssl.c vtls/x509asn1.c vquic/curl_msh3.c \ + vquic/curl_ngtcp2.c vquic/curl_osslq.c vquic/curl_quiche.c \ + vquic/vquic.c vquic/vquic-tls.c vssh/libssh.c vssh/libssh2.c \ + vssh/curl_path.c vssh/wolfssh.c altsvc.h amigaos.h \ arpa_telnet.h asyn.h bufq.h bufref.h c-hyper.h cf-h1-proxy.h \ cf-h2-proxy.h cf-haproxy.h cf-https-connect.h cf-socket.h \ cfilters.h conncache.h connect.h content_encoding.h cookie.h \ @@ -253,31 +256,32 @@ am__libcurl_la_SOURCES_DIST = altsvc.c amigaos.c asyn-ares.c \ curl_endian.h curl_fnmatch.h curl_get_line.h \ curl_gethostname.h curl_gssapi.h curl_hmac.h curl_krb5.h \ curl_ldap.h curl_md4.h curl_md5.h curl_memory.h curl_memrchr.h \ - curl_multibyte.h curl_ntlm_core.h curl_ntlm_wb.h curl_path.h \ - curl_printf.h curl_range.h curl_rtmp.h curl_sasl.h \ - curl_setup.h curl_setup_once.h curl_sha256.h curl_sspi.h \ - curl_threads.h curl_trc.h curlx.h dict.h doh.h dynbuf.h \ - dynhds.h easy_lock.h easyif.h easyoptions.h escape.h file.h \ - fileinfo.h fopen.h formdata.h functypes.h ftp.h \ - ftplistparser.h getinfo.h gopher.h hash.h headers.h hostip.h \ - hsts.h http.h http1.h http2.h http_chunks.h http_digest.h \ - http_negotiate.h http_ntlm.h http_proxy.h http_aws_sigv4.h \ - idn.h if2ip.h imap.h inet_ntop.h inet_pton.h llist.h macos.h \ - memdebug.h mime.h mqtt.h multihandle.h multiif.h netrc.h \ - nonblock.h noproxy.h parsedate.h pingpong.h pop3.h progress.h \ - psl.h rand.h rename.h rtsp.h select.h sendf.h setopt.h \ - setup-vms.h share.h sigpipe.h slist.h smb.h smtp.h sockaddr.h \ - socketpair.h socks.h speedcheck.h splay.h strcase.h strdup.h \ - strerror.h strtok.h strtoofft.h system_win32.h telnet.h tftp.h \ - timediff.h timeval.h transfer.h url.h urlapi-int.h urldata.h \ + curl_multibyte.h curl_ntlm_core.h curl_printf.h curl_range.h \ + curl_rtmp.h curl_sasl.h curl_setup.h curl_setup_once.h \ + curl_sha256.h curl_sha512_256.h curl_sspi.h curl_threads.h \ + curl_trc.h curlx.h cw-out.h dict.h doh.h dynbuf.h dynhds.h \ + easy_lock.h easyif.h easyoptions.h escape.h file.h fileinfo.h \ + fopen.h formdata.h ftp.h ftplistparser.h functypes.h getinfo.h \ + gopher.h hash.h headers.h hostip.h hsts.h http.h http1.h \ + http2.h http_aws_sigv4.h http_chunks.h http_digest.h \ + http_negotiate.h http_ntlm.h http_proxy.h idn.h if2ip.h imap.h \ + inet_ntop.h inet_pton.h llist.h macos.h memdebug.h mime.h \ + mqtt.h multihandle.h multiif.h netrc.h nonblock.h noproxy.h \ + parsedate.h pingpong.h pop3.h progress.h psl.h rand.h rename.h \ + request.h rtsp.h select.h sendf.h setopt.h setup-vms.h share.h \ + sigpipe.h slist.h smb.h smtp.h sockaddr.h socketpair.h socks.h \ + speedcheck.h splay.h strcase.h strdup.h strerror.h strtok.h \ + strtoofft.h system_win32.h telnet.h tftp.h timediff.h \ + timeval.h transfer.h url.h urlapi-int.h urldata.h \ version_win32.h warnless.h ws.h vauth/digest.h vauth/ntlm.h \ - vauth/vauth.h vtls/bearssl.h vtls/gtls.h vtls/hostcheck.h \ - vtls/keylog.h vtls/mbedtls.h vtls/mbedtls_threadlock.h \ - vtls/openssl.h vtls/rustls.h vtls/schannel.h \ - vtls/schannel_int.h vtls/sectransp.h vtls/vtls.h \ - vtls/vtls_int.h vtls/wolfssl.h vtls/x509asn1.h \ - vquic/curl_msh3.h vquic/curl_ngtcp2.h vquic/curl_quiche.h \ - vquic/vquic.h vquic/vquic_int.h vssh/ssh.h libcurl.rc + vauth/vauth.h vtls/bearssl.h vtls/cipher_suite.h vtls/gtls.h \ + vtls/hostcheck.h vtls/keylog.h vtls/mbedtls.h \ + vtls/mbedtls_threadlock.h vtls/openssl.h vtls/rustls.h \ + vtls/schannel.h vtls/schannel_int.h vtls/sectransp.h \ + vtls/vtls.h vtls/vtls_int.h vtls/wolfssl.h vtls/x509asn1.h \ + vquic/curl_msh3.h vquic/curl_ngtcp2.h vquic/curl_osslq.h \ + vquic/curl_quiche.h vquic/vquic.h vquic/vquic_int.h \ + vquic/vquic-tls.h vssh/curl_path.h vssh/ssh.h libcurl.rc am__objects_1 = libcurl_la-altsvc.lo libcurl_la-amigaos.lo \ libcurl_la-asyn-ares.lo libcurl_la-asyn-thread.lo \ libcurl_la-base64.lo libcurl_la-bufq.lo libcurl_la-bufref.lo \ @@ -291,15 +295,16 @@ am__objects_1 = libcurl_la-altsvc.lo libcurl_la-amigaos.lo \ libcurl_la-curl_fnmatch.lo libcurl_la-curl_get_line.lo \ libcurl_la-curl_gethostname.lo libcurl_la-curl_gssapi.lo \ libcurl_la-curl_memrchr.lo libcurl_la-curl_multibyte.lo \ - libcurl_la-curl_ntlm_core.lo libcurl_la-curl_ntlm_wb.lo \ - libcurl_la-curl_path.lo libcurl_la-curl_range.lo \ + libcurl_la-curl_ntlm_core.lo libcurl_la-curl_range.lo \ libcurl_la-curl_rtmp.lo libcurl_la-curl_sasl.lo \ - libcurl_la-curl_sspi.lo libcurl_la-curl_threads.lo \ - libcurl_la-curl_trc.lo libcurl_la-dict.lo libcurl_la-doh.lo \ - libcurl_la-dynbuf.lo libcurl_la-dynhds.lo libcurl_la-easy.lo \ - libcurl_la-easygetopt.lo libcurl_la-easyoptions.lo \ - libcurl_la-escape.lo libcurl_la-file.lo libcurl_la-fileinfo.lo \ - libcurl_la-fopen.lo libcurl_la-formdata.lo libcurl_la-ftp.lo \ + libcurl_la-curl_sha512_256.lo libcurl_la-curl_sspi.lo \ + libcurl_la-curl_threads.lo libcurl_la-curl_trc.lo \ + libcurl_la-cw-out.lo libcurl_la-dict.lo libcurl_la-dllmain.lo \ + libcurl_la-doh.lo libcurl_la-dynbuf.lo libcurl_la-dynhds.lo \ + libcurl_la-easy.lo libcurl_la-easygetopt.lo \ + libcurl_la-easyoptions.lo libcurl_la-escape.lo \ + libcurl_la-file.lo libcurl_la-fileinfo.lo libcurl_la-fopen.lo \ + libcurl_la-formdata.lo libcurl_la-ftp.lo \ libcurl_la-ftplistparser.lo libcurl_la-getenv.lo \ libcurl_la-getinfo.lo libcurl_la-gopher.lo libcurl_la-hash.lo \ libcurl_la-headers.lo libcurl_la-hmac.lo \ @@ -307,9 +312,9 @@ am__objects_1 = libcurl_la-altsvc.lo libcurl_la-amigaos.lo \ libcurl_la-hostip4.lo libcurl_la-hostip6.lo \ libcurl_la-hostsyn.lo libcurl_la-hsts.lo libcurl_la-http.lo \ libcurl_la-http1.lo libcurl_la-http2.lo \ - libcurl_la-http_chunks.lo libcurl_la-http_digest.lo \ - libcurl_la-http_negotiate.lo libcurl_la-http_ntlm.lo \ - libcurl_la-http_proxy.lo libcurl_la-http_aws_sigv4.lo \ + libcurl_la-http_aws_sigv4.lo libcurl_la-http_chunks.lo \ + libcurl_la-http_digest.lo libcurl_la-http_negotiate.lo \ + libcurl_la-http_ntlm.lo libcurl_la-http_proxy.lo \ libcurl_la-idn.lo libcurl_la-if2ip.lo libcurl_la-imap.lo \ libcurl_la-inet_ntop.lo libcurl_la-inet_pton.lo \ libcurl_la-krb5.lo libcurl_la-ldap.lo libcurl_la-llist.lo \ @@ -320,13 +325,14 @@ am__objects_1 = libcurl_la-altsvc.lo libcurl_la-amigaos.lo \ libcurl_la-noproxy.lo libcurl_la-openldap.lo \ libcurl_la-parsedate.lo libcurl_la-pingpong.lo \ libcurl_la-pop3.lo libcurl_la-progress.lo libcurl_la-psl.lo \ - libcurl_la-rand.lo libcurl_la-rename.lo libcurl_la-rtsp.lo \ - libcurl_la-select.lo libcurl_la-sendf.lo libcurl_la-setopt.lo \ - libcurl_la-sha256.lo libcurl_la-share.lo libcurl_la-slist.lo \ - libcurl_la-smb.lo libcurl_la-smtp.lo libcurl_la-socketpair.lo \ - libcurl_la-socks.lo libcurl_la-socks_gssapi.lo \ - libcurl_la-socks_sspi.lo libcurl_la-speedcheck.lo \ - libcurl_la-splay.lo libcurl_la-strcase.lo libcurl_la-strdup.lo \ + libcurl_la-rand.lo libcurl_la-rename.lo libcurl_la-request.lo \ + libcurl_la-rtsp.lo libcurl_la-select.lo libcurl_la-sendf.lo \ + libcurl_la-setopt.lo libcurl_la-sha256.lo libcurl_la-share.lo \ + libcurl_la-slist.lo libcurl_la-smb.lo libcurl_la-smtp.lo \ + libcurl_la-socketpair.lo libcurl_la-socks.lo \ + libcurl_la-socks_gssapi.lo libcurl_la-socks_sspi.lo \ + libcurl_la-speedcheck.lo libcurl_la-splay.lo \ + libcurl_la-strcase.lo libcurl_la-strdup.lo \ libcurl_la-strerror.lo libcurl_la-strtok.lo \ libcurl_la-strtoofft.lo libcurl_la-system_win32.lo \ libcurl_la-telnet.lo libcurl_la-tftp.lo libcurl_la-timediff.lo \ @@ -342,7 +348,8 @@ am__objects_2 = vauth/libcurl_la-cleartext.lo vauth/libcurl_la-cram.lo \ vauth/libcurl_la-ntlm_sspi.lo vauth/libcurl_la-oauth2.lo \ vauth/libcurl_la-spnego_gssapi.lo \ vauth/libcurl_la-spnego_sspi.lo vauth/libcurl_la-vauth.lo -am__objects_3 = vtls/libcurl_la-bearssl.lo vtls/libcurl_la-gtls.lo \ +am__objects_3 = vtls/libcurl_la-bearssl.lo \ + vtls/libcurl_la-cipher_suite.lo vtls/libcurl_la-gtls.lo \ vtls/libcurl_la-hostcheck.lo vtls/libcurl_la-keylog.lo \ vtls/libcurl_la-mbedtls.lo \ vtls/libcurl_la-mbedtls_threadlock.lo \ @@ -351,10 +358,11 @@ am__objects_3 = vtls/libcurl_la-bearssl.lo vtls/libcurl_la-gtls.lo \ vtls/libcurl_la-sectransp.lo vtls/libcurl_la-vtls.lo \ vtls/libcurl_la-wolfssl.lo vtls/libcurl_la-x509asn1.lo am__objects_4 = vquic/libcurl_la-curl_msh3.lo \ - vquic/libcurl_la-curl_ngtcp2.lo \ - vquic/libcurl_la-curl_quiche.lo vquic/libcurl_la-vquic.lo + vquic/libcurl_la-curl_ngtcp2.lo vquic/libcurl_la-curl_osslq.lo \ + vquic/libcurl_la-curl_quiche.lo vquic/libcurl_la-vquic.lo \ + vquic/libcurl_la-vquic-tls.lo am__objects_5 = vssh/libcurl_la-libssh.lo vssh/libcurl_la-libssh2.lo \ - vssh/libcurl_la-wolfssh.lo + vssh/libcurl_la-curl_path.lo vssh/libcurl_la-wolfssh.lo am__objects_6 = $(am__objects_1) $(am__objects_2) $(am__objects_3) \ $(am__objects_4) $(am__objects_5) am__objects_7 = @@ -362,18 +370,98 @@ am__objects_8 = $(am__objects_7) $(am__objects_7) $(am__objects_7) \ $(am__objects_7) $(am__objects_7) am__objects_9 = libcurl.lo @HAVE_WINDRES_TRUE@@USE_CPPFLAG_CURL_STATICLIB_FALSE@am__objects_10 = $(am__objects_9) -am_libcurl_la_OBJECTS = $(am__objects_6) $(am__objects_8) \ - $(am__objects_10) -libcurl_la_OBJECTS = $(am_libcurl_la_OBJECTS) +@DEBUGBUILD_TRUE@@USE_UNITY_TRUE@am__objects_11 = \ +@DEBUGBUILD_TRUE@@USE_UNITY_TRUE@ libcurl_la-memdebug.lo \ +@DEBUGBUILD_TRUE@@USE_UNITY_TRUE@ libcurl_la-curl_multibyte.lo +@USE_UNITY_TRUE@am__objects_12 = libcurl_la-curl_threads.lo \ +@USE_UNITY_TRUE@ libcurl_la-timediff.lo libcurl_la-warnless.lo \ +@USE_UNITY_TRUE@ $(am__objects_11) +@USE_UNITY_FALSE@am_libcurl_la_OBJECTS = $(am__objects_6) \ +@USE_UNITY_FALSE@ $(am__objects_8) $(am__objects_10) +@USE_UNITY_TRUE@am_libcurl_la_OBJECTS = $(am__objects_12) \ +@USE_UNITY_TRUE@ $(am__objects_10) +@USE_UNITY_TRUE@nodist_libcurl_la_OBJECTS = \ +@USE_UNITY_TRUE@ libcurl_la-libcurl_unity.lo +libcurl_la_OBJECTS = $(am_libcurl_la_OBJECTS) \ + $(nodist_libcurl_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent -am__v_lt_1 = +am__v_lt_1 = libcurl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(libcurl_la_CFLAGS) \ $(CFLAGS) $(libcurl_la_LDFLAGS) $(LDFLAGS) -o $@ libcurlu_la_LIBADD = -am__objects_11 = libcurlu_la-altsvc.lo libcurlu_la-amigaos.lo \ +am__libcurlu_la_SOURCES_DIST = altsvc.c amigaos.c asyn-ares.c \ + asyn-thread.c base64.c bufq.c bufref.c c-hyper.c cf-h1-proxy.c \ + cf-h2-proxy.c cf-haproxy.c cf-https-connect.c cf-socket.c \ + cfilters.c conncache.c connect.c content_encoding.c cookie.c \ + curl_addrinfo.c curl_des.c curl_endian.c curl_fnmatch.c \ + curl_get_line.c curl_gethostname.c curl_gssapi.c \ + curl_memrchr.c curl_multibyte.c curl_ntlm_core.c curl_range.c \ + curl_rtmp.c curl_sasl.c curl_sha512_256.c curl_sspi.c \ + curl_threads.c curl_trc.c cw-out.c dict.c dllmain.c doh.c \ + dynbuf.c dynhds.c easy.c easygetopt.c easyoptions.c escape.c \ + file.c fileinfo.c fopen.c formdata.c ftp.c ftplistparser.c \ + getenv.c getinfo.c gopher.c hash.c headers.c hmac.c hostasyn.c \ + hostip.c hostip4.c hostip6.c hostsyn.c hsts.c http.c http1.c \ + http2.c http_aws_sigv4.c http_chunks.c http_digest.c \ + http_negotiate.c http_ntlm.c http_proxy.c idn.c if2ip.c imap.c \ + inet_ntop.c inet_pton.c krb5.c ldap.c llist.c macos.c md4.c \ + md5.c memdebug.c mime.c mprintf.c mqtt.c multi.c netrc.c \ + nonblock.c noproxy.c openldap.c parsedate.c pingpong.c pop3.c \ + progress.c psl.c rand.c rename.c request.c rtsp.c select.c \ + sendf.c setopt.c sha256.c share.c slist.c smb.c smtp.c \ + socketpair.c socks.c socks_gssapi.c socks_sspi.c speedcheck.c \ + splay.c strcase.c strdup.c strerror.c strtok.c strtoofft.c \ + system_win32.c telnet.c tftp.c timediff.c timeval.c transfer.c \ + url.c urlapi.c version.c version_win32.c warnless.c ws.c \ + vauth/cleartext.c vauth/cram.c vauth/digest.c \ + vauth/digest_sspi.c vauth/gsasl.c vauth/krb5_gssapi.c \ + vauth/krb5_sspi.c vauth/ntlm.c vauth/ntlm_sspi.c \ + vauth/oauth2.c vauth/spnego_gssapi.c vauth/spnego_sspi.c \ + vauth/vauth.c vtls/bearssl.c vtls/cipher_suite.c vtls/gtls.c \ + vtls/hostcheck.c vtls/keylog.c vtls/mbedtls.c \ + vtls/mbedtls_threadlock.c vtls/openssl.c vtls/rustls.c \ + vtls/schannel.c vtls/schannel_verify.c vtls/sectransp.c \ + vtls/vtls.c vtls/wolfssl.c vtls/x509asn1.c vquic/curl_msh3.c \ + vquic/curl_ngtcp2.c vquic/curl_osslq.c vquic/curl_quiche.c \ + vquic/vquic.c vquic/vquic-tls.c vssh/libssh.c vssh/libssh2.c \ + vssh/curl_path.c vssh/wolfssh.c altsvc.h amigaos.h \ + arpa_telnet.h asyn.h bufq.h bufref.h c-hyper.h cf-h1-proxy.h \ + cf-h2-proxy.h cf-haproxy.h cf-https-connect.h cf-socket.h \ + cfilters.h conncache.h connect.h content_encoding.h cookie.h \ + curl_addrinfo.h curl_base64.h curl_ctype.h curl_des.h \ + curl_endian.h curl_fnmatch.h curl_get_line.h \ + curl_gethostname.h curl_gssapi.h curl_hmac.h curl_krb5.h \ + curl_ldap.h curl_md4.h curl_md5.h curl_memory.h curl_memrchr.h \ + curl_multibyte.h curl_ntlm_core.h curl_printf.h curl_range.h \ + curl_rtmp.h curl_sasl.h curl_setup.h curl_setup_once.h \ + curl_sha256.h curl_sha512_256.h curl_sspi.h curl_threads.h \ + curl_trc.h curlx.h cw-out.h dict.h doh.h dynbuf.h dynhds.h \ + easy_lock.h easyif.h easyoptions.h escape.h file.h fileinfo.h \ + fopen.h formdata.h ftp.h ftplistparser.h functypes.h getinfo.h \ + gopher.h hash.h headers.h hostip.h hsts.h http.h http1.h \ + http2.h http_aws_sigv4.h http_chunks.h http_digest.h \ + http_negotiate.h http_ntlm.h http_proxy.h idn.h if2ip.h imap.h \ + inet_ntop.h inet_pton.h llist.h macos.h memdebug.h mime.h \ + mqtt.h multihandle.h multiif.h netrc.h nonblock.h noproxy.h \ + parsedate.h pingpong.h pop3.h progress.h psl.h rand.h rename.h \ + request.h rtsp.h select.h sendf.h setopt.h setup-vms.h share.h \ + sigpipe.h slist.h smb.h smtp.h sockaddr.h socketpair.h socks.h \ + speedcheck.h splay.h strcase.h strdup.h strerror.h strtok.h \ + strtoofft.h system_win32.h telnet.h tftp.h timediff.h \ + timeval.h transfer.h url.h urlapi-int.h urldata.h \ + version_win32.h warnless.h ws.h vauth/digest.h vauth/ntlm.h \ + vauth/vauth.h vtls/bearssl.h vtls/cipher_suite.h vtls/gtls.h \ + vtls/hostcheck.h vtls/keylog.h vtls/mbedtls.h \ + vtls/mbedtls_threadlock.h vtls/openssl.h vtls/rustls.h \ + vtls/schannel.h vtls/schannel_int.h vtls/sectransp.h \ + vtls/vtls.h vtls/vtls_int.h vtls/wolfssl.h vtls/x509asn1.h \ + vquic/curl_msh3.h vquic/curl_ngtcp2.h vquic/curl_osslq.h \ + vquic/curl_quiche.h vquic/vquic.h vquic/vquic_int.h \ + vquic/vquic-tls.h vssh/curl_path.h vssh/ssh.h +am__objects_13 = libcurlu_la-altsvc.lo libcurlu_la-amigaos.lo \ libcurlu_la-asyn-ares.lo libcurlu_la-asyn-thread.lo \ libcurlu_la-base64.lo libcurlu_la-bufq.lo \ libcurlu_la-bufref.lo libcurlu_la-c-hyper.lo \ @@ -387,26 +475,27 @@ am__objects_11 = libcurlu_la-altsvc.lo libcurlu_la-amigaos.lo \ libcurlu_la-curl_get_line.lo libcurlu_la-curl_gethostname.lo \ libcurlu_la-curl_gssapi.lo libcurlu_la-curl_memrchr.lo \ libcurlu_la-curl_multibyte.lo libcurlu_la-curl_ntlm_core.lo \ - libcurlu_la-curl_ntlm_wb.lo libcurlu_la-curl_path.lo \ libcurlu_la-curl_range.lo libcurlu_la-curl_rtmp.lo \ - libcurlu_la-curl_sasl.lo libcurlu_la-curl_sspi.lo \ - libcurlu_la-curl_threads.lo libcurlu_la-curl_trc.lo \ - libcurlu_la-dict.lo libcurlu_la-doh.lo libcurlu_la-dynbuf.lo \ - libcurlu_la-dynhds.lo libcurlu_la-easy.lo \ - libcurlu_la-easygetopt.lo libcurlu_la-easyoptions.lo \ - libcurlu_la-escape.lo libcurlu_la-file.lo \ - libcurlu_la-fileinfo.lo libcurlu_la-fopen.lo \ - libcurlu_la-formdata.lo libcurlu_la-ftp.lo \ - libcurlu_la-ftplistparser.lo libcurlu_la-getenv.lo \ - libcurlu_la-getinfo.lo libcurlu_la-gopher.lo \ - libcurlu_la-hash.lo libcurlu_la-headers.lo libcurlu_la-hmac.lo \ + libcurlu_la-curl_sasl.lo libcurlu_la-curl_sha512_256.lo \ + libcurlu_la-curl_sspi.lo libcurlu_la-curl_threads.lo \ + libcurlu_la-curl_trc.lo libcurlu_la-cw-out.lo \ + libcurlu_la-dict.lo libcurlu_la-dllmain.lo libcurlu_la-doh.lo \ + libcurlu_la-dynbuf.lo libcurlu_la-dynhds.lo \ + libcurlu_la-easy.lo libcurlu_la-easygetopt.lo \ + libcurlu_la-easyoptions.lo libcurlu_la-escape.lo \ + libcurlu_la-file.lo libcurlu_la-fileinfo.lo \ + libcurlu_la-fopen.lo libcurlu_la-formdata.lo \ + libcurlu_la-ftp.lo libcurlu_la-ftplistparser.lo \ + libcurlu_la-getenv.lo libcurlu_la-getinfo.lo \ + libcurlu_la-gopher.lo libcurlu_la-hash.lo \ + libcurlu_la-headers.lo libcurlu_la-hmac.lo \ libcurlu_la-hostasyn.lo libcurlu_la-hostip.lo \ libcurlu_la-hostip4.lo libcurlu_la-hostip6.lo \ libcurlu_la-hostsyn.lo libcurlu_la-hsts.lo libcurlu_la-http.lo \ libcurlu_la-http1.lo libcurlu_la-http2.lo \ - libcurlu_la-http_chunks.lo libcurlu_la-http_digest.lo \ - libcurlu_la-http_negotiate.lo libcurlu_la-http_ntlm.lo \ - libcurlu_la-http_proxy.lo libcurlu_la-http_aws_sigv4.lo \ + libcurlu_la-http_aws_sigv4.lo libcurlu_la-http_chunks.lo \ + libcurlu_la-http_digest.lo libcurlu_la-http_negotiate.lo \ + libcurlu_la-http_ntlm.lo libcurlu_la-http_proxy.lo \ libcurlu_la-idn.lo libcurlu_la-if2ip.lo libcurlu_la-imap.lo \ libcurlu_la-inet_ntop.lo libcurlu_la-inet_pton.lo \ libcurlu_la-krb5.lo libcurlu_la-ldap.lo libcurlu_la-llist.lo \ @@ -418,8 +507,8 @@ am__objects_11 = libcurlu_la-altsvc.lo libcurlu_la-amigaos.lo \ libcurlu_la-openldap.lo libcurlu_la-parsedate.lo \ libcurlu_la-pingpong.lo libcurlu_la-pop3.lo \ libcurlu_la-progress.lo libcurlu_la-psl.lo libcurlu_la-rand.lo \ - libcurlu_la-rename.lo libcurlu_la-rtsp.lo \ - libcurlu_la-select.lo libcurlu_la-sendf.lo \ + libcurlu_la-rename.lo libcurlu_la-request.lo \ + libcurlu_la-rtsp.lo libcurlu_la-select.lo libcurlu_la-sendf.lo \ libcurlu_la-setopt.lo libcurlu_la-sha256.lo \ libcurlu_la-share.lo libcurlu_la-slist.lo libcurlu_la-smb.lo \ libcurlu_la-smtp.lo libcurlu_la-socketpair.lo \ @@ -434,7 +523,7 @@ am__objects_11 = libcurlu_la-altsvc.lo libcurlu_la-amigaos.lo \ libcurlu_la-url.lo libcurlu_la-urlapi.lo \ libcurlu_la-version.lo libcurlu_la-version_win32.lo \ libcurlu_la-warnless.lo libcurlu_la-ws.lo -am__objects_12 = vauth/libcurlu_la-cleartext.lo \ +am__objects_14 = vauth/libcurlu_la-cleartext.lo \ vauth/libcurlu_la-cram.lo vauth/libcurlu_la-digest.lo \ vauth/libcurlu_la-digest_sspi.lo vauth/libcurlu_la-gsasl.lo \ vauth/libcurlu_la-krb5_gssapi.lo \ @@ -442,7 +531,8 @@ am__objects_12 = vauth/libcurlu_la-cleartext.lo \ vauth/libcurlu_la-ntlm_sspi.lo vauth/libcurlu_la-oauth2.lo \ vauth/libcurlu_la-spnego_gssapi.lo \ vauth/libcurlu_la-spnego_sspi.lo vauth/libcurlu_la-vauth.lo -am__objects_13 = vtls/libcurlu_la-bearssl.lo vtls/libcurlu_la-gtls.lo \ +am__objects_15 = vtls/libcurlu_la-bearssl.lo \ + vtls/libcurlu_la-cipher_suite.lo vtls/libcurlu_la-gtls.lo \ vtls/libcurlu_la-hostcheck.lo vtls/libcurlu_la-keylog.lo \ vtls/libcurlu_la-mbedtls.lo \ vtls/libcurlu_la-mbedtls_threadlock.lo \ @@ -451,15 +541,29 @@ am__objects_13 = vtls/libcurlu_la-bearssl.lo vtls/libcurlu_la-gtls.lo \ vtls/libcurlu_la-schannel_verify.lo \ vtls/libcurlu_la-sectransp.lo vtls/libcurlu_la-vtls.lo \ vtls/libcurlu_la-wolfssl.lo vtls/libcurlu_la-x509asn1.lo -am__objects_14 = vquic/libcurlu_la-curl_msh3.lo \ +am__objects_16 = vquic/libcurlu_la-curl_msh3.lo \ vquic/libcurlu_la-curl_ngtcp2.lo \ - vquic/libcurlu_la-curl_quiche.lo vquic/libcurlu_la-vquic.lo -am__objects_15 = vssh/libcurlu_la-libssh.lo \ - vssh/libcurlu_la-libssh2.lo vssh/libcurlu_la-wolfssh.lo -am__objects_16 = $(am__objects_11) $(am__objects_12) $(am__objects_13) \ - $(am__objects_14) $(am__objects_15) -am_libcurlu_la_OBJECTS = $(am__objects_16) $(am__objects_8) -libcurlu_la_OBJECTS = $(am_libcurlu_la_OBJECTS) + vquic/libcurlu_la-curl_osslq.lo \ + vquic/libcurlu_la-curl_quiche.lo vquic/libcurlu_la-vquic.lo \ + vquic/libcurlu_la-vquic-tls.lo +am__objects_17 = vssh/libcurlu_la-libssh.lo \ + vssh/libcurlu_la-libssh2.lo vssh/libcurlu_la-curl_path.lo \ + vssh/libcurlu_la-wolfssh.lo +am__objects_18 = $(am__objects_13) $(am__objects_14) $(am__objects_15) \ + $(am__objects_16) $(am__objects_17) +@DEBUGBUILD_TRUE@@USE_UNITY_TRUE@am__objects_19 = \ +@DEBUGBUILD_TRUE@@USE_UNITY_TRUE@ libcurlu_la-memdebug.lo \ +@DEBUGBUILD_TRUE@@USE_UNITY_TRUE@ libcurlu_la-curl_multibyte.lo +@USE_UNITY_TRUE@am__objects_20 = libcurlu_la-curl_threads.lo \ +@USE_UNITY_TRUE@ libcurlu_la-timediff.lo \ +@USE_UNITY_TRUE@ libcurlu_la-warnless.lo $(am__objects_19) +@USE_UNITY_FALSE@am_libcurlu_la_OBJECTS = $(am__objects_18) \ +@USE_UNITY_FALSE@ $(am__objects_8) +@USE_UNITY_TRUE@am_libcurlu_la_OBJECTS = $(am__objects_20) +@USE_UNITY_TRUE@nodist_libcurlu_la_OBJECTS = \ +@USE_UNITY_TRUE@ libcurlu_la-libcurl_unity.lo +libcurlu_la_OBJECTS = $(am_libcurlu_la_OBJECTS) \ + $(nodist_libcurlu_la_OBJECTS) libcurlu_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(libcurlu_la_CFLAGS) \ $(CFLAGS) $(libcurlu_la_LDFLAGS) $(LDFLAGS) -o $@ @@ -471,12 +575,12 @@ am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; -am__v_GEN_1 = +am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ -am__v_at_1 = -DEFAULT_INCLUDES = +am__v_at_1 = +DEFAULT_INCLUDES = depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/libcurl_la-altsvc.Plo \ @@ -507,15 +611,17 @@ am__depfiles_remade = ./$(DEPDIR)/libcurl_la-altsvc.Plo \ ./$(DEPDIR)/libcurl_la-curl_memrchr.Plo \ ./$(DEPDIR)/libcurl_la-curl_multibyte.Plo \ ./$(DEPDIR)/libcurl_la-curl_ntlm_core.Plo \ - ./$(DEPDIR)/libcurl_la-curl_ntlm_wb.Plo \ - ./$(DEPDIR)/libcurl_la-curl_path.Plo \ ./$(DEPDIR)/libcurl_la-curl_range.Plo \ ./$(DEPDIR)/libcurl_la-curl_rtmp.Plo \ ./$(DEPDIR)/libcurl_la-curl_sasl.Plo \ + ./$(DEPDIR)/libcurl_la-curl_sha512_256.Plo \ ./$(DEPDIR)/libcurl_la-curl_sspi.Plo \ ./$(DEPDIR)/libcurl_la-curl_threads.Plo \ ./$(DEPDIR)/libcurl_la-curl_trc.Plo \ - ./$(DEPDIR)/libcurl_la-dict.Plo ./$(DEPDIR)/libcurl_la-doh.Plo \ + ./$(DEPDIR)/libcurl_la-cw-out.Plo \ + ./$(DEPDIR)/libcurl_la-dict.Plo \ + ./$(DEPDIR)/libcurl_la-dllmain.Plo \ + ./$(DEPDIR)/libcurl_la-doh.Plo \ ./$(DEPDIR)/libcurl_la-dynbuf.Plo \ ./$(DEPDIR)/libcurl_la-dynhds.Plo \ ./$(DEPDIR)/libcurl_la-easy.Plo \ @@ -556,6 +662,7 @@ am__depfiles_remade = ./$(DEPDIR)/libcurl_la-altsvc.Plo \ ./$(DEPDIR)/libcurl_la-inet_pton.Plo \ ./$(DEPDIR)/libcurl_la-krb5.Plo \ ./$(DEPDIR)/libcurl_la-ldap.Plo \ + ./$(DEPDIR)/libcurl_la-libcurl_unity.Plo \ ./$(DEPDIR)/libcurl_la-llist.Plo \ ./$(DEPDIR)/libcurl_la-macos.Plo \ ./$(DEPDIR)/libcurl_la-md4.Plo ./$(DEPDIR)/libcurl_la-md5.Plo \ @@ -574,6 +681,7 @@ am__depfiles_remade = ./$(DEPDIR)/libcurl_la-altsvc.Plo \ ./$(DEPDIR)/libcurl_la-progress.Plo \ ./$(DEPDIR)/libcurl_la-psl.Plo ./$(DEPDIR)/libcurl_la-rand.Plo \ ./$(DEPDIR)/libcurl_la-rename.Plo \ + ./$(DEPDIR)/libcurl_la-request.Plo \ ./$(DEPDIR)/libcurl_la-rtsp.Plo \ ./$(DEPDIR)/libcurl_la-select.Plo \ ./$(DEPDIR)/libcurl_la-sendf.Plo \ @@ -633,15 +741,16 @@ am__depfiles_remade = ./$(DEPDIR)/libcurl_la-altsvc.Plo \ ./$(DEPDIR)/libcurlu_la-curl_memrchr.Plo \ ./$(DEPDIR)/libcurlu_la-curl_multibyte.Plo \ ./$(DEPDIR)/libcurlu_la-curl_ntlm_core.Plo \ - ./$(DEPDIR)/libcurlu_la-curl_ntlm_wb.Plo \ - ./$(DEPDIR)/libcurlu_la-curl_path.Plo \ ./$(DEPDIR)/libcurlu_la-curl_range.Plo \ ./$(DEPDIR)/libcurlu_la-curl_rtmp.Plo \ ./$(DEPDIR)/libcurlu_la-curl_sasl.Plo \ + ./$(DEPDIR)/libcurlu_la-curl_sha512_256.Plo \ ./$(DEPDIR)/libcurlu_la-curl_sspi.Plo \ ./$(DEPDIR)/libcurlu_la-curl_threads.Plo \ ./$(DEPDIR)/libcurlu_la-curl_trc.Plo \ + ./$(DEPDIR)/libcurlu_la-cw-out.Plo \ ./$(DEPDIR)/libcurlu_la-dict.Plo \ + ./$(DEPDIR)/libcurlu_la-dllmain.Plo \ ./$(DEPDIR)/libcurlu_la-doh.Plo \ ./$(DEPDIR)/libcurlu_la-dynbuf.Plo \ ./$(DEPDIR)/libcurlu_la-dynhds.Plo \ @@ -683,6 +792,7 @@ am__depfiles_remade = ./$(DEPDIR)/libcurl_la-altsvc.Plo \ ./$(DEPDIR)/libcurlu_la-inet_pton.Plo \ ./$(DEPDIR)/libcurlu_la-krb5.Plo \ ./$(DEPDIR)/libcurlu_la-ldap.Plo \ + ./$(DEPDIR)/libcurlu_la-libcurl_unity.Plo \ ./$(DEPDIR)/libcurlu_la-llist.Plo \ ./$(DEPDIR)/libcurlu_la-macos.Plo \ ./$(DEPDIR)/libcurlu_la-md4.Plo \ @@ -703,6 +813,7 @@ am__depfiles_remade = ./$(DEPDIR)/libcurl_la-altsvc.Plo \ ./$(DEPDIR)/libcurlu_la-psl.Plo \ ./$(DEPDIR)/libcurlu_la-rand.Plo \ ./$(DEPDIR)/libcurlu_la-rename.Plo \ + ./$(DEPDIR)/libcurlu_la-request.Plo \ ./$(DEPDIR)/libcurlu_la-rtsp.Plo \ ./$(DEPDIR)/libcurlu_la-select.Plo \ ./$(DEPDIR)/libcurlu_la-sendf.Plo \ @@ -763,19 +874,26 @@ am__depfiles_remade = ./$(DEPDIR)/libcurl_la-altsvc.Plo \ vauth/$(DEPDIR)/libcurlu_la-vauth.Plo \ vquic/$(DEPDIR)/libcurl_la-curl_msh3.Plo \ vquic/$(DEPDIR)/libcurl_la-curl_ngtcp2.Plo \ + vquic/$(DEPDIR)/libcurl_la-curl_osslq.Plo \ vquic/$(DEPDIR)/libcurl_la-curl_quiche.Plo \ + vquic/$(DEPDIR)/libcurl_la-vquic-tls.Plo \ vquic/$(DEPDIR)/libcurl_la-vquic.Plo \ vquic/$(DEPDIR)/libcurlu_la-curl_msh3.Plo \ vquic/$(DEPDIR)/libcurlu_la-curl_ngtcp2.Plo \ + vquic/$(DEPDIR)/libcurlu_la-curl_osslq.Plo \ vquic/$(DEPDIR)/libcurlu_la-curl_quiche.Plo \ + vquic/$(DEPDIR)/libcurlu_la-vquic-tls.Plo \ vquic/$(DEPDIR)/libcurlu_la-vquic.Plo \ + vssh/$(DEPDIR)/libcurl_la-curl_path.Plo \ vssh/$(DEPDIR)/libcurl_la-libssh.Plo \ vssh/$(DEPDIR)/libcurl_la-libssh2.Plo \ vssh/$(DEPDIR)/libcurl_la-wolfssh.Plo \ + vssh/$(DEPDIR)/libcurlu_la-curl_path.Plo \ vssh/$(DEPDIR)/libcurlu_la-libssh.Plo \ vssh/$(DEPDIR)/libcurlu_la-libssh2.Plo \ vssh/$(DEPDIR)/libcurlu_la-wolfssh.Plo \ vtls/$(DEPDIR)/libcurl_la-bearssl.Plo \ + vtls/$(DEPDIR)/libcurl_la-cipher_suite.Plo \ vtls/$(DEPDIR)/libcurl_la-gtls.Plo \ vtls/$(DEPDIR)/libcurl_la-hostcheck.Plo \ vtls/$(DEPDIR)/libcurl_la-keylog.Plo \ @@ -790,6 +908,7 @@ am__depfiles_remade = ./$(DEPDIR)/libcurl_la-altsvc.Plo \ vtls/$(DEPDIR)/libcurl_la-wolfssl.Plo \ vtls/$(DEPDIR)/libcurl_la-x509asn1.Plo \ vtls/$(DEPDIR)/libcurlu_la-bearssl.Plo \ + vtls/$(DEPDIR)/libcurlu_la-cipher_suite.Plo \ vtls/$(DEPDIR)/libcurlu_la-gtls.Plo \ vtls/$(DEPDIR)/libcurlu_la-hostcheck.Plo \ vtls/$(DEPDIR)/libcurlu_la-keylog.Plo \ @@ -813,7 +932,7 @@ LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; -am__v_CC_1 = +am__v_CC_1 = CCLD = $(CC) LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ @@ -821,9 +940,11 @@ LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; -am__v_CCLD_1 = -SOURCES = $(libcurl_la_SOURCES) $(libcurlu_la_SOURCES) -DIST_SOURCES = $(am__libcurl_la_SOURCES_DIST) $(libcurlu_la_SOURCES) +am__v_CCLD_1 = +SOURCES = $(libcurl_la_SOURCES) $(nodist_libcurl_la_SOURCES) \ + $(libcurlu_la_SOURCES) $(nodist_libcurlu_la_SOURCES) +DIST_SOURCES = $(am__libcurl_la_SOURCES_DIST) \ + $(am__libcurlu_la_SOURCES_DIST) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ @@ -849,8 +970,7 @@ am__define_uniq_tagged_files = \ done | $(am__uniquify_input)` am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.inc \ $(srcdir)/Makefile.soname $(srcdir)/curl_config.h.in \ - $(srcdir)/libcurl.plist.in $(srcdir)/libcurl.vers.in \ - $(top_srcdir)/depcomp + $(srcdir)/libcurl.vers.in $(top_srcdir)/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ @@ -875,18 +995,20 @@ CFLAG_CURL_SYMBOL_HIDING = @CFLAG_CURL_SYMBOL_HIDING@ CONFIGURE_OPTIONS = @CONFIGURE_OPTIONS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ -CPPFLAG_CURL_STATICLIB = @CPPFLAG_CURL_STATICLIB@ CSCOPE = @CSCOPE@ CTAGS = @CTAGS@ CURLVERSION = @CURLVERSION@ CURL_CA_BUNDLE = @CURL_CA_BUNDLE@ +CURL_CA_EMBED = @CURL_CA_EMBED@ CURL_CFLAG_EXTRAS = @CURL_CFLAG_EXTRAS@ +CURL_CPP = @CURL_CPP@ CURL_DISABLE_DICT = @CURL_DISABLE_DICT@ CURL_DISABLE_FILE = @CURL_DISABLE_FILE@ CURL_DISABLE_FTP = @CURL_DISABLE_FTP@ CURL_DISABLE_GOPHER = @CURL_DISABLE_GOPHER@ CURL_DISABLE_HTTP = @CURL_DISABLE_HTTP@ CURL_DISABLE_IMAP = @CURL_DISABLE_IMAP@ +CURL_DISABLE_IPFS = @CURL_DISABLE_IPFS@ CURL_DISABLE_LDAP = @CURL_DISABLE_LDAP@ CURL_DISABLE_LDAPS = @CURL_DISABLE_LDAPS@ CURL_DISABLE_MQTT = @CURL_DISABLE_MQTT@ @@ -897,10 +1019,11 @@ CURL_DISABLE_SMB = @CURL_DISABLE_SMB@ CURL_DISABLE_SMTP = @CURL_DISABLE_SMTP@ CURL_DISABLE_TELNET = @CURL_DISABLE_TELNET@ CURL_DISABLE_TFTP = @CURL_DISABLE_TFTP@ -CURL_LT_SHLIB_VERSIONED_FLAVOUR = @CURL_LT_SHLIB_VERSIONED_FLAVOUR@ +CURL_DISABLE_WEBSOCKETS = @CURL_DISABLE_WEBSOCKETS@ +CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX = @CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX@ +CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME = @CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME@ CURL_NETWORK_AND_TIME_LIBS = @CURL_NETWORK_AND_TIME_LIBS@ CURL_NETWORK_LIBS = @CURL_NETWORK_LIBS@ -CURL_PLIST_VERSION = @CURL_PLIST_VERSION@ CURL_WITH_MULTI_SSL = @CURL_WITH_MULTI_SSL@ CYGPATH_W = @CYGPATH_W@ DEFAULT_SSL_BACKEND = @DEFAULT_SSL_BACKEND@ @@ -941,8 +1064,12 @@ IPV6_ENABLED = @IPV6_ENABLED@ LCOV = @LCOV@ LD = @LD@ LDFLAGS = @LDFLAGS@ -LIBCURL_LIBS = @LIBCURL_LIBS@ -LIBCURL_NO_SHARED = @LIBCURL_NO_SHARED@ +LIBCURL_PC_CFLAGS = @LIBCURL_PC_CFLAGS@ +LIBCURL_PC_CFLAGS_PRIVATE = @LIBCURL_PC_CFLAGS_PRIVATE@ +LIBCURL_PC_LIBS = @LIBCURL_PC_LIBS@ +LIBCURL_PC_LIBS_PRIVATE = @LIBCURL_PC_LIBS_PRIVATE@ +LIBCURL_PC_REQUIRES = @LIBCURL_PC_REQUIRES@ +LIBCURL_PC_REQUIRES_PRIVATE = @LIBCURL_PC_REQUIRES_PRIVATE@ LIBOBJS = @LIBOBJS@ # Prevent LIBS from being used for all link targets @@ -955,11 +1082,9 @@ LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ MAINT = @MAINT@ MAKEINFO = @MAKEINFO@ MANIFEST_TOOL = @MANIFEST_TOOL@ -MANOPT = @MANOPT@ MKDIR_P = @MKDIR_P@ NM = @NM@ NMEDIT = @NMEDIT@ -NROFF = @NROFF@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ OTOOL = @OTOOL@ @@ -977,10 +1102,8 @@ PKGADD_NAME = @PKGADD_NAME@ PKGADD_PKG = @PKGADD_PKG@ PKGADD_VENDOR = @PKGADD_VENDOR@ PKGCONFIG = @PKGCONFIG@ -RANDOM_FILE = @RANDOM_FILE@ RANLIB = @RANLIB@ RC = @RC@ -REQUIRE_LIB_DEPS = @REQUIRE_LIB_DEPS@ SED = @SED@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ @@ -991,22 +1114,29 @@ STRIP = @STRIP@ SUPPORT_FEATURES = @SUPPORT_FEATURES@ SUPPORT_PROTOCOLS = @SUPPORT_PROTOCOLS@ TEST_NGHTTPX = @TEST_NGHTTPX@ +USE_APPLE_IDN = @USE_APPLE_IDN@ USE_ARES = @USE_ARES@ USE_BEARSSL = @USE_BEARSSL@ USE_GNUTLS = @USE_GNUTLS@ USE_HYPER = @USE_HYPER@ +USE_LIBPSL = @USE_LIBPSL@ USE_LIBRTMP = @USE_LIBRTMP@ USE_LIBSSH = @USE_LIBSSH@ USE_LIBSSH2 = @USE_LIBSSH2@ +USE_LIBUV = @USE_LIBUV@ USE_MBEDTLS = @USE_MBEDTLS@ USE_MSH3 = @USE_MSH3@ USE_NGHTTP2 = @USE_NGHTTP2@ USE_NGHTTP3 = @USE_NGHTTP3@ USE_NGTCP2 = @USE_NGTCP2@ +USE_NGTCP2_CRYPTO_BORINGSSL = @USE_NGTCP2_CRYPTO_BORINGSSL@ USE_NGTCP2_CRYPTO_GNUTLS = @USE_NGTCP2_CRYPTO_GNUTLS@ USE_NGTCP2_CRYPTO_QUICTLS = @USE_NGTCP2_CRYPTO_QUICTLS@ USE_NGTCP2_CRYPTO_WOLFSSL = @USE_NGTCP2_CRYPTO_WOLFSSL@ +USE_NGTCP2_H3 = @USE_NGTCP2_H3@ USE_OPENLDAP = @USE_OPENLDAP@ +USE_OPENSSL_H3 = @USE_OPENSSL_H3@ +USE_OPENSSL_QUIC = @USE_OPENSSL_QUIC@ USE_QUICHE = @USE_QUICHE@ USE_RUSTLS = @USE_RUSTLS@ USE_SCHANNEL = @USE_SCHANNEL@ @@ -1020,6 +1150,7 @@ USE_WOLFSSH = @USE_WOLFSSH@ USE_WOLFSSL = @USE_WOLFSSL@ VERSION = @VERSION@ VERSIONNUM = @VERSIONNUM@ +VSFTPD = @VSFTPD@ ZLIB_LIBS = @ZLIB_LIBS@ ZSH_FUNCTIONS_DIR = @ZSH_FUNCTIONS_DIR@ abs_builddir = @abs_builddir@ @@ -1102,14 +1233,17 @@ top_srcdir = @top_srcdir@ ########################################################################### AUTOMAKE_OPTIONS = foreign nostdinc CMAKE_DIST = CMakeLists.txt curl_config.h.cmake -EXTRA_DIST = Makefile.mk config-win32.h config-win32ce.h config-plan9.h \ - config-riscos.h config-mac.h curl_config.h.in config-dos.h \ - libcurl.plist libcurl.rc config-amigaos.h config-win32ce.h \ - config-os400.h setup-os400.h $(CMAKE_DIST) setup-win32.h .checksrc \ - Makefile.soname +CHECKSRC_DIST = .checksrc vauth/.checksrc vquic/.checksrc vssh/.checksrc \ + vtls/.checksrc + +EXTRA_DIST = Makefile.mk config-win32.h config-win32ce.h config-plan9.h \ + config-riscos.h config-mac.h curl_config.h.in config-dos.h libcurl.rc \ + config-amigaos.h config-win32ce.h config-os400.h setup-os400.h \ + $(CMAKE_DIST) setup-win32.h Makefile.soname optiontable.pl libcurl.def \ + $(CHECKSRC_DIST) lib_LTLIBRARIES = libcurl.la -@BUILD_UNITTESTS_FALSE@noinst_LTLIBRARIES = +@BUILD_UNITTESTS_FALSE@noinst_LTLIBRARIES = @BUILD_UNITTESTS_TRUE@noinst_LTLIBRARIES = libcurlu.la # Specify our include paths here, and do it relative to $(top_srcdir) and @@ -1122,14 +1256,16 @@ lib_LTLIBRARIES = libcurl.la # $(top_srcdir)/lib for libcurl's lib/curl_setup.h and other "private" files AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/lib \ -I$(top_srcdir)/lib -DBUILDING_LIBCURL + +# Keep in sync with CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME in configure.ac VERSIONCHANGE = 12 VERSIONADD = 0 VERSIONDEL = 8 # libtool version: VERSIONINFO = -version-info $(VERSIONCHANGE):$(VERSIONADD):$(VERSIONDEL) -AM_LDFLAGS = -AM_CFLAGS = +AM_LDFLAGS = +AM_CFLAGS = LIB_VAUTH_CFILES = \ vauth/cleartext.c \ vauth/cram.c \ @@ -1152,6 +1288,7 @@ LIB_VAUTH_HFILES = \ LIB_VTLS_CFILES = \ vtls/bearssl.c \ + vtls/cipher_suite.c \ vtls/gtls.c \ vtls/hostcheck.c \ vtls/keylog.c \ @@ -1168,6 +1305,7 @@ LIB_VTLS_CFILES = \ LIB_VTLS_HFILES = \ vtls/bearssl.h \ + vtls/cipher_suite.h \ vtls/gtls.h \ vtls/hostcheck.h \ vtls/keylog.h \ @@ -1186,22 +1324,28 @@ LIB_VTLS_HFILES = \ LIB_VQUIC_CFILES = \ vquic/curl_msh3.c \ vquic/curl_ngtcp2.c \ + vquic/curl_osslq.c \ vquic/curl_quiche.c \ - vquic/vquic.c + vquic/vquic.c \ + vquic/vquic-tls.c LIB_VQUIC_HFILES = \ vquic/curl_msh3.h \ vquic/curl_ngtcp2.h \ + vquic/curl_osslq.h \ vquic/curl_quiche.h \ vquic/vquic.h \ - vquic/vquic_int.h + vquic/vquic_int.h \ + vquic/vquic-tls.h LIB_VSSH_CFILES = \ vssh/libssh.c \ vssh/libssh2.c \ + vssh/curl_path.c \ vssh/wolfssh.c LIB_VSSH_HFILES = \ + vssh/curl_path.h \ vssh/ssh.h LIB_CFILES = \ @@ -1233,15 +1377,16 @@ LIB_CFILES = \ curl_memrchr.c \ curl_multibyte.c \ curl_ntlm_core.c \ - curl_ntlm_wb.c \ - curl_path.c \ curl_range.c \ curl_rtmp.c \ curl_sasl.c \ + curl_sha512_256.c \ curl_sspi.c \ curl_threads.c \ curl_trc.c \ + cw-out.c \ dict.c \ + dllmain.c \ doh.c \ dynbuf.c \ dynhds.c \ @@ -1270,12 +1415,12 @@ LIB_CFILES = \ http.c \ http1.c \ http2.c \ + http_aws_sigv4.c \ http_chunks.c \ http_digest.c \ http_negotiate.c \ http_ntlm.c \ http_proxy.c \ - http_aws_sigv4.c \ idn.c \ if2ip.c \ imap.c \ @@ -1303,6 +1448,7 @@ LIB_CFILES = \ psl.c \ rand.c \ rename.c \ + request.c \ rtsp.c \ select.c \ sendf.c \ @@ -1372,8 +1518,6 @@ LIB_HFILES = \ curl_memrchr.h \ curl_multibyte.h \ curl_ntlm_core.h \ - curl_ntlm_wb.h \ - curl_path.h \ curl_printf.h \ curl_range.h \ curl_rtmp.h \ @@ -1381,10 +1525,12 @@ LIB_HFILES = \ curl_setup.h \ curl_setup_once.h \ curl_sha256.h \ + curl_sha512_256.h \ curl_sspi.h \ curl_threads.h \ curl_trc.h \ curlx.h \ + cw-out.h \ dict.h \ doh.h \ dynbuf.h \ @@ -1397,9 +1543,9 @@ LIB_HFILES = \ fileinfo.h \ fopen.h \ formdata.h \ - functypes.h \ ftp.h \ ftplistparser.h \ + functypes.h \ getinfo.h \ gopher.h \ hash.h \ @@ -1409,12 +1555,12 @@ LIB_HFILES = \ http.h \ http1.h \ http2.h \ + http_aws_sigv4.h \ http_chunks.h \ http_digest.h \ http_negotiate.h \ http_ntlm.h \ http_proxy.h \ - http_aws_sigv4.h \ idn.h \ if2ip.h \ imap.h \ @@ -1437,6 +1583,7 @@ LIB_HFILES = \ psl.h \ rand.h \ rename.h \ + request.h \ rtsp.h \ select.h \ sendf.h \ @@ -1479,21 +1626,32 @@ HHEADERS = $(LIB_HFILES) $(LIB_VAUTH_HFILES) $(LIB_VTLS_HFILES) \ # Makefile.inc provides the CSOURCES and HHEADERS defines -libcurl_la_SOURCES = $(CSOURCES) $(HHEADERS) $(am__append_7) -libcurlu_la_SOURCES = $(CSOURCES) $(HHEADERS) -libcurl_la_CPPFLAGS_EXTRA = $(am__append_6) $(am__append_8) -libcurl_la_LDFLAGS_EXTRA = $(am__append_1) $(am__append_2) \ - $(am__append_3) $(am__append_4) $(am__append_5) -libcurl_la_CFLAGS_EXTRA = $(am__append_9) + +# Keep these separate to avoid duplicate definitions when linking libtests +# in static mode. +@USE_UNITY_TRUE@curl_EXCLUDE = curl_threads.c timediff.c warnless.c \ +@USE_UNITY_TRUE@ $(am__append_1) +@USE_UNITY_TRUE@nodist_libcurl_la_SOURCES = libcurl_unity.c +@USE_UNITY_FALSE@libcurl_la_SOURCES = $(CSOURCES) $(HHEADERS) \ +@USE_UNITY_FALSE@ $(am__append_8) +@USE_UNITY_TRUE@libcurl_la_SOURCES = $(curl_EXCLUDE) $(am__append_8) +@USE_UNITY_TRUE@nodist_libcurlu_la_SOURCES = libcurl_unity.c +@USE_UNITY_FALSE@libcurlu_la_SOURCES = $(CSOURCES) $(HHEADERS) +@USE_UNITY_TRUE@libcurlu_la_SOURCES = $(curl_EXCLUDE) +@USE_UNITY_TRUE@CLEANFILES = libcurl_unity.c +libcurl_la_CPPFLAGS_EXTRA = $(am__append_7) $(am__append_9) +libcurl_la_LDFLAGS_EXTRA = $(am__append_2) $(am__append_3) \ + $(am__append_4) $(am__append_5) $(am__append_6) +libcurl_la_CFLAGS_EXTRA = $(am__append_10) libcurl_la_CPPFLAGS = $(AM_CPPFLAGS) $(libcurl_la_CPPFLAGS_EXTRA) -libcurl_la_LDFLAGS = $(AM_LDFLAGS) $(libcurl_la_LDFLAGS_EXTRA) $(LDFLAGS) $(LIBCURL_LIBS) +libcurl_la_LDFLAGS = $(AM_LDFLAGS) $(libcurl_la_LDFLAGS_EXTRA) $(CURL_LDFLAGS_LIB) $(LIBCURL_PC_LIBS_PRIVATE) libcurl_la_CFLAGS = $(AM_CFLAGS) $(libcurl_la_CFLAGS_EXTRA) libcurlu_la_CPPFLAGS = $(AM_CPPFLAGS) -DCURL_STATICLIB -DUNITTESTS -libcurlu_la_LDFLAGS = $(AM_LDFLAGS) -static $(LIBCURL_LIBS) +libcurlu_la_LDFLAGS = $(AM_LDFLAGS) -static $(LIBCURL_PC_LIBS_PRIVATE) libcurlu_la_CFLAGS = $(AM_CFLAGS) CHECKSRC = $(CS_$(V)) CS_0 = @echo " RUN " $@; -CS_1 = +CS_1 = CS_ = $(CS_0) # disable the tests that are mostly causing false positives @@ -1542,7 +1700,7 @@ curl_config.h: stamp-h1 stamp-h1: $(srcdir)/curl_config.h.in $(top_builddir)/config.status @rm -f stamp-h1 cd $(top_builddir) && $(SHELL) ./config.status lib/curl_config.h -$(srcdir)/curl_config.h.in: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) +$(srcdir)/curl_config.h.in: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) ($(am__cd) $(top_srcdir) && $(AUTOHEADER)) rm -f stamp-h1 touch $@ @@ -1551,8 +1709,6 @@ distclean-hdr: -rm -f curl_config.h stamp-h1 libcurl.vers: $(top_builddir)/config.status $(srcdir)/libcurl.vers.in cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ -libcurl.plist: $(top_builddir)/config.status $(srcdir)/libcurl.plist.in - cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ install-libLTLIBRARIES: $(lib_LTLIBRARIES) @$(NORMAL_INSTALL) @@ -1639,6 +1795,8 @@ vtls/$(DEPDIR)/$(am__dirstamp): @: > vtls/$(DEPDIR)/$(am__dirstamp) vtls/libcurl_la-bearssl.lo: vtls/$(am__dirstamp) \ vtls/$(DEPDIR)/$(am__dirstamp) +vtls/libcurl_la-cipher_suite.lo: vtls/$(am__dirstamp) \ + vtls/$(DEPDIR)/$(am__dirstamp) vtls/libcurl_la-gtls.lo: vtls/$(am__dirstamp) \ vtls/$(DEPDIR)/$(am__dirstamp) vtls/libcurl_la-hostcheck.lo: vtls/$(am__dirstamp) \ @@ -1675,10 +1833,14 @@ vquic/libcurl_la-curl_msh3.lo: vquic/$(am__dirstamp) \ vquic/$(DEPDIR)/$(am__dirstamp) vquic/libcurl_la-curl_ngtcp2.lo: vquic/$(am__dirstamp) \ vquic/$(DEPDIR)/$(am__dirstamp) +vquic/libcurl_la-curl_osslq.lo: vquic/$(am__dirstamp) \ + vquic/$(DEPDIR)/$(am__dirstamp) vquic/libcurl_la-curl_quiche.lo: vquic/$(am__dirstamp) \ vquic/$(DEPDIR)/$(am__dirstamp) vquic/libcurl_la-vquic.lo: vquic/$(am__dirstamp) \ vquic/$(DEPDIR)/$(am__dirstamp) +vquic/libcurl_la-vquic-tls.lo: vquic/$(am__dirstamp) \ + vquic/$(DEPDIR)/$(am__dirstamp) vssh/$(am__dirstamp): @$(MKDIR_P) vssh @: > vssh/$(am__dirstamp) @@ -1689,10 +1851,12 @@ vssh/libcurl_la-libssh.lo: vssh/$(am__dirstamp) \ vssh/$(DEPDIR)/$(am__dirstamp) vssh/libcurl_la-libssh2.lo: vssh/$(am__dirstamp) \ vssh/$(DEPDIR)/$(am__dirstamp) +vssh/libcurl_la-curl_path.lo: vssh/$(am__dirstamp) \ + vssh/$(DEPDIR)/$(am__dirstamp) vssh/libcurl_la-wolfssh.lo: vssh/$(am__dirstamp) \ vssh/$(DEPDIR)/$(am__dirstamp) -libcurl.la: $(libcurl_la_OBJECTS) $(libcurl_la_DEPENDENCIES) $(EXTRA_libcurl_la_DEPENDENCIES) +libcurl.la: $(libcurl_la_OBJECTS) $(libcurl_la_DEPENDENCIES) $(EXTRA_libcurl_la_DEPENDENCIES) $(AM_V_CCLD)$(libcurl_la_LINK) -rpath $(libdir) $(libcurl_la_OBJECTS) $(libcurl_la_LIBADD) $(LIBS) vauth/libcurlu_la-cleartext.lo: vauth/$(am__dirstamp) \ vauth/$(DEPDIR)/$(am__dirstamp) @@ -1722,6 +1886,8 @@ vauth/libcurlu_la-vauth.lo: vauth/$(am__dirstamp) \ vauth/$(DEPDIR)/$(am__dirstamp) vtls/libcurlu_la-bearssl.lo: vtls/$(am__dirstamp) \ vtls/$(DEPDIR)/$(am__dirstamp) +vtls/libcurlu_la-cipher_suite.lo: vtls/$(am__dirstamp) \ + vtls/$(DEPDIR)/$(am__dirstamp) vtls/libcurlu_la-gtls.lo: vtls/$(am__dirstamp) \ vtls/$(DEPDIR)/$(am__dirstamp) vtls/libcurlu_la-hostcheck.lo: vtls/$(am__dirstamp) \ @@ -1752,18 +1918,24 @@ vquic/libcurlu_la-curl_msh3.lo: vquic/$(am__dirstamp) \ vquic/$(DEPDIR)/$(am__dirstamp) vquic/libcurlu_la-curl_ngtcp2.lo: vquic/$(am__dirstamp) \ vquic/$(DEPDIR)/$(am__dirstamp) +vquic/libcurlu_la-curl_osslq.lo: vquic/$(am__dirstamp) \ + vquic/$(DEPDIR)/$(am__dirstamp) vquic/libcurlu_la-curl_quiche.lo: vquic/$(am__dirstamp) \ vquic/$(DEPDIR)/$(am__dirstamp) vquic/libcurlu_la-vquic.lo: vquic/$(am__dirstamp) \ vquic/$(DEPDIR)/$(am__dirstamp) +vquic/libcurlu_la-vquic-tls.lo: vquic/$(am__dirstamp) \ + vquic/$(DEPDIR)/$(am__dirstamp) vssh/libcurlu_la-libssh.lo: vssh/$(am__dirstamp) \ vssh/$(DEPDIR)/$(am__dirstamp) vssh/libcurlu_la-libssh2.lo: vssh/$(am__dirstamp) \ vssh/$(DEPDIR)/$(am__dirstamp) +vssh/libcurlu_la-curl_path.lo: vssh/$(am__dirstamp) \ + vssh/$(DEPDIR)/$(am__dirstamp) vssh/libcurlu_la-wolfssh.lo: vssh/$(am__dirstamp) \ vssh/$(DEPDIR)/$(am__dirstamp) -libcurlu.la: $(libcurlu_la_OBJECTS) $(libcurlu_la_DEPENDENCIES) $(EXTRA_libcurlu_la_DEPENDENCIES) +libcurlu.la: $(libcurlu_la_OBJECTS) $(libcurlu_la_DEPENDENCIES) $(EXTRA_libcurlu_la_DEPENDENCIES) $(AM_V_CCLD)$(libcurlu_la_LINK) $(am_libcurlu_la_rpath) $(libcurlu_la_OBJECTS) $(libcurlu_la_LIBADD) $(LIBS) mostlyclean-compile: @@ -1808,15 +1980,16 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-curl_memrchr.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-curl_multibyte.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-curl_ntlm_core.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-curl_ntlm_wb.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-curl_path.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-curl_range.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-curl_rtmp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-curl_sasl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-curl_sha512_256.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-curl_sspi.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-curl_threads.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-curl_trc.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-cw-out.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-dict.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-dllmain.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-doh.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-dynbuf.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-dynhds.Plo@am__quote@ # am--include-marker @@ -1858,6 +2031,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-inet_pton.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-krb5.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-ldap.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-libcurl_unity.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-llist.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-macos.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-md4.Plo@am__quote@ # am--include-marker @@ -1878,6 +2052,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-psl.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-rand.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-rename.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-request.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-rtsp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-select.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurl_la-sendf.Plo@am__quote@ # am--include-marker @@ -1938,15 +2113,16 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-curl_memrchr.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-curl_multibyte.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-curl_ntlm_core.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-curl_ntlm_wb.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-curl_path.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-curl_range.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-curl_rtmp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-curl_sasl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-curl_sha512_256.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-curl_sspi.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-curl_threads.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-curl_trc.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-cw-out.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-dict.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-dllmain.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-doh.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-dynbuf.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-dynhds.Plo@am__quote@ # am--include-marker @@ -1988,6 +2164,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-inet_pton.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-krb5.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-ldap.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-libcurl_unity.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-llist.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-macos.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-md4.Plo@am__quote@ # am--include-marker @@ -2008,6 +2185,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-psl.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-rand.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-rename.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-request.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-rtsp.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-select.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcurlu_la-sendf.Plo@am__quote@ # am--include-marker @@ -2068,19 +2246,26 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@vauth/$(DEPDIR)/libcurlu_la-vauth.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vquic/$(DEPDIR)/libcurl_la-curl_msh3.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vquic/$(DEPDIR)/libcurl_la-curl_ngtcp2.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@vquic/$(DEPDIR)/libcurl_la-curl_osslq.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vquic/$(DEPDIR)/libcurl_la-curl_quiche.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@vquic/$(DEPDIR)/libcurl_la-vquic-tls.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vquic/$(DEPDIR)/libcurl_la-vquic.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vquic/$(DEPDIR)/libcurlu_la-curl_msh3.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vquic/$(DEPDIR)/libcurlu_la-curl_ngtcp2.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@vquic/$(DEPDIR)/libcurlu_la-curl_osslq.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vquic/$(DEPDIR)/libcurlu_la-curl_quiche.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@vquic/$(DEPDIR)/libcurlu_la-vquic-tls.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vquic/$(DEPDIR)/libcurlu_la-vquic.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@vssh/$(DEPDIR)/libcurl_la-curl_path.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vssh/$(DEPDIR)/libcurl_la-libssh.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vssh/$(DEPDIR)/libcurl_la-libssh2.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vssh/$(DEPDIR)/libcurl_la-wolfssh.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@vssh/$(DEPDIR)/libcurlu_la-curl_path.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vssh/$(DEPDIR)/libcurlu_la-libssh.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vssh/$(DEPDIR)/libcurlu_la-libssh2.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vssh/$(DEPDIR)/libcurlu_la-wolfssh.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vtls/$(DEPDIR)/libcurl_la-bearssl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@vtls/$(DEPDIR)/libcurl_la-cipher_suite.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vtls/$(DEPDIR)/libcurl_la-gtls.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vtls/$(DEPDIR)/libcurl_la-hostcheck.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vtls/$(DEPDIR)/libcurl_la-keylog.Plo@am__quote@ # am--include-marker @@ -2095,6 +2280,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@vtls/$(DEPDIR)/libcurl_la-wolfssl.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vtls/$(DEPDIR)/libcurl_la-x509asn1.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vtls/$(DEPDIR)/libcurlu_la-bearssl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@vtls/$(DEPDIR)/libcurlu_la-cipher_suite.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vtls/$(DEPDIR)/libcurlu_la-gtls.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vtls/$(DEPDIR)/libcurlu_la-hostcheck.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vtls/$(DEPDIR)/libcurlu_la-keylog.Plo@am__quote@ # am--include-marker @@ -2335,20 +2521,6 @@ libcurl_la-curl_ntlm_core.lo: curl_ntlm_core.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-curl_ntlm_core.lo `test -f 'curl_ntlm_core.c' || echo '$(srcdir)/'`curl_ntlm_core.c -libcurl_la-curl_ntlm_wb.lo: curl_ntlm_wb.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-curl_ntlm_wb.lo -MD -MP -MF $(DEPDIR)/libcurl_la-curl_ntlm_wb.Tpo -c -o libcurl_la-curl_ntlm_wb.lo `test -f 'curl_ntlm_wb.c' || echo '$(srcdir)/'`curl_ntlm_wb.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-curl_ntlm_wb.Tpo $(DEPDIR)/libcurl_la-curl_ntlm_wb.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='curl_ntlm_wb.c' object='libcurl_la-curl_ntlm_wb.lo' libtool=yes @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-curl_ntlm_wb.lo `test -f 'curl_ntlm_wb.c' || echo '$(srcdir)/'`curl_ntlm_wb.c - -libcurl_la-curl_path.lo: curl_path.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-curl_path.lo -MD -MP -MF $(DEPDIR)/libcurl_la-curl_path.Tpo -c -o libcurl_la-curl_path.lo `test -f 'curl_path.c' || echo '$(srcdir)/'`curl_path.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-curl_path.Tpo $(DEPDIR)/libcurl_la-curl_path.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='curl_path.c' object='libcurl_la-curl_path.lo' libtool=yes @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-curl_path.lo `test -f 'curl_path.c' || echo '$(srcdir)/'`curl_path.c - libcurl_la-curl_range.lo: curl_range.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-curl_range.lo -MD -MP -MF $(DEPDIR)/libcurl_la-curl_range.Tpo -c -o libcurl_la-curl_range.lo `test -f 'curl_range.c' || echo '$(srcdir)/'`curl_range.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-curl_range.Tpo $(DEPDIR)/libcurl_la-curl_range.Plo @@ -2370,6 +2542,13 @@ libcurl_la-curl_sasl.lo: curl_sasl.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-curl_sasl.lo `test -f 'curl_sasl.c' || echo '$(srcdir)/'`curl_sasl.c +libcurl_la-curl_sha512_256.lo: curl_sha512_256.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-curl_sha512_256.lo -MD -MP -MF $(DEPDIR)/libcurl_la-curl_sha512_256.Tpo -c -o libcurl_la-curl_sha512_256.lo `test -f 'curl_sha512_256.c' || echo '$(srcdir)/'`curl_sha512_256.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-curl_sha512_256.Tpo $(DEPDIR)/libcurl_la-curl_sha512_256.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='curl_sha512_256.c' object='libcurl_la-curl_sha512_256.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-curl_sha512_256.lo `test -f 'curl_sha512_256.c' || echo '$(srcdir)/'`curl_sha512_256.c + libcurl_la-curl_sspi.lo: curl_sspi.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-curl_sspi.lo -MD -MP -MF $(DEPDIR)/libcurl_la-curl_sspi.Tpo -c -o libcurl_la-curl_sspi.lo `test -f 'curl_sspi.c' || echo '$(srcdir)/'`curl_sspi.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-curl_sspi.Tpo $(DEPDIR)/libcurl_la-curl_sspi.Plo @@ -2391,6 +2570,13 @@ libcurl_la-curl_trc.lo: curl_trc.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-curl_trc.lo `test -f 'curl_trc.c' || echo '$(srcdir)/'`curl_trc.c +libcurl_la-cw-out.lo: cw-out.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-cw-out.lo -MD -MP -MF $(DEPDIR)/libcurl_la-cw-out.Tpo -c -o libcurl_la-cw-out.lo `test -f 'cw-out.c' || echo '$(srcdir)/'`cw-out.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-cw-out.Tpo $(DEPDIR)/libcurl_la-cw-out.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cw-out.c' object='libcurl_la-cw-out.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-cw-out.lo `test -f 'cw-out.c' || echo '$(srcdir)/'`cw-out.c + libcurl_la-dict.lo: dict.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-dict.lo -MD -MP -MF $(DEPDIR)/libcurl_la-dict.Tpo -c -o libcurl_la-dict.lo `test -f 'dict.c' || echo '$(srcdir)/'`dict.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-dict.Tpo $(DEPDIR)/libcurl_la-dict.Plo @@ -2398,6 +2584,13 @@ libcurl_la-dict.lo: dict.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-dict.lo `test -f 'dict.c' || echo '$(srcdir)/'`dict.c +libcurl_la-dllmain.lo: dllmain.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-dllmain.lo -MD -MP -MF $(DEPDIR)/libcurl_la-dllmain.Tpo -c -o libcurl_la-dllmain.lo `test -f 'dllmain.c' || echo '$(srcdir)/'`dllmain.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-dllmain.Tpo $(DEPDIR)/libcurl_la-dllmain.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dllmain.c' object='libcurl_la-dllmain.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-dllmain.lo `test -f 'dllmain.c' || echo '$(srcdir)/'`dllmain.c + libcurl_la-doh.lo: doh.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-doh.lo -MD -MP -MF $(DEPDIR)/libcurl_la-doh.Tpo -c -o libcurl_la-doh.lo `test -f 'doh.c' || echo '$(srcdir)/'`doh.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-doh.Tpo $(DEPDIR)/libcurl_la-doh.Plo @@ -2594,6 +2787,13 @@ libcurl_la-http2.lo: http2.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-http2.lo `test -f 'http2.c' || echo '$(srcdir)/'`http2.c +libcurl_la-http_aws_sigv4.lo: http_aws_sigv4.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-http_aws_sigv4.lo -MD -MP -MF $(DEPDIR)/libcurl_la-http_aws_sigv4.Tpo -c -o libcurl_la-http_aws_sigv4.lo `test -f 'http_aws_sigv4.c' || echo '$(srcdir)/'`http_aws_sigv4.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-http_aws_sigv4.Tpo $(DEPDIR)/libcurl_la-http_aws_sigv4.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http_aws_sigv4.c' object='libcurl_la-http_aws_sigv4.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-http_aws_sigv4.lo `test -f 'http_aws_sigv4.c' || echo '$(srcdir)/'`http_aws_sigv4.c + libcurl_la-http_chunks.lo: http_chunks.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-http_chunks.lo -MD -MP -MF $(DEPDIR)/libcurl_la-http_chunks.Tpo -c -o libcurl_la-http_chunks.lo `test -f 'http_chunks.c' || echo '$(srcdir)/'`http_chunks.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-http_chunks.Tpo $(DEPDIR)/libcurl_la-http_chunks.Plo @@ -2629,13 +2829,6 @@ libcurl_la-http_proxy.lo: http_proxy.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-http_proxy.lo `test -f 'http_proxy.c' || echo '$(srcdir)/'`http_proxy.c -libcurl_la-http_aws_sigv4.lo: http_aws_sigv4.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-http_aws_sigv4.lo -MD -MP -MF $(DEPDIR)/libcurl_la-http_aws_sigv4.Tpo -c -o libcurl_la-http_aws_sigv4.lo `test -f 'http_aws_sigv4.c' || echo '$(srcdir)/'`http_aws_sigv4.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-http_aws_sigv4.Tpo $(DEPDIR)/libcurl_la-http_aws_sigv4.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http_aws_sigv4.c' object='libcurl_la-http_aws_sigv4.lo' libtool=yes @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-http_aws_sigv4.lo `test -f 'http_aws_sigv4.c' || echo '$(srcdir)/'`http_aws_sigv4.c - libcurl_la-idn.lo: idn.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-idn.lo -MD -MP -MF $(DEPDIR)/libcurl_la-idn.Tpo -c -o libcurl_la-idn.lo `test -f 'idn.c' || echo '$(srcdir)/'`idn.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-idn.Tpo $(DEPDIR)/libcurl_la-idn.Plo @@ -2825,6 +3018,13 @@ libcurl_la-rename.lo: rename.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-rename.lo `test -f 'rename.c' || echo '$(srcdir)/'`rename.c +libcurl_la-request.lo: request.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-request.lo -MD -MP -MF $(DEPDIR)/libcurl_la-request.Tpo -c -o libcurl_la-request.lo `test -f 'request.c' || echo '$(srcdir)/'`request.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-request.Tpo $(DEPDIR)/libcurl_la-request.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='request.c' object='libcurl_la-request.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-request.lo `test -f 'request.c' || echo '$(srcdir)/'`request.c + libcurl_la-rtsp.lo: rtsp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-rtsp.lo -MD -MP -MF $(DEPDIR)/libcurl_la-rtsp.Tpo -c -o libcurl_la-rtsp.lo `test -f 'rtsp.c' || echo '$(srcdir)/'`rtsp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-rtsp.Tpo $(DEPDIR)/libcurl_la-rtsp.Plo @@ -3147,6 +3347,13 @@ vtls/libcurl_la-bearssl.lo: vtls/bearssl.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o vtls/libcurl_la-bearssl.lo `test -f 'vtls/bearssl.c' || echo '$(srcdir)/'`vtls/bearssl.c +vtls/libcurl_la-cipher_suite.lo: vtls/cipher_suite.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT vtls/libcurl_la-cipher_suite.lo -MD -MP -MF vtls/$(DEPDIR)/libcurl_la-cipher_suite.Tpo -c -o vtls/libcurl_la-cipher_suite.lo `test -f 'vtls/cipher_suite.c' || echo '$(srcdir)/'`vtls/cipher_suite.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vtls/$(DEPDIR)/libcurl_la-cipher_suite.Tpo vtls/$(DEPDIR)/libcurl_la-cipher_suite.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='vtls/cipher_suite.c' object='vtls/libcurl_la-cipher_suite.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o vtls/libcurl_la-cipher_suite.lo `test -f 'vtls/cipher_suite.c' || echo '$(srcdir)/'`vtls/cipher_suite.c + vtls/libcurl_la-gtls.lo: vtls/gtls.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT vtls/libcurl_la-gtls.lo -MD -MP -MF vtls/$(DEPDIR)/libcurl_la-gtls.Tpo -c -o vtls/libcurl_la-gtls.lo `test -f 'vtls/gtls.c' || echo '$(srcdir)/'`vtls/gtls.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vtls/$(DEPDIR)/libcurl_la-gtls.Tpo vtls/$(DEPDIR)/libcurl_la-gtls.Plo @@ -3252,6 +3459,13 @@ vquic/libcurl_la-curl_ngtcp2.lo: vquic/curl_ngtcp2.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o vquic/libcurl_la-curl_ngtcp2.lo `test -f 'vquic/curl_ngtcp2.c' || echo '$(srcdir)/'`vquic/curl_ngtcp2.c +vquic/libcurl_la-curl_osslq.lo: vquic/curl_osslq.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT vquic/libcurl_la-curl_osslq.lo -MD -MP -MF vquic/$(DEPDIR)/libcurl_la-curl_osslq.Tpo -c -o vquic/libcurl_la-curl_osslq.lo `test -f 'vquic/curl_osslq.c' || echo '$(srcdir)/'`vquic/curl_osslq.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vquic/$(DEPDIR)/libcurl_la-curl_osslq.Tpo vquic/$(DEPDIR)/libcurl_la-curl_osslq.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='vquic/curl_osslq.c' object='vquic/libcurl_la-curl_osslq.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o vquic/libcurl_la-curl_osslq.lo `test -f 'vquic/curl_osslq.c' || echo '$(srcdir)/'`vquic/curl_osslq.c + vquic/libcurl_la-curl_quiche.lo: vquic/curl_quiche.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT vquic/libcurl_la-curl_quiche.lo -MD -MP -MF vquic/$(DEPDIR)/libcurl_la-curl_quiche.Tpo -c -o vquic/libcurl_la-curl_quiche.lo `test -f 'vquic/curl_quiche.c' || echo '$(srcdir)/'`vquic/curl_quiche.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vquic/$(DEPDIR)/libcurl_la-curl_quiche.Tpo vquic/$(DEPDIR)/libcurl_la-curl_quiche.Plo @@ -3266,6 +3480,13 @@ vquic/libcurl_la-vquic.lo: vquic/vquic.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o vquic/libcurl_la-vquic.lo `test -f 'vquic/vquic.c' || echo '$(srcdir)/'`vquic/vquic.c +vquic/libcurl_la-vquic-tls.lo: vquic/vquic-tls.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT vquic/libcurl_la-vquic-tls.lo -MD -MP -MF vquic/$(DEPDIR)/libcurl_la-vquic-tls.Tpo -c -o vquic/libcurl_la-vquic-tls.lo `test -f 'vquic/vquic-tls.c' || echo '$(srcdir)/'`vquic/vquic-tls.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vquic/$(DEPDIR)/libcurl_la-vquic-tls.Tpo vquic/$(DEPDIR)/libcurl_la-vquic-tls.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='vquic/vquic-tls.c' object='vquic/libcurl_la-vquic-tls.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o vquic/libcurl_la-vquic-tls.lo `test -f 'vquic/vquic-tls.c' || echo '$(srcdir)/'`vquic/vquic-tls.c + vssh/libcurl_la-libssh.lo: vssh/libssh.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT vssh/libcurl_la-libssh.lo -MD -MP -MF vssh/$(DEPDIR)/libcurl_la-libssh.Tpo -c -o vssh/libcurl_la-libssh.lo `test -f 'vssh/libssh.c' || echo '$(srcdir)/'`vssh/libssh.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vssh/$(DEPDIR)/libcurl_la-libssh.Tpo vssh/$(DEPDIR)/libcurl_la-libssh.Plo @@ -3280,6 +3501,13 @@ vssh/libcurl_la-libssh2.lo: vssh/libssh2.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o vssh/libcurl_la-libssh2.lo `test -f 'vssh/libssh2.c' || echo '$(srcdir)/'`vssh/libssh2.c +vssh/libcurl_la-curl_path.lo: vssh/curl_path.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT vssh/libcurl_la-curl_path.lo -MD -MP -MF vssh/$(DEPDIR)/libcurl_la-curl_path.Tpo -c -o vssh/libcurl_la-curl_path.lo `test -f 'vssh/curl_path.c' || echo '$(srcdir)/'`vssh/curl_path.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vssh/$(DEPDIR)/libcurl_la-curl_path.Tpo vssh/$(DEPDIR)/libcurl_la-curl_path.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='vssh/curl_path.c' object='vssh/libcurl_la-curl_path.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o vssh/libcurl_la-curl_path.lo `test -f 'vssh/curl_path.c' || echo '$(srcdir)/'`vssh/curl_path.c + vssh/libcurl_la-wolfssh.lo: vssh/wolfssh.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT vssh/libcurl_la-wolfssh.lo -MD -MP -MF vssh/$(DEPDIR)/libcurl_la-wolfssh.Tpo -c -o vssh/libcurl_la-wolfssh.lo `test -f 'vssh/wolfssh.c' || echo '$(srcdir)/'`vssh/wolfssh.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vssh/$(DEPDIR)/libcurl_la-wolfssh.Tpo vssh/$(DEPDIR)/libcurl_la-wolfssh.Plo @@ -3287,6 +3515,13 @@ vssh/libcurl_la-wolfssh.lo: vssh/wolfssh.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o vssh/libcurl_la-wolfssh.lo `test -f 'vssh/wolfssh.c' || echo '$(srcdir)/'`vssh/wolfssh.c +libcurl_la-libcurl_unity.lo: libcurl_unity.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -MT libcurl_la-libcurl_unity.lo -MD -MP -MF $(DEPDIR)/libcurl_la-libcurl_unity.Tpo -c -o libcurl_la-libcurl_unity.lo `test -f 'libcurl_unity.c' || echo '$(srcdir)/'`libcurl_unity.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurl_la-libcurl_unity.Tpo $(DEPDIR)/libcurl_la-libcurl_unity.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libcurl_unity.c' object='libcurl_la-libcurl_unity.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurl_la_CPPFLAGS) $(CPPFLAGS) $(libcurl_la_CFLAGS) $(CFLAGS) -c -o libcurl_la-libcurl_unity.lo `test -f 'libcurl_unity.c' || echo '$(srcdir)/'`libcurl_unity.c + libcurlu_la-altsvc.lo: altsvc.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-altsvc.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-altsvc.Tpo -c -o libcurlu_la-altsvc.lo `test -f 'altsvc.c' || echo '$(srcdir)/'`altsvc.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-altsvc.Tpo $(DEPDIR)/libcurlu_la-altsvc.Plo @@ -3483,20 +3718,6 @@ libcurlu_la-curl_ntlm_core.lo: curl_ntlm_core.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-curl_ntlm_core.lo `test -f 'curl_ntlm_core.c' || echo '$(srcdir)/'`curl_ntlm_core.c -libcurlu_la-curl_ntlm_wb.lo: curl_ntlm_wb.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-curl_ntlm_wb.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-curl_ntlm_wb.Tpo -c -o libcurlu_la-curl_ntlm_wb.lo `test -f 'curl_ntlm_wb.c' || echo '$(srcdir)/'`curl_ntlm_wb.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-curl_ntlm_wb.Tpo $(DEPDIR)/libcurlu_la-curl_ntlm_wb.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='curl_ntlm_wb.c' object='libcurlu_la-curl_ntlm_wb.lo' libtool=yes @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-curl_ntlm_wb.lo `test -f 'curl_ntlm_wb.c' || echo '$(srcdir)/'`curl_ntlm_wb.c - -libcurlu_la-curl_path.lo: curl_path.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-curl_path.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-curl_path.Tpo -c -o libcurlu_la-curl_path.lo `test -f 'curl_path.c' || echo '$(srcdir)/'`curl_path.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-curl_path.Tpo $(DEPDIR)/libcurlu_la-curl_path.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='curl_path.c' object='libcurlu_la-curl_path.lo' libtool=yes @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-curl_path.lo `test -f 'curl_path.c' || echo '$(srcdir)/'`curl_path.c - libcurlu_la-curl_range.lo: curl_range.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-curl_range.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-curl_range.Tpo -c -o libcurlu_la-curl_range.lo `test -f 'curl_range.c' || echo '$(srcdir)/'`curl_range.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-curl_range.Tpo $(DEPDIR)/libcurlu_la-curl_range.Plo @@ -3518,6 +3739,13 @@ libcurlu_la-curl_sasl.lo: curl_sasl.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-curl_sasl.lo `test -f 'curl_sasl.c' || echo '$(srcdir)/'`curl_sasl.c +libcurlu_la-curl_sha512_256.lo: curl_sha512_256.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-curl_sha512_256.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-curl_sha512_256.Tpo -c -o libcurlu_la-curl_sha512_256.lo `test -f 'curl_sha512_256.c' || echo '$(srcdir)/'`curl_sha512_256.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-curl_sha512_256.Tpo $(DEPDIR)/libcurlu_la-curl_sha512_256.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='curl_sha512_256.c' object='libcurlu_la-curl_sha512_256.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-curl_sha512_256.lo `test -f 'curl_sha512_256.c' || echo '$(srcdir)/'`curl_sha512_256.c + libcurlu_la-curl_sspi.lo: curl_sspi.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-curl_sspi.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-curl_sspi.Tpo -c -o libcurlu_la-curl_sspi.lo `test -f 'curl_sspi.c' || echo '$(srcdir)/'`curl_sspi.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-curl_sspi.Tpo $(DEPDIR)/libcurlu_la-curl_sspi.Plo @@ -3539,6 +3767,13 @@ libcurlu_la-curl_trc.lo: curl_trc.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-curl_trc.lo `test -f 'curl_trc.c' || echo '$(srcdir)/'`curl_trc.c +libcurlu_la-cw-out.lo: cw-out.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-cw-out.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-cw-out.Tpo -c -o libcurlu_la-cw-out.lo `test -f 'cw-out.c' || echo '$(srcdir)/'`cw-out.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-cw-out.Tpo $(DEPDIR)/libcurlu_la-cw-out.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cw-out.c' object='libcurlu_la-cw-out.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-cw-out.lo `test -f 'cw-out.c' || echo '$(srcdir)/'`cw-out.c + libcurlu_la-dict.lo: dict.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-dict.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-dict.Tpo -c -o libcurlu_la-dict.lo `test -f 'dict.c' || echo '$(srcdir)/'`dict.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-dict.Tpo $(DEPDIR)/libcurlu_la-dict.Plo @@ -3546,6 +3781,13 @@ libcurlu_la-dict.lo: dict.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-dict.lo `test -f 'dict.c' || echo '$(srcdir)/'`dict.c +libcurlu_la-dllmain.lo: dllmain.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-dllmain.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-dllmain.Tpo -c -o libcurlu_la-dllmain.lo `test -f 'dllmain.c' || echo '$(srcdir)/'`dllmain.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-dllmain.Tpo $(DEPDIR)/libcurlu_la-dllmain.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dllmain.c' object='libcurlu_la-dllmain.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-dllmain.lo `test -f 'dllmain.c' || echo '$(srcdir)/'`dllmain.c + libcurlu_la-doh.lo: doh.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-doh.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-doh.Tpo -c -o libcurlu_la-doh.lo `test -f 'doh.c' || echo '$(srcdir)/'`doh.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-doh.Tpo $(DEPDIR)/libcurlu_la-doh.Plo @@ -3742,6 +3984,13 @@ libcurlu_la-http2.lo: http2.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-http2.lo `test -f 'http2.c' || echo '$(srcdir)/'`http2.c +libcurlu_la-http_aws_sigv4.lo: http_aws_sigv4.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-http_aws_sigv4.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-http_aws_sigv4.Tpo -c -o libcurlu_la-http_aws_sigv4.lo `test -f 'http_aws_sigv4.c' || echo '$(srcdir)/'`http_aws_sigv4.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-http_aws_sigv4.Tpo $(DEPDIR)/libcurlu_la-http_aws_sigv4.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http_aws_sigv4.c' object='libcurlu_la-http_aws_sigv4.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-http_aws_sigv4.lo `test -f 'http_aws_sigv4.c' || echo '$(srcdir)/'`http_aws_sigv4.c + libcurlu_la-http_chunks.lo: http_chunks.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-http_chunks.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-http_chunks.Tpo -c -o libcurlu_la-http_chunks.lo `test -f 'http_chunks.c' || echo '$(srcdir)/'`http_chunks.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-http_chunks.Tpo $(DEPDIR)/libcurlu_la-http_chunks.Plo @@ -3777,13 +4026,6 @@ libcurlu_la-http_proxy.lo: http_proxy.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-http_proxy.lo `test -f 'http_proxy.c' || echo '$(srcdir)/'`http_proxy.c -libcurlu_la-http_aws_sigv4.lo: http_aws_sigv4.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-http_aws_sigv4.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-http_aws_sigv4.Tpo -c -o libcurlu_la-http_aws_sigv4.lo `test -f 'http_aws_sigv4.c' || echo '$(srcdir)/'`http_aws_sigv4.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-http_aws_sigv4.Tpo $(DEPDIR)/libcurlu_la-http_aws_sigv4.Plo -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http_aws_sigv4.c' object='libcurlu_la-http_aws_sigv4.lo' libtool=yes @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-http_aws_sigv4.lo `test -f 'http_aws_sigv4.c' || echo '$(srcdir)/'`http_aws_sigv4.c - libcurlu_la-idn.lo: idn.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-idn.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-idn.Tpo -c -o libcurlu_la-idn.lo `test -f 'idn.c' || echo '$(srcdir)/'`idn.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-idn.Tpo $(DEPDIR)/libcurlu_la-idn.Plo @@ -3973,6 +4215,13 @@ libcurlu_la-rename.lo: rename.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-rename.lo `test -f 'rename.c' || echo '$(srcdir)/'`rename.c +libcurlu_la-request.lo: request.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-request.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-request.Tpo -c -o libcurlu_la-request.lo `test -f 'request.c' || echo '$(srcdir)/'`request.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-request.Tpo $(DEPDIR)/libcurlu_la-request.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='request.c' object='libcurlu_la-request.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-request.lo `test -f 'request.c' || echo '$(srcdir)/'`request.c + libcurlu_la-rtsp.lo: rtsp.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-rtsp.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-rtsp.Tpo -c -o libcurlu_la-rtsp.lo `test -f 'rtsp.c' || echo '$(srcdir)/'`rtsp.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-rtsp.Tpo $(DEPDIR)/libcurlu_la-rtsp.Plo @@ -4295,6 +4544,13 @@ vtls/libcurlu_la-bearssl.lo: vtls/bearssl.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o vtls/libcurlu_la-bearssl.lo `test -f 'vtls/bearssl.c' || echo '$(srcdir)/'`vtls/bearssl.c +vtls/libcurlu_la-cipher_suite.lo: vtls/cipher_suite.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT vtls/libcurlu_la-cipher_suite.lo -MD -MP -MF vtls/$(DEPDIR)/libcurlu_la-cipher_suite.Tpo -c -o vtls/libcurlu_la-cipher_suite.lo `test -f 'vtls/cipher_suite.c' || echo '$(srcdir)/'`vtls/cipher_suite.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vtls/$(DEPDIR)/libcurlu_la-cipher_suite.Tpo vtls/$(DEPDIR)/libcurlu_la-cipher_suite.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='vtls/cipher_suite.c' object='vtls/libcurlu_la-cipher_suite.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o vtls/libcurlu_la-cipher_suite.lo `test -f 'vtls/cipher_suite.c' || echo '$(srcdir)/'`vtls/cipher_suite.c + vtls/libcurlu_la-gtls.lo: vtls/gtls.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT vtls/libcurlu_la-gtls.lo -MD -MP -MF vtls/$(DEPDIR)/libcurlu_la-gtls.Tpo -c -o vtls/libcurlu_la-gtls.lo `test -f 'vtls/gtls.c' || echo '$(srcdir)/'`vtls/gtls.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vtls/$(DEPDIR)/libcurlu_la-gtls.Tpo vtls/$(DEPDIR)/libcurlu_la-gtls.Plo @@ -4400,6 +4656,13 @@ vquic/libcurlu_la-curl_ngtcp2.lo: vquic/curl_ngtcp2.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o vquic/libcurlu_la-curl_ngtcp2.lo `test -f 'vquic/curl_ngtcp2.c' || echo '$(srcdir)/'`vquic/curl_ngtcp2.c +vquic/libcurlu_la-curl_osslq.lo: vquic/curl_osslq.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT vquic/libcurlu_la-curl_osslq.lo -MD -MP -MF vquic/$(DEPDIR)/libcurlu_la-curl_osslq.Tpo -c -o vquic/libcurlu_la-curl_osslq.lo `test -f 'vquic/curl_osslq.c' || echo '$(srcdir)/'`vquic/curl_osslq.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vquic/$(DEPDIR)/libcurlu_la-curl_osslq.Tpo vquic/$(DEPDIR)/libcurlu_la-curl_osslq.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='vquic/curl_osslq.c' object='vquic/libcurlu_la-curl_osslq.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o vquic/libcurlu_la-curl_osslq.lo `test -f 'vquic/curl_osslq.c' || echo '$(srcdir)/'`vquic/curl_osslq.c + vquic/libcurlu_la-curl_quiche.lo: vquic/curl_quiche.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT vquic/libcurlu_la-curl_quiche.lo -MD -MP -MF vquic/$(DEPDIR)/libcurlu_la-curl_quiche.Tpo -c -o vquic/libcurlu_la-curl_quiche.lo `test -f 'vquic/curl_quiche.c' || echo '$(srcdir)/'`vquic/curl_quiche.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vquic/$(DEPDIR)/libcurlu_la-curl_quiche.Tpo vquic/$(DEPDIR)/libcurlu_la-curl_quiche.Plo @@ -4414,6 +4677,13 @@ vquic/libcurlu_la-vquic.lo: vquic/vquic.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o vquic/libcurlu_la-vquic.lo `test -f 'vquic/vquic.c' || echo '$(srcdir)/'`vquic/vquic.c +vquic/libcurlu_la-vquic-tls.lo: vquic/vquic-tls.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT vquic/libcurlu_la-vquic-tls.lo -MD -MP -MF vquic/$(DEPDIR)/libcurlu_la-vquic-tls.Tpo -c -o vquic/libcurlu_la-vquic-tls.lo `test -f 'vquic/vquic-tls.c' || echo '$(srcdir)/'`vquic/vquic-tls.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vquic/$(DEPDIR)/libcurlu_la-vquic-tls.Tpo vquic/$(DEPDIR)/libcurlu_la-vquic-tls.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='vquic/vquic-tls.c' object='vquic/libcurlu_la-vquic-tls.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o vquic/libcurlu_la-vquic-tls.lo `test -f 'vquic/vquic-tls.c' || echo '$(srcdir)/'`vquic/vquic-tls.c + vssh/libcurlu_la-libssh.lo: vssh/libssh.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT vssh/libcurlu_la-libssh.lo -MD -MP -MF vssh/$(DEPDIR)/libcurlu_la-libssh.Tpo -c -o vssh/libcurlu_la-libssh.lo `test -f 'vssh/libssh.c' || echo '$(srcdir)/'`vssh/libssh.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vssh/$(DEPDIR)/libcurlu_la-libssh.Tpo vssh/$(DEPDIR)/libcurlu_la-libssh.Plo @@ -4428,6 +4698,13 @@ vssh/libcurlu_la-libssh2.lo: vssh/libssh2.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o vssh/libcurlu_la-libssh2.lo `test -f 'vssh/libssh2.c' || echo '$(srcdir)/'`vssh/libssh2.c +vssh/libcurlu_la-curl_path.lo: vssh/curl_path.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT vssh/libcurlu_la-curl_path.lo -MD -MP -MF vssh/$(DEPDIR)/libcurlu_la-curl_path.Tpo -c -o vssh/libcurlu_la-curl_path.lo `test -f 'vssh/curl_path.c' || echo '$(srcdir)/'`vssh/curl_path.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vssh/$(DEPDIR)/libcurlu_la-curl_path.Tpo vssh/$(DEPDIR)/libcurlu_la-curl_path.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='vssh/curl_path.c' object='vssh/libcurlu_la-curl_path.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o vssh/libcurlu_la-curl_path.lo `test -f 'vssh/curl_path.c' || echo '$(srcdir)/'`vssh/curl_path.c + vssh/libcurlu_la-wolfssh.lo: vssh/wolfssh.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT vssh/libcurlu_la-wolfssh.lo -MD -MP -MF vssh/$(DEPDIR)/libcurlu_la-wolfssh.Tpo -c -o vssh/libcurlu_la-wolfssh.lo `test -f 'vssh/wolfssh.c' || echo '$(srcdir)/'`vssh/wolfssh.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) vssh/$(DEPDIR)/libcurlu_la-wolfssh.Tpo vssh/$(DEPDIR)/libcurlu_la-wolfssh.Plo @@ -4435,6 +4712,13 @@ vssh/libcurlu_la-wolfssh.lo: vssh/wolfssh.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o vssh/libcurlu_la-wolfssh.lo `test -f 'vssh/wolfssh.c' || echo '$(srcdir)/'`vssh/wolfssh.c +libcurlu_la-libcurl_unity.lo: libcurl_unity.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -MT libcurlu_la-libcurl_unity.lo -MD -MP -MF $(DEPDIR)/libcurlu_la-libcurl_unity.Tpo -c -o libcurlu_la-libcurl_unity.lo `test -f 'libcurl_unity.c' || echo '$(srcdir)/'`libcurl_unity.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcurlu_la-libcurl_unity.Tpo $(DEPDIR)/libcurlu_la-libcurl_unity.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libcurl_unity.c' object='libcurlu_la-libcurl_unity.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcurlu_la_CPPFLAGS) $(CPPFLAGS) $(libcurlu_la_CFLAGS) $(CFLAGS) -c -o libcurlu_la-libcurl_unity.lo `test -f 'libcurl_unity.c' || echo '$(srcdir)/'`libcurl_unity.c + mostlyclean-libtool: -rm -f *.lo @@ -4531,7 +4815,7 @@ distdir-am: $(DISTFILES) done check-am: all-am check: check-am -@CURLDEBUG_FALSE@all-local: +@DEBUGBUILD_FALSE@all-local: all-am: Makefile $(LTLIBRARIES) curl_config.h all-local installdirs: for dir in "$(DESTDIR)$(libdir)"; do \ @@ -4559,6 +4843,7 @@ install-strip: mostlyclean-generic: clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) @@ -4609,15 +4894,16 @@ distclean: distclean-am -rm -f ./$(DEPDIR)/libcurl_la-curl_memrchr.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_multibyte.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_ntlm_core.Plo - -rm -f ./$(DEPDIR)/libcurl_la-curl_ntlm_wb.Plo - -rm -f ./$(DEPDIR)/libcurl_la-curl_path.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_range.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_rtmp.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_sasl.Plo + -rm -f ./$(DEPDIR)/libcurl_la-curl_sha512_256.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_sspi.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_threads.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_trc.Plo + -rm -f ./$(DEPDIR)/libcurl_la-cw-out.Plo -rm -f ./$(DEPDIR)/libcurl_la-dict.Plo + -rm -f ./$(DEPDIR)/libcurl_la-dllmain.Plo -rm -f ./$(DEPDIR)/libcurl_la-doh.Plo -rm -f ./$(DEPDIR)/libcurl_la-dynbuf.Plo -rm -f ./$(DEPDIR)/libcurl_la-dynhds.Plo @@ -4659,6 +4945,7 @@ distclean: distclean-am -rm -f ./$(DEPDIR)/libcurl_la-inet_pton.Plo -rm -f ./$(DEPDIR)/libcurl_la-krb5.Plo -rm -f ./$(DEPDIR)/libcurl_la-ldap.Plo + -rm -f ./$(DEPDIR)/libcurl_la-libcurl_unity.Plo -rm -f ./$(DEPDIR)/libcurl_la-llist.Plo -rm -f ./$(DEPDIR)/libcurl_la-macos.Plo -rm -f ./$(DEPDIR)/libcurl_la-md4.Plo @@ -4679,6 +4966,7 @@ distclean: distclean-am -rm -f ./$(DEPDIR)/libcurl_la-psl.Plo -rm -f ./$(DEPDIR)/libcurl_la-rand.Plo -rm -f ./$(DEPDIR)/libcurl_la-rename.Plo + -rm -f ./$(DEPDIR)/libcurl_la-request.Plo -rm -f ./$(DEPDIR)/libcurl_la-rtsp.Plo -rm -f ./$(DEPDIR)/libcurl_la-select.Plo -rm -f ./$(DEPDIR)/libcurl_la-sendf.Plo @@ -4739,15 +5027,16 @@ distclean: distclean-am -rm -f ./$(DEPDIR)/libcurlu_la-curl_memrchr.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_multibyte.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_ntlm_core.Plo - -rm -f ./$(DEPDIR)/libcurlu_la-curl_ntlm_wb.Plo - -rm -f ./$(DEPDIR)/libcurlu_la-curl_path.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_range.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_rtmp.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_sasl.Plo + -rm -f ./$(DEPDIR)/libcurlu_la-curl_sha512_256.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_sspi.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_threads.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_trc.Plo + -rm -f ./$(DEPDIR)/libcurlu_la-cw-out.Plo -rm -f ./$(DEPDIR)/libcurlu_la-dict.Plo + -rm -f ./$(DEPDIR)/libcurlu_la-dllmain.Plo -rm -f ./$(DEPDIR)/libcurlu_la-doh.Plo -rm -f ./$(DEPDIR)/libcurlu_la-dynbuf.Plo -rm -f ./$(DEPDIR)/libcurlu_la-dynhds.Plo @@ -4789,6 +5078,7 @@ distclean: distclean-am -rm -f ./$(DEPDIR)/libcurlu_la-inet_pton.Plo -rm -f ./$(DEPDIR)/libcurlu_la-krb5.Plo -rm -f ./$(DEPDIR)/libcurlu_la-ldap.Plo + -rm -f ./$(DEPDIR)/libcurlu_la-libcurl_unity.Plo -rm -f ./$(DEPDIR)/libcurlu_la-llist.Plo -rm -f ./$(DEPDIR)/libcurlu_la-macos.Plo -rm -f ./$(DEPDIR)/libcurlu_la-md4.Plo @@ -4809,6 +5099,7 @@ distclean: distclean-am -rm -f ./$(DEPDIR)/libcurlu_la-psl.Plo -rm -f ./$(DEPDIR)/libcurlu_la-rand.Plo -rm -f ./$(DEPDIR)/libcurlu_la-rename.Plo + -rm -f ./$(DEPDIR)/libcurlu_la-request.Plo -rm -f ./$(DEPDIR)/libcurlu_la-rtsp.Plo -rm -f ./$(DEPDIR)/libcurlu_la-select.Plo -rm -f ./$(DEPDIR)/libcurlu_la-sendf.Plo @@ -4869,19 +5160,26 @@ distclean: distclean-am -rm -f vauth/$(DEPDIR)/libcurlu_la-vauth.Plo -rm -f vquic/$(DEPDIR)/libcurl_la-curl_msh3.Plo -rm -f vquic/$(DEPDIR)/libcurl_la-curl_ngtcp2.Plo + -rm -f vquic/$(DEPDIR)/libcurl_la-curl_osslq.Plo -rm -f vquic/$(DEPDIR)/libcurl_la-curl_quiche.Plo + -rm -f vquic/$(DEPDIR)/libcurl_la-vquic-tls.Plo -rm -f vquic/$(DEPDIR)/libcurl_la-vquic.Plo -rm -f vquic/$(DEPDIR)/libcurlu_la-curl_msh3.Plo -rm -f vquic/$(DEPDIR)/libcurlu_la-curl_ngtcp2.Plo + -rm -f vquic/$(DEPDIR)/libcurlu_la-curl_osslq.Plo -rm -f vquic/$(DEPDIR)/libcurlu_la-curl_quiche.Plo + -rm -f vquic/$(DEPDIR)/libcurlu_la-vquic-tls.Plo -rm -f vquic/$(DEPDIR)/libcurlu_la-vquic.Plo + -rm -f vssh/$(DEPDIR)/libcurl_la-curl_path.Plo -rm -f vssh/$(DEPDIR)/libcurl_la-libssh.Plo -rm -f vssh/$(DEPDIR)/libcurl_la-libssh2.Plo -rm -f vssh/$(DEPDIR)/libcurl_la-wolfssh.Plo + -rm -f vssh/$(DEPDIR)/libcurlu_la-curl_path.Plo -rm -f vssh/$(DEPDIR)/libcurlu_la-libssh.Plo -rm -f vssh/$(DEPDIR)/libcurlu_la-libssh2.Plo -rm -f vssh/$(DEPDIR)/libcurlu_la-wolfssh.Plo -rm -f vtls/$(DEPDIR)/libcurl_la-bearssl.Plo + -rm -f vtls/$(DEPDIR)/libcurl_la-cipher_suite.Plo -rm -f vtls/$(DEPDIR)/libcurl_la-gtls.Plo -rm -f vtls/$(DEPDIR)/libcurl_la-hostcheck.Plo -rm -f vtls/$(DEPDIR)/libcurl_la-keylog.Plo @@ -4896,6 +5194,7 @@ distclean: distclean-am -rm -f vtls/$(DEPDIR)/libcurl_la-wolfssl.Plo -rm -f vtls/$(DEPDIR)/libcurl_la-x509asn1.Plo -rm -f vtls/$(DEPDIR)/libcurlu_la-bearssl.Plo + -rm -f vtls/$(DEPDIR)/libcurlu_la-cipher_suite.Plo -rm -f vtls/$(DEPDIR)/libcurlu_la-gtls.Plo -rm -f vtls/$(DEPDIR)/libcurlu_la-hostcheck.Plo -rm -f vtls/$(DEPDIR)/libcurlu_la-keylog.Plo @@ -4982,15 +5281,16 @@ maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/libcurl_la-curl_memrchr.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_multibyte.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_ntlm_core.Plo - -rm -f ./$(DEPDIR)/libcurl_la-curl_ntlm_wb.Plo - -rm -f ./$(DEPDIR)/libcurl_la-curl_path.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_range.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_rtmp.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_sasl.Plo + -rm -f ./$(DEPDIR)/libcurl_la-curl_sha512_256.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_sspi.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_threads.Plo -rm -f ./$(DEPDIR)/libcurl_la-curl_trc.Plo + -rm -f ./$(DEPDIR)/libcurl_la-cw-out.Plo -rm -f ./$(DEPDIR)/libcurl_la-dict.Plo + -rm -f ./$(DEPDIR)/libcurl_la-dllmain.Plo -rm -f ./$(DEPDIR)/libcurl_la-doh.Plo -rm -f ./$(DEPDIR)/libcurl_la-dynbuf.Plo -rm -f ./$(DEPDIR)/libcurl_la-dynhds.Plo @@ -5032,6 +5332,7 @@ maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/libcurl_la-inet_pton.Plo -rm -f ./$(DEPDIR)/libcurl_la-krb5.Plo -rm -f ./$(DEPDIR)/libcurl_la-ldap.Plo + -rm -f ./$(DEPDIR)/libcurl_la-libcurl_unity.Plo -rm -f ./$(DEPDIR)/libcurl_la-llist.Plo -rm -f ./$(DEPDIR)/libcurl_la-macos.Plo -rm -f ./$(DEPDIR)/libcurl_la-md4.Plo @@ -5052,6 +5353,7 @@ maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/libcurl_la-psl.Plo -rm -f ./$(DEPDIR)/libcurl_la-rand.Plo -rm -f ./$(DEPDIR)/libcurl_la-rename.Plo + -rm -f ./$(DEPDIR)/libcurl_la-request.Plo -rm -f ./$(DEPDIR)/libcurl_la-rtsp.Plo -rm -f ./$(DEPDIR)/libcurl_la-select.Plo -rm -f ./$(DEPDIR)/libcurl_la-sendf.Plo @@ -5112,15 +5414,16 @@ maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/libcurlu_la-curl_memrchr.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_multibyte.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_ntlm_core.Plo - -rm -f ./$(DEPDIR)/libcurlu_la-curl_ntlm_wb.Plo - -rm -f ./$(DEPDIR)/libcurlu_la-curl_path.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_range.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_rtmp.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_sasl.Plo + -rm -f ./$(DEPDIR)/libcurlu_la-curl_sha512_256.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_sspi.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_threads.Plo -rm -f ./$(DEPDIR)/libcurlu_la-curl_trc.Plo + -rm -f ./$(DEPDIR)/libcurlu_la-cw-out.Plo -rm -f ./$(DEPDIR)/libcurlu_la-dict.Plo + -rm -f ./$(DEPDIR)/libcurlu_la-dllmain.Plo -rm -f ./$(DEPDIR)/libcurlu_la-doh.Plo -rm -f ./$(DEPDIR)/libcurlu_la-dynbuf.Plo -rm -f ./$(DEPDIR)/libcurlu_la-dynhds.Plo @@ -5162,6 +5465,7 @@ maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/libcurlu_la-inet_pton.Plo -rm -f ./$(DEPDIR)/libcurlu_la-krb5.Plo -rm -f ./$(DEPDIR)/libcurlu_la-ldap.Plo + -rm -f ./$(DEPDIR)/libcurlu_la-libcurl_unity.Plo -rm -f ./$(DEPDIR)/libcurlu_la-llist.Plo -rm -f ./$(DEPDIR)/libcurlu_la-macos.Plo -rm -f ./$(DEPDIR)/libcurlu_la-md4.Plo @@ -5182,6 +5486,7 @@ maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/libcurlu_la-psl.Plo -rm -f ./$(DEPDIR)/libcurlu_la-rand.Plo -rm -f ./$(DEPDIR)/libcurlu_la-rename.Plo + -rm -f ./$(DEPDIR)/libcurlu_la-request.Plo -rm -f ./$(DEPDIR)/libcurlu_la-rtsp.Plo -rm -f ./$(DEPDIR)/libcurlu_la-select.Plo -rm -f ./$(DEPDIR)/libcurlu_la-sendf.Plo @@ -5242,19 +5547,26 @@ maintainer-clean: maintainer-clean-am -rm -f vauth/$(DEPDIR)/libcurlu_la-vauth.Plo -rm -f vquic/$(DEPDIR)/libcurl_la-curl_msh3.Plo -rm -f vquic/$(DEPDIR)/libcurl_la-curl_ngtcp2.Plo + -rm -f vquic/$(DEPDIR)/libcurl_la-curl_osslq.Plo -rm -f vquic/$(DEPDIR)/libcurl_la-curl_quiche.Plo + -rm -f vquic/$(DEPDIR)/libcurl_la-vquic-tls.Plo -rm -f vquic/$(DEPDIR)/libcurl_la-vquic.Plo -rm -f vquic/$(DEPDIR)/libcurlu_la-curl_msh3.Plo -rm -f vquic/$(DEPDIR)/libcurlu_la-curl_ngtcp2.Plo + -rm -f vquic/$(DEPDIR)/libcurlu_la-curl_osslq.Plo -rm -f vquic/$(DEPDIR)/libcurlu_la-curl_quiche.Plo + -rm -f vquic/$(DEPDIR)/libcurlu_la-vquic-tls.Plo -rm -f vquic/$(DEPDIR)/libcurlu_la-vquic.Plo + -rm -f vssh/$(DEPDIR)/libcurl_la-curl_path.Plo -rm -f vssh/$(DEPDIR)/libcurl_la-libssh.Plo -rm -f vssh/$(DEPDIR)/libcurl_la-libssh2.Plo -rm -f vssh/$(DEPDIR)/libcurl_la-wolfssh.Plo + -rm -f vssh/$(DEPDIR)/libcurlu_la-curl_path.Plo -rm -f vssh/$(DEPDIR)/libcurlu_la-libssh.Plo -rm -f vssh/$(DEPDIR)/libcurlu_la-libssh2.Plo -rm -f vssh/$(DEPDIR)/libcurlu_la-wolfssh.Plo -rm -f vtls/$(DEPDIR)/libcurl_la-bearssl.Plo + -rm -f vtls/$(DEPDIR)/libcurl_la-cipher_suite.Plo -rm -f vtls/$(DEPDIR)/libcurl_la-gtls.Plo -rm -f vtls/$(DEPDIR)/libcurl_la-hostcheck.Plo -rm -f vtls/$(DEPDIR)/libcurl_la-keylog.Plo @@ -5269,6 +5581,7 @@ maintainer-clean: maintainer-clean-am -rm -f vtls/$(DEPDIR)/libcurl_la-wolfssl.Plo -rm -f vtls/$(DEPDIR)/libcurl_la-x509asn1.Plo -rm -f vtls/$(DEPDIR)/libcurlu_la-bearssl.Plo + -rm -f vtls/$(DEPDIR)/libcurlu_la-cipher_suite.Plo -rm -f vtls/$(DEPDIR)/libcurlu_la-gtls.Plo -rm -f vtls/$(DEPDIR)/libcurlu_la-hostcheck.Plo -rm -f vtls/$(DEPDIR)/libcurlu_la-keylog.Plo @@ -5332,6 +5645,8 @@ uninstall-am: uninstall-libLTLIBRARIES # 3. if interfaces were removed, then use C+1:0:0 # # For the full guide on libcurl ABI rules, see docs/libcurl/ABI +@USE_UNITY_TRUE@libcurl_unity.c: $(top_srcdir)/scripts/mk-unity.pl $(CSOURCES) +@USE_UNITY_TRUE@ @PERL@ $(top_srcdir)/scripts/mk-unity.pl $(srcdir) $(CSOURCES) --exclude $(curl_EXCLUDE) > libcurl_unity.c @HAVE_WINDRES_TRUE@@USE_CPPFLAG_CURL_STATICLIB_FALSE@$(LIB_RCFILES): $(top_srcdir)/include/curl/curlver.h checksrc: @@ -5340,7 +5655,7 @@ checksrc: $(srcdir)/vtls/*.[ch] $(srcdir)/vquic/*.[ch] $(srcdir)/vssh/*.[ch]) # for debug builds, we scan the sources on all regular make invokes -@CURLDEBUG_TRUE@all-local: checksrc +@DEBUGBUILD_TRUE@all-local: checksrc tidy: $(TIDY) $(CSOURCES) $(TIDYFLAGS) -- $(AM_CPPFLAGS) $(CPPFLAGS) -DHAVE_CONFIG_H diff --git a/deps/curl/lib/Makefile.inc b/deps/curl/lib/Makefile.inc index a08180b531b..1d3f69a23c3 100644 --- a/deps/curl/lib/Makefile.inc +++ b/deps/curl/lib/Makefile.inc @@ -44,6 +44,7 @@ LIB_VAUTH_HFILES = \ LIB_VTLS_CFILES = \ vtls/bearssl.c \ + vtls/cipher_suite.c \ vtls/gtls.c \ vtls/hostcheck.c \ vtls/keylog.c \ @@ -60,6 +61,7 @@ LIB_VTLS_CFILES = \ LIB_VTLS_HFILES = \ vtls/bearssl.h \ + vtls/cipher_suite.h \ vtls/gtls.h \ vtls/hostcheck.h \ vtls/keylog.h \ @@ -78,22 +80,28 @@ LIB_VTLS_HFILES = \ LIB_VQUIC_CFILES = \ vquic/curl_msh3.c \ vquic/curl_ngtcp2.c \ + vquic/curl_osslq.c \ vquic/curl_quiche.c \ - vquic/vquic.c + vquic/vquic.c \ + vquic/vquic-tls.c LIB_VQUIC_HFILES = \ vquic/curl_msh3.h \ vquic/curl_ngtcp2.h \ + vquic/curl_osslq.h \ vquic/curl_quiche.h \ vquic/vquic.h \ - vquic/vquic_int.h + vquic/vquic_int.h \ + vquic/vquic-tls.h LIB_VSSH_CFILES = \ vssh/libssh.c \ vssh/libssh2.c \ + vssh/curl_path.c \ vssh/wolfssh.c -LIB_VSSH_HFILES = \ +LIB_VSSH_HFILES = \ + vssh/curl_path.h \ vssh/ssh.h LIB_CFILES = \ @@ -125,15 +133,16 @@ LIB_CFILES = \ curl_memrchr.c \ curl_multibyte.c \ curl_ntlm_core.c \ - curl_ntlm_wb.c \ - curl_path.c \ curl_range.c \ curl_rtmp.c \ curl_sasl.c \ + curl_sha512_256.c \ curl_sspi.c \ curl_threads.c \ curl_trc.c \ + cw-out.c \ dict.c \ + dllmain.c \ doh.c \ dynbuf.c \ dynhds.c \ @@ -162,12 +171,12 @@ LIB_CFILES = \ http.c \ http1.c \ http2.c \ + http_aws_sigv4.c \ http_chunks.c \ http_digest.c \ http_negotiate.c \ http_ntlm.c \ http_proxy.c \ - http_aws_sigv4.c \ idn.c \ if2ip.c \ imap.c \ @@ -195,6 +204,7 @@ LIB_CFILES = \ psl.c \ rand.c \ rename.c \ + request.c \ rtsp.c \ select.c \ sendf.c \ @@ -264,8 +274,6 @@ LIB_HFILES = \ curl_memrchr.h \ curl_multibyte.h \ curl_ntlm_core.h \ - curl_ntlm_wb.h \ - curl_path.h \ curl_printf.h \ curl_range.h \ curl_rtmp.h \ @@ -273,10 +281,12 @@ LIB_HFILES = \ curl_setup.h \ curl_setup_once.h \ curl_sha256.h \ + curl_sha512_256.h \ curl_sspi.h \ curl_threads.h \ curl_trc.h \ curlx.h \ + cw-out.h \ dict.h \ doh.h \ dynbuf.h \ @@ -289,9 +299,9 @@ LIB_HFILES = \ fileinfo.h \ fopen.h \ formdata.h \ - functypes.h \ ftp.h \ ftplistparser.h \ + functypes.h \ getinfo.h \ gopher.h \ hash.h \ @@ -301,12 +311,12 @@ LIB_HFILES = \ http.h \ http1.h \ http2.h \ + http_aws_sigv4.h \ http_chunks.h \ http_digest.h \ http_negotiate.h \ http_ntlm.h \ http_proxy.h \ - http_aws_sigv4.h \ idn.h \ if2ip.h \ imap.h \ @@ -329,6 +339,7 @@ LIB_HFILES = \ psl.h \ rand.h \ rename.h \ + request.h \ rtsp.h \ select.h \ sendf.h \ diff --git a/deps/curl/lib/Makefile.mk b/deps/curl/lib/Makefile.mk index e1f782bac8f..e130b758e57 100644 --- a/deps/curl/lib/Makefile.mk +++ b/deps/curl/lib/Makefile.mk @@ -24,10 +24,10 @@ # Makefile to build curl parts with GCC-like toolchains and optional features. # -# Usage: [mingw32-]make -f Makefile.mk CFG=-feat1[-feat2][-feat3][...] -# Example: [mingw32-]make -f Makefile.mk CFG=-zlib-ssl-libssh2-ipv6 +# Usage: make -f Makefile.mk CFG=-feat1[-feat2][-feat3][...] +# Example: make -f Makefile.mk CFG=-zlib-ssl-libssh2-ipv6 # -# Look for ' ?=' to find all accepted customization variables. +# Look for ' ?=' to find accepted customization variables. # This script is reused by 'src' and 'docs/examples' Makefile.mk scripts. @@ -40,10 +40,7 @@ endif CFLAGS ?= CPPFLAGS ?= -RCFLAGS ?= LDFLAGS ?= -CURL_LDFLAGS_BIN ?= -CURL_LDFLAGS_LIB ?= LIBS ?= CROSSPREFIX ?= @@ -53,46 +50,21 @@ ifeq ($(CC),cc) endif CC := $(CROSSPREFIX)$(CC) AR := $(CROSSPREFIX)$(AR) -RC ?= $(CROSSPREFIX)windres - -# For compatibility -ARCH ?= -ifeq ($(ARCH),w64) - TRIPLET := x86_64-w64-mingw32 - CFLAGS += -m64 - LDFLAGS += -m64 - RCFLAGS += --target=pe-x86-64 -else ifdef ARCH - TRIPLET := i686-w64-mingw32 - CFLAGS += -m32 - LDFLAGS += -m32 - RCFLAGS += --target=pe-i386 -else - TRIPLET ?= $(shell $(CC) -dumpmachine) -endif -BIN_EXT := .exe +TRIPLET ?= $(shell $(CC) -dumpmachine) + +BIN_EXT := -ifneq ($(findstring -w,$(TRIPLET)),) - WIN32 := 1 -else ifneq ($(findstring msdos,$(TRIPLET)),) +ifneq ($(findstring msdos,$(TRIPLET)),) # Cross-tools: https://github.com/andrewwutw/build-djgpp MSDOS := 1 + BIN_EXT := .exe else ifneq ($(findstring amigaos,$(TRIPLET)),) # Cross-tools: https://github.com/bebbo/amiga-gcc AMIGA := 1 endif CPPFLAGS += -I. -I$(PROOT)/include -RCFLAGS += -I$(PROOT)/include - -ifndef WIN32 - DYN := -endif - -ifdef AMIGA - BIN_EXT := -endif ### Deprecated settings. For compatibility. @@ -115,61 +87,47 @@ ifneq ($(findstring -map,$(CFG)),) MAP := 1 endif -ifdef WIN32 - ifneq ($(findstring -unicode,$(CFG)),) - CPPFLAGS += -DUNICODE -D_UNICODE - CURL_LDFLAGS_BIN += -municode - endif -endif - # CPPFLAGS below are only necessary when building libcurl via 'lib' (see # comments below about exceptions). Always include them anyway to match # behavior of other build systems. -# Linker options to exclude for shared mode executables. -_LDFLAGS := -_LIBS := - ifneq ($(findstring -sync,$(CFG)),) CPPFLAGS += -DUSE_SYNC_DNS else ifneq ($(findstring -ares,$(CFG)),) LIBCARES_PATH ?= $(PROOT)/../c-ares CPPFLAGS += -DUSE_ARES - CPPFLAGS += -I"$(LIBCARES_PATH)/include" - _LDFLAGS += -L"$(LIBCARES_PATH)/lib" - _LIBS += -lcares + CPPFLAGS += -isystem "$(LIBCARES_PATH)/include" + LDFLAGS += -L"$(LIBCARES_PATH)/lib" + LIBS += -lcares endif ifneq ($(findstring -rtmp,$(CFG)),) LIBRTMP_PATH ?= $(PROOT)/../librtmp CPPFLAGS += -DUSE_LIBRTMP - CPPFLAGS += -I"$(LIBRTMP_PATH)" - _LDFLAGS += -L"$(LIBRTMP_PATH)/librtmp" - _LIBS += -lrtmp -lwinmm + CPPFLAGS += -isystem "$(LIBRTMP_PATH)" + LDFLAGS += -L"$(LIBRTMP_PATH)/librtmp" + LIBS += -lrtmp ZLIB := 1 endif ifneq ($(findstring -ssh2,$(CFG)),) LIBSSH2_PATH ?= $(PROOT)/../libssh2 CPPFLAGS += -DUSE_LIBSSH2 - CPPFLAGS += -I"$(LIBSSH2_PATH)/include" - _LDFLAGS += -L"$(LIBSSH2_PATH)/lib" - ifdef WIN32 - _LDFLAGS += -L"$(LIBSSH2_PATH)/win32" - endif - _LIBS += -lssh2 + CPPFLAGS += -isystem "$(LIBSSH2_PATH)/include" + LDFLAGS += -L"$(LIBSSH2_PATH)/lib" + LIBS += -lssh2 else ifneq ($(findstring -libssh,$(CFG)),) LIBSSH_PATH ?= $(PROOT)/../libssh CPPFLAGS += -DUSE_LIBSSH - CPPFLAGS += -I"$(LIBSSH_PATH)/include" - _LDFLAGS += -L"$(LIBSSH_PATH)/lib" - _LIBS += -lssh + CPPFLAGS += -isystem "$(LIBSSH_PATH)/include" + LDFLAGS += -L"$(LIBSSH_PATH)/lib" + LIBS += -lssh else ifneq ($(findstring -wolfssh,$(CFG)),) WOLFSSH_PATH ?= $(PROOT)/../wolfssh CPPFLAGS += -DUSE_WOLFSSH - CPPFLAGS += -I"$(WOLFSSH_PATH)/include" - _LDFLAGS += -L"$(WOLFSSH_PATH)/lib" - _LIBS += -lwolfssh + CPPFLAGS += -isystem "$(WOLFSSH_PATH)/include" + LDFLAGS += -L"$(WOLFSSH_PATH)/lib" + LIBS += -lwolfssh endif ifneq ($(findstring -ssl,$(CFG)),) @@ -178,10 +136,10 @@ ifneq ($(findstring -ssl,$(CFG)),) CPPFLAGS += -DCURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG OPENSSL_INCLUDE ?= $(OPENSSL_PATH)/include OPENSSL_LIBPATH ?= $(OPENSSL_PATH)/lib - CPPFLAGS += -I"$(OPENSSL_INCLUDE)" - _LDFLAGS += -L"$(OPENSSL_LIBPATH)" + CPPFLAGS += -isystem "$(OPENSSL_INCLUDE)" + LDFLAGS += -L"$(OPENSSL_LIBPATH)" OPENSSL_LIBS ?= -lssl -lcrypto - _LIBS += $(OPENSSL_LIBS) + LIBS += $(OPENSSL_LIBS) ifneq ($(findstring -srp,$(CFG)),) ifneq ($(wildcard $(OPENSSL_INCLUDE)/openssl/srp.h),) @@ -195,43 +153,47 @@ ifneq ($(findstring -wolfssl,$(CFG)),) WOLFSSL_PATH ?= $(PROOT)/../wolfssl CPPFLAGS += -DUSE_WOLFSSL CPPFLAGS += -DSIZEOF_LONG_LONG=8 - CPPFLAGS += -I"$(WOLFSSL_PATH)/include" - _LDFLAGS += -L"$(WOLFSSL_PATH)/lib" - _LIBS += -lwolfssl + CPPFLAGS += -isystem "$(WOLFSSL_PATH)/include" + LDFLAGS += -L"$(WOLFSSL_PATH)/lib" + LIBS += -lwolfssl SSLLIBS += 1 endif ifneq ($(findstring -mbedtls,$(CFG)),) MBEDTLS_PATH ?= $(PROOT)/../mbedtls CPPFLAGS += -DUSE_MBEDTLS - CPPFLAGS += -I"$(MBEDTLS_PATH)/include" - _LDFLAGS += -L"$(MBEDTLS_PATH)/lib" - _LIBS += -lmbedtls -lmbedx509 -lmbedcrypto + CPPFLAGS += -isystem "$(MBEDTLS_PATH)/include" + LDFLAGS += -L"$(MBEDTLS_PATH)/lib" + LIBS += -lmbedtls -lmbedx509 -lmbedcrypto SSLLIBS += 1 endif -ifneq ($(findstring -schannel,$(CFG)),) - CPPFLAGS += -DUSE_SCHANNEL +ifneq ($(findstring -bearssl,$(CFG)),) + BEARSSL_PATH ?= $(PROOT)/../bearssl + CPPFLAGS += -DUSE_BEARSSL + CPPFLAGS += -isystem "$(BEARSSL_PATH)/inc" + LDFLAGS += -L"$(BEARSSL_PATH)/build" + LIBS += -lbearssl SSLLIBS += 1 endif ifneq ($(findstring -nghttp2,$(CFG)),) NGHTTP2_PATH ?= $(PROOT)/../nghttp2 CPPFLAGS += -DUSE_NGHTTP2 - CPPFLAGS += -I"$(NGHTTP2_PATH)/include" - _LDFLAGS += -L"$(NGHTTP2_PATH)/lib" - _LIBS += -lnghttp2 + CPPFLAGS += -isystem "$(NGHTTP2_PATH)/include" + LDFLAGS += -L"$(NGHTTP2_PATH)/lib" + LIBS += -lnghttp2 endif ifeq ($(findstring -nghttp3,$(CFG))$(findstring -ngtcp2,$(CFG)),-nghttp3-ngtcp2) NGHTTP3_PATH ?= $(PROOT)/../nghttp3 CPPFLAGS += -DUSE_NGHTTP3 - CPPFLAGS += -I"$(NGHTTP3_PATH)/include" - _LDFLAGS += -L"$(NGHTTP3_PATH)/lib" - _LIBS += -lnghttp3 + CPPFLAGS += -isystem "$(NGHTTP3_PATH)/include" + LDFLAGS += -L"$(NGHTTP3_PATH)/lib" + LIBS += -lnghttp3 NGTCP2_PATH ?= $(PROOT)/../ngtcp2 CPPFLAGS += -DUSE_NGTCP2 - CPPFLAGS += -I"$(NGTCP2_PATH)/include" - _LDFLAGS += -L"$(NGTCP2_PATH)/lib" + CPPFLAGS += -isystem "$(NGTCP2_PATH)/include" + LDFLAGS += -L"$(NGTCP2_PATH)/lib" NGTCP2_LIBS ?= ifeq ($(NGTCP2_LIBS),) @@ -246,94 +208,74 @@ ifeq ($(findstring -nghttp3,$(CFG))$(findstring -ngtcp2,$(CFG)),-nghttp3-ngtcp2) endif endif - _LIBS += -lngtcp2 $(NGTCP2_LIBS) + LIBS += -lngtcp2 $(NGTCP2_LIBS) endif ifneq ($(findstring -zlib,$(CFG))$(ZLIB),) ZLIB_PATH ?= $(PROOT)/../zlib # These CPPFLAGS are also required when compiling the curl tool via 'src'. CPPFLAGS += -DHAVE_LIBZ - CPPFLAGS += -I"$(ZLIB_PATH)/include" - _LDFLAGS += -L"$(ZLIB_PATH)/lib" + CPPFLAGS += -isystem "$(ZLIB_PATH)/include" + LDFLAGS += -L"$(ZLIB_PATH)/lib" ZLIB_LIBS ?= -lz - _LIBS += $(ZLIB_LIBS) + LIBS += $(ZLIB_LIBS) ZLIB := 1 endif ifneq ($(findstring -zstd,$(CFG)),) ZSTD_PATH ?= $(PROOT)/../zstd CPPFLAGS += -DHAVE_ZSTD - CPPFLAGS += -I"$(ZSTD_PATH)/include" - _LDFLAGS += -L"$(ZSTD_PATH)/lib" + CPPFLAGS += -isystem "$(ZSTD_PATH)/include" + LDFLAGS += -L"$(ZSTD_PATH)/lib" ZSTD_LIBS ?= -lzstd - _LIBS += $(ZSTD_LIBS) + LIBS += $(ZSTD_LIBS) endif ifneq ($(findstring -brotli,$(CFG)),) BROTLI_PATH ?= $(PROOT)/../brotli CPPFLAGS += -DHAVE_BROTLI - CPPFLAGS += -I"$(BROTLI_PATH)/include" - _LDFLAGS += -L"$(BROTLI_PATH)/lib" + CPPFLAGS += -isystem "$(BROTLI_PATH)/include" + LDFLAGS += -L"$(BROTLI_PATH)/lib" BROTLI_LIBS ?= -lbrotlidec -lbrotlicommon - _LIBS += $(BROTLI_LIBS) + LIBS += $(BROTLI_LIBS) endif ifneq ($(findstring -gsasl,$(CFG)),) LIBGSASL_PATH ?= $(PROOT)/../gsasl CPPFLAGS += -DUSE_GSASL - CPPFLAGS += -I"$(LIBGSASL_PATH)/include" - _LDFLAGS += -L"$(LIBGSASL_PATH)/lib" - _LIBS += -lgsasl + CPPFLAGS += -isystem "$(LIBGSASL_PATH)/include" + LDFLAGS += -L"$(LIBGSASL_PATH)/lib" + LIBS += -lgsasl endif ifneq ($(findstring -idn2,$(CFG)),) LIBIDN2_PATH ?= $(PROOT)/../libidn2 - CPPFLAGS += -DUSE_LIBIDN2 - CPPFLAGS += -I"$(LIBIDN2_PATH)/include" - _LDFLAGS += -L"$(LIBIDN2_PATH)/lib" - _LIBS += -lidn2 + CPPFLAGS += -DHAVE_LIBIDN2 -DHAVE_IDN2_H + CPPFLAGS += -isystem "$(LIBIDN2_PATH)/include" + LDFLAGS += -L"$(LIBIDN2_PATH)/lib" + LIBS += -lidn2 ifneq ($(findstring -psl,$(CFG)),) LIBPSL_PATH ?= $(PROOT)/../libpsl CPPFLAGS += -DUSE_LIBPSL - CPPFLAGS += -I"$(LIBPSL_PATH)/include" - _LDFLAGS += -L"$(LIBPSL_PATH)/lib" - _LIBS += -lpsl + CPPFLAGS += -isystem "$(LIBPSL_PATH)/include" + LDFLAGS += -L"$(LIBPSL_PATH)/lib" + LIBS += -lpsl endif -else ifneq ($(findstring -winidn,$(CFG)),) - CPPFLAGS += -DUSE_WIN32_IDN - _LIBS += -lnormaliz endif -ifneq ($(findstring -sspi,$(CFG)),) - ifdef WIN32 - CPPFLAGS += -DUSE_WINDOWS_SSPI - endif -endif ifneq ($(findstring -ipv6,$(CFG)),) - CPPFLAGS += -DENABLE_IPV6 + CPPFLAGS += -DUSE_IPV6 endif ifneq ($(findstring -watt,$(CFG))$(MSDOS),) WATT_PATH ?= $(PROOT)/../watt - CPPFLAGS += -I"$(WATT_PATH)/inc" - _LDFLAGS += -L"$(WATT_PATH)/lib" - _LIBS += -lwatt -endif - -ifdef WIN32 - ifeq ($(findstring -lldap,$(LIBS)),) - _LIBS += -lwldap32 - endif - _LIBS += -lws2_32 -lcrypt32 -lbcrypt + CPPFLAGS += -isystem "$(WATT_PATH)/inc" + LDFLAGS += -L"$(WATT_PATH)/lib" + LIBS += -lwatt endif ifneq ($(findstring 11,$(subst $(subst ,, ),,$(SSLLIBS))),) CPPFLAGS += -DCURL_WITH_MULTI_SSL endif -ifndef DYN - LDFLAGS += $(_LDFLAGS) - LIBS += $(_LIBS) -endif - ### Common rules OBJ_DIR := $(TRIPLET) @@ -343,13 +285,11 @@ DEL = rm -f $1 COPY = -cp -afv $1 $2 MKDIR = mkdir -p $1 RMDIR = rm -fr $1 -WHICH = $(SHELL) -c "command -v $1" else DEL = -del 2>NUL /q /f $(subst /,\,$1) COPY = -copy 2>NUL /y $(subst /,\,$1) $(subst /,\,$2) MKDIR = -md 2>NUL $(subst /,\,$1) RMDIR = -rd 2>NUL /q /s $(subst /,\,$1) -WHICH = where $1 endif all: $(TARGETS) @@ -360,9 +300,6 @@ $(OBJ_DIR): $(OBJ_DIR)/%.o: %.c $(CC) -W -Wall $(CFLAGS) $(CPPFLAGS) -c $< -o $@ -$(OBJ_DIR)/%.res: %.rc - $(RC) -O coff $(RCFLAGS) -i $< -o $@ - clean: @$(call DEL, $(TOCLEAN)) @$(RMDIR) $(OBJ_DIR) @@ -378,34 +315,20 @@ CPPFLAGS += -DBUILDING_LIBCURL ### Sources and targets -# Provides CSOURCES, HHEADERS, LIB_RCFILES +# Provides CSOURCES, HHEADERS include Makefile.inc vpath %.c vauth vquic vssh vtls libcurl_a_LIBRARY := libcurl.a -ifdef WIN32 -CURL_DLL_SUFFIX ?= -libcurl_dll_LIBRARY := libcurl$(CURL_DLL_SUFFIX).dll -libcurl_dll_a_LIBRARY := libcurl.dll.a -CURL_LDFLAGS_LIB += $(PROOT)/libcurl.def -ifdef MAP -libcurl_map_LIBRARY := libcurl$(CURL_DLL_SUFFIX).map -CURL_LDFLAGS_LIB += -Wl,-Map,$(libcurl_map_LIBRARY) -endif -endif -TARGETS := $(libcurl_a_LIBRARY) $(libcurl_dll_LIBRARY) +TARGETS := $(libcurl_a_LIBRARY) libcurl_a_OBJECTS := $(patsubst %.c,$(OBJ_DIR)/%.o,$(notdir $(strip $(CSOURCES)))) libcurl_a_DEPENDENCIES := $(strip $(CSOURCES) $(HHEADERS)) -ifdef WIN32 -libcurl_dll_OBJECTS := $(libcurl_a_OBJECTS) -libcurl_dll_OBJECTS += $(patsubst %.rc,$(OBJ_DIR)/%.res,$(strip $(LIB_RCFILES))) -endif -TOCLEAN := $(libcurl_dll_OBJECTS) -TOVCLEAN := $(libcurl_dll_LIBRARY:.dll=.def) $(libcurl_dll_a_LIBRARY) $(libcurl_map_LIBRARY) +TOCLEAN := +TOVCLEAN := ### Rules @@ -413,9 +336,5 @@ $(libcurl_a_LIBRARY): $(libcurl_a_OBJECTS) $(libcurl_a_DEPENDENCIES) @$(call DEL, $@) $(AR) rcs $@ $(libcurl_a_OBJECTS) -$(libcurl_dll_LIBRARY): $(libcurl_dll_OBJECTS) - $(CC) $(LDFLAGS) -shared $(CURL_LDFLAGS_LIB) -o $@ $(libcurl_dll_OBJECTS) $(LIBS) \ - -Wl,--output-def,$(@:.dll=.def),--out-implib,$(libcurl_dll_a_LIBRARY) - all: $(OBJ_DIR) $(TARGETS) endif diff --git a/deps/curl/lib/Makefile.soname b/deps/curl/lib/Makefile.soname index 02e003a8aa1..eee661b7504 100644 --- a/deps/curl/lib/Makefile.soname +++ b/deps/curl/lib/Makefile.soname @@ -22,6 +22,7 @@ # ########################################################################### +# Keep in sync with CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME in configure.ac VERSIONCHANGE=12 VERSIONADD=0 VERSIONDEL=8 diff --git a/deps/curl/lib/altsvc.c b/deps/curl/lib/altsvc.c index 22b0b69c77f..ea37b0afc1a 100644 --- a/deps/curl/lib/altsvc.c +++ b/deps/curl/lib/altsvc.c @@ -64,6 +64,8 @@ static enum alpnid alpn2alpnid(char *name) return ALPN_h2; if(strcasecompare(name, H3VERSION)) return ALPN_h3; + if(strcasecompare(name, "http/1.1")) + return ALPN_h1; return ALPN_none; /* unknown, probably rubbish input */ } @@ -92,23 +94,24 @@ static void altsvc_free(struct altsvc *as) static struct altsvc *altsvc_createid(const char *srchost, const char *dsthost, + size_t dlen, /* dsthost length */ enum alpnid srcalpnid, enum alpnid dstalpnid, unsigned int srcport, unsigned int dstport) { - struct altsvc *as = calloc(sizeof(struct altsvc), 1); + struct altsvc *as = calloc(1, sizeof(struct altsvc)); size_t hlen; - size_t dlen; if(!as) return NULL; hlen = strlen(srchost); - dlen = strlen(dsthost); DEBUGASSERT(hlen); DEBUGASSERT(dlen); - if(!hlen || !dlen) + if(!hlen || !dlen) { /* bad input */ + free(as); return NULL; + } if((hlen > 2) && srchost[0] == '[') { /* IPv6 address, strip off brackets */ srchost++; @@ -123,15 +126,13 @@ static struct altsvc *altsvc_createid(const char *srchost, dlen -= 2; } - as->src.host = Curl_memdup(srchost, hlen + 1); + as->src.host = Curl_memdup0(srchost, hlen); if(!as->src.host) goto error; - as->src.host[hlen] = 0; - as->dst.host = Curl_memdup(dsthost, dlen + 1); + as->dst.host = Curl_memdup0(dsthost, dlen); if(!as->dst.host) goto error; - as->dst.host[dlen] = 0; as->src.alpnid = srcalpnid; as->dst.alpnid = dstalpnid; @@ -155,7 +156,8 @@ static struct altsvc *altsvc_create(char *srchost, enum alpnid srcalpnid = alpn2alpnid(srcalpn); if(!srcalpnid || !dstalpnid) return NULL; - return altsvc_createid(srchost, dsthost, srcalpnid, dstalpnid, + return altsvc_createid(srchost, dsthost, strlen(dsthost), + srcalpnid, dstalpnid, srcport, dstport); } @@ -191,7 +193,7 @@ static CURLcode altsvc_add(struct altsvcinfo *asi, char *line) as->expires = expires; as->prio = prio; as->persist = persist ? 1 : 0; - Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node); + Curl_llist_append(&asi->list, as, &as->node); } } @@ -209,10 +211,9 @@ static CURLcode altsvc_add(struct altsvcinfo *asi, char *line) static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file) { CURLcode result = CURLE_OK; - char *line = NULL; FILE *fp; - /* we need a private copy of the file name so that the altsvc cache file + /* we need a private copy of the filename so that the altsvc cache file name survives an easy handle reset */ free(asi->filename); asi->filename = strdup(file); @@ -221,11 +222,10 @@ static CURLcode altsvc_load(struct altsvcinfo *asi, const char *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; + struct dynbuf buf; + Curl_dyn_init(&buf, MAX_ALTSVC_LINE); + while(Curl_get_line(&buf, fp)) { + char *lineptr = Curl_dyn_ptr(&buf); while(*lineptr && ISBLANK(*lineptr)) lineptr++; if(*lineptr == '#') @@ -234,16 +234,10 @@ static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file) altsvc_add(asi, lineptr); } - free(line); /* free the line buffer */ + Curl_dyn_free(&buf); /* free the line buffer */ fclose(fp); } return result; - -fail: - Curl_safefree(asi->filename); - free(line); - fclose(fp); - return CURLE_OUT_OF_MEMORY; } /* @@ -260,7 +254,7 @@ static CURLcode altsvc_out(struct altsvc *as, FILE *fp) CURLcode result = Curl_gmtime(as->expires, &stamp); if(result) return result; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else { char ipv6_unused[16]; if(1 == Curl_inet_pton(AF_INET6, as->dst.host, ipv6_unused)) { @@ -278,7 +272,7 @@ static CURLcode altsvc_out(struct altsvc *as, FILE *fp) "%s %s%s%s %u " "\"%d%02d%02d " "%02d:%02d:%02d\" " - "%u %d\n", + "%u %u\n", Curl_alpnid2str(as->src.alpnid), src6_pre, as->src.host, src6_post, as->src.port, @@ -301,7 +295,7 @@ static CURLcode altsvc_out(struct altsvc *as, FILE *fp) */ struct altsvcinfo *Curl_altsvc_init(void) { - struct altsvcinfo *asi = calloc(sizeof(struct altsvcinfo), 1); + struct altsvcinfo *asi = calloc(1, sizeof(struct altsvcinfo)); if(!asi) return NULL; Curl_llist_init(&asi->list, NULL); @@ -311,7 +305,7 @@ struct altsvcinfo *Curl_altsvc_init(void) #ifdef USE_HTTP2 | CURLALTSVC_H2 #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 | CURLALTSVC_H3 #endif ; @@ -323,10 +317,8 @@ struct altsvcinfo *Curl_altsvc_init(void) */ CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file) { - CURLcode result; DEBUGASSERT(asi); - result = altsvc_load(asi, file); - return result; + return altsvc_load(asi, file); } /* @@ -335,9 +327,6 @@ CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file) 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; } @@ -348,13 +337,13 @@ CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl) */ void Curl_altsvc_cleanup(struct altsvcinfo **altsvcp) { - struct Curl_llist_element *e; - struct Curl_llist_element *n; if(*altsvcp) { + struct Curl_llist_node *e; + struct Curl_llist_node *n; struct altsvcinfo *altsvc = *altsvcp; - for(e = altsvc->list.head; e; e = n) { - struct altsvc *as = e->ptr; - n = e->next; + for(e = Curl_llist_head(&altsvc->list); e; e = n) { + struct altsvc *as = Curl_node_elem(e); + n = Curl_node_next(e); altsvc_free(as); } free(altsvc->filename); @@ -369,8 +358,6 @@ void Curl_altsvc_cleanup(struct altsvcinfo **altsvcp) CURLcode Curl_altsvc_save(struct Curl_easy *data, struct altsvcinfo *altsvc, const char *file) { - struct Curl_llist_element *e; - struct Curl_llist_element *n; CURLcode result = CURLE_OK; FILE *out; char *tempstore = NULL; @@ -384,17 +371,19 @@ CURLcode Curl_altsvc_save(struct Curl_easy *data, file = altsvc->filename; if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file || !file[0]) - /* marked as read-only, no file or zero length file name */ + /* marked as read-only, no file or zero length filename */ return CURLE_OK; result = Curl_fopen(data, file, &out, &tempstore); if(!result) { + struct Curl_llist_node *e; + struct Curl_llist_node *n; fputs("# Your alt-svc cache. https://curl.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; + for(e = Curl_llist_head(&altsvc->list); e; e = n) { + struct altsvc *as = Curl_node_elem(e); + n = Curl_node_next(e); result = altsvc_out(as, out); if(result) break; @@ -441,7 +430,7 @@ static bool hostcompare(const char *host, const char *check) if(hlen && (host[hlen - 1] == '.')) hlen--; if(hlen != clen) - /* they can't match if they have different lengths */ + /* they cannot match if they have different lengths */ return FALSE; return strncasecompare(host, check, hlen); } @@ -451,15 +440,15 @@ static bool hostcompare(const char *host, const char *check) 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; + struct Curl_llist_node *e; + struct Curl_llist_node *n; + for(e = Curl_llist_head(&asi->list); e; e = n) { + struct altsvc *as = Curl_node_elem(e); + n = Curl_node_next(e); if((srcalpnid == as->src.alpnid) && (srcport == as->src.port) && hostcompare(srchost, as->src.host)) { - Curl_llist_remove(&asi->list, e, NULL); + Curl_node_remove(e); altsvc_free(as); } } @@ -473,7 +462,7 @@ static time_t altsvc_debugtime(void *unused) char *timestr = getenv("CURL_TIME"); (void)unused; if(timestr) { - unsigned long val = strtol(timestr, NULL, 10); + long val = strtol(timestr, NULL, 10); return (time_t)val; } return time(NULL); @@ -488,11 +477,11 @@ static time_t altsvc_debugtime(void *unused) * 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 + * 'value' points to the header *value*. That is contents to the right of the * header name. * * Currently this function rejects invalid data without returning an error. - * Invalid host name, port number will result in the specific alternative + * Invalid hostname, port number will result in the specific alternative * being rejected. Unknown protocols are skipped. */ CURLcode Curl_altsvc_parse(struct Curl_easy *data, @@ -501,8 +490,6 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data, unsigned short srcport) { const char *p = value; - size_t len; - char namebuf[MAX_ALTSVC_HOSTLEN] = ""; char alpnbuf[MAX_ALTSVC_ALPNLEN] = ""; struct altsvc *as; unsigned short dstport = srcport; /* the same by default */ @@ -532,6 +519,7 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data, p++; if(*p == '\"') { const char *dsthost = ""; + size_t dstlen = 0; /* destination hostname length */ const char *value_ptr; char option[32]; unsigned long num; @@ -542,36 +530,35 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data, bool valid = TRUE; p++; if(*p != ':') { - /* host name starts here */ + /* hostname starts here */ const char *hostp = p; if(*p == '[') { /* pass all valid IPv6 letters - does not handle zone id */ - len = strspn(++p, "0123456789abcdefABCDEF:."); - if(p[len] != ']') + dstlen = strspn(++p, "0123456789abcdefABCDEF:."); + if(p[dstlen] != ']') /* invalid host syntax, bail out */ break; /* we store the IPv6 numerical address *with* brackets */ - len += 2; - p = &p[len-1]; + dstlen += 2; + p = &p[dstlen-1]; } else { while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-'))) p++; - len = p - hostp; + dstlen = p - hostp; } - if(!len || (len >= MAX_ALTSVC_HOSTLEN)) { - infof(data, "Excessive alt-svc host name, ignoring."); + if(!dstlen || (dstlen >= MAX_ALTSVC_HOSTLEN)) { + infof(data, "Excessive alt-svc hostname, ignoring."); valid = FALSE; } else { - memcpy(namebuf, hostp, len); - namebuf[len] = 0; - dsthost = namebuf; + dsthost = hostp; } } else { /* no destination name, use source host */ dsthost = srchost; + dstlen = strlen(srchost); } if(*p == ':') { unsigned long port = 0; @@ -635,7 +622,7 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data, num = strtoul(value_ptr, &end_ptr, 10); if((end_ptr != value_ptr) && (num < ULONG_MAX)) { if(strcasecompare("ma", option)) - maxage = num; + maxage = (time_t)num; else if(strcasecompare("persist", option) && (num == 1)) persist = TRUE; } @@ -646,7 +633,7 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data, this is the first entry of the line. */ altsvc_flush(asi, srcalpnid, srchost, srcport); - as = altsvc_createid(srchost, dsthost, + as = altsvc_createid(srchost, dsthost, dstlen, srcalpnid, dstalpnid, srcport, dstport); if(as) { @@ -654,7 +641,7 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data, 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); + Curl_llist_append(&asi->list, as, &as->node); infof(data, "Added alt-svc: %s:%d over %s", dsthost, dstport, Curl_alpnid2str(dstalpnid)); } @@ -662,7 +649,7 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data, } else break; - /* after the double quote there can be a comma if there's another + /* after the double quote there can be a comma if there is another string or a semicolon if no more */ if(*p == ',') { /* comma means another alternative is presented */ @@ -688,26 +675,26 @@ bool Curl_altsvc_lookup(struct altsvcinfo *asi, struct altsvc **dstentry, const int versions) /* one or more bits */ { - struct Curl_llist_element *e; - struct Curl_llist_element *n; + struct Curl_llist_node *e; + struct Curl_llist_node *n; time_t now = time(NULL); DEBUGASSERT(asi); DEBUGASSERT(srchost); DEBUGASSERT(dstentry); - for(e = asi->list.head; e; e = n) { - struct altsvc *as = e->ptr; - n = e->next; + for(e = Curl_llist_head(&asi->list); e; e = n) { + struct altsvc *as = Curl_node_elem(e); + n = Curl_node_next(e); if(as->expires < now) { /* an expired entry, remove */ - Curl_llist_remove(&asi->list, e, NULL); + Curl_node_remove(e); altsvc_free(as); continue; } if((as->src.alpnid == srcalpnid) && hostcompare(srchost, as->src.host) && (as->src.port == srcport) && - (versions & as->dst.alpnid)) { + (versions & (int)as->dst.alpnid)) { /* match */ *dstentry = as; return TRUE; diff --git a/deps/curl/lib/altsvc.h b/deps/curl/lib/altsvc.h index 7fea1434a54..48999efb317 100644 --- a/deps/curl/lib/altsvc.h +++ b/deps/curl/lib/altsvc.h @@ -47,8 +47,8 @@ struct altsvc { struct althost dst; time_t expires; bool persist; - int prio; - struct Curl_llist_element node; + unsigned int prio; + struct Curl_llist_node node; }; struct altsvcinfo { diff --git a/deps/curl/lib/amigaos.c b/deps/curl/lib/amigaos.c index 139309b1162..1321c53c484 100644 --- a/deps/curl/lib/amigaos.c +++ b/deps/curl/lib/amigaos.c @@ -117,7 +117,7 @@ void Curl_amiga_cleanup(void) #ifdef CURLRES_AMIGA /* - * Because we need to handle the different cases in hostip4.c at run-time, + * Because we need to handle the different cases in hostip4.c at runtime, * not at compile-time, based on what was detected in Curl_amiga_init(), * we replace it completely with our own as to not complicate the baseline * code. Assumes malloc/calloc/free are thread safe because Curl_he2ai() diff --git a/deps/curl/lib/arpa_telnet.h b/deps/curl/lib/arpa_telnet.h index de1373800ce..d641a01da81 100644 --- a/deps/curl/lib/arpa_telnet.h +++ b/deps/curl/lib/arpa_telnet.h @@ -56,12 +56,14 @@ static const char * const telnetoptions[]= "TERM SPEED", "LFLOW", "LINEMODE", "XDISPLOC", "OLD-ENVIRON", "AUTHENTICATION", "ENCRYPT", "NEW-ENVIRON" }; +#define CURL_TELOPT(x) telnetoptions[x] +#else +#define CURL_TELOPT(x) "" #endif #define CURL_TELOPT_MAXIMUM CURL_TELOPT_NEW_ENVIRON #define CURL_TELOPT_OK(x) ((x) <= CURL_TELOPT_MAXIMUM) -#define CURL_TELOPT(x) telnetoptions[x] #define CURL_NTELOPTS 40 @@ -75,7 +77,7 @@ static const char * const telnetoptions[]= #define CURL_GA 249 /* Go Ahead, reverse the line */ #define CURL_SB 250 /* SuBnegotiation */ #define CURL_WILL 251 /* Our side WILL use this option */ -#define CURL_WONT 252 /* Our side WON'T use this option */ +#define CURL_WONT 252 /* Our side will not use this option */ #define CURL_DO 253 /* DO use this option! */ #define CURL_DONT 254 /* DON'T use this option! */ #define CURL_IAC 255 /* Interpret As Command */ @@ -103,7 +105,12 @@ static const char * const telnetcmds[]= #define CURL_TELCMD_OK(x) ( ((unsigned int)(x) >= CURL_TELCMD_MINIMUM) && \ ((unsigned int)(x) <= CURL_TELCMD_MAXIMUM) ) + +#ifndef CURL_DISABLE_VERBOSE_STRINGS #define CURL_TELCMD(x) telnetcmds[(x)-CURL_TELCMD_MINIMUM] +#else +#define CURL_TELCMD(x) "" +#endif #endif /* CURL_DISABLE_TELNET */ diff --git a/deps/curl/lib/asyn-ares.c b/deps/curl/lib/asyn-ares.c index e73e41dab9f..ae436f236e0 100644 --- a/deps/curl/lib/asyn-ares.c +++ b/deps/curl/lib/asyn-ares.c @@ -60,13 +60,13 @@ #include "progress.h" #include "timediff.h" -# if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ - defined(WIN32) -# define CARES_STATICLIB -# endif -# include -# include /* really old c-ares didn't include this by - itself */ +#if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ + defined(_WIN32) +# define CARES_STATICLIB +#endif +#include +#include /* really old c-ares did not include this by + itself */ #if ARES_VERSION >= 0x010500 /* c-ares 1.5.0 or later, the callback proto is modified */ @@ -112,8 +112,8 @@ struct thread_data { /* How long we are willing to wait for additional parallel responses after obtaining a "definitive" one. For old c-ares without getaddrinfo. - This is intended to equal the c-ares default timeout. cURL always uses that - default value. Unfortunately, c-ares doesn't expose its default timeout in + This is intended to equal the c-ares default timeout. cURL always uses that + default value. Unfortunately, c-ares does not expose its default timeout in its API, but it is officially documented as 5 seconds. See query_completed_cb() for an explanation of how this is used. @@ -122,10 +122,12 @@ struct thread_data { #define CARES_TIMEOUT_PER_ATTEMPT 2000 +static int ares_ver = 0; + /* * Curl_resolver_global_init() - the generic low-level asynchronous name - * resolve API. Called from curl_global_init() to initialize global resolver - * environment. Initializes ares library. + * resolve API. Called from curl_global_init() to initialize global resolver + * environment. Initializes ares library. */ int Curl_resolver_global_init(void) { @@ -134,6 +136,7 @@ int Curl_resolver_global_init(void) return CURLE_FAILED_INIT; } #endif + ares_version(&ares_ver); return CURLE_OK; } @@ -166,7 +169,7 @@ static void sock_state_cb(void *data, ares_socket_t socket_fd, * * Called from curl_easy_init() -> Curl_open() to initialize resolver * URL-state specific environment ('resolver' member of the UrlState - * structure). Fills the passed pointer by the initialized ares_channel. + * structure). Fills the passed pointer by the initialized ares_channel. */ CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) { @@ -175,8 +178,21 @@ CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) int optmask = ARES_OPT_SOCK_STATE_CB; options.sock_state_cb = sock_state_cb; options.sock_state_cb_data = easy; - options.timeout = CARES_TIMEOUT_PER_ATTEMPT; - optmask |= ARES_OPT_TIMEOUTMS; + + /* + if c ares < 1.20.0: curl set timeout to CARES_TIMEOUT_PER_ATTEMPT (2s) + + if c-ares >= 1.20.0 it already has the timeout to 2s, curl does not need + to set the timeout value; + + if c-ares >= 1.24.0, user can set the timeout via /etc/resolv.conf to + overwrite c-ares' timeout. + */ + DEBUGASSERT(ares_ver); + if(ares_ver < 0x011400) { + options.timeout = CARES_TIMEOUT_PER_ATTEMPT; + optmask |= ARES_OPT_TIMEOUTMS; + } status = ares_init_options((ares_channel*)resolver, &options, optmask); if(status != ARES_SUCCESS) { @@ -195,7 +211,7 @@ CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) * * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver * URL-state specific environment ('resolver' member of the UrlState - * structure). Destroys the ares channel. + * structure). Destroys the ares channel. */ void Curl_resolver_cleanup(void *resolver) { @@ -206,7 +222,7 @@ void Curl_resolver_cleanup(void *resolver) * Curl_resolver_duphandle() * * Called from curl_easy_duphandle() to duplicate resolver URL-state specific - * environment ('resolver' member of the UrlState structure). Duplicates the + * environment ('resolver' member of the UrlState structure). Duplicates the * 'from' ares channel and passes the resulting channel to the 'to' pointer. */ CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from) @@ -234,12 +250,12 @@ void Curl_resolver_cancel(struct Curl_easy *data) } /* - * We're equivalent to Curl_resolver_cancel() for the c-ares resolver. We + * We are equivalent to Curl_resolver_cancel() for the c-ares resolver. We * never block. */ void Curl_resolver_kill(struct Curl_easy *data) { - /* We don't need to check the resolver state because we can be called safely + /* We do not need to check the resolver state because we can be called safely at any time and we always do the same thing. */ Curl_resolver_cancel(data); } @@ -264,7 +280,7 @@ static void destroy_async_data(struct Curl_async *async) /* * Curl_resolver_getsock() is called when someone from the outside world - * (using curl_multi_fdset()) wants to get our fd_set setup and we're talking + * (using curl_multi_fdset()) wants to get our fd_set setup and we are talking * with ares. The caller must make sure that this function is only called when * we have a working ares channel. * @@ -274,23 +290,14 @@ static void destroy_async_data(struct Curl_async *async) int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *socks) { - struct timeval maxtime; + struct timeval maxtime = { CURL_TIMEOUT_RESOLVE, 0 }; struct timeval timebuf; - struct timeval *timeout; - long milli; int max = ares_getsock((ares_channel)data->state.async.resolver, (ares_socket_t *)socks, MAX_SOCKSPEREASYHANDLE); - - maxtime.tv_sec = CURL_TIMEOUT_RESOLVE; - maxtime.tv_usec = 0; - - timeout = ares_timeout((ares_channel)data->state.async.resolver, &maxtime, - &timebuf); - milli = (long)curlx_tvtoms(timeout); - if(milli == 0) - milli += 10; + struct timeval *timeout = + ares_timeout((ares_channel)data->state.async.resolver, &maxtime, &timebuf); + timediff_t milli = curlx_tvtoms(timeout); Curl_expire(data, milli, EXPIRE_ASYNC_NAME); - return max; } @@ -334,7 +341,7 @@ static int waitperform(struct Curl_easy *data, timediff_t timeout_ms) } if(num) { - nfds = Curl_poll(pfd, num, timeout_ms); + nfds = Curl_poll(pfd, (unsigned int)num, timeout_ms); if(nfds < 0) return -1; } @@ -343,17 +350,17 @@ static int waitperform(struct Curl_easy *data, timediff_t timeout_ms) if(!nfds) /* Call ares_process() unconditionally here, even if we simply timed out - above, as otherwise the ares name resolve won't timeout! */ + above, as otherwise the ares name resolve will not timeout! */ ares_process_fd((ares_channel)data->state.async.resolver, ARES_SOCKET_BAD, ARES_SOCKET_BAD); else { /* move through the descriptors and ask for processing on them */ for(i = 0; i < num; i++) ares_process_fd((ares_channel)data->state.async.resolver, - (pfd[i].revents & (POLLRDNORM|POLLIN))? - pfd[i].fd:ARES_SOCKET_BAD, - (pfd[i].revents & (POLLWRNORM|POLLOUT))? - pfd[i].fd:ARES_SOCKET_BAD); + (pfd[i].revents & (POLLRDNORM|POLLIN)) ? + pfd[i].fd : ARES_SOCKET_BAD, + (pfd[i].revents & (POLLWRNORM|POLLOUT)) ? + pfd[i].fd : ARES_SOCKET_BAD); } return nfds; } @@ -378,8 +385,8 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, return CURLE_UNRECOVERABLE_POLL; #ifndef HAVE_CARES_GETADDRINFO - /* Now that we've checked for any last minute results above, see if there are - any responses still pending when the EXPIRE_HAPPY_EYEBALLS_DNS timer + /* Now that we have checked for any last minute results above, see if there + are any responses still pending when the EXPIRE_HAPPY_EYEBALLS_DNS timer expires. */ if(res && res->num_pending @@ -394,7 +401,7 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, &res->happy_eyeballs_dns_time, 0, sizeof(res->happy_eyeballs_dns_time)); /* Cancel the raw c-ares request, which will fire query_completed_cb() with - ARES_ECANCELLED synchronously for all pending responses. This will + ARES_ECANCELLED synchronously for all pending responses. This will leave us with res->num_pending == 0, which is perfect for the next block. */ ares_cancel((ares_channel)data->state.async.resolver); @@ -507,7 +514,7 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, *entry = data->state.async.dns; if(result) - /* close the connection, since we can't return failure here without + /* close the connection, since we cannot return failure here without cleaning up this connection properly. */ connclose(data->conn, "c-ares resolve failed"); @@ -523,7 +530,7 @@ static void compound_results(struct thread_data *res, if(!ai) return; -#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */ +#ifdef USE_IPV6 /* CURLRES_IPV6 */ if(res->temp_ai && res->temp_ai->ai_family == PF_INET6) { /* We have results already, put the new IPv6 entries at the head of the list. */ @@ -587,57 +594,57 @@ static void query_completed_cb(void *arg, /* (struct connectdata *) */ /* If there are responses still pending, we presume they must be the complementary IPv4 or IPv6 lookups that we started in parallel in - Curl_resolver_getaddrinfo() (for Happy Eyeballs). If we've got a + Curl_resolver_getaddrinfo() (for Happy Eyeballs). If we have got a "definitive" response from one of a set of parallel queries, we need to - think about how long we're willing to wait for more responses. */ + think about how long we are willing to wait for more responses. */ if(res->num_pending /* Only these c-ares status values count as "definitive" for these - purposes. For example, ARES_ENODATA is what we expect when there is - no IPv6 entry for a domain name, and that's not a reason to get more - aggressive in our timeouts for the other response. Other errors are + purposes. For example, ARES_ENODATA is what we expect when there is + no IPv6 entry for a domain name, and that is not a reason to get more + aggressive in our timeouts for the other response. Other errors are either a result of bad input (which should affect all parallel requests), local or network conditions, non-definitive server responses, or us cancelling the request. */ && (status == ARES_SUCCESS || status == ARES_ENOTFOUND)) { - /* Right now, there can only be up to two parallel queries, so don't + /* Right now, there can only be up to two parallel queries, so do not bother handling any other cases. */ DEBUGASSERT(res->num_pending == 1); - /* It's possible that one of these parallel queries could succeed - quickly, but the other could always fail or timeout (when we're + /* it is possible that one of these parallel queries could succeed + quickly, but the other could always fail or timeout (when we are talking to a pool of DNS servers that can only successfully resolve IPv4 address, for example). - It's also possible that the other request could always just take + it is also possible that the other request could always just take longer because it needs more time or only the second DNS server can - fulfill it successfully. But, to align with the philosophy of Happy - Eyeballs, we don't want to wait _too_ long or users will think - requests are slow when IPv6 lookups don't actually work (but IPv4 ones - do). + fulfill it successfully. But, to align with the philosophy of Happy + Eyeballs, we do not want to wait _too_ long or users will think + requests are slow when IPv6 lookups do not actually work (but IPv4 + ones do). So, now that we have a usable answer (some IPv4 addresses, some IPv6 addresses, or "no such domain"), we start a timeout for the remaining - pending responses. Even though it is typical that this resolved - request came back quickly, that needn't be the case. It might be that - this completing request didn't get a result from the first DNS server - or even the first round of the whole DNS server pool. So it could - already be quite some time after we issued the DNS queries in the - first place. Without modifying c-ares, we can't know exactly where in - its retry cycle we are. We could guess based on how much time has - gone by, but it doesn't really matter. Happy Eyeballs tells us that, - given usable information in hand, we simply don't want to wait "too - much longer" after we get a result. + pending responses. Even though it is typical that this resolved + request came back quickly, that needn't be the case. It might be that + this completing request did not get a result from the first DNS + server or even the first round of the whole DNS server pool. So it + could already be quite some time after we issued the DNS queries in + the first place. Without modifying c-ares, we cannot know exactly + where in its retry cycle we are. We could guess based on how much + time has gone by, but it does not really matter. Happy Eyeballs tells + us that, given usable information in hand, we simply do not want to + wait "too much longer" after we get a result. We simply wait an additional amount of time equal to the default - c-ares query timeout. That is enough time for a typical parallel - response to arrive without being "too long". Even on a network + c-ares query timeout. That is enough time for a typical parallel + response to arrive without being "too long". Even on a network where one of the two types of queries is failing or timing out constantly, this will usually mean we wait a total of the default c-ares timeout (5 seconds) plus the round trip time for the successful - request, which seems bearable. The downside is that c-ares might race + request, which seems bearable. The downside is that c-ares might race with us to issue one more retry just before we give up, but it seems better to "waste" that request instead of trying to guess the perfect - timeout to prevent it. After all, we don't even know where in the + timeout to prevent it. After all, we do not even know where in the c-ares retry cycle each request is. */ res->happy_eyeballs_dns_time = Curl_now(); @@ -668,7 +675,7 @@ static struct Curl_addrinfo *ares2addr(struct ares_addrinfo_node *node) /* settle family-specific sockaddr structure size. */ if(ai->ai_family == AF_INET) ss_size = sizeof(struct sockaddr_in); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else if(ai->ai_family == AF_INET6) ss_size = sizeof(struct sockaddr_in6); #endif @@ -755,7 +762,7 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, size_t namelen = strlen(hostname); *waitp = 0; /* default to synchronous response */ - res = calloc(sizeof(struct thread_data) + namelen, 1); + res = calloc(1, sizeof(struct thread_data) + namelen); if(res) { strcpy(res->hostname, hostname); data->state.async.hostname = res->hostname; @@ -785,7 +792,7 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, } #endif /* CURLRES_IPV6 */ hints.ai_family = pf; - hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP)? + hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP) ? SOCK_STREAM : SOCK_DGRAM; /* Since the service is a numerical one, set the hint flags * accordingly to save a call to getservbyname in inside C-Ares @@ -833,8 +840,8 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data, /* If server is NULL or empty, this would purge all DNS servers * from ares library, which will cause any and all queries to fail. - * So, just return OK if none are configured and don't actually make - * any changes to c-ares. This lets c-ares use it's defaults, which + * So, just return OK if none are configured and do not actually make + * any changes to c-ares. This lets c-ares use its defaults, which * it gets from the OS (for instance from /etc/resolv.conf on Linux). */ if(!(servers && servers[0])) @@ -858,6 +865,7 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data, case ARES_ENODATA: case ARES_EBADSTR: default: + DEBUGF(infof(data, "bad servers set")); result = CURLE_BAD_FUNCTION_ARGUMENT; break; } @@ -896,6 +904,7 @@ CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, } else { if(Curl_inet_pton(AF_INET, local_ip4, &a4) != 1) { + DEBUGF(infof(data, "bad DNS IPv4 address")); return CURLE_BAD_FUNCTION_ARGUMENT; } } @@ -914,7 +923,7 @@ CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, const char *local_ip6) { -#if defined(HAVE_CARES_SET_LOCAL) && defined(ENABLE_IPV6) +#if defined(HAVE_CARES_SET_LOCAL) && defined(USE_IPV6) unsigned char a6[INET6_ADDRSTRLEN]; if((!local_ip6) || (local_ip6[0] == 0)) { @@ -923,6 +932,7 @@ CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, } else { if(Curl_inet_pton(AF_INET6, local_ip6, a6) != 1) { + DEBUGF(infof(data, "bad DNS IPv6 address")); return CURLE_BAD_FUNCTION_ARGUMENT; } } diff --git a/deps/curl/lib/asyn-thread.c b/deps/curl/lib/asyn-thread.c index a2e294f8f51..a58e4b79049 100644 --- a/deps/curl/lib/asyn-thread.c +++ b/deps/curl/lib/asyn-thread.c @@ -154,7 +154,7 @@ struct thread_sync_data { duplicate */ #ifndef CURL_DISABLE_SOCKETPAIR struct Curl_easy *data; - curl_socket_t sock_pair[2]; /* socket pair */ + curl_socket_t sock_pair[2]; /* eventfd/pipes/socket pair */ #endif int sock_error; struct Curl_addrinfo *res; @@ -196,7 +196,7 @@ void destroy_thread_sync_data(struct thread_sync_data *tsd) * the other end (for reading) is always closed in the parent thread. */ if(tsd->sock_pair[1] != CURL_SOCKET_BAD) { - sclose(tsd->sock_pair[1]); + wakeup_close(tsd->sock_pair[1]); } #endif memset(tsd, 0, sizeof(*tsd)); @@ -233,8 +233,8 @@ int init_thread_sync_data(struct thread_data *td, Curl_mutex_init(tsd->mtx); #ifndef CURL_DISABLE_SOCKETPAIR - /* create socket pair, avoid AF_LOCAL since it doesn't build on Solaris */ - if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &tsd->sock_pair[0]) < 0) { + /* create socket pair or pipe */ + if(wakeup_create(tsd->sock_pair, FALSE) < 0) { tsd->sock_pair[0] = CURL_SOCKET_BAD; tsd->sock_pair[1] = CURL_SOCKET_BAD; goto err_exit; @@ -254,7 +254,7 @@ int init_thread_sync_data(struct thread_data *td, err_exit: #ifndef CURL_DISABLE_SOCKETPAIR if(tsd->sock_pair[0] != CURL_SOCKET_BAD) { - sclose(tsd->sock_pair[0]); + wakeup_close(tsd->sock_pair[0]); tsd->sock_pair[0] = CURL_SOCKET_BAD; } #endif @@ -269,7 +269,7 @@ static CURLcode getaddrinfo_complete(struct Curl_easy *data) result = Curl_addrinfo_callback(data, tsd->sock_error, tsd->res); /* The tsd->res structure has been copied to async.dns and perhaps the DNS - cache. Set our copy to NULL so destroy_thread_sync_data doesn't free it. + cache. Set our copy to NULL so destroy_thread_sync_data does not free it. */ tsd->res = NULL; @@ -282,17 +282,28 @@ static CURLcode getaddrinfo_complete(struct Curl_easy *data) /* * getaddrinfo_thread() resolves a name and then exits. * - * For builds without ARES, but with ENABLE_IPV6, create a resolver thread + * For builds without ARES, but with USE_IPV6, create a resolver thread * and wait on it. */ -static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg) +static +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) +DWORD +#else +unsigned int +#endif +CURL_STDCALL getaddrinfo_thread(void *arg) { struct thread_sync_data *tsd = (struct thread_sync_data *)arg; struct thread_data *td = tsd->td; char service[12]; int rc; #ifndef CURL_DISABLE_SOCKETPAIR +#ifdef USE_EVENTFD + const void *buf; + const uint64_t val = 1; +#else char buf[1]; +#endif #endif msnprintf(service, sizeof(service), "%d", tsd->port); @@ -300,7 +311,7 @@ static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg) rc = Curl_getaddrinfo_ex(tsd->hostname, service, &tsd->hints, &tsd->res); if(rc) { - tsd->sock_error = SOCKERRNO?SOCKERRNO:rc; + tsd->sock_error = SOCKERRNO ? SOCKERRNO : rc; if(tsd->sock_error == 0) tsd->sock_error = RESOLVER_ENOMEM; } @@ -318,9 +329,13 @@ static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg) else { #ifndef CURL_DISABLE_SOCKETPAIR if(tsd->sock_pair[1] != CURL_SOCKET_BAD) { - /* DNS has been resolved, signal client task */ +#ifdef USE_EVENTFD + buf = &val; +#else buf[0] = 1; - if(swrite(tsd->sock_pair[1], buf, sizeof(buf)) < 0) { +#endif + /* DNS has been resolved, signal client task */ + if(wakeup_write(tsd->sock_pair[1], buf, sizeof(buf)) < 0) { /* update sock_erro to errno */ tsd->sock_error = SOCKERRNO; } @@ -338,7 +353,13 @@ static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg) /* * gethostbyname_thread() resolves a name and then exits. */ -static unsigned int CURL_STDCALL gethostbyname_thread(void *arg) +static +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) +DWORD +#else +unsigned int +#endif +CURL_STDCALL gethostbyname_thread(void *arg) { struct thread_sync_data *tsd = (struct thread_sync_data *)arg; struct thread_data *td = tsd->td; @@ -407,7 +428,7 @@ static void destroy_async_data(struct Curl_async *async) * before the FD is invalidated to avoid EBADF on EPOLL_CTL_DEL */ Curl_multi_closed(data, sock_rd); - sclose(sock_rd); + wakeup_close(sock_rd); #endif } async->tdata = NULL; @@ -460,7 +481,7 @@ static bool init_resolve_thread(struct Curl_easy *data, td->thread_hnd = Curl_thread_create(gethostbyname_thread, &td->tsd); #endif - if(!td->thread_hnd) { + if(td->thread_hnd == curl_thread_t_null) { /* The thread never started, so mark it as done here for proper cleanup. */ td->tsd.done = 1; err = errno; @@ -526,8 +547,8 @@ void Curl_resolver_kill(struct Curl_easy *data) { struct thread_data *td = data->state.async.tdata; - /* If we're still resolving, we must wait for the threads to fully clean up, - unfortunately. Otherwise, we can simply cancel to clean up any resolver + /* If we are still resolving, we must wait for the threads to fully clean up, + unfortunately. Otherwise, we can simply cancel to clean up any resolver data. */ if(td && td->thread_hnd != curl_thread_t_null && (data->set.quick_exit != 1L)) @@ -591,7 +612,7 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, } else { /* poll for name lookup done with exponential backoff up to 250ms */ - /* should be fine even if this converts to 32 bit */ + /* should be fine even if this converts to 32-bit */ timediff_t elapsed = Curl_timediff(Curl_now(), data->progress.t_startsingle); if(elapsed < 0) @@ -707,7 +728,7 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, memset(&hints, 0, sizeof(hints)); hints.ai_family = pf; - hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP)? + hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP) ? SOCK_STREAM : SOCK_DGRAM; reslv->start = Curl_now(); diff --git a/deps/curl/lib/asyn.h b/deps/curl/lib/asyn.h index 7e207c4f564..0ff20488455 100644 --- a/deps/curl/lib/asyn.h +++ b/deps/curl/lib/asyn.h @@ -58,7 +58,7 @@ void Curl_resolver_global_cleanup(void); * Curl_resolver_init() * Called from curl_easy_init() -> Curl_open() to initialize resolver * URL-state specific environment ('resolver' member of the UrlState - * structure). Should fill the passed pointer by the initialized handler. + * structure). Should fill the passed pointer by the initialized handler. * Returning anything else than CURLE_OK fails curl_easy_init() with the * correspondent code. */ @@ -68,7 +68,7 @@ CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver); * Curl_resolver_cleanup() * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver * URL-state specific environment ('resolver' member of the UrlState - * structure). Should destroy the handler and free all resources connected to + * structure). Should destroy the handler and free all resources connected to * it. */ void Curl_resolver_cleanup(void *resolver); @@ -76,9 +76,9 @@ void Curl_resolver_cleanup(void *resolver); /* * Curl_resolver_duphandle() * Called from curl_easy_duphandle() to duplicate resolver URL-state specific - * environment ('resolver' member of the UrlState structure). Should + * environment ('resolver' member of the UrlState structure). Should * duplicate the 'from' handle and pass the resulting handle to the 'to' - * pointer. Returning anything else than CURLE_OK causes failed + * pointer. Returning anything else than CURLE_OK causes failed * curl_easy_duphandle() call. */ CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, @@ -89,7 +89,7 @@ CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, * * It is called from inside other functions to cancel currently performing * resolver request. Should also free any temporary resources allocated to - * perform a request. This never waits for resolver threads to complete. + * perform a request. This never waits for resolver threads to complete. * * It is safe to call this when conn is in any state. */ @@ -99,8 +99,8 @@ void Curl_resolver_cancel(struct Curl_easy *data); * Curl_resolver_kill(). * * This acts like Curl_resolver_cancel() except it will block until any threads - * associated with the resolver are complete. This never blocks for resolvers - * that do not use threads. This is intended to be the "last chance" function + * associated with the resolver are complete. This never blocks for resolvers + * that do not use threads. This is intended to be the "last chance" function * that cleans up an in-progress resolver completely (before its owner is about * to die). * @@ -161,7 +161,7 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, int *waitp); #ifndef CURLRES_ASYNCH -/* convert these functions if an asynch resolver isn't used */ +/* convert these functions if an asynch resolver is not used */ #define Curl_resolver_cancel(x) Curl_nop_stmt #define Curl_resolver_kill(x) Curl_nop_stmt #define Curl_resolver_is_resolved(x,y) CURLE_COULDNT_RESOLVE_HOST diff --git a/deps/curl/lib/base64.c b/deps/curl/lib/base64.c index 30db7e5889f..8373115d207 100644 --- a/deps/curl/lib/base64.c +++ b/deps/curl/lib/base64.c @@ -31,7 +31,8 @@ !defined(CURL_DISABLE_SMTP) || \ !defined(CURL_DISABLE_POP3) || \ !defined(CURL_DISABLE_IMAP) || \ - !defined(CURL_DISABLE_DOH) || defined(USE_SSL) + !defined(CURL_DISABLE_DIGEST_AUTH) || \ + !defined(CURL_DISABLE_DOH) || defined(USE_SSL) || defined(BUILDING_CURL) #include "curl/curl.h" #include "warnless.h" #include "curl_base64.h" @@ -242,7 +243,7 @@ static CURLcode base64_encode(const char *table64, *outptr = base64data; /* Return the length of the new data */ - *outlen = output - base64data; + *outlen = (size_t)(output - base64data); return CURLE_OK; } diff --git a/deps/curl/lib/bufq.c b/deps/curl/lib/bufq.c index 155544993dd..547d4d37629 100644 --- a/deps/curl/lib/bufq.c +++ b/deps/curl/lib/bufq.c @@ -91,6 +91,23 @@ static size_t chunk_read(struct buf_chunk *chunk, } } +static size_t chunk_unwrite(struct buf_chunk *chunk, size_t len) +{ + size_t n = chunk->w_offset - chunk->r_offset; + DEBUGASSERT(chunk->w_offset >= chunk->r_offset); + if(!n) { + return 0; + } + else if(n <= len) { + chunk->r_offset = chunk->w_offset = 0; + return n; + } + else { + chunk->w_offset -= len; + return len; + } +} + static ssize_t chunk_slurpn(struct buf_chunk *chunk, size_t max_len, Curl_bufq_reader *reader, void *reader_ctx, CURLcode *err) @@ -144,21 +161,6 @@ static size_t chunk_skip(struct buf_chunk *chunk, size_t amount) return n; } -static void chunk_shift(struct buf_chunk *chunk) -{ - if(chunk->r_offset) { - if(!chunk_is_empty(chunk)) { - size_t n = chunk->w_offset - chunk->r_offset; - memmove(chunk->x.data, chunk->x.data + chunk->r_offset, n); - chunk->w_offset -= chunk->r_offset; - chunk->r_offset = 0; - } - else { - chunk->r_offset = chunk->w_offset = 0; - } - } -} - static void chunk_list_free(struct buf_chunk **anchor) { struct buf_chunk *chunk; @@ -378,6 +380,49 @@ static void prune_head(struct bufq *q) } } +static struct buf_chunk *chunk_prev(struct buf_chunk *head, + struct buf_chunk *chunk) +{ + while(head) { + if(head == chunk) + return NULL; + if(head->next == chunk) + return head; + head = head->next; + } + return NULL; +} + +static void prune_tail(struct bufq *q) +{ + struct buf_chunk *chunk; + + while(q->tail && chunk_is_empty(q->tail)) { + chunk = q->tail; + q->tail = chunk_prev(q->head, chunk); + if(q->tail) + q->tail->next = NULL; + if(q->head == chunk) + q->head = q->tail; + if(q->pool) { + bufcp_put(q->pool, chunk); + --q->chunk_count; + } + else if((q->chunk_count > q->max_chunks) || + (q->opts & BUFQ_OPT_NO_SPARES)) { + /* SOFT_LIMIT allowed us more than max. free spares until + * we are at max again. Or free them if we are configured + * to not use spares. */ + free(chunk); + --q->chunk_count; + } + else { + chunk->next = q->spare; + q->spare = chunk; + } + } +} + static struct buf_chunk *get_non_full_tail(struct bufq *q) { struct buf_chunk *chunk; @@ -411,7 +456,7 @@ ssize_t Curl_bufq_write(struct bufq *q, while(len) { tail = get_non_full_tail(q); if(!tail) { - if(q->chunk_count < q->max_chunks) { + if((q->chunk_count < q->max_chunks) || (q->opts & BUFQ_OPT_SOFT_LIMIT)) { *err = CURLE_OUT_OF_MEMORY; return -1; } @@ -432,6 +477,26 @@ ssize_t Curl_bufq_write(struct bufq *q, return nwritten; } +CURLcode Curl_bufq_cwrite(struct bufq *q, + const char *buf, size_t len, + size_t *pnwritten) +{ + ssize_t n; + CURLcode result; + n = Curl_bufq_write(q, (const unsigned char *)buf, len, &result); + *pnwritten = (n < 0) ? 0 : (size_t)n; + return result; +} + +CURLcode Curl_bufq_unwrite(struct bufq *q, size_t len) +{ + while(len && q->tail) { + len -= chunk_unwrite(q->tail, len); + prune_tail(q); + } + return len ? CURLE_AGAIN : CURLE_OK; +} + ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len, CURLcode *err) { @@ -455,6 +520,16 @@ ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len, return nread; } +CURLcode Curl_bufq_cread(struct bufq *q, char *buf, size_t len, + size_t *pnread) +{ + ssize_t n; + CURLcode result; + n = Curl_bufq_read(q, (unsigned char *)buf, len, &result); + *pnread = (n < 0) ? 0 : (size_t)n; + return result; +} + bool Curl_bufq_peek(struct bufq *q, const unsigned char **pbuf, size_t *plen) { @@ -504,13 +579,6 @@ void Curl_bufq_skip(struct bufq *q, size_t amount) } } -void Curl_bufq_skip_and_shift(struct bufq *q, size_t amount) -{ - Curl_bufq_skip(q, amount); - if(q->tail) - chunk_shift(q->tail); -} - ssize_t Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer, void *writer_ctx, CURLcode *err) { diff --git a/deps/curl/lib/bufq.h b/deps/curl/lib/bufq.h index 89b5c84efe9..ec415648fd4 100644 --- a/deps/curl/lib/bufq.h +++ b/deps/curl/lib/bufq.h @@ -85,7 +85,7 @@ void Curl_bufcp_free(struct bufc_pool *pool); * preferably never fail (except for memory exhaustion). * * By default and without a pool, a bufq will keep chunks that read - * read empty in its `spare` list. Option `BUFQ_OPT_NO_SPARES` will + * empty in its `spare` list. Option `BUFQ_OPT_NO_SPARES` will * disable that and free chunks once they become empty. * * When providing a pool to a bufq, all chunk creation and spare handling @@ -178,6 +178,16 @@ ssize_t Curl_bufq_write(struct bufq *q, const unsigned char *buf, size_t len, CURLcode *err); +CURLcode Curl_bufq_cwrite(struct bufq *q, + const char *buf, size_t len, + size_t *pnwritten); + +/** + * Remove `len` bytes from the end of the buffer queue again. + * Returns CURLE_AGAIN if less than `len` bytes were in the queue. + */ +CURLcode Curl_bufq_unwrite(struct bufq *q, size_t len); + /** * Read buf from the start of the buffer queue. The buf is copied * and the amount of copied bytes is returned. @@ -187,6 +197,9 @@ ssize_t Curl_bufq_write(struct bufq *q, ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len, CURLcode *err); +CURLcode Curl_bufq_cread(struct bufq *q, char *buf, size_t len, + size_t *pnread); + /** * Peek at the head chunk in the buffer queue. Returns a pointer to * the chunk buf (at the current offset) and its length. Does not @@ -209,12 +222,6 @@ bool Curl_bufq_peek_at(struct bufq *q, size_t offset, */ void Curl_bufq_skip(struct bufq *q, size_t amount); -/** - * Same as `skip` but shift tail data to the start afterwards, - * so that further writes will find room in tail. - */ -void Curl_bufq_skip_and_shift(struct bufq *q, size_t amount); - typedef ssize_t Curl_bufq_writer(void *writer_ctx, const unsigned char *buf, size_t len, CURLcode *err); diff --git a/deps/curl/lib/bufref.c b/deps/curl/lib/bufref.c index ce686b6f377..f048b570119 100644 --- a/deps/curl/lib/bufref.c +++ b/deps/curl/lib/bufref.c @@ -25,6 +25,7 @@ #include "curl_setup.h" #include "urldata.h" #include "bufref.h" +#include "strdup.h" #include "curl_memory.h" #include "memdebug.h" @@ -47,7 +48,7 @@ void Curl_bufref_init(struct bufref *br) } /* - * Free the buffer and re-init the necessary fields. It doesn't touch the + * Free the buffer and re-init the necessary fields. It does not touch the * 'signature' field and thus this buffer reference can be reused. */ @@ -116,12 +117,9 @@ CURLcode Curl_bufref_memdup(struct bufref *br, const void *ptr, size_t len) DEBUGASSERT(len <= CURL_MAX_INPUT_LENGTH); if(ptr) { - cpy = malloc(len + 1); + cpy = Curl_memdup0(ptr, len); if(!cpy) return CURLE_OUT_OF_MEMORY; - if(len) - memcpy(cpy, ptr, len); - cpy[len] = '\0'; } Curl_bufref_set(br, cpy, len, curl_free); diff --git a/deps/curl/lib/c-hyper.c b/deps/curl/lib/c-hyper.c index 61ca29a3aca..2b8eb957075 100644 --- a/deps/curl/lib/c-hyper.c +++ b/deps/curl/lib/c-hyper.c @@ -22,6 +22,10 @@ * ***************************************************************************/ +/* Curl's integration with Hyper. This replaces certain functions in http.c, + * based on configuration #defines. This implementation supports HTTP/1.1 but + * not HTTP/2. + */ #include "curl_setup.h" #if !defined(CURL_DISABLE_HTTP) && defined(USE_HYPER) @@ -49,7 +53,9 @@ #include #include "urldata.h" +#include "cfilters.h" #include "sendf.h" +#include "headers.h" #include "transfer.h" #include "multiif.h" #include "progress.h" @@ -61,6 +67,9 @@ #include "curl_memory.h" #include "memdebug.h" + +static CURLcode cr_hyper_add(struct Curl_easy *data); + typedef enum { USERDATA_NOT_SET = 0, /* for tasks with no userdata set; must be zero */ USERDATA_RESP_BODY @@ -69,7 +78,8 @@ typedef enum { size_t Curl_hyper_recv(void *userp, hyper_context *ctx, uint8_t *buf, size_t buflen) { - struct Curl_easy *data = userp; + struct hyp_io_ctx *io_ctx = userp; + struct Curl_easy *data = io_ctx->data; struct connectdata *conn = data->conn; CURLcode result; ssize_t nread; @@ -77,7 +87,8 @@ size_t Curl_hyper_recv(void *userp, hyper_context *ctx, (void)ctx; DEBUGF(infof(data, "Curl_hyper_recv(%zu)", buflen)); - result = Curl_read(data, conn->sockfd, (char *)buf, buflen, &nread); + result = Curl_conn_recv(data, io_ctx->sockindex, + (char *)buf, buflen, &nread); if(result == CURLE_AGAIN) { /* would block, register interest */ DEBUGF(infof(data, "Curl_hyper_recv(%zu) -> EAGAIN", buflen)); @@ -101,15 +112,14 @@ size_t Curl_hyper_recv(void *userp, hyper_context *ctx, size_t Curl_hyper_send(void *userp, hyper_context *ctx, const uint8_t *buf, size_t buflen) { - struct Curl_easy *data = userp; - struct connectdata *conn = data->conn; + struct hyp_io_ctx *io_ctx = userp; + struct Curl_easy *data = io_ctx->data; CURLcode result; - ssize_t nwrote; + size_t nwrote; DEBUGF(infof(data, "Curl_hyper_send(%zu)", buflen)); - result = Curl_write(data, conn->sockfd, (void *)buf, buflen, &nwrote); - if(!result && !nwrote) - result = CURLE_AGAIN; + result = Curl_conn_send(data, io_ctx->sockindex, + (void *)buf, buflen, FALSE, &nwrote); if(result == CURLE_AGAIN) { DEBUGF(infof(data, "Curl_hyper_send(%zu) -> EAGAIN", buflen)); /* would block, register interest */ @@ -144,13 +154,10 @@ static int hyper_each_header(void *userdata, if(name_len + value_len + 2 > CURL_MAX_HTTP_HEADER) { failf(data, "Too long response header"); - data->state.hresult = CURLE_OUT_OF_MEMORY; + data->state.hresult = CURLE_TOO_LARGE; return HYPER_ITER_BREAK; } - if(!data->req.bytecount) - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - Curl_dyn_reset(&data->state.headerb); if(name_len) { if(Curl_dyn_addf(&data->state.headerb, "%.*s: %.*s\r\n", @@ -164,7 +171,7 @@ static int hyper_each_header(void *userdata, len = Curl_dyn_len(&data->state.headerb); headp = Curl_dyn_ptr(&data->state.headerb); - result = Curl_http_header(data, data->conn, headp); + result = Curl_http_header(data, headp, len); if(result) { data->state.hresult = result; return HYPER_ITER_BREAK; @@ -172,19 +179,15 @@ static int hyper_each_header(void *userdata, Curl_debug(data, CURLINFO_HEADER_IN, headp, len); - if(!data->state.hconnect || !data->set.suppress_connect_headers) { - writetype = CLIENTWRITE_HEADER; - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; - if(data->state.hconnect) - writetype |= CLIENTWRITE_CONNECT; - if(data->req.httpcode/100 == 1) - writetype |= CLIENTWRITE_1XX; - result = Curl_client_write(data, writetype, headp, len); - if(result) { - data->state.hresult = CURLE_ABORTED_BY_CALLBACK; - return HYPER_ITER_BREAK; - } + writetype = CLIENTWRITE_HEADER; + if(data->state.hconnect) + writetype |= CLIENTWRITE_CONNECT; + if(data->req.httpcode/100 == 1) + writetype |= CLIENTWRITE_1XX; + result = Curl_client_write(data, writetype, headp, len); + if(result) { + data->state.hresult = CURLE_ABORTED_BY_CALLBACK; + return HYPER_ITER_BREAK; } result = Curl_bump_headersize(data, len, FALSE); @@ -203,8 +206,7 @@ static int hyper_body_chunk(void *userdata, const hyper_buf *chunk) struct SingleRequest *k = &data->req; CURLcode result = CURLE_OK; - if(0 == k->bodywrites++) { - bool done = FALSE; + if(!k->bodywritten) { #if defined(USE_NTLM) struct connectdata *conn = data->conn; if(conn->bits.close && @@ -217,50 +219,38 @@ static int hyper_body_chunk(void *userdata, const hyper_buf *chunk) Curl_safefree(data->req.newurl); } #endif - if(data->state.expect100header) { - Curl_expire_done(data, EXPIRE_100_TIMEOUT); + if(Curl_http_exp100_is_selected(data)) { if(data->req.httpcode < 400) { - k->exp100 = EXP100_SEND_DATA; - if(data->hyp.exp100_waker) { - hyper_waker_wake(data->hyp.exp100_waker); - data->hyp.exp100_waker = NULL; + Curl_http_exp100_got100(data); + if(data->hyp.send_body_waker) { + hyper_waker_wake(data->hyp.send_body_waker); + data->hyp.send_body_waker = NULL; } } else { /* >= 4xx */ - k->exp100 = EXP100_FAILED; + Curl_req_abort_sending(data); } } if(data->state.hconnect && (data->req.httpcode/100 != 2) && data->state.authproxy.done) { - done = TRUE; + data->req.done = TRUE; result = CURLE_OK; } else - result = Curl_http_firstwrite(data, data->conn, &done); - if(result || done) { + result = Curl_http_firstwrite(data); + if(result || data->req.done) { infof(data, "Return early from hyper_body_chunk"); data->state.hresult = result; return HYPER_ITER_BREAK; } } - if(k->ignorebody) - return HYPER_ITER_CONTINUE; - if(0 == len) - return HYPER_ITER_CONTINUE; - Curl_debug(data, CURLINFO_DATA_IN, buf, len); - if(!data->set.http_ce_skip && k->writer_stack) - /* content-encoded data */ - result = Curl_unencode_write(data, k->writer_stack, buf, len); - else - result = Curl_client_write(data, CLIENTWRITE_BODY, buf, len); + result = Curl_client_write(data, CLIENTWRITE_BODY, buf, len); if(result) { data->state.hresult = result; return HYPER_ITER_BREAK; } - data->req.bytecount += len; - Curl_pgrsSetDownloadCounter(data, data->req.bytecount); return HYPER_ITER_CONTINUE; } @@ -284,14 +274,13 @@ static CURLcode status_line(struct Curl_easy *data, /* We need to set 'httpcodeq' for functions that check the response code in a single place. */ data->req.httpcode = http_status; - + data->req.httpversion = http_version == HYPER_HTTP_VERSION_1_1 ? 11 : + (http_version == HYPER_HTTP_VERSION_2 ? 20 : 10); if(data->state.hconnect) /* CONNECT */ data->info.httpproxycode = http_status; else { - conn->httpversion = - http_version == HYPER_HTTP_VERSION_1_1 ? 11 : - (http_version == HYPER_HTTP_VERSION_2 ? 20 : 10); + conn->httpversion = (unsigned char)data->req.httpversion; if(http_version == HYPER_HTTP_VERSION_1_0) data->state.httpwant = CURL_HTTP_VERSION_1_0; @@ -312,15 +301,14 @@ static CURLcode status_line(struct Curl_easy *data, Curl_debug(data, CURLINFO_HEADER_IN, Curl_dyn_ptr(&data->state.headerb), len); - if(!data->state.hconnect || !data->set.suppress_connect_headers) { - writetype = CLIENTWRITE_HEADER|CLIENTWRITE_STATUS; - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; - result = Curl_client_write(data, writetype, - Curl_dyn_ptr(&data->state.headerb), len); - if(result) - return result; - } + writetype = CLIENTWRITE_HEADER|CLIENTWRITE_STATUS; + if(data->state.hconnect) + writetype |= CLIENTWRITE_CONNECT; + result = Curl_client_write(data, writetype, + Curl_dyn_ptr(&data->state.headerb), len); + if(result) + return result; + result = Curl_bump_headersize(data, len, FALSE); return result; } @@ -336,7 +324,10 @@ static CURLcode empty_header(struct Curl_easy *data) result = hyper_each_header(data, NULL, 0, NULL, 0) ? CURLE_WRITE_ERROR : CURLE_OK; if(result) - failf(data, "hyperstream: couldn't pass blank header"); + failf(data, "hyperstream: could not pass blank header"); + /* Hyper does chunked decoding itself. If it was added during + * response header processing, remove it again. */ + Curl_cwriter_remove_by_name(data, "chunked"); } return result; } @@ -344,7 +335,6 @@ static CURLcode empty_header(struct Curl_easy *data) CURLcode Curl_hyper_stream(struct Curl_easy *data, struct connectdata *conn, int *didwhat, - bool *done, int select_res) { hyper_response *resp = NULL; @@ -361,20 +351,11 @@ CURLcode Curl_hyper_stream(struct Curl_easy *data, struct SingleRequest *k = &data->req; (void)conn; - if(k->exp100 > EXP100_SEND_DATA) { - struct curltime now = Curl_now(); - timediff_t ms = Curl_timediff(now, k->start100); - if(ms >= data->set.expect_100_timeout) { - /* we've waited long enough, continue anyway */ - k->exp100 = EXP100_SEND_DATA; - k->keepon |= KEEP_SEND; - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - infof(data, "Done waiting for 100-continue"); - if(data->hyp.exp100_waker) { - hyper_waker_wake(data->hyp.exp100_waker); - data->hyp.exp100_waker = NULL; - } - } + if(data->hyp.send_body_waker) { + /* If there is still something to upload, wake it to give it + * another try. */ + hyper_waker_wake(data->hyp.send_body_waker); + data->hyp.send_body_waker = NULL; } if(select_res & CURL_CSELECT_IN) { @@ -388,8 +369,7 @@ CURLcode Curl_hyper_stream(struct Curl_easy *data, h->write_waker = NULL; } - *done = FALSE; - do { + while(1) { hyper_task_return_type t; task = hyper_executor_poll(h->exec); if(!task) { @@ -413,141 +393,152 @@ CURLcode Curl_hyper_stream(struct Curl_easy *data, switch(code) { case HYPERE_ABORTED_BY_CALLBACK: result = CURLE_OK; - break; + goto out; case HYPERE_UNEXPECTED_EOF: if(!data->req.bytecount) result = CURLE_GOT_NOTHING; else result = CURLE_RECV_ERROR; - break; + goto out; case HYPERE_INVALID_PEER_MESSAGE: /* bump headerbytecount to avoid the count remaining at zero and appearing to not having read anything from the peer at all */ data->req.headerbytecount++; result = CURLE_UNSUPPORTED_PROTOCOL; /* maybe */ - break; + goto out; default: result = CURLE_RECV_ERROR; - break; + goto out; } } - *done = TRUE; + data->req.done = TRUE; hyper_error_free(hypererr); break; } else if(t == HYPER_TASK_EMPTY) { void *userdata = hyper_task_userdata(task); hyper_task_free(task); - if((userdata_t)userdata == USERDATA_RESP_BODY) { + if(userdata == (void *)USERDATA_RESP_BODY) { /* end of transfer */ - *done = TRUE; + data->req.done = TRUE; infof(data, "hyperstream is done"); - if(!k->bodywrites) { - /* hyper doesn't always call the body write callback */ - bool stilldone; - result = Curl_http_firstwrite(data, data->conn, &stilldone); + if(!k->bodywritten) { + /* hyper does not always call the body write callback */ + result = Curl_http_firstwrite(data); } break; } else { /* A background task for hyper; ignore */ + DEBUGF(infof(data, "hyper: some background task done")); continue; } } + else if(t == HYPER_TASK_RESPONSE) { + resp = hyper_task_value(task); + hyper_task_free(task); - DEBUGASSERT(HYPER_TASK_RESPONSE); - - resp = hyper_task_value(task); - hyper_task_free(task); + *didwhat = KEEP_RECV; + if(!resp) { + failf(data, "hyperstream: could not get response"); + result = CURLE_RECV_ERROR; + goto out; + } - *didwhat = KEEP_RECV; - if(!resp) { - failf(data, "hyperstream: couldn't get response"); - return CURLE_RECV_ERROR; - } + http_status = hyper_response_status(resp); + http_version = hyper_response_version(resp); + reasonp = hyper_response_reason_phrase(resp); + reason_len = hyper_response_reason_phrase_len(resp); - http_status = hyper_response_status(resp); - http_version = hyper_response_version(resp); - reasonp = hyper_response_reason_phrase(resp); - reason_len = hyper_response_reason_phrase_len(resp); + if(http_status == 417 && Curl_http_exp100_is_selected(data)) { + infof(data, "Got 417 while waiting for a 100"); + data->state.disableexpect = TRUE; + data->req.newurl = strdup(data->state.url); + Curl_req_abort_sending(data); + } - if(http_status == 417 && data->state.expect100header) { - infof(data, "Got 417 while waiting for a 100"); - data->state.disableexpect = TRUE; - data->req.newurl = strdup(data->state.url); - Curl_done_sending(data, k); - } + result = status_line(data, conn, + http_status, http_version, reasonp, reason_len); + if(result) + goto out; - result = status_line(data, conn, - http_status, http_version, reasonp, reason_len); - if(result) - break; + headers = hyper_response_headers(resp); + if(!headers) { + failf(data, "hyperstream: could not get response headers"); + result = CURLE_RECV_ERROR; + goto out; + } - headers = hyper_response_headers(resp); - if(!headers) { - failf(data, "hyperstream: couldn't get response headers"); - result = CURLE_RECV_ERROR; - break; - } + /* the headers are already received */ + hyper_headers_foreach(headers, hyper_each_header, data); + if(data->state.hresult) { + result = data->state.hresult; + goto out; + } - /* the headers are already received */ - hyper_headers_foreach(headers, hyper_each_header, data); - if(data->state.hresult) { - result = data->state.hresult; - break; - } + result = empty_header(data); + if(result) + goto out; + + k->deductheadercount = + (100 <= http_status && 199 >= http_status) ? k->headerbytecount : 0; +#ifndef CURL_DISABLE_WEBSOCKETS + if(k->upgr101 == UPGR101_WS) { + if(http_status == 101) { + /* verify the response */ + result = Curl_ws_accept(data, NULL, 0); + if(result) + goto out; + } + else { + failf(data, "Expected 101, got %u", k->httpcode); + result = CURLE_HTTP_RETURNED_ERROR; + goto out; + } + } +#endif - result = empty_header(data); - if(result) - break; + /* Curl_http_auth_act() checks what authentication methods that are + * available and decides which one (if any) to use. It will set 'newurl' + * if an auth method was picked. */ + result = Curl_http_auth_act(data); + if(result) + goto out; - k->deductheadercount = - (100 <= http_status && 199 >= http_status)?k->headerbytecount:0; -#ifdef USE_WEBSOCKETS - if(k->upgr101 == UPGR101_WS) { - if(http_status == 101) { - /* verify the response */ - result = Curl_ws_accept(data, NULL, 0); - if(result) - return result; + resp_body = hyper_response_body(resp); + if(!resp_body) { + failf(data, "hyperstream: could not get response body"); + result = CURLE_RECV_ERROR; + goto out; } - else { - failf(data, "Expected 101, got %u", k->httpcode); - result = CURLE_HTTP_RETURNED_ERROR; - break; + foreach = hyper_body_foreach(resp_body, hyper_body_chunk, data); + if(!foreach) { + failf(data, "hyperstream: body foreach failed"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + hyper_task_set_userdata(foreach, (void *)USERDATA_RESP_BODY); + if(HYPERE_OK != hyper_executor_push(h->exec, foreach)) { + failf(data, "Couldn't hyper_executor_push the body-foreach"); + result = CURLE_OUT_OF_MEMORY; + goto out; } - } -#endif - - /* Curl_http_auth_act() checks what authentication methods that are - * available and decides which one (if any) to use. It will set 'newurl' - * if an auth method was picked. */ - result = Curl_http_auth_act(data); - if(result) - break; - resp_body = hyper_response_body(resp); - if(!resp_body) { - failf(data, "hyperstream: couldn't get response body"); - result = CURLE_RECV_ERROR; - break; + hyper_response_free(resp); + resp = NULL; } - foreach = hyper_body_foreach(resp_body, hyper_body_chunk, data); - if(!foreach) { - failf(data, "hyperstream: body foreach failed"); - result = CURLE_OUT_OF_MEMORY; - break; - } - hyper_task_set_userdata(foreach, (void *)USERDATA_RESP_BODY); - if(HYPERE_OK != hyper_executor_push(h->exec, foreach)) { - failf(data, "Couldn't hyper_executor_push the body-foreach"); - result = CURLE_OUT_OF_MEMORY; - break; + else { + DEBUGF(infof(data, "hyper: unhandled tasktype %x", t)); } + } /* while(1) */ - hyper_response_free(resp); - resp = NULL; - } while(1); + if(!result && Curl_xfer_needs_flush(data)) { + DEBUGF(infof(data, "Curl_hyper_stream(), connection needs flush")); + result = Curl_xfer_flush(data); + } + +out: + DEBUGF(infof(data, "Curl_hyper_stream() -> %d", result)); if(resp) hyper_response_free(resp); return result; @@ -555,11 +546,9 @@ CURLcode Curl_hyper_stream(struct Curl_easy *data, static CURLcode debug_request(struct Curl_easy *data, const char *method, - const char *path, - bool h2) + const char *path) { - char *req = aprintf("%s %s HTTP/%s\r\n", method, path, - h2?"2":"1.1"); + char *req = aprintf("%s %s HTTP/1.1\r\n", method, path); if(!req) return CURLE_OUT_OF_MEMORY; Curl_debug(data, CURLINFO_HEADER_OUT, req, strlen(req)); @@ -641,7 +630,6 @@ CURLcode Curl_hyper_header(struct Curl_easy *data, hyper_headers *headers, static CURLcode request_target(struct Curl_easy *data, struct connectdata *conn, const char *method, - bool h2, hyper_request *req) { CURLcode result; @@ -653,176 +641,121 @@ static CURLcode request_target(struct Curl_easy *data, if(result) return result; - if(h2 && hyper_request_set_uri_parts(req, - /* scheme */ - (uint8_t *)data->state.up.scheme, - strlen(data->state.up.scheme), - /* authority */ - (uint8_t *)conn->host.name, - strlen(conn->host.name), - /* path_and_query */ - (uint8_t *)Curl_dyn_uptr(&r), - Curl_dyn_len(&r))) { - failf(data, "error setting uri parts to hyper"); - result = CURLE_OUT_OF_MEMORY; - } - else if(!h2 && hyper_request_set_uri(req, (uint8_t *)Curl_dyn_uptr(&r), + if(hyper_request_set_uri(req, (uint8_t *)Curl_dyn_uptr(&r), Curl_dyn_len(&r))) { failf(data, "error setting uri to hyper"); result = CURLE_OUT_OF_MEMORY; } else - result = debug_request(data, method, Curl_dyn_ptr(&r), h2); + result = debug_request(data, method, Curl_dyn_ptr(&r)); Curl_dyn_free(&r); return result; } -static int uploadpostfields(void *userdata, hyper_context *ctx, - hyper_buf **chunk) -{ - struct Curl_easy *data = (struct Curl_easy *)userdata; - (void)ctx; - if(data->req.exp100 > EXP100_SEND_DATA) { - if(data->req.exp100 == EXP100_FAILED) - return HYPER_POLL_ERROR; - - /* still waiting confirmation */ - if(data->hyp.exp100_waker) - hyper_waker_free(data->hyp.exp100_waker); - data->hyp.exp100_waker = hyper_context_waker(ctx); - return HYPER_POLL_PENDING; - } - if(data->req.upload_done) - *chunk = NULL; /* nothing more to deliver */ - else { - /* send everything off in a single go */ - hyper_buf *copy = hyper_buf_copy(data->set.postfields, - (size_t)data->req.p.http->postsize); - if(copy) - *chunk = copy; - else { - data->state.hresult = CURLE_OUT_OF_MEMORY; - return HYPER_POLL_ERROR; - } - /* increasing the writebytecount here is a little premature but we - don't know exactly when the body is sent */ - data->req.writebytecount += (size_t)data->req.p.http->postsize; - Curl_pgrsSetUploadCounter(data, data->req.writebytecount); - data->req.upload_done = TRUE; - } - return HYPER_POLL_READY; -} - static int uploadstreamed(void *userdata, hyper_context *ctx, hyper_buf **chunk) { size_t fillcount; struct Curl_easy *data = (struct Curl_easy *)userdata; - struct connectdata *conn = (struct connectdata *)data->conn; CURLcode result; + char *xfer_ulbuf; + size_t xfer_ulblen; + bool eos; + int rc = HYPER_POLL_ERROR; (void)ctx; - if(data->req.exp100 > EXP100_SEND_DATA) { - if(data->req.exp100 == EXP100_FAILED) - return HYPER_POLL_ERROR; + result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen); + if(result) + goto out; - /* still waiting confirmation */ - if(data->hyp.exp100_waker) - hyper_waker_free(data->hyp.exp100_waker); - data->hyp.exp100_waker = hyper_context_waker(ctx); - return HYPER_POLL_PENDING; - } + result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &fillcount, &eos); + if(result) + goto out; - if(data->req.upload_chunky && conn->bits.authneg) { - fillcount = 0; - data->req.upload_chunky = FALSE; - result = CURLE_OK; - } - else { - result = Curl_fillreadbuffer(data, data->set.upload_buffer_size, - &fillcount); - } - if(result) { - data->state.hresult = result; - return HYPER_POLL_ERROR; - } - if(!fillcount) { - if((data->req.keepon & KEEP_SEND_PAUSE) != KEEP_SEND_PAUSE) - /* done! */ - *chunk = NULL; - else { - /* paused, save a waker */ - if(data->hyp.send_body_waker) - hyper_waker_free(data->hyp.send_body_waker); - data->hyp.send_body_waker = hyper_context_waker(ctx); - return HYPER_POLL_PENDING; - } - } - else { - hyper_buf *copy = hyper_buf_copy((uint8_t *)data->state.ulbuf, fillcount); + if(fillcount) { + hyper_buf *copy = hyper_buf_copy((uint8_t *)xfer_ulbuf, fillcount); if(copy) *chunk = copy; else { - data->state.hresult = CURLE_OUT_OF_MEMORY; - return HYPER_POLL_ERROR; + result = CURLE_OUT_OF_MEMORY; + goto out; } /* increasing the writebytecount here is a little premature but we - don't know exactly when the body is sent */ + do not know exactly when the body is sent */ data->req.writebytecount += fillcount; + if(eos) + data->req.eos_read = TRUE; Curl_pgrsSetUploadCounter(data, data->req.writebytecount); + rc = HYPER_POLL_READY; + } + else if(eos) { + data->req.eos_read = TRUE; + *chunk = NULL; + rc = HYPER_POLL_READY; } - return HYPER_POLL_READY; + else { + /* paused, save a waker */ + if(data->hyp.send_body_waker) + hyper_waker_free(data->hyp.send_body_waker); + data->hyp.send_body_waker = hyper_context_waker(ctx); + rc = HYPER_POLL_PENDING; + } + + if(!data->req.upload_done && data->req.eos_read) { + DEBUGF(infof(data, "hyper: uploadstreamed(), upload is done")); + result = Curl_req_set_upload_done(data); + } + +out: + Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf); + data->state.hresult = result; + DEBUGF(infof(data, "hyper: uploadstreamed() -> %d", result)); + return rc; } /* - * bodysend() sets up headers in the outgoing request for an HTTP transfer that - * sends a body + * finalize_request() sets up last headers and optional body settings */ - -static CURLcode bodysend(struct Curl_easy *data, - struct connectdata *conn, - hyper_headers *headers, - hyper_request *hyperreq, - Curl_HttpReq httpreq) +static CURLcode finalize_request(struct Curl_easy *data, + hyper_headers *headers, + hyper_request *hyperreq, + Curl_HttpReq httpreq) { - struct HTTP *http = data->req.p.http; CURLcode result = CURLE_OK; struct dynbuf req; - if((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) + if((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) { Curl_pgrsSetUploadSize(data, 0); /* no request body */ + } else { hyper_body *body; Curl_dyn_init(&req, DYN_HTTP_REQUEST); - result = Curl_http_bodysend(data, conn, &req, httpreq); + result = Curl_http_req_complete(data, &req, httpreq); + if(result) + return result; - if(!result) + /* if the "complete" above did produce more than the closing line, + parse the added headers */ + if(Curl_dyn_len(&req) != 2 || strcmp(Curl_dyn_ptr(&req), "\r\n")) { result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&req)); + if(result) + return result; + } Curl_dyn_free(&req); body = hyper_body_new(); hyper_body_set_userdata(body, data); - if(data->set.postfields) - hyper_body_set_data_func(body, uploadpostfields); - else { - result = Curl_get_upload_buffer(data); - if(result) { - hyper_body_free(body); - return result; - } - /* init the "upload from here" pointer */ - data->req.upload_fromhere = data->state.ulbuf; - hyper_body_set_data_func(body, uploadstreamed); - } + hyper_body_set_data_func(body, uploadstreamed); + if(HYPERE_OK != hyper_request_set_body(hyperreq, body)) { /* fail */ result = CURLE_OUT_OF_MEMORY; } } - http->sending = HTTPSEND_BODY; - return result; + + return cr_hyper_add(data); } static CURLcode cookies(struct Curl_easy *data, @@ -863,7 +796,7 @@ static void http1xx_cb(void *arg, struct hyper_response *resp) if(!result) { headers = hyper_response_headers(resp); if(!headers) { - failf(data, "hyperstream: couldn't get 1xx response headers"); + failf(data, "hyperstream: could not get 1xx response headers"); result = CURLE_RECV_ERROR; } } @@ -903,7 +836,6 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) const char *p_accept; /* Accept: string */ const char *method; Curl_HttpReq httpreq; - bool h2 = FALSE; const char *te = NULL; /* transfer-encoding */ hyper_code rc; @@ -911,51 +843,67 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) may be parts of the request that is not yet sent, since we can deal with the rest of the request in the PERFORM phase. */ *done = TRUE; + result = Curl_client_start(data); + if(result) + goto out; + + /* Add collecting of headers written to client. For a new connection, + * we might have done that already, but reuse + * or multiplex needs it here as well. */ + result = Curl_headers_init(data); + if(result) + goto out; infof(data, "Time for the Hyper dance"); memset(h, 0, sizeof(struct hyptransfer)); result = Curl_http_host(data, conn); if(result) - return result; + goto out; Curl_http_method(data, conn, &method, &httpreq); + DEBUGASSERT(data->req.bytecount == 0); + /* setup the authentication headers */ { char *pq = NULL; if(data->state.up.query) { pq = aprintf("%s?%s", data->state.up.path, data->state.up.query); - if(!pq) - return CURLE_OUT_OF_MEMORY; + if(!pq) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } } result = Curl_http_output_auth(data, conn, method, httpreq, (pq ? pq : data->state.up.path), FALSE); free(pq); if(result) - return result; + goto out; } - result = Curl_http_resume(data, conn, httpreq); + result = Curl_http_req_set_reader(data, httpreq, &te); if(result) - return result; + goto out; result = Curl_http_range(data, httpreq); if(result) - return result; + goto out; result = Curl_http_useragent(data); if(result) - return result; + goto out; io = hyper_io_new(); if(!io) { failf(data, "Couldn't create hyper IO"); result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } /* tell Hyper how to read/write network data */ - hyper_io_set_userdata(io, data); + h->io_ctx.data = data; + h->io_ctx.sockindex = FIRSTSOCKET; + hyper_io_set_userdata(io, &h->io_ctx); hyper_io_set_read(io, Curl_hyper_recv); hyper_io_set_write(io, Curl_hyper_send); @@ -965,7 +913,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) if(!h->exec) { failf(data, "Couldn't create hyper executor"); result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } } @@ -973,11 +921,12 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) if(!options) { failf(data, "Couldn't create hyper client options"); result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } if(conn->alpn == CURL_HTTP_VERSION_2) { - hyper_clientconn_options_http2(options, 1); - h2 = TRUE; + failf(data, "ALPN protocol h2 not supported with Hyper"); + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; } hyper_clientconn_options_set_preserve_header_case(options, 1); hyper_clientconn_options_set_preserve_header_order(options, 1); @@ -990,7 +939,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) if(!handshake) { failf(data, "Couldn't create hyper client handshake"); result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } io = NULL; options = NULL; @@ -998,7 +947,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) if(HYPERE_OK != hyper_executor_push(h->exec, handshake)) { failf(data, "Couldn't hyper_executor_push the handshake"); result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } handshake = NULL; /* ownership passed on */ @@ -1006,7 +955,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) if(!task) { failf(data, "Couldn't hyper_executor_poll the handshake"); result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } client = hyper_task_value(task); @@ -1016,7 +965,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) if(!req) { failf(data, "Couldn't hyper_request_new"); result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } if(!Curl_use_http_1_1plus(data, conn)) { @@ -1024,73 +973,57 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) HYPER_HTTP_VERSION_1_0)) { failf(data, "error setting HTTP version"); result = CURLE_OUT_OF_MEMORY; - goto error; - } - } - else { - if(!h2 && !data->state.disableexpect) { - data->state.expect100header = TRUE; + goto out; } } if(hyper_request_set_method(req, (uint8_t *)method, strlen(method))) { failf(data, "error setting method"); result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } - result = request_target(data, conn, method, h2, req); + result = request_target(data, conn, method, req); if(result) - goto error; + goto out; headers = hyper_request_headers(req); if(!headers) { failf(data, "hyper_request_headers"); result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } rc = hyper_request_on_informational(req, http1xx_cb, data); if(rc) { result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } - result = Curl_http_body(data, conn, httpreq, &te); - if(result) - goto error; - - if(!h2) { - if(data->state.aptr.host) { - result = Curl_hyper_header(data, headers, data->state.aptr.host); - if(result) - goto error; - } - } - else { - /* For HTTP/2, we show the Host: header as if we sent it, to make it look - like for HTTP/1 but it isn't actually sent since :authority is then - used. */ - Curl_debug(data, CURLINFO_HEADER_OUT, data->state.aptr.host, - strlen(data->state.aptr.host)); + if(data->state.aptr.host) { + result = Curl_hyper_header(data, headers, data->state.aptr.host); + if(result) + goto out; } +#ifndef CURL_DISABLE_PROXY if(data->state.aptr.proxyuserpwd) { result = Curl_hyper_header(data, headers, data->state.aptr.proxyuserpwd); if(result) - goto error; + goto out; } +#endif if(data->state.aptr.userpwd) { result = Curl_hyper_header(data, headers, data->state.aptr.userpwd); if(result) - goto error; + goto out; } if((data->state.use_range && data->state.aptr.rangeline)) { result = Curl_hyper_header(data, headers, data->state.aptr.rangeline); if(result) - goto error; + goto out; } if(data->set.str[STRING_USERAGENT] && @@ -1098,20 +1031,20 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) data->state.aptr.uagent) { result = Curl_hyper_header(data, headers, data->state.aptr.uagent); if(result) - goto error; + goto out; } p_accept = Curl_checkheaders(data, - STRCONST("Accept"))?NULL:"Accept: */*\r\n"; + STRCONST("Accept")) ? NULL : "Accept: */*\r\n"; if(p_accept) { result = Curl_hyper_header(data, headers, p_accept); if(result) - goto error; + goto out; } if(te) { result = Curl_hyper_header(data, headers, te); if(result) - goto error; + goto out; } #ifndef CURL_DISABLE_ALTSVC @@ -1120,11 +1053,11 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) conn->conn_to_host.name, conn->conn_to_port); if(!altused) { result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } result = Curl_hyper_header(data, headers, altused); if(result) - goto error; + goto out; free(altused); } #endif @@ -1135,7 +1068,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) !Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection"))) { result = Curl_hyper_header(data, headers, "Proxy-Connection: Keep-Alive"); if(result) - goto error; + goto out; } #endif @@ -1147,17 +1080,17 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) else result = Curl_hyper_header(data, headers, data->state.aptr.ref); if(result) - goto error; + goto out; } #ifdef HAVE_LIBZ /* we only consider transfer-encoding magic if libz support is built-in */ result = Curl_transferencode(data); if(result) - goto error; + goto out; result = Curl_hyper_header(data, headers, data->state.aptr.te); if(result) - goto error; + goto out; #endif if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && @@ -1171,33 +1104,33 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) result = Curl_hyper_header(data, headers, data->state.aptr.accept_encoding); if(result) - goto error; + goto out; } else Curl_safefree(data->state.aptr.accept_encoding); result = cookies(data, conn, headers); if(result) - goto error; + goto out; if(!result && conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS)) result = Curl_ws_request(data, headers); result = Curl_add_timecondition(data, headers); if(result) - goto error; + goto out; result = Curl_add_custom_headers(data, FALSE, headers); if(result) - goto error; + goto out; - result = bodysend(data, conn, headers, req, httpreq); + result = finalize_request(data, headers, req, httpreq); if(result) - goto error; + goto out; Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"\r\n", 2); - if(data->req.upload_chunky && conn->bits.authneg) { + if(data->req.upload_chunky && data->req.authneg) { data->req.upload_chunky = TRUE; } else { @@ -1207,14 +1140,14 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) if(!sendtask) { failf(data, "hyper_clientconn_send"); result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } req = NULL; if(HYPERE_OK != hyper_executor_push(h->exec, sendtask)) { failf(data, "Couldn't hyper_executor_push the send"); result = CURLE_OUT_OF_MEMORY; - goto error; + goto out; } sendtask = NULL; /* ownership passed on */ @@ -1224,36 +1157,34 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) if((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) { /* HTTP GET/HEAD download */ Curl_pgrsSetUploadSize(data, 0); /* nothing */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); + result = Curl_req_set_upload_done(data); + if(result) + goto out; } + + Curl_xfer_setup1(data, CURL_XFER_SENDRECV, -1, TRUE); conn->datastream = Curl_hyper_stream; - if(data->state.expect100header) - /* Timeout count starts now since with Hyper we don't know exactly when - the full request has been sent. */ - data->req.start100 = Curl_now(); /* clear userpwd and proxyuserpwd to avoid reusing old credentials * from reused connections */ Curl_safefree(data->state.aptr.userpwd); +#ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuserpwd); - return CURLE_OK; -error: - DEBUGASSERT(result); - if(io) - hyper_io_free(io); - - if(options) - hyper_clientconn_options_free(options); - - if(handshake) - hyper_task_free(handshake); - - if(client) - hyper_clientconn_free(client); - - if(req) - hyper_request_free(req); +#endif +out: + if(result) { + if(io) + hyper_io_free(io); + if(options) + hyper_clientconn_options_free(options); + if(handshake) + hyper_task_free(handshake); + if(client) + hyper_clientconn_free(client); + if(req) + hyper_request_free(req); + } return result; } @@ -1272,10 +1203,52 @@ void Curl_hyper_done(struct Curl_easy *data) hyper_waker_free(h->write_waker); h->write_waker = NULL; } - if(h->exp100_waker) { - hyper_waker_free(h->exp100_waker); - h->exp100_waker = NULL; + if(h->send_body_waker) { + hyper_waker_free(h->send_body_waker); + h->send_body_waker = NULL; } } +static CURLcode cr_hyper_unpause(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)reader; + if(data->hyp.send_body_waker) { + hyper_waker_wake(data->hyp.send_body_waker); + data->hyp.send_body_waker = NULL; + } + return CURLE_OK; +} + +/* Hyper client reader, handling unpausing */ +static const struct Curl_crtype cr_hyper_protocol = { + "cr-hyper", + Curl_creader_def_init, + Curl_creader_def_read, + Curl_creader_def_close, + Curl_creader_def_needs_rewind, + Curl_creader_def_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + cr_hyper_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct Curl_creader) +}; + +static CURLcode cr_hyper_add(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; + + result = Curl_creader_create(&reader, data, &cr_hyper_protocol, + CURL_CR_PROTOCOL); + if(!result) + result = Curl_creader_add(data, reader); + + if(result && reader) + Curl_creader_free(data, reader); + return result; +} + #endif /* !defined(CURL_DISABLE_HTTP) && defined(USE_HYPER) */ diff --git a/deps/curl/lib/c-hyper.h b/deps/curl/lib/c-hyper.h index 0c7de90b70b..89dd53b8fdb 100644 --- a/deps/curl/lib/c-hyper.h +++ b/deps/curl/lib/c-hyper.h @@ -29,13 +29,18 @@ #include +struct hyp_io_ctx { + struct Curl_easy *data; + int sockindex; +}; + /* per-transfer data for the Hyper backend */ struct hyptransfer { hyper_waker *write_waker; hyper_waker *read_waker; const hyper_executor *exec; - hyper_waker *exp100_waker; hyper_waker *send_body_waker; + struct hyp_io_ctx io_ctx; }; size_t Curl_hyper_recv(void *userp, hyper_context *ctx, @@ -45,7 +50,6 @@ size_t Curl_hyper_send(void *userp, hyper_context *ctx, CURLcode Curl_hyper_stream(struct Curl_easy *data, struct connectdata *conn, int *didwhat, - bool *done, int select_res); CURLcode Curl_hyper_header(struct Curl_easy *data, hyper_headers *headers, diff --git a/deps/curl/lib/cf-h1-proxy.c b/deps/curl/lib/cf-h1-proxy.c index e9bc13d2a89..6b7f9831bcb 100644 --- a/deps/curl/lib/cf-h1-proxy.c +++ b/deps/curl/lib/cf-h1-proxy.c @@ -34,6 +34,7 @@ #include "dynbuf.h" #include "sendf.h" #include "http.h" +#include "http1.h" #include "http_proxy.h" #include "url.h" #include "select.h" @@ -64,14 +65,11 @@ typedef enum { /* struct for HTTP CONNECT tunneling */ struct h1_tunnel_state { - int sockindex; - const char *hostname; - int remote_port; - struct HTTP CONNECT; struct dynbuf rcvbuf; - struct dynbuf req; - size_t nsend; + struct dynbuf request_data; + size_t nsent; size_t headerlines; + struct Curl_chunker ch; enum keeponval { KEEPON_DONE, KEEPON_CONNECT, @@ -94,67 +92,46 @@ static bool tunnel_is_failed(struct h1_tunnel_state *ts) return ts && (ts->tunnel_state == H1_TUNNEL_FAILED); } -static CURLcode tunnel_reinit(struct h1_tunnel_state *ts, - struct connectdata *conn, - struct Curl_easy *data) +static CURLcode tunnel_reinit(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts) { (void)data; + (void)cf; DEBUGASSERT(ts); Curl_dyn_reset(&ts->rcvbuf); - Curl_dyn_reset(&ts->req); + Curl_dyn_reset(&ts->request_data); ts->tunnel_state = H1_TUNNEL_INIT; ts->keepon = KEEPON_CONNECT; ts->cl = 0; ts->close_connection = FALSE; - - if(conn->bits.conn_to_host) - ts->hostname = conn->conn_to_host.name; - else if(ts->sockindex == SECONDARYSOCKET) - ts->hostname = conn->secondaryhostname; - else - ts->hostname = conn->host.name; - - if(ts->sockindex == SECONDARYSOCKET) - ts->remote_port = conn->secondary_port; - else if(conn->bits.conn_to_port) - ts->remote_port = conn->conn_to_port; - else - ts->remote_port = conn->remote_port; - return CURLE_OK; } -static CURLcode tunnel_init(struct h1_tunnel_state **pts, +static CURLcode tunnel_init(struct Curl_cfilter *cf, struct Curl_easy *data, - struct connectdata *conn, - int sockindex) + struct h1_tunnel_state **pts) { struct h1_tunnel_state *ts; - CURLcode result; - if(conn->handler->flags & PROTOPT_NOTCPPROXY) { - failf(data, "%s cannot be done over CONNECT", conn->handler->scheme); + if(cf->conn->handler->flags & PROTOPT_NOTCPPROXY) { + failf(data, "%s cannot be done over CONNECT", cf->conn->handler->scheme); return CURLE_UNSUPPORTED_PROTOCOL; } - /* we might need the upload buffer for streaming a partial request */ - result = Curl_get_upload_buffer(data); - if(result) - return result; - ts = calloc(1, sizeof(*ts)); if(!ts) return CURLE_OUT_OF_MEMORY; - ts->sockindex = sockindex; infof(data, "allocate connect buffer"); Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS); - Curl_dyn_init(&ts->req, DYN_HTTP_REQUEST); + Curl_dyn_init(&ts->request_data, DYN_HTTP_REQUEST); + Curl_httpchunk_init(data, &ts->ch, TRUE); *pts = ts; - connkeep(conn, "HTTP proxy CONNECT"); - return tunnel_reinit(ts, conn, data); + connkeep(cf->conn, "HTTP proxy CONNECT"); + return tunnel_reinit(cf, data, ts); } static void h1_tunnel_go_state(struct Curl_cfilter *cf, @@ -164,19 +141,11 @@ static void h1_tunnel_go_state(struct Curl_cfilter *cf, { if(ts->tunnel_state == new_state) return; - /* leaving this one */ - switch(ts->tunnel_state) { - case H1_TUNNEL_CONNECT: - data->req.ignorebody = FALSE; - break; - default: - break; - } /* entering this one */ switch(new_state) { case H1_TUNNEL_INIT: CURL_TRC_CF(data, cf, "new tunnel state 'init'"); - tunnel_reinit(ts, cf->conn, data); + tunnel_reinit(cf, data, ts); break; case H1_TUNNEL_CONNECT: @@ -201,19 +170,19 @@ static void h1_tunnel_go_state(struct Curl_cfilter *cf, infof(data, "CONNECT phase completed"); data->state.authproxy.done = TRUE; data->state.authproxy.multipass = FALSE; - /* FALLTHROUGH */ + FALLTHROUGH(); case H1_TUNNEL_FAILED: if(new_state == H1_TUNNEL_FAILED) CURL_TRC_CF(data, cf, "new tunnel state 'failed'"); ts->tunnel_state = new_state; Curl_dyn_reset(&ts->rcvbuf); - Curl_dyn_reset(&ts->req); + Curl_dyn_reset(&ts->request_data); /* restore the protocol pointer */ data->info.httpcode = 0; /* clear it as it might've been used for the proxy */ /* If a proxy-authorization header was used for the proxy, then we should - make sure that it isn't accidentally used for the document request - after we've connected. So let's free and clear it here. */ + make sure that it is not accidentally used for the document request + after we have connected. So let's free and clear it here. */ Curl_safefree(data->state.aptr.proxyuserpwd); #ifdef USE_HYPER data->state.hconnect = FALSE; @@ -225,46 +194,22 @@ static void h1_tunnel_go_state(struct Curl_cfilter *cf, static void tunnel_free(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct h1_tunnel_state *ts = cf->ctx; - if(ts) { - h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); - Curl_dyn_free(&ts->rcvbuf); - Curl_dyn_free(&ts->req); - free(ts); - cf->ctx = NULL; + if(cf) { + struct h1_tunnel_state *ts = cf->ctx; + if(ts) { + h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); + Curl_dyn_free(&ts->rcvbuf); + Curl_dyn_free(&ts->request_data); + Curl_httpchunk_free(data, &ts->ch); + free(ts); + cf->ctx = NULL; + } } } -static CURLcode CONNECT_host(struct Curl_easy *data, - struct connectdata *conn, - const char *hostname, - int remote_port, - char **connecthostp, - char **hostp) +static bool tunnel_want_send(struct h1_tunnel_state *ts) { - char *hostheader; /* for CONNECT */ - char *host = NULL; /* Host: */ - bool ipv6_ip = conn->bits.ipv6_ip; - - /* the hostname may be different */ - if(hostname != conn->host.name) - ipv6_ip = (strchr(hostname, ':') != NULL); - hostheader = /* host:port with IPv6 support */ - aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"", - remote_port); - if(!hostheader) - return CURLE_OUT_OF_MEMORY; - - if(!Curl_checkProxyheaders(data, conn, STRCONST("Host"))) { - host = aprintf("Host: %s\r\n", hostheader); - if(!host) { - free(hostheader); - return CURLE_OUT_OF_MEMORY; - } - } - *connecthostp = hostheader; - *hostp = host; - return CURLE_OK; + return (ts->tunnel_state == H1_TUNNEL_CONNECT); } #ifndef USE_HYPER @@ -272,128 +217,71 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, struct Curl_easy *data, struct h1_tunnel_state *ts) { - struct connectdata *conn = cf->conn; - char *hostheader = NULL; - char *host = NULL; - const char *httpv; + struct httpreq *req = NULL; + int http_minor; CURLcode result; - infof(data, "Establish HTTP proxy tunnel to %s:%d", - ts->hostname, ts->remote_port); - - /* This only happens if we've looped here due to authentication - reasons, and we don't really use the newly cloned URL here + /* This only happens if we have looped here due to authentication + reasons, and we do not really use the newly cloned URL here then. Just free() it. */ Curl_safefree(data->req.newurl); - result = CONNECT_host(data, conn, - ts->hostname, ts->remote_port, - &hostheader, &host); - if(result) - goto out; - - /* Setup the proxy-authorization header, if any */ - result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET, - hostheader, TRUE); - if(result) - goto out; - - httpv = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? "1.0" : "1.1"; - - result = - Curl_dyn_addf(&ts->req, - "CONNECT %s HTTP/%s\r\n" - "%s" /* Host: */ - "%s", /* Proxy-Authorization */ - hostheader, - httpv, - host?host:"", - data->state.aptr.proxyuserpwd? - data->state.aptr.proxyuserpwd:""); - if(result) - goto out; - - if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent")) - && data->set.str[STRING_USERAGENT]) - result = Curl_dyn_addf(&ts->req, "User-Agent: %s\r\n", - data->set.str[STRING_USERAGENT]); - if(result) - goto out; - - if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection"))) - result = Curl_dyn_addn(&ts->req, - STRCONST("Proxy-Connection: Keep-Alive\r\n")); + result = Curl_http_proxy_create_CONNECT(&req, cf, data, 1); if(result) goto out; - result = Curl_add_custom_headers(data, TRUE, &ts->req); - if(result) - goto out; - - /* CRLF terminate the request */ - result = Curl_dyn_addn(&ts->req, STRCONST("\r\n")); - if(result) - goto out; + infof(data, "Establish HTTP proxy tunnel to %s", req->authority); - /* Send the connect request to the proxy */ - result = Curl_buffer_send(&ts->req, data, &ts->CONNECT, - &data->info.request_size, 0, - ts->sockindex); + Curl_dyn_reset(&ts->request_data); + ts->nsent = 0; ts->headerlines = 0; + http_minor = (cf->conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? 0 : 1; + + result = Curl_h1_req_write_head(req, http_minor, &ts->request_data); + if(!result) + result = Curl_creader_set_null(data); out: if(result) failf(data, "Failed sending CONNECT to proxy"); - free(host); - free(hostheader); + if(req) + Curl_http_req_free(req); return result; } -static CURLcode send_CONNECT(struct Curl_easy *data, - struct connectdata *conn, +static CURLcode send_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, struct h1_tunnel_state *ts, bool *done) { - struct SingleRequest *k = &data->req; - struct HTTP *http = &ts->CONNECT; + char *buf = Curl_dyn_ptr(&ts->request_data); + size_t request_len = Curl_dyn_len(&ts->request_data); + size_t blen = request_len; CURLcode result = CURLE_OK; + ssize_t nwritten; - if(http->sending != HTTPSEND_REQUEST) - goto out; + if(blen <= ts->nsent) + goto out; /* we are done */ - if(!ts->nsend) { - size_t fillcount; - k->upload_fromhere = data->state.ulbuf; - result = Curl_fillreadbuffer(data, data->set.upload_buffer_size, - &fillcount); - if(result) - goto out; - ts->nsend = fillcount; - } - if(ts->nsend) { - ssize_t bytes_written; - /* write to socket (send away data) */ - result = Curl_write(data, - conn->writesockfd, /* socket to send to */ - k->upload_fromhere, /* buffer pointer */ - ts->nsend, /* buffer size */ - &bytes_written); /* actually sent */ - if(result) - goto out; - /* send to debug callback! */ - Curl_debug(data, CURLINFO_HEADER_OUT, - k->upload_fromhere, bytes_written); + blen -= ts->nsent; + buf += ts->nsent; - ts->nsend -= bytes_written; - k->upload_fromhere += bytes_written; + nwritten = cf->next->cft->do_send(cf->next, data, buf, blen, FALSE, &result); + if(nwritten < 0) { + if(result == CURLE_AGAIN) { + result = CURLE_OK; + } + goto out; } - if(!ts->nsend) - http->sending = HTTPSEND_NADA; + + DEBUGASSERT(blen >= (size_t)nwritten); + ts->nsent += (size_t)nwritten; + Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)nwritten); out: if(result) failf(data, "Failed sending CONNECT to proxy"); - *done = (http->sending != HTTPSEND_REQUEST); + *done = (!result && (ts->nsent >= request_len)); return result; } @@ -411,7 +299,7 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, (checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode))) { - bool proxy = (k->httpcode == 407) ? TRUE : FALSE; + bool proxy = (k->httpcode == 407); char *auth = Curl_copy_header_value(header); if(!auth) return CURLE_OUT_OF_MEMORY; @@ -453,8 +341,8 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, STRCONST("chunked"))) { infof(data, "CONNECT responded chunked"); ts->chunked_encoding = TRUE; - /* init our chunky engine */ - Curl_httpchunk_init(data); + /* reset our chunky engine */ + Curl_httpchunk_reset(data, &ts->ch, TRUE); } } else if(Curl_compareheader(header, @@ -480,10 +368,9 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, { CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; - curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data); char *linep; - size_t perline; - int error; + size_t line_len; + int error, writetype; #define SELECT_OK 0 #define SELECT_ERROR 1 @@ -491,16 +378,16 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, error = SELECT_OK; *done = FALSE; - if(!Curl_conn_data_pending(data, ts->sockindex)) + if(!Curl_conn_data_pending(data, cf->sockindex)) return CURLE_OK; while(ts->keepon) { - ssize_t gotbytes; + ssize_t nread; char byte; /* Read one byte at a time to avoid a race condition. Wait at most one second before looping to ensure continuous pgrsUpdates. */ - result = Curl_read(data, tunnelsocket, &byte, 1, &gotbytes); + result = Curl_conn_recv(data, cf->sockindex, &byte, 1, &nread); if(result == CURLE_AGAIN) /* socket buffer drained, return */ return CURLE_OK; @@ -513,7 +400,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, break; } - if(gotbytes <= 0) { + if(nread <= 0) { if(data->set.proxyauth && data->state.authproxy.avail && data->state.aptr.proxyuserpwd) { /* proxy auth was requested and there was proxy auth available, @@ -534,25 +421,25 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, if(ts->cl) { /* A Content-Length based body: simply count down the counter - and make sure to break out of the loop when we're done! */ + and make sure to break out of the loop when we are done! */ ts->cl--; if(ts->cl <= 0) { ts->keepon = KEEPON_DONE; break; } } - else { + else if(ts->chunked_encoding) { /* chunked-encoded body, so we need to do the chunked dance properly to know when the end of the body is reached */ - CHUNKcode r; - CURLcode extra; - ssize_t tookcareof = 0; + size_t consumed = 0; /* now parse the chunked piece of data so that we can properly tell when the stream ends */ - r = Curl_httpchunk_read(data, &byte, 1, &tookcareof, &extra); - if(r == CHUNKE_STOP) { - /* we're done reading chunks! */ + result = Curl_httpchunk_read(data, &ts->ch, &byte, 1, &consumed); + if(result) + return result; + if(Curl_httpchunk_is_done(data, &ts->ch)) { + /* we are done reading chunks! */ infof(data, "chunk reading DONE"); ts->keepon = KEEPON_DONE; } @@ -571,27 +458,23 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, ts->headerlines++; linep = Curl_dyn_ptr(&ts->rcvbuf); - perline = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */ + line_len = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */ /* output debug if that is requested */ - Curl_debug(data, CURLINFO_HEADER_IN, linep, perline); - - if(!data->set.suppress_connect_headers) { - /* send the header to the callback */ - int writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT | - (data->set.include_header ? CLIENTWRITE_BODY : 0) | - (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0); + Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len); - result = Curl_client_write(data, writetype, linep, perline); - if(result) - return result; - } + /* send the header to the callback */ + writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT | + (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0); + result = Curl_client_write(data, writetype, linep, line_len); + if(result) + return result; - result = Curl_bump_headersize(data, perline, TRUE); + result = Curl_bump_headersize(data, line_len, TRUE); if(result) return result; - /* Newlines are CRLF, so the CR is ignored as the line isn't + /* Newlines are CRLF, so the CR is ignored as the line is not really terminated until the LF comes. Treat a following CR as end-of-headers as well.*/ @@ -606,37 +489,14 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, ts->keepon = KEEPON_IGNORE; if(ts->cl) { - infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T - " bytes of response-body", ts->cl); + infof(data, "Ignore %" FMT_OFF_T " bytes of response-body", ts->cl); } else if(ts->chunked_encoding) { - CHUNKcode r; - CURLcode extra; - infof(data, "Ignore chunked response-body"); - - /* We set ignorebody true here since the chunked decoder - function will acknowledge that. Pay attention so that this is - cleared again when this function returns! */ - k->ignorebody = TRUE; - - if(linep[1] == '\n') - /* this can only be a LF if the letter at index 0 was a CR */ - linep++; - - /* now parse the chunked piece of data so that we can properly - tell when the stream ends */ - r = Curl_httpchunk_read(data, linep + 1, 1, &gotbytes, - &extra); - if(r == CHUNKE_STOP) { - /* we're done reading chunks! */ - infof(data, "chunk reading DONE"); - ts->keepon = KEEPON_DONE; - } } else { /* without content-length or chunked encoding, we - can't keep the connection alive since the close is + cannot keep the connection alive since the close is the end signal so we bail out at once instead */ CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked"); ts->keepon = KEEPON_DONE; @@ -656,7 +516,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, return result; Curl_dyn_reset(&ts->rcvbuf); - } /* while there's buffer left and loop is requested */ + } /* while there is buffer left and loop is requested */ if(error) result = CURLE_RECV_ERROR; @@ -670,6 +530,41 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, } #else /* USE_HYPER */ + +static CURLcode CONNECT_host(struct Curl_cfilter *cf, + struct Curl_easy *data, + char **pauthority, + char **phost_header) +{ + const char *hostname; + int port; + bool ipv6_ip; + CURLcode result; + char *authority; /* for CONNECT, the destination host + port */ + char *host_header = NULL; /* Host: authority */ + + result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip); + if(result) + return result; + + authority = aprintf("%s%s%s:%d", ipv6_ip ? "[":"", hostname, + ipv6_ip ? "]" : "", port); + if(!authority) + return CURLE_OUT_OF_MEMORY; + + /* If user is not overriding the Host header later */ + if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) { + host_header = aprintf("Host: %s\r\n", authority); + if(!host_header) { + free(authority); + return CURLE_OUT_OF_MEMORY; + } + } + *pauthority = authority; + *phost_header = host_header; + return CURLE_OK; +} + /* The Hyper version of CONNECT */ static CURLcode start_CONNECT(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -686,9 +581,10 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, hyper_task *task = NULL; /* for the handshake */ hyper_clientconn *client = NULL; hyper_task *sendtask = NULL; /* for the send */ - char *hostheader = NULL; /* for CONNECT */ - char *host = NULL; /* Host: */ + char *authority = NULL; /* for CONNECT */ + char *host_header = NULL; /* Host: */ CURLcode result = CURLE_OUT_OF_MEMORY; + (void)ts; io = hyper_io_new(); if(!io) { @@ -697,7 +593,9 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, goto error; } /* tell Hyper how to read/write network data */ - hyper_io_set_userdata(io, data); + h->io_ctx.data = data; + h->io_ctx.sockindex = cf->sockindex; + hyper_io_set_userdata(io, &h->io_ctx); hyper_io_set_read(io, Curl_hyper_recv); hyper_io_set_write(io, Curl_hyper_send); conn->sockfd = tunnelsocket; @@ -766,27 +664,25 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, goto error; } - infof(data, "Establish HTTP proxy tunnel to %s:%d", - ts->hostname, ts->remote_port); - - /* This only happens if we've looped here due to authentication - reasons, and we don't really use the newly cloned URL here + /* This only happens if we have looped here due to authentication + reasons, and we do not really use the newly cloned URL here then. Just free() it. */ Curl_safefree(data->req.newurl); - result = CONNECT_host(data, conn, ts->hostname, ts->remote_port, - &hostheader, &host); + result = CONNECT_host(cf, data, &authority, &host_header); if(result) goto error; - if(hyper_request_set_uri(req, (uint8_t *)hostheader, - strlen(hostheader))) { + infof(data, "Establish HTTP proxy tunnel to %s", authority); + + if(hyper_request_set_uri(req, (uint8_t *)authority, + strlen(authority))) { failf(data, "error setting path"); result = CURLE_OUT_OF_MEMORY; goto error; } if(data->set.verbose) { - char *se = aprintf("CONNECT %s HTTP/1.1\r\n", hostheader); + char *se = aprintf("CONNECT %s HTTP/1.1\r\n", authority); if(!se) { result = CURLE_OUT_OF_MEMORY; goto error; @@ -796,10 +692,10 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, } /* Setup the proxy-authorization header, if any */ result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET, - hostheader, TRUE); + authority, TRUE); if(result) goto error; - Curl_safefree(hostheader); + Curl_safefree(authority); /* default is 1.1 */ if((conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) && @@ -816,11 +712,11 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, result = CURLE_OUT_OF_MEMORY; goto error; } - if(host) { - result = Curl_hyper_header(data, headers, host); + if(host_header) { + result = Curl_hyper_header(data, headers, host_header); if(result) goto error; - Curl_safefree(host); + Curl_safefree(host_header); } if(data->state.aptr.proxyuserpwd) { @@ -831,7 +727,7 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, } if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent")) && - data->set.str[STRING_USERAGENT]) { + data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) { struct dynbuf ua; Curl_dyn_init(&ua, DYN_HTTP_REQUEST); result = Curl_dyn_addf(&ua, "User-Agent: %s\r\n", @@ -855,6 +751,10 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, if(result) goto error; + result = Curl_creader_set_null(data); + if(result) + goto error; + sendtask = hyper_clientconn_send(client, req); if(!sendtask) { failf(data, "hyper_clientconn_send"); @@ -874,8 +774,8 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, client = NULL; error: - free(host); - free(hostheader); + free(host_header); + free(authority); if(io) hyper_io_free(io); if(options) @@ -890,12 +790,13 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, return result; } -static CURLcode send_CONNECT(struct Curl_easy *data, - struct connectdata *conn, +static CURLcode send_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, struct h1_tunnel_state *ts, bool *done) { struct hyptransfer *h = &data->hyp; + struct connectdata *conn = cf->conn; hyper_task *task = NULL; hyper_error *hypererr = NULL; CURLcode result = CURLE_OK; @@ -937,9 +838,9 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, int didwhat; (void)ts; - *done = FALSE; - result = Curl_hyper_stream(data, cf->conn, &didwhat, done, + result = Curl_hyper_stream(data, cf->conn, &didwhat, CURL_CSELECT_IN | CURL_CSELECT_OUT); + *done = data->req.done; if(result || !*done) return result; if(h->exec) { @@ -990,16 +891,16 @@ static CURLcode H1_CONNECT(struct Curl_cfilter *cf, if(result) goto out; h1_tunnel_go_state(cf, ts, H1_TUNNEL_CONNECT, data); - /* FALLTHROUGH */ + FALLTHROUGH(); case H1_TUNNEL_CONNECT: /* see that the request is completely sent */ CURL_TRC_CF(data, cf, "CONNECT send"); - result = send_CONNECT(data, cf->conn, ts, &done); + result = send_CONNECT(cf, data, ts, &done); if(result || !done) goto out; h1_tunnel_go_state(cf, ts, H1_TUNNEL_RECEIVE, data); - /* FALLTHROUGH */ + FALLTHROUGH(); case H1_TUNNEL_RECEIVE: /* read what is there */ @@ -1014,7 +915,7 @@ static CURLcode H1_CONNECT(struct Curl_cfilter *cf, goto out; /* got it */ h1_tunnel_go_state(cf, ts, H1_TUNNEL_RESPONSE, data); - /* FALLTHROUGH */ + FALLTHROUGH(); case H1_TUNNEL_RESPONSE: CURL_TRC_CF(data, cf, "CONNECT response"); @@ -1023,6 +924,7 @@ static CURLcode H1_CONNECT(struct Curl_cfilter *cf, * If the other side indicated a connection close, or if someone * else told us to close this connection, do so now. */ + Curl_req_soft_reset(&data->req, data); if(ts->close_connection || conn->bits.close) { /* Close this filter and the sub-chain, re-connect the * sub-chain and continue. Closing this filter will @@ -1051,7 +953,7 @@ static CURLcode H1_CONNECT(struct Curl_cfilter *cf, DEBUGASSERT(ts->tunnel_state == H1_TUNNEL_RESPONSE); if(data->info.httpproxycode/100 != 2) { - /* a non-2xx response and we have no next url to try. */ + /* a non-2xx response and we have no next URL to try. */ Curl_safefree(data->req.newurl); /* failure, close this connection to avoid reuse */ streamclose(conn, "proxy CONNECT failure"); @@ -1090,7 +992,7 @@ static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf, *done = FALSE; if(!ts) { - result = tunnel_init(&ts, data, cf->conn, cf->sockindex); + result = tunnel_init(cf, data, &ts); if(result) return result; cf->ctx = ts; @@ -1108,36 +1010,40 @@ static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf, *done = (result == CURLE_OK) && tunnel_is_established(cf->ctx); if(*done) { cf->connected = TRUE; + /* The real request will follow the CONNECT, reset request partially */ + Curl_req_soft_reset(&data->req, data); + Curl_client_reset(data); + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + tunnel_free(cf, data); } return result; } -static int cf_h1_proxy_get_select_socks(struct Curl_cfilter *cf, +static void cf_h1_proxy_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) + struct easy_pollset *ps) { struct h1_tunnel_state *ts = cf->ctx; - int fds; - fds = cf->next->cft->get_select_socks(cf->next, data, socks); - if(!fds && cf->next->connected && !cf->connected) { + if(!cf->connected) { /* If we are not connected, but the filter "below" is * and not waiting on something, we are tunneling. */ - socks[0] = Curl_conn_cf_get_socket(cf, data); + curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); if(ts) { - /* when we've sent a CONNECT to a proxy, we should rather either + /* when we have sent a CONNECT to a proxy, we should rather either wait for the socket to become readable to be able to get the - response headers or if we're still sending the request, wait + response headers or if we are still sending the request, wait for write. */ - if(ts->CONNECT.sending == HTTPSEND_REQUEST) { - return GETSOCK_WRITESOCK(0); - } - return GETSOCK_READSOCK(0); + if(tunnel_want_send(ts)) + Curl_pollset_set_out_only(data, ps, sock); + else + Curl_pollset_set_in_only(data, ps, sock); } - return GETSOCK_WRITESOCK(0); + else + Curl_pollset_set_out_only(data, ps, sock); } - return fds; } static void cf_h1_proxy_destroy(struct Curl_cfilter *cf, @@ -1151,24 +1057,27 @@ static void cf_h1_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data) { CURL_TRC_CF(data, cf, "close"); - cf->connected = FALSE; - if(cf->ctx) { - h1_tunnel_go_state(cf, cf->ctx, H1_TUNNEL_INIT, data); + if(cf) { + cf->connected = FALSE; + if(cf->ctx) { + h1_tunnel_go_state(cf, cf->ctx, H1_TUNNEL_INIT, data); + } + if(cf->next) + cf->next->cft->do_close(cf->next, data); } - if(cf->next) - cf->next->cft->do_close(cf->next, data); } struct Curl_cftype Curl_cft_h1_proxy = { "H1-PROXY", - CF_TYPE_IP_CONNECT, + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, 0, cf_h1_proxy_destroy, cf_h1_proxy_connect, cf_h1_proxy_close, + Curl_cf_def_shutdown, Curl_cf_http_proxy_get_host, - cf_h1_proxy_get_select_socks, + cf_h1_proxy_adjust_pollset, Curl_cf_def_data_pending, Curl_cf_def_send, Curl_cf_def_recv, diff --git a/deps/curl/lib/cf-h2-proxy.c b/deps/curl/lib/cf-h2-proxy.c index a3feefca072..038952d6417 100644 --- a/deps/curl/lib/cf-h2-proxy.c +++ b/deps/curl/lib/cf-h2-proxy.c @@ -38,6 +38,7 @@ #include "http2.h" #include "http_proxy.h" #include "multiif.h" +#include "sendf.h" #include "cf-h2-proxy.h" /* The last 3 #include files should be in this order */ @@ -72,7 +73,6 @@ struct tunnel_stream { char *authority; int32_t stream_id; uint32_t error; - size_t upload_blocked_len; h2_tunnel_state state; BIT(has_final_response); BIT(closed); @@ -84,7 +84,8 @@ static CURLcode tunnel_stream_init(struct Curl_cfilter *cf, { const char *hostname; int port; - bool ipv6_ip = cf->conn->bits.ipv6_ip; + bool ipv6_ip; + CURLcode result; ts->state = H2_TUNNEL_INIT; ts->stream_id = -1; @@ -92,25 +93,13 @@ static CURLcode tunnel_stream_init(struct Curl_cfilter *cf, BUFQ_OPT_SOFT_LIMIT); Curl_bufq_init(&ts->sendbuf, PROXY_H2_CHUNK_SIZE, H2_TUNNEL_SEND_CHUNKS); - if(cf->conn->bits.conn_to_host) - hostname = cf->conn->conn_to_host.name; - else if(cf->sockindex == SECONDARYSOCKET) - hostname = cf->conn->secondaryhostname; - else - hostname = cf->conn->host.name; - - if(cf->sockindex == SECONDARYSOCKET) - port = cf->conn->secondary_port; - else if(cf->conn->bits.conn_to_port) - port = cf->conn->conn_to_port; - else - port = cf->conn->remote_port; - - if(hostname != cf->conn->host.name) - ipv6_ip = (strchr(hostname, ':') != NULL); + result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip); + if(result) + return result; ts->authority = /* host:port with IPv6 support */ - aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"", port); + aprintf("%s%s%s:%d", ipv6_ip ? "[":"", hostname, + ipv6_ip ? "]" : "", port); if(!ts->authority) return CURLE_OUT_OF_MEMORY; @@ -167,14 +156,14 @@ static void h2_tunnel_go_state(struct Curl_cfilter *cf, infof(data, "CONNECT phase completed"); data->state.authproxy.done = TRUE; data->state.authproxy.multipass = FALSE; - /* FALLTHROUGH */ + FALLTHROUGH(); case H2_TUNNEL_FAILED: if(new_state == H2_TUNNEL_FAILED) CURL_TRC_CF(data, cf, "[%d] new tunnel state 'failed'", ts->stream_id); ts->state = new_state; /* If a proxy-authorization header was used for the proxy, then we should - make sure that it isn't accidentally used for the document request - after we've connected. So let's free and clear it here. */ + make sure that it is not accidentally used for the document request + after we have connected. So let's free and clear it here. */ Curl_safefree(data->state.aptr.proxyuserpwd); break; } @@ -192,7 +181,8 @@ struct cf_h2_proxy_ctx { int32_t goaway_error; int32_t last_stream_id; BIT(conn_closed); - BIT(goaway); + BIT(rcvd_goaway); + BIT(sent_goaway); BIT(nw_out_blocked); }; @@ -227,16 +217,18 @@ static void drain_tunnel(struct Curl_cfilter *cf, struct Curl_easy *data, struct tunnel_stream *tunnel) { + struct cf_h2_proxy_ctx *ctx = cf->ctx; unsigned char bits; (void)cf; bits = CURL_CSELECT_IN; - if(!tunnel->closed && !tunnel->reset && tunnel->upload_blocked_len) + if(!tunnel->closed && !tunnel->reset && + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits || 1) { - CURL_TRC_CF(data, cf, "[%d] DRAIN dselect_bits=%x", + if(data->state.select_bits != bits) { + CURL_TRC_CF(data, cf, "[%d] DRAIN select_bits=%x", tunnel->stream_id, bits); - data->state.dselect_bits = bits; + data->state.select_bits = bits; Curl_expire(data, 0, EXPIRE_RUN_NOW); } } @@ -270,7 +262,7 @@ static ssize_t proxy_h2_nw_out_writer(void *writer_ctx, if(cf) { struct Curl_easy *data = CF_DATA_CURRENT(cf); nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf, buflen, - err); + FALSE, err); CURL_TRC_CF(data, cf, "[0] nw_out_writer(len=%zu) -> %zd, %d", buflen, nwritten, *err); } @@ -309,8 +301,9 @@ static int proxy_h2_on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp); #ifndef CURL_DISABLE_VERBOSE_STRINGS -static int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, - void *userp); +static int proxy_h2_on_frame_send(nghttp2_session *session, + const nghttp2_frame *frame, + void *userp); #endif static int proxy_h2_on_stream_close(nghttp2_session *session, int32_t stream_id, @@ -355,7 +348,8 @@ static CURLcode cf_h2_proxy_ctx_init(struct Curl_cfilter *cf, nghttp2_session_callbacks_set_on_frame_recv_callback( cbs, proxy_h2_on_frame_recv); #ifndef CURL_DISABLE_VERBOSE_STRINGS - nghttp2_session_callbacks_set_on_frame_send_callback(cbs, on_frame_send); + nghttp2_session_callbacks_set_on_frame_send_callback(cbs, + proxy_h2_on_frame_send); #endif nghttp2_session_callbacks_set_on_data_chunk_recv_callback( cbs, tunnel_recv_callback); @@ -436,7 +430,7 @@ static CURLcode proxy_h2_nw_out_flush(struct Curl_cfilter *cf, return result; } CURL_TRC_CF(data, cf, "[0] nw send buffer flushed"); - return Curl_bufq_is_empty(&ctx->outbufq)? CURLE_OK: CURLE_AGAIN; + return Curl_bufq_is_empty(&ctx->outbufq) ? CURLE_OK : CURLE_AGAIN; } /* @@ -575,7 +569,8 @@ static ssize_t on_session_send(nghttp2_session *h2, } #ifndef CURL_DISABLE_VERBOSE_STRINGS -static int fr_print(const nghttp2_frame *frame, char *buffer, size_t blen) +static int proxy_h2_fr_print(const nghttp2_frame *frame, + char *buffer, size_t blen) { switch(frame->hd.type) { case NGHTTP2_DATA: { @@ -610,29 +605,27 @@ static int fr_print(const nghttp2_frame *frame, char *buffer, size_t blen) return msnprintf(buffer, blen, "FRAME[SETTINGS, len=%d]", (int)frame->hd.length); } - case NGHTTP2_PUSH_PROMISE: { + case NGHTTP2_PUSH_PROMISE: return msnprintf(buffer, blen, "FRAME[PUSH_PROMISE, len=%d, hend=%d]", (int)frame->hd.length, !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS)); - } - case NGHTTP2_PING: { + case NGHTTP2_PING: return msnprintf(buffer, blen, "FRAME[PING, len=%d, ack=%d]", (int)frame->hd.length, - frame->hd.flags&NGHTTP2_FLAG_ACK); - } + frame->hd.flags & NGHTTP2_FLAG_ACK); case NGHTTP2_GOAWAY: { char scratch[128]; size_t s_len = sizeof(scratch)/sizeof(scratch[0]); - size_t len = (frame->goaway.opaque_data_len < s_len)? - frame->goaway.opaque_data_len : s_len-1; - if(len) - memcpy(scratch, frame->goaway.opaque_data, len); - scratch[len] = '\0'; - return msnprintf(buffer, blen, "FRAME[GOAWAY, error=%d, reason='%s', " - "last_stream=%d]", frame->goaway.error_code, - scratch, frame->goaway.last_stream_id); + size_t len = (frame->goaway.opaque_data_len < s_len) ? + frame->goaway.opaque_data_len : s_len-1; + if(len) + memcpy(scratch, frame->goaway.opaque_data, len); + scratch[len] = '\0'; + return msnprintf(buffer, blen, "FRAME[GOAWAY, error=%d, reason='%s', " + "last_stream=%d]", frame->goaway.error_code, + scratch, frame->goaway.last_stream_id); } case NGHTTP2_WINDOW_UPDATE: { return msnprintf(buffer, blen, @@ -646,8 +639,9 @@ static int fr_print(const nghttp2_frame *frame, char *buffer, size_t blen) } } -static int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, - void *userp) +static int proxy_h2_on_frame_send(nghttp2_session *session, + const nghttp2_frame *frame, + void *userp) { struct Curl_cfilter *cf = userp; struct Curl_easy *data = CF_DATA_CURRENT(cf); @@ -657,7 +651,7 @@ static int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, if(data && Curl_trc_cf_is_verbose(cf, data)) { char buffer[256]; int len; - len = fr_print(frame, buffer, sizeof(buffer)-1); + len = proxy_h2_fr_print(frame, buffer, sizeof(buffer)-1); buffer[len] = 0; CURL_TRC_CF(data, cf, "[%d] -> %s", frame->hd.stream_id, buffer); } @@ -680,7 +674,7 @@ static int proxy_h2_on_frame_recv(nghttp2_session *session, if(Curl_trc_cf_is_verbose(cf, data)) { char buffer[256]; int len; - len = fr_print(frame, buffer, sizeof(buffer)-1); + len = proxy_h2_fr_print(frame, buffer, sizeof(buffer)-1); buffer[len] = 0; CURL_TRC_CF(data, cf, "[%d] <- %s",frame->hd.stream_id, buffer); } @@ -696,16 +690,12 @@ static int proxy_h2_on_frame_recv(nghttp2_session *session, * window and *assume* that we treat this like a WINDOW_UPDATE. Some * servers send an explicit WINDOW_UPDATE, but not all seem to do that. * To be safe, we UNHOLD a stream in order not to stall. */ - if((data->req.keepon & KEEP_SEND_HOLD) && - (data->req.keepon & KEEP_SEND)) { - data->req.keepon &= ~KEEP_SEND_HOLD; + if(CURL_WANT_SEND(data)) { drain_tunnel(cf, data, &ctx->tunnel); - CURL_TRC_CF(data, cf, "[%d] un-holding after SETTINGS", - stream_id); } break; case NGHTTP2_GOAWAY: - ctx->goaway = TRUE; + ctx->rcvd_goaway = TRUE; break; default: break; @@ -735,12 +725,8 @@ static int proxy_h2_on_frame_recv(nghttp2_session *session, } break; case NGHTTP2_WINDOW_UPDATE: - if((data->req.keepon & KEEP_SEND_HOLD) && - (data->req.keepon & KEEP_SEND)) { - data->req.keepon &= ~KEEP_SEND_HOLD; - Curl_expire(data, 0, EXPIRE_RUN_NOW); - CURL_TRC_CF(data, cf, "[%d] unpausing after win update", - stream_id); + if(CURL_WANT_SEND(data)) { + drain_tunnel(cf, data, &ctx->tunnel); } break; default: @@ -917,7 +903,6 @@ static CURLcode proxy_h2_submit(int32_t *pstream_id, { struct dynhds h2_headers; nghttp2_nv *nva = NULL; - unsigned int i; int32_t stream_id = -1; size_t nheader; CURLcode result; @@ -928,22 +913,12 @@ static CURLcode proxy_h2_submit(int32_t *pstream_id, if(result) goto out; - nheader = Curl_dynhds_count(&h2_headers); - nva = malloc(sizeof(nghttp2_nv) * nheader); + nva = Curl_dynhds_to_nva(&h2_headers, &nheader); if(!nva) { result = CURLE_OUT_OF_MEMORY; goto out; } - for(i = 0; i < nheader; ++i) { - struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i); - nva[i].name = (unsigned char *)e->name; - nva[i].namelen = e->namelen; - nva[i].value = (unsigned char *)e->value; - nva[i].valuelen = e->valuelen; - nva[i].flags = NGHTTP2_NV_FLAG_NONE; - } - if(read_callback) { nghttp2_data_provider data_prd; @@ -980,38 +955,14 @@ static CURLcode submit_CONNECT(struct Curl_cfilter *cf, CURLcode result; struct httpreq *req = NULL; - infof(data, "Establish HTTP/2 proxy tunnel to %s", ts->authority); - - result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT")-1, - NULL, 0, ts->authority, strlen(ts->authority), - NULL, 0); + result = Curl_http_proxy_create_CONNECT(&req, cf, data, 2); if(result) goto out; - - /* Setup the proxy-authorization header, if any */ - result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET, - req->authority, TRUE); + result = Curl_creader_set_null(data); if(result) goto out; - if(data->state.aptr.proxyuserpwd) { - result = Curl_dynhds_h1_cadd_line(&req->headers, - data->state.aptr.proxyuserpwd); - if(result) - goto out; - } - - if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) - && data->set.str[STRING_USERAGENT]) { - result = Curl_dynhds_cadd(&req->headers, "User-Agent", - data->set.str[STRING_USERAGENT]); - if(result) - goto out; - } - - result = Curl_dynhds_add_custom(data, TRUE, &req->headers); - if(result) - goto out; + infof(data, "Establish HTTP/2 proxy tunnel to %s", req->authority); result = proxy_h2_submit(&ts->stream_id, cf, data, ctx->h2, req, NULL, ts, tunnel_send_callback, cf); @@ -1058,7 +1009,7 @@ static CURLcode inspect_response(struct Curl_cfilter *cf, if(result) return result; if(data->req.newurl) { - /* Inidicator that we should try again */ + /* Indicator that we should try again */ Curl_safefree(data->req.newurl); h2_tunnel_go_state(cf, ts, H2_TUNNEL_INIT, data); return CURLE_OK; @@ -1087,7 +1038,7 @@ static CURLcode H2_CONNECT(struct Curl_cfilter *cf, if(result) goto out; h2_tunnel_go_state(cf, ts, H2_TUNNEL_CONNECT, data); - /* FALLTHROUGH */ + FALLTHROUGH(); case H2_TUNNEL_CONNECT: /* see that the request is completely sent */ @@ -1106,7 +1057,7 @@ static CURLcode H2_CONNECT(struct Curl_cfilter *cf, result = CURLE_OK; goto out; } - /* FALLTHROUGH */ + FALLTHROUGH(); case H2_TUNNEL_RESPONSE: DEBUGASSERT(ts->has_final_response); @@ -1128,7 +1079,7 @@ static CURLcode H2_CONNECT(struct Curl_cfilter *cf, } while(ts->state == H2_TUNNEL_INIT); out: - if(result || ctx->tunnel.closed) + if((result && (result != CURLE_AGAIN)) || ctx->tunnel.closed) h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data); return result; } @@ -1179,7 +1130,12 @@ static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf, out: *done = (result == CURLE_OK) && (ts->state == H2_TUNNEL_ESTABLISHED); - cf->connected = *done; + if(*done) { + cf->connected = TRUE; + /* The real request will follow the CONNECT, reset request partially */ + Curl_req_soft_reset(&data->req, data); + Curl_client_reset(data); + } CF_DATA_RESTORE(cf, save); return result; } @@ -1195,6 +1151,8 @@ static void cf_h2_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data) cf_h2_proxy_ctx_clear(ctx); CF_DATA_RESTORE(cf, save); } + if(cf->next) + cf->next->cft->do_close(cf->next, data); } static void cf_h2_proxy_destroy(struct Curl_cfilter *cf, @@ -1209,6 +1167,50 @@ static void cf_h2_proxy_destroy(struct Curl_cfilter *cf, } } +static CURLcode cf_h2_proxy_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + struct cf_call_data save; + CURLcode result; + int rv; + + if(!cf->connected || !ctx->h2 || cf->shutdown || ctx->conn_closed) { + *done = TRUE; + return CURLE_OK; + } + + CF_DATA_SAVE(save, cf, data); + + if(!ctx->sent_goaway) { + rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE, + 0, 0, + (const uint8_t *)"shutdown", + sizeof("shutdown")); + if(rv) { + failf(data, "nghttp2_submit_goaway() failed: %s(%d)", + nghttp2_strerror(rv), rv); + result = CURLE_SEND_ERROR; + goto out; + } + ctx->sent_goaway = TRUE; + } + /* GOAWAY submitted, process egress and ingress until nghttp2 is done. */ + result = CURLE_OK; + if(nghttp2_session_want_write(ctx->h2)) + result = proxy_h2_progress_egress(cf, data); + if(!result && nghttp2_session_want_read(ctx->h2)) + result = proxy_h2_progress_ingress(cf, data); + + *done = (ctx->conn_closed || + (!result && !nghttp2_session_want_write(ctx->h2) && + !nghttp2_session_want_read(ctx->h2))); +out: + CF_DATA_RESTORE(cf, save); + cf->shutdown = (result || *done); + return result; +} + static bool cf_h2_proxy_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { @@ -1217,28 +1219,58 @@ static bool cf_h2_proxy_data_pending(struct Curl_cfilter *cf, (ctx && ctx->tunnel.state == H2_TUNNEL_ESTABLISHED && !Curl_bufq_is_empty(&ctx->tunnel.recvbuf))) return TRUE; - return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE; + return cf->next ? cf->next->cft->has_data_pending(cf->next, data) : FALSE; } -static int cf_h2_proxy_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *sock) +static void cf_h2_proxy_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) { struct cf_h2_proxy_ctx *ctx = cf->ctx; - int bitmap = GETSOCK_BLANK; struct cf_call_data save; + curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); + bool want_recv, want_send; - CF_DATA_SAVE(save, cf, data); - sock[0] = Curl_conn_cf_get_socket(cf, data); - bitmap |= GETSOCK_READSOCK(0); + if(!cf->connected && ctx->h2) { + want_send = nghttp2_session_want_write(ctx->h2) || + !Curl_bufq_is_empty(&ctx->outbufq) || + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf); + want_recv = nghttp2_session_want_read(ctx->h2); + } + else + Curl_pollset_check(data, ps, sock, &want_recv, &want_send); - /* HTTP/2 layer wants to send data) AND there's a window to send data in */ - if(nghttp2_session_want_write(ctx->h2) && - nghttp2_session_get_remote_window_size(ctx->h2)) - bitmap |= GETSOCK_WRITESOCK(0); + if(ctx->h2 && (want_recv || want_send)) { + bool c_exhaust, s_exhaust; - CF_DATA_RESTORE(cf, save); - return bitmap; + CF_DATA_SAVE(save, cf, data); + c_exhaust = !nghttp2_session_get_remote_window_size(ctx->h2); + s_exhaust = ctx->tunnel.stream_id >= 0 && + !nghttp2_session_get_stream_remote_window_size( + ctx->h2, ctx->tunnel.stream_id); + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + (!c_exhaust && nghttp2_session_want_write(ctx->h2)) || + !Curl_bufq_is_empty(&ctx->outbufq) || + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf); + + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CURL_TRC_CF(data, cf, "adjust_pollset, want_recv=%d want_send=%d", + want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } + else if(ctx->sent_goaway && !cf->shutdown) { + /* shutdown in progress */ + CF_DATA_SAVE(save, cf, data); + want_send = nghttp2_session_want_write(ctx->h2) || + !Curl_bufq_is_empty(&ctx->outbufq) || + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf); + want_recv = nghttp2_session_want_read(ctx->h2); + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CURL_TRC_CF(data, cf, "adjust_pollset, want_recv=%d want_send=%d", + want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } } static ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf, @@ -1251,7 +1283,7 @@ static ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf, if(ctx->tunnel.error == NGHTTP2_REFUSED_STREAM) { CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new " "connection", ctx->tunnel.stream_id); - connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */ + connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */ *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ return -1; } @@ -1296,7 +1328,8 @@ static ssize_t tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } else if(ctx->tunnel.reset || (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) || - (ctx->goaway && ctx->last_stream_id < ctx->tunnel.stream_id)) { + (ctx->rcvd_goaway && + ctx->last_stream_id < ctx->tunnel.stream_id)) { *err = CURLE_RECV_ERROR; nread = -1; } @@ -1342,16 +1375,7 @@ static ssize_t cf_h2_proxy_recv(struct Curl_cfilter *cf, } result = proxy_h2_progress_egress(cf, data); - if(result == CURLE_AGAIN) { - /* pending data to send, need to be called again. Ideally, we'd - * monitor the socket for POLLOUT, but we might not be in SENDING - * transfer state any longer and are unable to make this happen. - */ - CURL_TRC_CF(data, cf, "[%d] egress blocked, DRAIN", - ctx->tunnel.stream_id); - drain_tunnel(cf, data, &ctx->tunnel); - } - else if(result) { + if(result && (result != CURLE_AGAIN)) { *err = result; nread = -1; } @@ -1371,15 +1395,16 @@ static ssize_t cf_h2_proxy_recv(struct Curl_cfilter *cf, static ssize_t cf_h2_proxy_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { struct cf_h2_proxy_ctx *ctx = cf->ctx; struct cf_call_data save; int rv; ssize_t nwritten; CURLcode result; - int blocked = 0; + (void)eos; /* TODO, maybe useful for blocks? */ if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) { *err = CURLE_SEND_ERROR; return -1; @@ -1391,29 +1416,10 @@ static ssize_t cf_h2_proxy_send(struct Curl_cfilter *cf, *err = CURLE_SEND_ERROR; goto out; } - else if(ctx->tunnel.upload_blocked_len) { - /* the data in `buf` has already been submitted or added to the - * buffers, but have been EAGAINed on the last invocation. */ - DEBUGASSERT(len >= ctx->tunnel.upload_blocked_len); - if(len < ctx->tunnel.upload_blocked_len) { - /* Did we get called again with a smaller `len`? This should not - * happen. We are not prepared to handle that. */ - failf(data, "HTTP/2 proxy, send again with decreased length"); - *err = CURLE_HTTP2; - nwritten = -1; - goto out; - } - nwritten = (ssize_t)ctx->tunnel.upload_blocked_len; - ctx->tunnel.upload_blocked_len = 0; - *err = CURLE_OK; - } else { nwritten = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, err); - if(nwritten < 0) { - if(*err != CURLE_AGAIN) - goto out; - nwritten = 0; - } + if(nwritten < 0 && (*err != CURLE_AGAIN)) + goto out; } if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) { @@ -1436,52 +1442,13 @@ static ssize_t cf_h2_proxy_send(struct Curl_cfilter *cf, /* Call the nghttp2 send loop and flush to write ALL buffered data, * headers and/or request body completely out to the network */ result = proxy_h2_progress_egress(cf, data); - if(result == CURLE_AGAIN) { - blocked = 1; - } - else if(result) { + if(result && (result != CURLE_AGAIN)) { *err = result; nwritten = -1; goto out; } - else if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) { - /* although we wrote everything that nghttp2 wants to send now, - * there is data left in our stream send buffer unwritten. This may - * be due to the stream's HTTP/2 flow window being exhausted. */ - blocked = 1; - } - - if(blocked) { - /* Unable to send all data, due to connection blocked or H2 window - * exhaustion. Data is left in our stream buffer, or nghttp2's internal - * frame buffer or our network out buffer. */ - size_t rwin = nghttp2_session_get_stream_remote_window_size( - ctx->h2, ctx->tunnel.stream_id); - if(rwin == 0) { - /* H2 flow window exhaustion. - * FIXME: there is no way to HOLD all transfers that use this - * proxy connection AND to UNHOLD all of them again when the - * window increases. - * We *could* iterate over all data on this conn maybe? */ - CURL_TRC_CF(data, cf, "[%d] remote flow " - "window is exhausted", ctx->tunnel.stream_id); - } - /* Whatever the cause, we need to return CURL_EAGAIN for this call. - * We have unwritten state that needs us being invoked again and EAGAIN - * is the only way to ensure that. */ - ctx->tunnel.upload_blocked_len = nwritten; - CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) BLOCK: win %u/%zu " - "blocked_len=%zu", - ctx->tunnel.stream_id, len, - nghttp2_session_get_remote_window_size(ctx->h2), rwin, - nwritten); - drain_tunnel(cf, data, &ctx->tunnel); - *err = CURLE_AGAIN; - nwritten = -1; - goto out; - } - else if(proxy_h2_should_close_session(ctx)) { + if(proxy_h2_should_close_session(ctx)) { /* nghttp2 thinks this session is done. If the stream has not been * closed, this is an error state for out transfer */ if(ctx->tunnel.closed) { @@ -1514,6 +1481,38 @@ static ssize_t cf_h2_proxy_send(struct Curl_cfilter *cf, return nwritten; } +static CURLcode cf_h2_proxy_flush(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + struct cf_call_data save; + CURLcode result = CURLE_OK; + + CF_DATA_SAVE(save, cf, data); + if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) { + /* resume the potentially suspended tunnel */ + int rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id); + if(nghttp2_is_fatal(rv)) { + result = CURLE_SEND_ERROR; + goto out; + } + } + + result = proxy_h2_progress_egress(cf, data); + +out: + CURL_TRC_CF(data, cf, "[%d] flush -> %d, " + "h2 windows %d-%d (stream-conn), buffers %zu-%zu (stream-conn)", + ctx->tunnel.stream_id, result, + nghttp2_session_get_stream_remote_window_size( + ctx->h2, ctx->tunnel.stream_id), + nghttp2_session_get_remote_window_size(ctx->h2), + Curl_bufq_len(&ctx->tunnel.sendbuf), + Curl_bufq_len(&ctx->outbufq)); + CF_DATA_RESTORE(cf, save); + return result; +} + static bool proxy_h2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data, bool *input_pending) @@ -1526,8 +1525,8 @@ static bool proxy_h2_connisalive(struct Curl_cfilter *cf, return FALSE; if(*input_pending) { - /* This happens before we've sent off a request and the connection is - not in use by any other transfer, there shouldn't be any data here, + /* This happens before we have sent off a request and the connection is + not in use by any other transfer, there should not be any data here, only "protocol frames" */ CURLcode result; ssize_t nread = -1; @@ -1567,22 +1566,69 @@ static bool cf_h2_proxy_is_alive(struct Curl_cfilter *cf, return result; } +static CURLcode cf_h2_proxy_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + + switch(query) { + case CF_QUERY_NEED_FLUSH: { + if(!Curl_bufq_is_empty(&ctx->outbufq) || + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) { + CURL_TRC_CF(data, cf, "needs flush"); + *pres1 = TRUE; + return CURLE_OK; + } + break; + } + default: + break; + } + return cf->next ? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +static CURLcode cf_h2_proxy_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + CURLcode result = CURLE_OK; + struct cf_call_data save; + + (void)arg1; + (void)arg2; + + switch(event) { + case CF_CTRL_FLUSH: + CF_DATA_SAVE(save, cf, data); + result = cf_h2_proxy_flush(cf, data); + CF_DATA_RESTORE(cf, save); + break; + default: + break; + } + return result; +} + struct Curl_cftype Curl_cft_h2_proxy = { "H2-PROXY", - CF_TYPE_IP_CONNECT, + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, CURL_LOG_LVL_NONE, cf_h2_proxy_destroy, cf_h2_proxy_connect, cf_h2_proxy_close, + cf_h2_proxy_shutdown, Curl_cf_http_proxy_get_host, - cf_h2_proxy_get_select_socks, + cf_h2_proxy_adjust_pollset, cf_h2_proxy_data_pending, cf_h2_proxy_send, cf_h2_proxy_recv, - Curl_cf_def_cntrl, + cf_h2_proxy_cntrl, cf_h2_proxy_is_alive, Curl_cf_def_conn_keep_alive, - Curl_cf_def_query, + cf_h2_proxy_query, }; CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, @@ -1593,7 +1639,7 @@ CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, CURLcode result = CURLE_OUT_OF_MEMORY; (void)data; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) goto out; diff --git a/deps/curl/lib/cf-haproxy.c b/deps/curl/lib/cf-haproxy.c index 39ac4157175..ae2402f2241 100644 --- a/deps/curl/lib/cf-haproxy.c +++ b/deps/curl/lib/cf-haproxy.c @@ -70,8 +70,9 @@ static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf, { struct cf_haproxy_ctx *ctx = cf->ctx; CURLcode result; - const char *tcp_version; const char *client_ip; + struct ip_quadruple ipquad; + int is_ipv6; DEBUGASSERT(ctx); DEBUGASSERT(ctx->state == HAPROXY_INIT); @@ -81,19 +82,20 @@ static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf, result = Curl_dyn_addn(&ctx->data_out, STRCONST("PROXY UNKNOWN\r\n")); else { #endif /* USE_UNIX_SOCKETS */ + result = Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad); + if(result) + return result; + /* Emit the correct prefix for IPv6 */ - tcp_version = cf->conn->bits.ipv6 ? "TCP6" : "TCP4"; if(data->set.str[STRING_HAPROXY_CLIENT_IP]) client_ip = data->set.str[STRING_HAPROXY_CLIENT_IP]; else - client_ip = data->info.conn_local_ip; + client_ip = ipquad.local_ip; result = Curl_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n", - tcp_version, - client_ip, - data->info.conn_primary_ip, - data->info.conn_local_port, - data->info.conn_primary_port); + is_ipv6 ? "TCP6" : "TCP4", + client_ip, ipquad.remote_ip, + ipquad.local_port, ipquad.remote_port); #ifdef USE_UNIX_SOCKETS } @@ -125,23 +127,28 @@ static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf, if(result) goto out; ctx->state = HAPROXY_SEND; - /* FALLTHROUGH */ + FALLTHROUGH(); case HAPROXY_SEND: len = Curl_dyn_len(&ctx->data_out); if(len > 0) { - ssize_t written = Curl_conn_send(data, cf->sockindex, - Curl_dyn_ptr(&ctx->data_out), - len, &result); - if(written < 0) - goto out; - Curl_dyn_tail(&ctx->data_out, len - (size_t)written); + ssize_t nwritten; + nwritten = Curl_conn_cf_send(cf->next, data, + Curl_dyn_ptr(&ctx->data_out), len, FALSE, + &result); + if(nwritten < 0) { + if(result != CURLE_AGAIN) + goto out; + result = CURLE_OK; + nwritten = 0; + } + Curl_dyn_tail(&ctx->data_out, len - (size_t)nwritten); if(Curl_dyn_len(&ctx->data_out) > 0) { result = CURLE_OK; goto out; } } ctx->state = HAPROXY_DONE; - /* FALLTHROUGH */ + FALLTHROUGH(); default: Curl_dyn_free(&ctx->data_out); break; @@ -171,32 +178,27 @@ static void cf_haproxy_close(struct Curl_cfilter *cf, cf->next->cft->do_close(cf->next, data); } -static int cf_haproxy_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) +static void cf_haproxy_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) { - int fds; - - fds = cf->next->cft->get_select_socks(cf->next, data, socks); - if(!fds && cf->next->connected && !cf->connected) { + if(cf->next->connected && !cf->connected) { /* If we are not connected, but the filter "below" is * and not waiting on something, we are sending. */ - socks[0] = Curl_conn_cf_get_socket(cf, data); - return GETSOCK_WRITESOCK(0); + Curl_pollset_set_out_only(data, ps, Curl_conn_cf_get_socket(cf, data)); } - return fds; } - struct Curl_cftype Curl_cft_haproxy = { "HAPROXY", - 0, + CF_TYPE_PROXY, 0, cf_haproxy_destroy, cf_haproxy_connect, cf_haproxy_close, + Curl_cf_def_shutdown, Curl_cf_def_get_host, - cf_haproxy_get_select_socks, + cf_haproxy_adjust_pollset, Curl_cf_def_data_pending, Curl_cf_def_send, Curl_cf_def_recv, @@ -214,7 +216,7 @@ static CURLcode cf_haproxy_create(struct Curl_cfilter **pcf, CURLcode result; (void)data; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -229,7 +231,7 @@ static CURLcode cf_haproxy_create(struct Curl_cfilter **pcf, out: cf_haproxy_ctx_free(ctx); - *pcf = result? NULL : cf; + *pcf = result ? NULL : cf; return result; } diff --git a/deps/curl/lib/cf-https-connect.c b/deps/curl/lib/cf-https-connect.c index be54aec7402..dd7cdcb0514 100644 --- a/deps/curl/lib/cf-https-connect.c +++ b/deps/curl/lib/cf-https-connect.c @@ -55,7 +55,8 @@ struct cf_hc_baller { CURLcode result; struct curltime started; int reply_ms; - bool enabled; + BIT(enabled); + BIT(shutdown); }; static void cf_hc_baller_reset(struct cf_hc_baller *b, @@ -95,6 +96,21 @@ static bool cf_hc_baller_data_pending(struct cf_hc_baller *b, return b->cf && !b->result && b->cf->cft->has_data_pending(b->cf, data); } +static bool cf_hc_baller_needs_flush(struct cf_hc_baller *b, + struct Curl_easy *data) +{ + return b->cf && !b->result && Curl_conn_cf_needs_flush(b->cf, data); +} + +static CURLcode cf_hc_baller_cntrl(struct cf_hc_baller *b, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + if(b->cf && !b->result) + return Curl_conn_cf_cntrl(b->cf, data, FALSE, event, arg1, arg2); + return CURLE_OK; +} + struct cf_hc_ctx { cf_hc_state state; const struct Curl_dns_entry *remotehost; @@ -102,8 +118,8 @@ struct cf_hc_ctx { CURLcode result; /* overall result */ struct cf_hc_baller h3_baller; struct cf_hc_baller h21_baller; - int soft_eyeballs_timeout_ms; - int hard_eyeballs_timeout_ms; + unsigned int soft_eyeballs_timeout_ms; + unsigned int hard_eyeballs_timeout_ms; }; static void cf_hc_baller_init(struct cf_hc_baller *b, @@ -158,6 +174,7 @@ static CURLcode baller_connected(struct Curl_cfilter *cf, { struct cf_hc_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; + int reply_ms; DEBUGASSERT(winner->cf); if(winner != &ctx->h3_baller) @@ -165,15 +182,20 @@ static CURLcode baller_connected(struct Curl_cfilter *cf, if(winner != &ctx->h21_baller) cf_hc_baller_reset(&ctx->h21_baller, data); - CURL_TRC_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms", - winner->name, (int)Curl_timediff(Curl_now(), winner->started), - cf_hc_baller_reply_ms(winner, data)); + reply_ms = cf_hc_baller_reply_ms(winner, data); + if(reply_ms >= 0) + CURL_TRC_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms", + winner->name, (int)Curl_timediff(Curl_now(), winner->started), + reply_ms); + else + CURL_TRC_CF(data, cf, "deferred handshake %s: %dms", + winner->name, (int)Curl_timediff(Curl_now(), winner->started)); + cf->next = winner->cf; winner->cf = NULL; switch(cf->conn->alpn) { case CURL_HTTP_VERSION_3: - infof(data, "using HTTP/3"); break; case CURL_HTTP_VERSION_2: #ifdef USE_NGHTTP2 @@ -186,19 +208,12 @@ static CURLcode baller_connected(struct Curl_cfilter *cf, return result; } #endif - infof(data, "using HTTP/2"); - break; - case CURL_HTTP_VERSION_1_1: - infof(data, "using HTTP/1.1"); break; default: - infof(data, "using HTTP/1.x"); break; } ctx->state = CF_HC_SUCCESS; cf->connected = TRUE; - Curl_conn_cf_cntrl(cf->next, data, TRUE, - CF_CTRL_CONN_INFO_UPDATE, 0, NULL); return result; } @@ -267,9 +282,9 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, } else if(ctx->h21_baller.enabled) cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21", - cf->conn->transport); + cf->conn->transport); ctx->state = CF_HC_CONNECT; - /* FALLTHROUGH */ + FALLTHROUGH(); case CF_HC_CONNECT: if(cf_hc_baller_is_active(&ctx->h3_baller)) { @@ -298,8 +313,8 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, (!ctx->h21_baller.enabled || ctx->h21_baller.result)) { /* both failed or disabled. we give up */ CURL_TRC_CF(data, cf, "connect, all failed"); - result = ctx->result = ctx->h3_baller.enabled? - ctx->h3_baller.result : ctx->h21_baller.result; + result = ctx->result = ctx->h3_baller.enabled ? + ctx->h3_baller.result : ctx->h21_baller.result; ctx->state = CF_HC_FAILURE; goto out; } @@ -325,42 +340,68 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, return result; } -static int cf_hc_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) +static CURLcode cf_hc_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) { struct cf_hc_ctx *ctx = cf->ctx; - size_t i, j, s; - int brc, rc = GETSOCK_BLANK; - curl_socket_t bsocks[MAX_SOCKSPEREASYHANDLE]; struct cf_hc_baller *ballers[2]; + size_t i; + CURLcode result = CURLE_OK; - if(cf->connected) - return cf->next->cft->get_select_socks(cf->next, data, socks); + DEBUGASSERT(data); + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + /* shutdown all ballers that have not done so already. If one fails, + * continue shutting down others until all are shutdown. */ ballers[0] = &ctx->h3_baller; ballers[1] = &ctx->h21_baller; - for(i = s = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { + for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { struct cf_hc_baller *b = ballers[i]; - if(!cf_hc_baller_is_active(b)) + bool bdone = FALSE; + if(!cf_hc_baller_is_active(b) || b->shutdown) continue; - brc = Curl_conn_cf_get_select_socks(b->cf, data, bsocks); - CURL_TRC_CF(data, cf, "get_selected_socks(%s) -> %x", b->name, brc); - if(!brc) - continue; - for(j = 0; j < MAX_SOCKSPEREASYHANDLE && s < MAX_SOCKSPEREASYHANDLE; ++j) { - if((brc & GETSOCK_WRITESOCK(j)) || (brc & GETSOCK_READSOCK(j))) { - socks[s] = bsocks[j]; - if(brc & GETSOCK_WRITESOCK(j)) - rc |= GETSOCK_WRITESOCK(s); - if(brc & GETSOCK_READSOCK(j)) - rc |= GETSOCK_READSOCK(s); - s++; - } + b->result = b->cf->cft->do_shutdown(b->cf, data, &bdone); + if(b->result || bdone) + b->shutdown = TRUE; /* treat a failed shutdown as done */ + } + + *done = TRUE; + for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { + if(ballers[i] && !ballers[i]->shutdown) + *done = FALSE; + } + if(*done) { + for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { + if(ballers[i] && ballers[i]->result) + result = ballers[i]->result; } } - CURL_TRC_CF(data, cf, "get_selected_socks -> %x", rc); - return rc; + CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done); + return result; +} + +static void cf_hc_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + if(!cf->connected) { + struct cf_hc_ctx *ctx = cf->ctx; + struct cf_hc_baller *ballers[2]; + size_t i; + + ballers[0] = &ctx->h3_baller; + ballers[1] = &ctx->h21_baller; + for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { + struct cf_hc_baller *b = ballers[i]; + if(!cf_hc_baller_is_active(b)) + continue; + Curl_conn_cf_adjust_pollset(b->cf, data, ps); + } + CURL_TRC_CF(data, cf, "adjust_pollset -> %d socks", ps->num); + } } static bool cf_hc_data_pending(struct Curl_cfilter *cf, @@ -386,13 +427,13 @@ static struct curltime cf_get_max_baller_time(struct Curl_cfilter *cf, memset(&tmax, 0, sizeof(tmax)); memset(&t, 0, sizeof(t)); - cfb = ctx->h21_baller.enabled? ctx->h21_baller.cf : NULL; + cfb = ctx->h21_baller.enabled ? ctx->h21_baller.cf : NULL; if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) { if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) tmax = t; } memset(&t, 0, sizeof(t)); - cfb = ctx->h3_baller.enabled? ctx->h3_baller.cf : NULL; + cfb = ctx->h3_baller.enabled ? ctx->h3_baller.cf : NULL; if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) { if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) tmax = t; @@ -404,6 +445,8 @@ static CURLcode cf_hc_query(struct Curl_cfilter *cf, struct Curl_easy *data, int query, int *pres1, void *pres2) { + struct cf_hc_ctx *ctx = cf->ctx; + if(!cf->connected) { switch(query) { case CF_QUERY_TIMER_CONNECT: { @@ -416,15 +459,40 @@ static CURLcode cf_hc_query(struct Curl_cfilter *cf, *when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT); return CURLE_OK; } + case CF_QUERY_NEED_FLUSH: { + if(cf_hc_baller_needs_flush(&ctx->h3_baller, data) + || cf_hc_baller_needs_flush(&ctx->h21_baller, data)) { + *pres1 = TRUE; + return CURLE_OK; + } + break; + } default: break; } } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } +static CURLcode cf_hc_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_hc_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(!cf->connected) { + result = cf_hc_baller_cntrl(&ctx->h3_baller, data, event, arg1, arg2); + if(!result || (result == CURLE_AGAIN)) + result = cf_hc_baller_cntrl(&ctx->h21_baller, data, event, arg1, arg2); + if(result == CURLE_AGAIN) + result = CURLE_OK; + } + return result; +} + static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data) { CURL_TRC_CF(data, cf, "close"); @@ -454,12 +522,13 @@ struct Curl_cftype Curl_cft_http_connect = { cf_hc_destroy, cf_hc_connect, cf_hc_close, + cf_hc_shutdown, Curl_cf_def_get_host, - cf_hc_get_select_socks, + cf_hc_adjust_pollset, cf_hc_data_pending, Curl_cf_def_send, Curl_cf_def_recv, - Curl_cf_def_cntrl, + cf_hc_cntrl, Curl_cf_def_conn_is_alive, Curl_cf_def_conn_keep_alive, cf_hc_query, @@ -475,7 +544,7 @@ static CURLcode cf_hc_create(struct Curl_cfilter **pcf, CURLcode result = CURLE_OK; (void)data; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -491,7 +560,7 @@ static CURLcode cf_hc_create(struct Curl_cfilter **pcf, cf_hc_reset(cf, data); out: - *pcf = result? NULL : cf; + *pcf = result ? NULL : cf; free(ctx); return result; } @@ -530,7 +599,7 @@ CURLcode Curl_cf_https_setup(struct Curl_easy *data, if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) { result = Curl_conn_may_http3(data, conn); - if(result) /* can't do it */ + if(result) /* cannot do it */ goto out; try_h3 = TRUE; try_h21 = FALSE; diff --git a/deps/curl/lib/cf-socket.c b/deps/curl/lib/cf-socket.c index effe6e649d1..b4840f7ebc5 100644 --- a/deps/curl/lib/cf-socket.c +++ b/deps/curl/lib/cf-socket.c @@ -35,6 +35,9 @@ #elif defined(HAVE_NETINET_TCP_H) #include #endif +#ifdef HAVE_NETINET_UDP_H +#include +#endif #ifdef HAVE_SYS_IOCTL_H #include #endif @@ -53,6 +56,11 @@ #include #endif +#ifdef __DragonFly__ +/* Required for __DragonFly_version */ +#include +#endif + #include "urldata.h" #include "bufq.h" #include "sendf.h" @@ -73,6 +81,7 @@ #include "multihandle.h" #include "rand.h" #include "share.h" +#include "strdup.h" #include "version_win32.h" /* The last 3 #include files should be in this order */ @@ -81,7 +90,7 @@ #include "memdebug.h" -#if defined(ENABLE_IPV6) && defined(IPV6_V6ONLY) && defined(WIN32) +#if defined(USE_IPV6) && defined(IPV6_V6ONLY) && defined(_WIN32) /* It makes support for IPv4-mapped IPv6 addresses. * Linux kernel, NetBSD, FreeBSD and Darwin: default is off; * Windows Vista and later: default is on; @@ -102,11 +111,7 @@ static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd) #if defined(TCP_NODELAY) curl_socklen_t onoff = (curl_socklen_t) 1; int level = IPPROTO_TCP; -#if !defined(CURL_DISABLE_VERBOSE_STRINGS) char buffer[STRERROR_LEN]; -#else - (void) data; -#endif if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff, sizeof(onoff)) < 0) @@ -119,7 +124,7 @@ static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd) } #ifdef SO_NOSIGPIPE -/* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when +/* The preferred method on macOS (10.2 and later) to prevent SIGPIPEs when sending data to a dead peer (instead of relying on the 4th argument to send being MSG_NOSIGNAL). Possibly also existing and in use on other BSD systems? */ @@ -127,6 +132,7 @@ static void nosigpipe(struct Curl_easy *data, curl_socket_t sockfd) { int onoff = 1; + (void)data; if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff, sizeof(onoff)) < 0) { #if !defined(CURL_DISABLE_VERBOSE_STRINGS) @@ -140,14 +146,24 @@ static void nosigpipe(struct Curl_easy *data, #define nosigpipe(x,y) Curl_nop_stmt #endif -#if defined(__DragonFly__) || defined(HAVE_WINSOCK2_H) -/* DragonFlyBSD and Windows use millisecond units */ +#if defined(USE_WINSOCK) && \ + defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL) && defined(TCP_KEEPCNT) +/* Win 10, v 1709 (10.0.16299) and later can use SetSockOpt TCP_KEEP____ + * so should use seconds */ +#define CURL_WINSOCK_KEEP_SSO +#define KEEPALIVE_FACTOR(x) +#elif defined(USE_WINSOCK) || \ + (defined(__sun) && !defined(TCP_KEEPIDLE)) || \ + (defined(__DragonFly__) && __DragonFly_version < 500702) || \ + (defined(_WIN32) && !defined(TCP_KEEPIDLE)) +/* Solaris < 11.4, DragonFlyBSD < 500702 and Windows < 10.0.16299 + * use millisecond units. */ #define KEEPALIVE_FACTOR(x) (x *= 1000) #else #define KEEPALIVE_FACTOR(x) #endif -#if defined(HAVE_WINSOCK2_H) && !defined(SIO_KEEPALIVE_VALS) +#if defined(USE_WINSOCK) && !defined(SIO_KEEPALIVE_VALS) #define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4) struct tcp_keepalive { @@ -161,44 +177,87 @@ static void tcpkeepalive(struct Curl_easy *data, curl_socket_t sockfd) { - int optval = data->set.tcp_keepalive?1:0; + int optval = data->set.tcp_keepalive ? 1 : 0; /* only set IDLE and INTVL if setting KEEPALIVE is successful */ if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set SO_KEEPALIVE on fd %d", sockfd); + infof(data, "Failed to set SO_KEEPALIVE on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); } else { -#if defined(SIO_KEEPALIVE_VALS) +#if defined(SIO_KEEPALIVE_VALS) /* Windows */ +/* Windows 10, version 1709 (10.0.16299) and later versions */ +#if defined(CURL_WINSOCK_KEEP_SSO) + optval = curlx_sltosi(data->set.tcp_keepidle); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, + (const char *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPIDLE on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } + optval = curlx_sltosi(data->set.tcp_keepintvl); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, + (const char *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPINTVL on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } + optval = curlx_sltosi(data->set.tcp_keepcnt); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, + (const char *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPCNT on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } +#else /* Windows < 10.0.16299 */ struct tcp_keepalive vals; DWORD dummy; vals.onoff = 1; optval = curlx_sltosi(data->set.tcp_keepidle); KEEPALIVE_FACTOR(optval); - vals.keepalivetime = optval; + vals.keepalivetime = (u_long)optval; optval = curlx_sltosi(data->set.tcp_keepintvl); KEEPALIVE_FACTOR(optval); - vals.keepaliveinterval = optval; + vals.keepaliveinterval = (u_long)optval; if(WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, (LPVOID) &vals, sizeof(vals), NULL, 0, &dummy, NULL, NULL) != 0) { - infof(data, "Failed to set SIO_KEEPALIVE_VALS on fd %d: %d", - (int)sockfd, WSAGetLastError()); + infof(data, "Failed to set SIO_KEEPALIVE_VALS on fd " + "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); } -#else +#endif +#else /* !Windows */ #ifdef TCP_KEEPIDLE optval = curlx_sltosi(data->set.tcp_keepidle); KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set TCP_KEEPIDLE on fd %d", sockfd); + infof(data, "Failed to set TCP_KEEPIDLE on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); } #elif defined(TCP_KEEPALIVE) - /* Mac OS X style */ + /* macOS style */ optval = curlx_sltosi(data->set.tcp_keepidle); KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE, (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set TCP_KEEPALIVE on fd %d", sockfd); + infof(data, "Failed to set TCP_KEEPALIVE on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } +#elif defined(TCP_KEEPALIVE_THRESHOLD) + /* Solaris <11.4 style */ + optval = curlx_sltosi(data->set.tcp_keepidle); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_THRESHOLD, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPALIVE_THRESHOLD on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); } #endif #ifdef TCP_KEEPINTVL @@ -206,7 +265,37 @@ tcpkeepalive(struct Curl_easy *data, KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set TCP_KEEPINTVL on fd %d", sockfd); + infof(data, "Failed to set TCP_KEEPINTVL on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } +#elif defined(TCP_KEEPALIVE_ABORT_THRESHOLD) + /* Solaris <11.4 style */ + /* TCP_KEEPALIVE_ABORT_THRESHOLD should equal to + * TCP_KEEPCNT * TCP_KEEPINTVL on other platforms. + * The default value of TCP_KEEPCNT is 9 on Linux, + * 8 on *BSD/macOS, 5 or 10 on Windows. We use the + * default config for Solaris <11.4 because there is + * no default value for TCP_KEEPCNT on Solaris 11.4. + * + * Note that the consequent probes will not be sent + * at equal intervals on Solaris, but will be sent + * using the exponential backoff algorithm. */ + optval = curlx_sltosi(data->set.tcp_keepcnt) * + curlx_sltosi(data->set.tcp_keepintvl); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_ABORT_THRESHOLD, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPALIVE_ABORT_THRESHOLD on fd " + "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); + } +#endif +#ifdef TCP_KEEPCNT + optval = curlx_sltosi(data->set.tcp_keepcnt); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPCNT on fd " + "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); } #endif #endif @@ -243,11 +332,11 @@ void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest, dest->protocol = IPPROTO_UDP; break; } - dest->addrlen = ai->ai_addrlen; + dest->addrlen = (unsigned int)ai->ai_addrlen; if(dest->addrlen > sizeof(struct Curl_sockaddr_storage)) dest->addrlen = sizeof(struct Curl_sockaddr_storage); - memcpy(&dest->sa_addr, ai->ai_addr, dest->addrlen); + memcpy(&dest->curl_sa_addr, ai->ai_addr, dest->addrlen); } static CURLcode socket_open(struct Curl_easy *data, @@ -266,11 +355,11 @@ static CURLcode socket_open(struct Curl_easy *data, * might have been changed and this 'new' address will actually be used * here to connect. */ - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); *sockfd = data->set.fopensocket(data->set.opensocket_client, CURLSOCKTYPE_IPCXN, (struct curl_sockaddr *)addr); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); } else { /* opensocket callback not set, so simply create the socket now */ @@ -281,9 +370,9 @@ static CURLcode socket_open(struct Curl_easy *data, /* no socket, no connection */ return CURLE_COULDNT_CONNECT; -#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) +#if defined(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) if(data->conn->scope_id && (addr->family == AF_INET6)) { - struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr; + struct sockaddr_in6 * const sa6 = (void *)&addr->curl_sa_addr; sa6->sin6_scope_id = data->conn->scope_id; } #endif @@ -308,7 +397,7 @@ CURLcode Curl_socket_open(struct Curl_easy *data, struct Curl_sockaddr_ex dummy; if(!addr) - /* if the caller doesn't want info back, use a local temp copy */ + /* if the caller does not want info back, use a local temp copy */ addr = &dummy; Curl_sock_assign_addr(addr, ai, transport); @@ -318,12 +407,15 @@ CURLcode Curl_socket_open(struct Curl_easy *data, static int socket_close(struct Curl_easy *data, struct connectdata *conn, int use_callback, curl_socket_t sock) { + if(CURL_SOCKET_BAD == sock) + return 0; + if(use_callback && conn && conn->fclosesocket) { int rc; Curl_multi_closed(data, sock); - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); rc = conn->fclosesocket(conn->closesocket_client, sock); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); return rc; } @@ -357,14 +449,14 @@ int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn, Buffer Size The problem described in this knowledge-base is applied only to pre-Vista - Windows. Following function trying to detect OS version and skips + Windows. Following function trying to detect OS version and skips SO_SNDBUF adjustment for Windows Vista and above. */ #define DETECT_OS_NONE 0 #define DETECT_OS_PREVISTA 1 #define DETECT_OS_VISTA_OR_LATER 2 -void Curl_sndbufset(curl_socket_t sockfd) +void Curl_sndbuf_init(curl_socket_t sockfd) { int val = CURL_MAX_WRITE_SIZE + 32; int curval = 0; @@ -389,7 +481,88 @@ void Curl_sndbufset(curl_socket_t sockfd) setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val)); } -#endif +#endif /* USE_WINSOCK */ + +/* + * Curl_parse_interface() + * + * This is used to parse interface argument in the following formats. + * In all the examples, `host` can be an IP address or a hostname. + * + * - can be either an interface name or a host. + * if! - interface name. + * host! - hostname. + * ifhost!! - interface name and hostname. + * + * Parameters: + * + * input [in] - input string. + * len [in] - length of the input string. + * dev [in/out] - address where a pointer to newly allocated memory + * holding the interface-or-host will be stored upon + * completion. + * iface [in/out] - address where a pointer to newly allocated memory + * holding the interface will be stored upon completion. + * host [in/out] - address where a pointer to newly allocated memory + * holding the host will be stored upon completion. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_parse_interface(const char *input, + char **dev, char **iface, char **host) +{ + static const char if_prefix[] = "if!"; + static const char host_prefix[] = "host!"; + static const char if_host_prefix[] = "ifhost!"; + size_t len; + + DEBUGASSERT(dev); + DEBUGASSERT(iface); + DEBUGASSERT(host); + + len = strlen(input); + if(len > 512) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(!strncmp(if_prefix, input, strlen(if_prefix))) { + input += strlen(if_prefix); + if(!*input) + return CURLE_BAD_FUNCTION_ARGUMENT; + *iface = Curl_memdup0(input, len - strlen(if_prefix)); + return *iface ? CURLE_OK : CURLE_OUT_OF_MEMORY; + } + else if(!strncmp(host_prefix, input, strlen(host_prefix))) { + input += strlen(host_prefix); + if(!*input) + return CURLE_BAD_FUNCTION_ARGUMENT; + *host = Curl_memdup0(input, len - strlen(host_prefix)); + return *host ? CURLE_OK : CURLE_OUT_OF_MEMORY; + } + else if(!strncmp(if_host_prefix, input, strlen(if_host_prefix))) { + const char *host_part; + input += strlen(if_host_prefix); + len -= strlen(if_host_prefix); + host_part = memchr(input, '!', len); + if(!host_part || !*(host_part + 1)) + return CURLE_BAD_FUNCTION_ARGUMENT; + *iface = Curl_memdup0(input, host_part - input); + if(!*iface) + return CURLE_OUT_OF_MEMORY; + ++host_part; + *host = Curl_memdup0(host_part, len - (host_part - input)); + if(!*host) { + free(*iface); + *iface = NULL; + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; + } + + if(!*input) + return CURLE_BAD_FUNCTION_ARGUMENT; + *dev = Curl_memdup0(input, len); + return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY; +} #ifndef CURL_DISABLE_BINDLOCAL static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, @@ -399,7 +572,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */ curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */ struct sockaddr_in *si4 = (struct sockaddr_in *)&sa; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa; #endif @@ -409,94 +582,92 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, /* how many port numbers to try to bind to, increasing one at a time */ int portnum = data->set.localportrange; const char *dev = data->set.str[STRING_DEVICE]; + const char *iface_input = data->set.str[STRING_INTERFACE]; + const char *host_input = data->set.str[STRING_BINDHOST]; + const char *iface = iface_input ? iface_input : dev; + const char *host = host_input ? host_input : dev; int error; #ifdef IP_BIND_ADDRESS_NO_PORT int on = 1; #endif -#ifndef ENABLE_IPV6 +#ifndef USE_IPV6 (void)scope; #endif /************************************************************* * Select device to bind socket to *************************************************************/ - if(!dev && !port) + if(!iface && !host && !port) /* no local kind of binding was requested */ return CURLE_OK; memset(&sa, 0, sizeof(struct Curl_sockaddr_storage)); - if(dev && (strlen(dev)<255) ) { + if(iface && (strlen(iface) < 255) ) { char myhost[256] = ""; int done = 0; /* -1 for error, 1 for address found */ - bool is_interface = FALSE; - bool is_host = FALSE; - static const char *if_prefix = "if!"; - static const char *host_prefix = "host!"; - - if(strncmp(if_prefix, dev, strlen(if_prefix)) == 0) { - dev += strlen(if_prefix); - is_interface = TRUE; - } - else if(strncmp(host_prefix, dev, strlen(host_prefix)) == 0) { - dev += strlen(host_prefix); - is_host = TRUE; - } + if2ip_result_t if2ip_result = IF2IP_NOT_FOUND; /* interface */ - if(!is_host) { #ifdef SO_BINDTODEVICE - /* - * This binds the local socket to a particular interface. This will - * force even requests to other local interfaces to go out the external - * interface. Only bind to the interface when specified as interface, - * not just as a hostname or ip address. - * - * The interface might be a VRF, eg: vrf-blue, which means it cannot be - * converted to an IP address and would fail Curl_if2ip. Simply try to - * use it straight away. - */ - if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, - dev, (curl_socklen_t)strlen(dev) + 1) == 0) { - /* This is often "errno 1, error: Operation not permitted" if you're - * not running as root or another suitable privileged user. If it - * succeeds it means the parameter was a valid interface and not an IP - * address. Return immediately. - */ - infof(data, "socket successfully bound to interface '%s'", dev); + /* + * This binds the local socket to a particular interface. This will + * force even requests to other local interfaces to go out the external + * interface. Only bind to the interface when specified as interface, + * not just as a hostname or ip address. + * + * The interface might be a VRF, eg: vrf-blue, which means it cannot be + * converted to an IP address and would fail Curl_if2ip. Simply try to + * use it straight away. + */ + if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, + iface, (curl_socklen_t)strlen(iface) + 1) == 0) { + /* This is often "errno 1, error: Operation not permitted" if you are + * not running as root or another suitable privileged user. If it + * succeeds it means the parameter was a valid interface and not an IP + * address. Return immediately. + */ + if(!host_input) { + infof(data, "socket successfully bound to interface '%s'", iface); return CURLE_OK; } + } #endif - - switch(Curl_if2ip(af, -#ifdef ENABLE_IPV6 - scope, conn->scope_id, + if(!host_input) { + /* Discover IP from input device, then bind to it */ + if2ip_result = Curl_if2ip(af, +#ifdef USE_IPV6 + scope, conn->scope_id, #endif - dev, myhost, sizeof(myhost))) { - case IF2IP_NOT_FOUND: - if(is_interface) { - /* Do not fall back to treating it as a host name */ - failf(data, "Couldn't bind to interface '%s'", dev); - return CURLE_INTERFACE_FAILED; - } - break; - case IF2IP_AF_NOT_SUPPORTED: - /* Signal the caller to try another address family if available */ - return CURLE_UNSUPPORTED_PROTOCOL; - case IF2IP_FOUND: - is_interface = TRUE; - /* - * We now have the numerical IP address in the 'myhost' buffer - */ - infof(data, "Local Interface %s is ip %s using address family %i", - dev, myhost, af); - done = 1; - break; - } + iface, myhost, sizeof(myhost)); } - if(!is_interface) { + switch(if2ip_result) { + case IF2IP_NOT_FOUND: + if(iface_input && !host_input) { + /* Do not fall back to treating it as a hostname */ + char buffer[STRERROR_LEN]; + data->state.os_errno = error = SOCKERRNO; + failf(data, "Couldn't bind to interface '%s' with errno %d: %s", + iface, error, Curl_strerror(error, buffer, sizeof(buffer))); + return CURLE_INTERFACE_FAILED; + } + break; + case IF2IP_AF_NOT_SUPPORTED: + /* Signal the caller to try another address family if available */ + return CURLE_UNSUPPORTED_PROTOCOL; + case IF2IP_FOUND: + /* + * We now have the numerical IP address in the 'myhost' buffer + */ + host = myhost; + infof(data, "Local Interface %s is ip %s using address family %i", + iface, host, af); + done = 1; + break; + } + if(!iface_input || host_input) { /* - * This was not an interface, resolve the name as a host name + * This was not an interface, resolve the name as a hostname * or IP number * * Temporarily force name resolution to use only the address type @@ -508,23 +679,24 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, if(af == AF_INET) conn->ip_version = CURL_IPRESOLVE_V4; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else if(af == AF_INET6) conn->ip_version = CURL_IPRESOLVE_V6; #endif - rc = Curl_resolv(data, dev, 80, FALSE, &h); + rc = Curl_resolv(data, host, 80, FALSE, &h); if(rc == CURLRESOLV_PENDING) (void)Curl_resolver_wait_resolv(data, &h); conn->ip_version = ipver; if(h) { + int h_af = h->addr->ai_family; /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ Curl_printable_address(h->addr, myhost, sizeof(myhost)); infof(data, "Name '%s' family %i resolved to '%s' family %i", - dev, af, myhost, h->addr->ai_family); - Curl_resolv_unlock(data, h); - if(af != h->addr->ai_family) { + host, af, myhost, h_af); + Curl_resolv_unlink(data, &h); /* this will NULL, potential free h */ + if(af != h_af) { /* bad IP version combo, signal the caller to try another address family if available */ return CURLE_UNSUPPORTED_PROTOCOL; @@ -534,14 +706,14 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, else { /* * provided dev was no interface (or interfaces are not supported - * e.g. solaris) no ip address and no domain we fail here + * e.g. Solaris) no ip address and no domain we fail here */ done = -1; } } if(done > 0) { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* IPv6 address */ if(af == AF_INET6) { #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID @@ -556,7 +728,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, if(scope_ptr) { /* The "myhost" string either comes from Curl_if2ip or from Curl_printable_address. The latter returns only numeric scope - IDs and the former returns none at all. So the scope ID, if + IDs and the former returns none at all. So the scope ID, if present, is known to be numeric */ unsigned long scope_id = strtoul(scope_ptr, NULL, 10); if(scope_id > UINT_MAX) @@ -583,14 +755,17 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, /* errorbuf is set false so failf will overwrite any message already in the error buffer, so the user receives this error message instead of a generic resolve error. */ + char buffer[STRERROR_LEN]; data->state.errorbuf = FALSE; - failf(data, "Couldn't bind to '%s'", dev); + data->state.os_errno = error = SOCKERRNO; + failf(data, "Couldn't bind to '%s' with errno %d: %s", + host, error, Curl_strerror(error, buffer, sizeof(buffer))); return CURLE_INTERFACE_FAILED; } } else { /* no device was given, prepare sa to match af's needs */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(af == AF_INET6) { si6->sin6_family = AF_INET6; si6->sin6_port = htons(port); @@ -610,16 +785,6 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, for(;;) { if(bind(sockfd, sock, sizeof_sa) >= 0) { /* we succeeded to bind */ - struct Curl_sockaddr_storage add; - curl_socklen_t size = sizeof(add); - memset(&add, 0, sizeof(struct Curl_sockaddr_storage)); - if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) { - char buffer[STRERROR_LEN]; - data->state.os_errno = error = SOCKERRNO; - failf(data, "getsockname() failed with errno %d: %s", - error, Curl_strerror(error, buffer, sizeof(buffer))); - return CURLE_INTERFACE_FAILED; - } infof(data, "Local port: %hu", port); conn->bits.bound = TRUE; return CURLE_OK; @@ -633,7 +798,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, /* We reuse/clobber the port variable here below */ if(sock->sa_family == AF_INET) si4->sin_port = ntohs(port); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else si6->sin6_port = ntohs(port); #endif @@ -662,7 +827,7 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) int err = 0; curl_socklen_t errSize = sizeof(err); -#ifdef WIN32 +#ifdef _WIN32 /* * In October 2003 we effectively nullified this function on Windows due to * problems with it using all CPU in multi-threaded cases. @@ -671,8 +836,8 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) * Gisle Vanem could reproduce the former problems with this function, but * could avoid them by adding this SleepEx() call below: * - * "I don't have Rational Quantify, but the hint from his post was - * ntdll::NtRemoveIoCompletion(). So I'd assume the SleepEx (or maybe + * "I do not have Rational Quantify, but the hint from his post was + * ntdll::NtRemoveIoCompletion(). I would assume the SleepEx (or maybe * just Sleep(0) would be enough?) would release whatever * mutex/critical-section the ntdll call is waiting on. * @@ -690,14 +855,14 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) if(0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize)) err = SOCKERRNO; #ifdef _WIN32_WCE - /* Old WinCE versions don't support SO_ERROR */ + /* Old Windows CE versions do not support SO_ERROR */ if(WSAENOPROTOOPT == err) { SET_SOCKERRNO(0); err = 0; } #endif #if defined(EBADIOCTL) && defined(__minix) - /* Minix 3.1.x doesn't support getsockopt on UDP sockets */ + /* Minix 3.1.x does not support getsockopt on UDP sockets */ if(EBADIOCTL == err) { SET_SOCKERRNO(0); err = 0; @@ -707,7 +872,7 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) /* we are connected, awesome! */ rc = TRUE; else - /* This wasn't a successful connect */ + /* This was not a successful connect */ rc = FALSE; if(error) *error = err; @@ -769,23 +934,26 @@ struct cf_socket_ctx { int transport; struct Curl_sockaddr_ex addr; /* address to connect to */ curl_socket_t sock; /* current attempt socket */ - struct bufq recvbuf; /* used when `buffer_recv` is set */ - char r_ip[MAX_IPADR_LEN]; /* remote IP as string */ - int r_port; /* remote port number */ - char l_ip[MAX_IPADR_LEN]; /* local IP as string */ - int l_port; /* local port number */ + struct ip_quadruple ip; /* The IP quadruple 2x(addr+port) */ struct curltime started_at; /* when socket was created */ struct curltime connected_at; /* when socket connected/got first byte */ struct curltime first_byte_at; /* when first byte was recvd */ +#ifdef USE_WINSOCK + struct curltime last_sndbuf_query_at; /* when SO_SNDBUF last queried */ + ULONG sndbuf_size; /* the last set SO_SNDBUF size */ +#endif int error; /* errno of last failure or 0 */ #ifdef DEBUGBUILD int wblock_percent; /* percent of writes doing EAGAIN */ int wpartial_percent; /* percent of bytes written in send */ + int rblock_percent; /* percent of reads doing EAGAIN */ + size_t recv_max; /* max enforced read size */ #endif BIT(got_first_byte); /* if first byte was received */ + BIT(listening); /* socket is listening */ BIT(accepted); /* socket was accepted, not connected */ + BIT(sock_connected); /* socket is "connected", e.g. in UDP */ BIT(active); - BIT(buffer_recv); }; static void cf_socket_ctx_init(struct cf_socket_ctx *ctx, @@ -796,7 +964,6 @@ static void cf_socket_ctx_init(struct cf_socket_ctx *ctx, ctx->sock = CURL_SOCKET_BAD; ctx->transport = transport; Curl_sock_assign_addr(&ctx->addr, ai, transport); - Curl_bufq_init(&ctx->recvbuf, NW_RECV_CHUNK_SIZE, NW_RECV_CHUNKS); #ifdef DEBUGBUILD { char *p = getenv("CURL_DBG_SOCK_WBLOCK"); @@ -811,57 +978,20 @@ static void cf_socket_ctx_init(struct cf_socket_ctx *ctx, if(l >= 0 && l <= 100) ctx->wpartial_percent = (int)l; } - } -#endif -} - -struct reader_ctx { - struct Curl_cfilter *cf; - struct Curl_easy *data; -}; - -static ssize_t nw_in_read(void *reader_ctx, - unsigned char *buf, size_t len, - CURLcode *err) -{ - struct reader_ctx *rctx = reader_ctx; - struct cf_socket_ctx *ctx = rctx->cf->ctx; - ssize_t nread; - - *err = CURLE_OK; - nread = sread(ctx->sock, buf, len); - - if(-1 == nread) { - int sockerr = SOCKERRNO; - - if( -#ifdef WSAEWOULDBLOCK - /* This is how Windows does it */ - (WSAEWOULDBLOCK == sockerr) -#else - /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned - due to its inability to send off data without blocking. We therefore - treat both error codes the same here */ - (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr) -#endif - ) { - /* this is just a case of EWOULDBLOCK */ - *err = CURLE_AGAIN; - nread = -1; + p = getenv("CURL_DBG_SOCK_RBLOCK"); + if(p) { + long l = strtol(p, NULL, 10); + if(l >= 0 && l <= 100) + ctx->rblock_percent = (int)l; } - else { - char buffer[STRERROR_LEN]; - - failf(rctx->data, "Recv failure: %s", - Curl_strerror(sockerr, buffer, sizeof(buffer))); - rctx->data->state.os_errno = sockerr; - *err = CURLE_RECV_ERROR; - nread = -1; + p = getenv("CURL_DBG_SOCK_RMAX"); + if(p) { + long l = strtol(p, NULL, 10); + if(l >= 0) + ctx->recv_max = (size_t)l; } } - CURL_TRC_CF(rctx->data, rctx->cf, "nw_in_read(len=%zu) -> %d, err=%d", - len, (int)nread, *err); - return nread; +#endif } static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -869,37 +999,14 @@ static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data) struct cf_socket_ctx *ctx = cf->ctx; if(ctx && CURL_SOCKET_BAD != ctx->sock) { - if(ctx->active) { - /* We share our socket at cf->conn->sock[cf->sockindex] when active. - * If it is no longer there, someone has stolen (and hopefully - * closed it) and we just forget about it. - */ - if(ctx->sock == cf->conn->sock[cf->sockindex]) { - CURL_TRC_CF(data, cf, "cf_socket_close(%" CURL_FORMAT_SOCKET_T - ", active)", ctx->sock); - socket_close(data, cf->conn, !ctx->accepted, ctx->sock); - cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD; - } - else { - CURL_TRC_CF(data, cf, "cf_socket_close(%" CURL_FORMAT_SOCKET_T - ") no longer at conn->sock[], discarding", ctx->sock); - /* TODO: we do not want this to happen. Need to check which - * code is messing with conn->sock[cf->sockindex] */ - } - ctx->sock = CURL_SOCKET_BAD; - if(cf->sockindex == FIRSTSOCKET) - cf->conn->remote_addr = NULL; - } - else { - /* this is our local socket, we did never publish it */ - CURL_TRC_CF(data, cf, "cf_socket_close(%" CURL_FORMAT_SOCKET_T - ", not active)", ctx->sock); - socket_close(data, cf->conn, !ctx->accepted, ctx->sock); - ctx->sock = CURL_SOCKET_BAD; - } - Curl_bufq_reset(&ctx->recvbuf); + CURL_TRC_CF(data, cf, "cf_socket_close(%" FMT_SOCKET_T ")", ctx->sock); + if(ctx->sock == cf->conn->sock[cf->sockindex]) + cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD; + socket_close(data, cf->conn, !ctx->accepted, ctx->sock); + ctx->sock = CURL_SOCKET_BAD; + if(ctx->active && cf->sockindex == FIRSTSOCKET) + cf->conn->remote_addr = NULL; ctx->active = FALSE; - ctx->buffer_recv = FALSE; memset(&ctx->started_at, 0, sizeof(ctx->started_at)); memset(&ctx->connected_at, 0, sizeof(ctx->connected_at)); } @@ -907,13 +1014,34 @@ static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data) cf->connected = FALSE; } +static CURLcode cf_socket_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + if(cf->connected) { + struct cf_socket_ctx *ctx = cf->ctx; + + CURL_TRC_CF(data, cf, "cf_socket_shutdown(%" FMT_SOCKET_T ")", ctx->sock); + /* On TCP, and when the socket looks well and non-blocking mode + * can be enabled, receive dangling bytes before close to avoid + * entering RST states unnecessarily. */ + if(ctx->sock != CURL_SOCKET_BAD && + ctx->transport == TRNSPRT_TCP && + (curlx_nonblock(ctx->sock, TRUE) >= 0)) { + unsigned char buf[1024]; + (void)sread(ctx->sock, buf, sizeof(buf)); + } + } + *done = TRUE; + return CURLE_OK; +} + static void cf_socket_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_socket_ctx *ctx = cf->ctx; cf_socket_close(cf, data); CURL_TRC_CF(data, cf, "destroy"); - Curl_bufq_free(&ctx->recvbuf); free(ctx); cf->ctx = NULL; } @@ -924,7 +1052,8 @@ static CURLcode set_local_ip(struct Curl_cfilter *cf, struct cf_socket_ctx *ctx = cf->ctx; #ifdef HAVE_GETSOCKNAME - if(!(data->conn->handler->protocol & CURLPROTO_TFTP)) { + if((ctx->sock != CURL_SOCKET_BAD) && + !(data->conn->handler->protocol & CURLPROTO_TFTP)) { /* TFTP does not connect, so it cannot get the IP like this */ char buffer[STRERROR_LEN]; @@ -939,7 +1068,7 @@ static CURLcode set_local_ip(struct Curl_cfilter *cf, return CURLE_FAILED_INIT; } if(!Curl_addr2string((struct sockaddr*)&ssloc, slen, - ctx->l_ip, &ctx->l_port)) { + ctx->ip.local_ip, &ctx->ip.local_port)) { failf(data, "ssloc inet_ntop() failed with errno %d: %s", errno, Curl_strerror(errno, buffer, sizeof(buffer))); return CURLE_FAILED_INIT; @@ -947,8 +1076,8 @@ static CURLcode set_local_ip(struct Curl_cfilter *cf, } #else (void)data; - ctx->l_ip[0] = 0; - ctx->l_port = -1; + ctx->ip.local_ip[0] = 0; + ctx->ip.local_port = -1; #endif return CURLE_OK; } @@ -959,13 +1088,14 @@ static CURLcode set_remote_ip(struct Curl_cfilter *cf, struct cf_socket_ctx *ctx = cf->ctx; /* store remote address and port used in this connection attempt */ - if(!Curl_addr2string(&ctx->addr.sa_addr, ctx->addr.addrlen, - ctx->r_ip, &ctx->r_port)) { + if(!Curl_addr2string(&ctx->addr.curl_sa_addr, + (curl_socklen_t)ctx->addr.addrlen, + ctx->ip.remote_ip, &ctx->ip.remote_port)) { char buffer[STRERROR_LEN]; ctx->error = errno; /* malformed address or bug in inet_ntop, try next address */ - failf(data, "sa_addr inet_ntop() failed with errno %d: %s", + failf(data, "curl_sa_addr inet_ntop() failed with errno %d: %s", errno, Curl_strerror(errno, buffer, sizeof(buffer))); return CURLE_FAILED_INIT; } @@ -984,7 +1114,20 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, (void)data; DEBUGASSERT(ctx->sock == CURL_SOCKET_BAD); ctx->started_at = Curl_now(); +#ifdef SOCK_NONBLOCK + /* Do not tuck SOCK_NONBLOCK into socktype when opensocket callback is set + * because we would not know how socketype is about to be used in the + * callback, SOCK_NONBLOCK might get factored out before calling socket(). + */ + if(!data->set.fopensocket) + ctx->addr.socktype |= SOCK_NONBLOCK; +#endif result = socket_open(data, &ctx->addr, &ctx->sock); +#ifdef SOCK_NONBLOCK + /* Restore the socktype after the socket is created. */ + if(!data->set.fopensocket) + ctx->addr.socktype &= ~SOCK_NONBLOCK; +#endif if(result) goto out; @@ -992,22 +1135,16 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, if(result) goto out; -#ifndef CURL_DISABLE_VERBOSE_STRINGS - { - const char *ipmsg; -#ifdef ENABLE_IPV6 - if(ctx->addr.family == AF_INET6) { - set_ipv6_v6only(ctx->sock, 0); - ipmsg = " Trying [%s]:%d..."; - } - else -#endif - ipmsg = " Trying %s:%d..."; - infof(data, ipmsg, ctx->r_ip, ctx->r_port); +#ifdef USE_IPV6 + if(ctx->addr.family == AF_INET6) { + set_ipv6_v6only(ctx->sock, 0); + infof(data, " Trying [%s]:%d...", ctx->ip.remote_ip, ctx->ip.remote_port); } + else #endif + infof(data, " Trying %s:%d...", ctx->ip.remote_ip, ctx->ip.remote_port); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 is_tcp = (ctx->addr.family == AF_INET || ctx->addr.family == AF_INET6) && ctx->addr.socktype == SOCK_STREAM; @@ -1020,18 +1157,18 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, nosigpipe(data, ctx->sock); - Curl_sndbufset(ctx->sock); + Curl_sndbuf_init(ctx->sock); if(is_tcp && data->set.tcp_keepalive) tcpkeepalive(data, ctx->sock); if(data->set.fsockopt) { /* activate callback for setting socket options */ - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); error = data->set.fsockopt(data->set.sockopt_client, ctx->sock, CURLSOCKTYPE_IPCXN); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); if(error == CURL_SOCKOPT_ALREADY_CONNECTED) isconnected = TRUE; @@ -1044,12 +1181,12 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, #ifndef CURL_DISABLE_BINDLOCAL /* possibly bind the local end to an IP, interface or port */ if(ctx->addr.family == AF_INET -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 || ctx->addr.family == AF_INET6 #endif ) { result = bindlocal(data, cf->conn, ctx->sock, ctx->addr.family, - Curl_ipv6_scope(&ctx->addr.sa_addr)); + Curl_ipv6_scope(&ctx->addr.curl_sa_addr)); if(result) { if(result == CURLE_UNSUPPORTED_PROTOCOL) { /* The address family is not supported on this interface. @@ -1061,9 +1198,28 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, } #endif - /* set socket non-blocking */ - (void)curlx_nonblock(ctx->sock, TRUE); - +#ifndef SOCK_NONBLOCK + /* Set socket non-blocking, must be a non-blocking socket for + * a non-blocking connect. */ + error = curlx_nonblock(ctx->sock, TRUE); + if(error < 0) { + result = CURLE_UNSUPPORTED_PROTOCOL; + ctx->error = SOCKERRNO; + goto out; + } +#else + if(data->set.fopensocket) { + /* Set socket non-blocking, must be a non-blocking socket for + * a non-blocking connect. */ + error = curlx_nonblock(ctx->sock, TRUE); + if(error < 0) { + result = CURLE_UNSUPPORTED_PROTOCOL; + ctx->error = SOCKERRNO; + goto out; + } + } +#endif + ctx->sock_connected = (ctx->addr.socktype != SOCK_DGRAM); out: if(result) { if(ctx->sock != CURL_SOCKET_BAD) { @@ -1076,7 +1232,7 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, ctx->connected_at = Curl_now(); cf->connected = TRUE; } - CURL_TRC_CF(data, cf, "cf_socket_open() -> %d, fd=%" CURL_FORMAT_SOCKET_T, + CURL_TRC_CF(data, cf, "cf_socket_open() -> %d, fd=%" FMT_SOCKET_T, result, ctx->sock); return result; } @@ -1102,7 +1258,7 @@ static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data, endpoints.sae_srcif = 0; endpoints.sae_srcaddr = NULL; endpoints.sae_srcaddrlen = 0; - endpoints.sae_dstaddr = &ctx->addr.sa_addr; + endpoints.sae_dstaddr = &ctx->addr.curl_sa_addr; endpoints.sae_dstaddrlen = ctx->addr.addrlen; rc = connectx(ctx->sock, &endpoints, SAE_ASSOCID_ANY, @@ -1110,27 +1266,28 @@ static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data, NULL, 0, NULL, NULL); } else { - rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, ctx->addr.addrlen); } # else - rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, ctx->addr.addrlen); # endif /* HAVE_BUILTIN_AVAILABLE */ #elif defined(TCP_FASTOPEN_CONNECT) /* Linux >= 4.11 */ if(setsockopt(ctx->sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, (void *)&optval, sizeof(optval)) < 0) - infof(data, "Failed to enable TCP Fast Open on fd %" - CURL_FORMAT_SOCKET_T, ctx->sock); + infof(data, "Failed to enable TCP Fast Open on fd %" FMT_SOCKET_T, + ctx->sock); - rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, ctx->addr.addrlen); #elif defined(MSG_FASTOPEN) /* old Linux */ if(cf->conn->given->flags & PROTOPT_SSL) - rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, ctx->addr.addrlen); else rc = 0; /* Do nothing */ #endif } else { - rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, + (curl_socklen_t)ctx->addr.addrlen); } return rc; } @@ -1153,8 +1310,9 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, if(blocking) return CURLE_UNSUPPORTED_PROTOCOL; - *done = FALSE; /* a very negative world view is best */ + *done = FALSE; /* a negative world view is best */ if(ctx->sock == CURL_SOCKET_BAD) { + int error; result = cf_socket_open(cf, data); if(result) @@ -1167,8 +1325,12 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, /* Connect TCP socket */ rc = do_connect(cf, data, cf->conn->bits.tcp_fastopen); + error = SOCKERRNO; + set_local_ip(cf, data); + CURL_TRC_CF(data, cf, "local address %s port %d...", + ctx->ip.local_ip, ctx->ip.local_port); if(-1 == rc) { - result = socket_connect_result(data, ctx->r_ip, SOCKERRNO); + result = socket_connect_result(data, ctx->ip.remote_ip, error); goto out; } } @@ -1206,13 +1368,15 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, out: if(result) { if(ctx->error) { + set_local_ip(cf, data); data->state.os_errno = ctx->error; SET_SOCKERRNO(ctx->error); #ifndef CURL_DISABLE_VERBOSE_STRINGS { char buffer[STRERROR_LEN]; - infof(data, "connect to %s port %u failed: %s", - ctx->r_ip, ctx->r_port, + infof(data, "connect to %s port %u from %s port %d failed: %s", + ctx->ip.remote_ip, ctx->ip.remote_port, + ctx->ip.local_ip, ctx->ip.local_port, Curl_strerror(ctx->error, buffer, sizeof(buffer))); } #endif @@ -1232,26 +1396,40 @@ static void cf_socket_get_host(struct Curl_cfilter *cf, const char **pdisplay_host, int *pport) { + struct cf_socket_ctx *ctx = cf->ctx; (void)data; *phost = cf->conn->host.name; *pdisplay_host = cf->conn->host.dispname; - *pport = cf->conn->port; + *pport = ctx->ip.remote_port; } -static int cf_socket_get_select_socks(struct Curl_cfilter *cf, +static void cf_socket_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) + struct easy_pollset *ps) { struct cf_socket_ctx *ctx = cf->ctx; - int rc = GETSOCK_BLANK; - (void)data; - if(!cf->connected && ctx->sock != CURL_SOCKET_BAD) { - socks[0] = ctx->sock; - rc |= GETSOCK_WRITESOCK(0); + if(ctx->sock != CURL_SOCKET_BAD) { + /* A listening socket filter needs to be connected before the accept + * for some weird FTP interaction. This should be rewritten, so that + * FTP no longer does the socket checks and accept calls and delegates + * all that to the filter. TODO. */ + if(ctx->listening) { + Curl_pollset_set_in_only(data, ps, ctx->sock); + CURL_TRC_CF(data, cf, "adjust_pollset, listening, POLLIN fd=%" + FMT_SOCKET_T, ctx->sock); + } + else if(!cf->connected) { + Curl_pollset_set_out_only(data, ps, ctx->sock); + CURL_TRC_CF(data, cf, "adjust_pollset, !connected, POLLOUT fd=%" + FMT_SOCKET_T, ctx->sock); + } + else if(!ctx->active) { + Curl_pollset_add_in(data, ps, ctx->sock); + CURL_TRC_CF(data, cf, "adjust_pollset, !active, POLLIN fd=%" + FMT_SOCKET_T, ctx->sock); + } } - - return rc; } static bool cf_socket_data_pending(struct Curl_cfilter *cf, @@ -1261,21 +1439,46 @@ static bool cf_socket_data_pending(struct Curl_cfilter *cf, int readable; (void)data; - if(!Curl_bufq_is_empty(&ctx->recvbuf)) - return TRUE; - readable = SOCKET_READABLE(ctx->sock, 0); return (readable > 0 && (readable & CURL_CSELECT_IN)); } +#ifdef USE_WINSOCK + +#ifndef SIO_IDEAL_SEND_BACKLOG_QUERY +#define SIO_IDEAL_SEND_BACKLOG_QUERY 0x4004747B +#endif + +static void win_update_sndbuf_size(struct cf_socket_ctx *ctx) +{ + ULONG ideal; + DWORD ideallen; + struct curltime n = Curl_now(); + + if(Curl_timediff(n, ctx->last_sndbuf_query_at) > 1000) { + if(!WSAIoctl(ctx->sock, SIO_IDEAL_SEND_BACKLOG_QUERY, 0, 0, + &ideal, sizeof(ideal), &ideallen, 0, 0) && + ideal != ctx->sndbuf_size && + !setsockopt(ctx->sock, SOL_SOCKET, SO_SNDBUF, + (const char *)&ideal, sizeof(ideal))) { + ctx->sndbuf_size = ideal; + } + ctx->last_sndbuf_query_at = n; + } +} + +#endif /* USE_WINSOCK */ + static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { struct cf_socket_ctx *ctx = cf->ctx; curl_socket_t fdsave; ssize_t nwritten; size_t orig_len = len; + (void)eos; /* unused */ *err = CURLE_OK; fdsave = cf->conn->sock[cf->sockindex]; cf->conn->sock[cf->sockindex] = ctx->sock; @@ -1283,8 +1486,8 @@ static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, #ifdef DEBUGBUILD /* simulate network blocking/partial writes */ if(ctx->wblock_percent > 0) { - unsigned char c; - Curl_rand(data, &c, 1); + unsigned char c = 0; + Curl_rand_bytes(data, FALSE, &c, 1); if(c >= ((100-ctx->wblock_percent)*256/100)) { CURL_TRC_CF(data, cf, "send(len=%zu) SIMULATE EWOULDBLOCK", orig_len); *err = CURLE_AGAIN; @@ -1305,7 +1508,7 @@ static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, #if defined(MSG_FASTOPEN) && !defined(TCP_FASTOPEN_CONNECT) /* Linux */ if(cf->conn->bits.tcp_fastopen) { nwritten = sendto(ctx->sock, buf, len, MSG_FASTOPEN, - &cf->conn->remote_addr->sa_addr, + &cf->conn->remote_addr->curl_sa_addr, cf->conn->remote_addr->addrlen); cf->conn->bits.tcp_fastopen = FALSE; } @@ -1340,6 +1543,11 @@ static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, } } +#if defined(USE_WINSOCK) + if(!*err) + win_update_sndbuf_size(ctx); +#endif + CURL_TRC_CF(data, cf, "send(len=%zu) -> %d, err=%d", orig_len, (int)nwritten, *err); cf->conn->sock[cf->sockindex] = fdsave; @@ -1350,98 +1558,78 @@ static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { struct cf_socket_ctx *ctx = cf->ctx; - curl_socket_t fdsave; ssize_t nread; *err = CURLE_OK; - fdsave = cf->conn->sock[cf->sockindex]; - cf->conn->sock[cf->sockindex] = ctx->sock; - - if(ctx->buffer_recv && !Curl_bufq_is_empty(&ctx->recvbuf)) { - CURL_TRC_CF(data, cf, "recv from buffer"); - nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err); +#ifdef DEBUGBUILD + /* simulate network blocking/partial reads */ + if(cf->cft != &Curl_cft_udp && ctx->rblock_percent > 0) { + unsigned char c = 0; + Curl_rand(data, &c, 1); + if(c >= ((100-ctx->rblock_percent)*256/100)) { + CURL_TRC_CF(data, cf, "recv(len=%zu) SIMULATE EWOULDBLOCK", len); + *err = CURLE_AGAIN; + return -1; + } } - else { - struct reader_ctx rctx; - - rctx.cf = cf; - rctx.data = data; - - /* "small" reads may trigger filling our buffer, "large" reads - * are probably not worth the additional copy */ - if(ctx->buffer_recv && len < NW_SMALL_READS) { - ssize_t nwritten; - nwritten = Curl_bufq_slurp(&ctx->recvbuf, nw_in_read, &rctx, err); - if(nwritten < 0 && !Curl_bufq_is_empty(&ctx->recvbuf)) { - /* we have a partial read with an error. need to deliver - * what we got, return the error later. */ - CURL_TRC_CF(data, cf, "partial read: empty buffer first"); - nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err); - } - else if(nwritten < 0) { - nread = -1; - goto out; - } - else if(nwritten == 0) { - /* eof */ - *err = CURLE_OK; - nread = 0; - } - else { - CURL_TRC_CF(data, cf, "buffered %zd additional bytes", nwritten); - nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err); - } + if(cf->cft != &Curl_cft_udp && ctx->recv_max && ctx->recv_max < len) { + size_t orig_len = len; + len = ctx->recv_max; + CURL_TRC_CF(data, cf, "recv(len=%zu) SIMULATE max read of %zu bytes", + orig_len, len); + } +#endif + + *err = CURLE_OK; + nread = sread(ctx->sock, buf, len); + + if(-1 == nread) { + int sockerr = SOCKERRNO; + + if( +#ifdef WSAEWOULDBLOCK + /* This is how Windows does it */ + (WSAEWOULDBLOCK == sockerr) +#else + /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned + due to its inability to send off data without blocking. We therefore + treat both error codes the same here */ + (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr) +#endif + ) { + /* this is just a case of EWOULDBLOCK */ + *err = CURLE_AGAIN; } else { - nread = nw_in_read(&rctx, (unsigned char *)buf, len, err); + char buffer[STRERROR_LEN]; + + failf(data, "Recv failure: %s", + Curl_strerror(sockerr, buffer, sizeof(buffer))); + data->state.os_errno = sockerr; + *err = CURLE_RECV_ERROR; } } -out: CURL_TRC_CF(data, cf, "recv(len=%zu) -> %d, err=%d", len, (int)nread, *err); if(nread > 0 && !ctx->got_first_byte) { ctx->first_byte_at = Curl_now(); ctx->got_first_byte = TRUE; } - cf->conn->sock[cf->sockindex] = fdsave; return nread; } -static void conn_set_primary_ip(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void cf_socket_update_data(struct Curl_cfilter *cf, + struct Curl_easy *data) { -#ifdef HAVE_GETPEERNAME - struct cf_socket_ctx *ctx = cf->ctx; - if(!(data->conn->handler->protocol & CURLPROTO_TFTP)) { - /* TFTP does not connect the endpoint: getpeername() failed with errno - 107: Transport endpoint is not connected */ - - char buffer[STRERROR_LEN]; - struct Curl_sockaddr_storage ssrem; - curl_socklen_t plen; - int port; - - plen = sizeof(ssrem); - memset(&ssrem, 0, plen); - if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) { - int error = SOCKERRNO; - failf(data, "getpeername() failed with errno %d: %s", - error, Curl_strerror(error, buffer, sizeof(buffer))); - return; - } - if(!Curl_addr2string((struct sockaddr*)&ssrem, plen, - cf->conn->primary_ip, &port)) { - failf(data, "ssrem inet_ntop() failed with errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - return; - } + /* Update the IP info held in the transfer, if we have that. */ + if(cf->connected && (cf->sockindex == FIRSTSOCKET)) { + struct cf_socket_ctx *ctx = cf->ctx; + data->info.primary = ctx->ip; + /* not sure if this is redundant... */ + data->info.conn_remote_port = cf->conn->remote_port; } -#else - cf->conn->primary_ip[0] = 0; - (void)data; -#endif } static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -1450,20 +1638,16 @@ static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data) /* use this socket from now on */ cf->conn->sock[cf->sockindex] = ctx->sock; - /* the first socket info gets set at conn and data */ + set_local_ip(cf, data); if(cf->sockindex == FIRSTSOCKET) { + cf->conn->primary = ctx->ip; cf->conn->remote_addr = &ctx->addr; - #ifdef ENABLE_IPV6 - cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE; + #ifdef USE_IPV6 + cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6); #endif - conn_set_primary_ip(cf, data); - set_local_ip(cf, data); - Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port); - /* buffering is currently disabled by default because we have stalls - * in parallel transfers where not all buffered data is consumed and no - * socket events happen. - */ - ctx->buffer_recv = FALSE; + } + else { + cf->conn->secondary = ctx->ip; } ctx->active = TRUE; } @@ -1479,9 +1663,13 @@ static CURLcode cf_socket_cntrl(struct Curl_cfilter *cf, switch(event) { case CF_CTRL_CONN_INFO_UPDATE: cf_socket_active(cf, data); + cf_socket_update_data(cf, data); break; case CF_CTRL_DATA_SETUP: - Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port); + cf_socket_update_data(cf, data); + break; + case CF_CTRL_FORGET_SOCKET: + ctx->sock = CURL_SOCKET_BAD; break; } return CURLE_OK; @@ -1538,7 +1726,7 @@ static CURLcode cf_socket_query(struct Curl_cfilter *cf, case CF_QUERY_CONNECT_REPLY_MS: if(ctx->got_first_byte) { timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at); - *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX; + *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; } else *pres1 = -1; @@ -1554,17 +1742,25 @@ static CURLcode cf_socket_query(struct Curl_cfilter *cf, *when = ctx->first_byte_at; break; } - /* FALLTHROUGH */ + FALLTHROUGH(); default: *when = ctx->connected_at; break; } return CURLE_OK; } + case CF_QUERY_IP_INFO: +#ifdef USE_IPV6 + *pres1 = (ctx->addr.family == AF_INET6); +#else + *pres1 = FALSE; +#endif + *(struct ip_quadruple *)pres2 = ctx->ip; + return CURLE_OK; default: break; } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -1576,8 +1772,9 @@ struct Curl_cftype Curl_cft_tcp = { cf_socket_destroy, cf_tcp_connect, cf_socket_close, + cf_socket_shutdown, cf_socket_get_host, - cf_socket_get_select_socks, + cf_socket_adjust_pollset, cf_socket_data_pending, cf_socket_send, cf_socket_recv, @@ -1600,7 +1797,7 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, (void)data; (void)conn; DEBUGASSERT(transport == TRNSPRT_TCP); - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -1610,7 +1807,7 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, result = Curl_cf_create(&cf, &Curl_cft_tcp, ctx); out: - *pcf = (!result)? cf : NULL; + *pcf = (!result) ? cf : NULL; if(result) { Curl_safefree(cf); Curl_safefree(ctx); @@ -1620,25 +1817,35 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, } static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data) { struct cf_socket_ctx *ctx = cf->ctx; int rc; + int one = 1; + + (void)one; /* QUIC needs a connected socket, nonblocking */ DEBUGASSERT(ctx->sock != CURL_SOCKET_BAD); - rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, + (curl_socklen_t)ctx->addr.addrlen); if(-1 == rc) { - return socket_connect_result(data, ctx->r_ip, SOCKERRNO); + return socket_connect_result(data, ctx->ip.remote_ip, SOCKERRNO); } + ctx->sock_connected = TRUE; set_local_ip(cf, data); - CURL_TRC_CF(data, cf, "%s socket %" CURL_FORMAT_SOCKET_T + CURL_TRC_CF(data, cf, "%s socket %" FMT_SOCKET_T " connected: [%s:%d] -> [%s:%d]", - (ctx->transport == TRNSPRT_QUIC)? "QUIC" : "UDP", - ctx->sock, ctx->l_ip, ctx->l_port, ctx->r_ip, ctx->r_port); - - (void)curlx_nonblock(ctx->sock, TRUE); + (ctx->transport == TRNSPRT_QUIC) ? "QUIC" : "UDP", + ctx->sock, ctx->ip.local_ip, ctx->ip.local_port, + ctx->ip.remote_ip, ctx->ip.remote_port); + + /* Currently, cf->ctx->sock is always non-blocking because the only + * caller to cf_udp_setup_quic() is cf_udp_connect() that passes the + * non-blocking socket created by cf_socket_open() to it. Thus, we + * do not need to call curlx_nonblock() in cf_udp_setup_quic() anymore. + */ switch(ctx->addr.family) { #if defined(__linux__) && defined(IP_MTU_DISCOVER) case AF_INET: { @@ -1657,6 +1864,14 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, } #endif } + +#if defined(__linux__) && defined(UDP_GRO) && \ + (defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG)) && \ + ((defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || defined(USE_QUICHE)) + (void)setsockopt(ctx->sock, IPPROTO_UDP, UDP_GRO, &one, + (socklen_t)sizeof(one)); +#endif + return CURLE_OK; } @@ -1685,12 +1900,12 @@ static CURLcode cf_udp_connect(struct Curl_cfilter *cf, if(result) goto out; CURL_TRC_CF(data, cf, "cf_udp_connect(), opened socket=%" - CURL_FORMAT_SOCKET_T " (%s:%d)", - ctx->sock, ctx->l_ip, ctx->l_port); + FMT_SOCKET_T " (%s:%d)", + ctx->sock, ctx->ip.local_ip, ctx->ip.local_port); } else { CURL_TRC_CF(data, cf, "cf_udp_connect(), opened socket=%" - CURL_FORMAT_SOCKET_T " (unconnected)", ctx->sock); + FMT_SOCKET_T " (unconnected)", ctx->sock); } *done = TRUE; cf->connected = TRUE; @@ -1706,8 +1921,9 @@ struct Curl_cftype Curl_cft_udp = { cf_socket_destroy, cf_udp_connect, cf_socket_close, + cf_socket_shutdown, cf_socket_get_host, - cf_socket_get_select_socks, + cf_socket_adjust_pollset, cf_socket_data_pending, cf_socket_send, cf_socket_recv, @@ -1730,7 +1946,7 @@ CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, (void)data; (void)conn; DEBUGASSERT(transport == TRNSPRT_UDP || transport == TRNSPRT_QUIC); - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -1740,7 +1956,7 @@ CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, result = Curl_cf_create(&cf, &Curl_cft_udp, ctx); out: - *pcf = (!result)? cf : NULL; + *pcf = (!result) ? cf : NULL; if(result) { Curl_safefree(cf); Curl_safefree(ctx); @@ -1757,8 +1973,9 @@ struct Curl_cftype Curl_cft_unix = { cf_socket_destroy, cf_tcp_connect, cf_socket_close, + cf_socket_shutdown, cf_socket_get_host, - cf_socket_get_select_socks, + cf_socket_adjust_pollset, cf_socket_data_pending, cf_socket_send, cf_socket_recv, @@ -1781,7 +1998,7 @@ CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, (void)data; (void)conn; DEBUGASSERT(transport == TRNSPRT_UNIX); - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -1791,7 +2008,7 @@ CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, result = Curl_cf_create(&cf, &Curl_cft_unix, ctx); out: - *pcf = (!result)? cf : NULL; + *pcf = (!result) ? cf : NULL; if(result) { Curl_safefree(cf); Curl_safefree(ctx); @@ -1800,10 +2017,84 @@ CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, return result; } +static timediff_t cf_tcp_accept_timeleft(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + timediff_t timeout_ms = DEFAULT_ACCEPT_TIMEOUT; + timediff_t other; + struct curltime now; + +#ifndef CURL_DISABLE_FTP + if(data->set.accepttimeout > 0) + timeout_ms = data->set.accepttimeout; +#endif + + now = Curl_now(); + /* check if the generic timeout possibly is set shorter */ + other = Curl_timeleft(data, &now, FALSE); + if(other && (other < timeout_ms)) + /* note that this also works fine for when other happens to be negative + due to it already having elapsed */ + timeout_ms = other; + else { + /* subtract elapsed time */ + timeout_ms -= Curl_timediff(now, ctx->started_at); + if(!timeout_ms) + /* avoid returning 0 as that means no timeout! */ + timeout_ms = -1; + } + return timeout_ms; +} + +static void cf_tcp_set_accepted_remote_ip(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; +#ifdef HAVE_GETPEERNAME + char buffer[STRERROR_LEN]; + struct Curl_sockaddr_storage ssrem; + curl_socklen_t plen; + + ctx->ip.remote_ip[0] = 0; + ctx->ip.remote_port = 0; + plen = sizeof(ssrem); + memset(&ssrem, 0, plen); + if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) { + int error = SOCKERRNO; + failf(data, "getpeername() failed with errno %d: %s", + error, Curl_strerror(error, buffer, sizeof(buffer))); + return; + } + if(!Curl_addr2string((struct sockaddr*)&ssrem, plen, + ctx->ip.remote_ip, &ctx->ip.remote_port)) { + failf(data, "ssrem inet_ntop() failed with errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + return; + } +#else + ctx->ip.remote_ip[0] = 0; + ctx->ip.remote_port = 0; + (void)data; +#endif +} + static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool blocking, bool *done) { + struct cf_socket_ctx *ctx = cf->ctx; +#ifdef USE_IPV6 + struct Curl_sockaddr_storage add; +#else + struct sockaddr_in add; +#endif + curl_socklen_t size = (curl_socklen_t) sizeof(add); + curl_socket_t s_accepted = CURL_SOCKET_BAD; + timediff_t timeout_ms; + int socketstate = 0; + bool incoming = FALSE; + /* we start accepted, if we ever close, we cannot go on */ (void)data; (void)blocking; @@ -1811,7 +2102,79 @@ static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf, *done = TRUE; return CURLE_OK; } - return CURLE_FAILED_INIT; + + timeout_ms = cf_tcp_accept_timeleft(cf, data); + if(timeout_ms < 0) { + /* if a timeout was already reached, bail out */ + failf(data, "Accept timeout occurred while waiting server connect"); + return CURLE_FTP_ACCEPT_TIMEOUT; + } + + CURL_TRC_CF(data, cf, "Checking for incoming on fd=%" FMT_SOCKET_T + " ip=%s:%d", ctx->sock, ctx->ip.local_ip, ctx->ip.local_port); + socketstate = Curl_socket_check(ctx->sock, CURL_SOCKET_BAD, + CURL_SOCKET_BAD, 0); + CURL_TRC_CF(data, cf, "socket_check -> %x", socketstate); + switch(socketstate) { + case -1: /* error */ + /* let's die here */ + failf(data, "Error while waiting for server connect"); + return CURLE_FTP_ACCEPT_FAILED; + default: + if(socketstate & CURL_CSELECT_IN) { + infof(data, "Ready to accept data connection from server"); + incoming = TRUE; + } + break; + } + + if(!incoming) { + CURL_TRC_CF(data, cf, "nothing heard from the server yet"); + *done = FALSE; + return CURLE_OK; + } + + if(0 == getsockname(ctx->sock, (struct sockaddr *) &add, &size)) { + size = sizeof(add); + s_accepted = accept(ctx->sock, (struct sockaddr *) &add, &size); + } + + if(CURL_SOCKET_BAD == s_accepted) { + failf(data, "Error accept()ing server connect"); + return CURLE_FTP_PORT_FAILED; + } + + infof(data, "Connection accepted from server"); + (void)curlx_nonblock(s_accepted, TRUE); /* enable non-blocking */ + /* Replace any filter on SECONDARY with one listening on this socket */ + ctx->listening = FALSE; + ctx->accepted = TRUE; + socket_close(data, cf->conn, TRUE, ctx->sock); + ctx->sock = s_accepted; + + cf->conn->sock[cf->sockindex] = ctx->sock; + cf_tcp_set_accepted_remote_ip(cf, data); + set_local_ip(cf, data); + ctx->active = TRUE; + ctx->connected_at = Curl_now(); + cf->connected = TRUE; + CURL_TRC_CF(data, cf, "accepted_set(sock=%" FMT_SOCKET_T + ", remote=%s port=%d)", + ctx->sock, ctx->ip.remote_ip, ctx->ip.remote_port); + + if(data->set.fsockopt) { + int error = 0; + + /* activate callback for setting socket options */ + Curl_set_in_callback(data, true); + error = data->set.fsockopt(data->set.sockopt_client, + ctx->sock, CURLSOCKTYPE_ACCEPT); + Curl_set_in_callback(data, false); + + if(error) + return CURLE_ABORTED_BY_CALLBACK; + } + return CURLE_OK; } struct Curl_cftype Curl_cft_tcp_accept = { @@ -1821,8 +2184,9 @@ struct Curl_cftype Curl_cft_tcp_accept = { cf_socket_destroy, cf_tcp_accept_connect, cf_socket_close, + cf_socket_shutdown, cf_socket_get_host, /* TODO: not accurate */ - cf_socket_get_select_socks, + cf_socket_adjust_pollset, cf_socket_data_pending, cf_socket_send, cf_socket_recv, @@ -1844,26 +2208,26 @@ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, Curl_conn_cf_discard_all(data, conn, sockindex); DEBUGASSERT(conn->sock[sockindex] == CURL_SOCKET_BAD); - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } ctx->transport = conn->transport; ctx->sock = *s; + ctx->listening = TRUE; ctx->accepted = FALSE; result = Curl_cf_create(&cf, &Curl_cft_tcp_accept, ctx); if(result) goto out; Curl_conn_cf_add(data, conn, sockindex, cf); + ctx->started_at = Curl_now(); conn->sock[sockindex] = ctx->sock; set_local_ip(cf, data); - ctx->active = TRUE; - ctx->connected_at = Curl_now(); - cf->connected = TRUE; - CURL_TRC_CF(data, cf, "Curl_conn_tcp_listen_set(%" - CURL_FORMAT_SOCKET_T ")", ctx->sock); + CURL_TRC_CF(data, cf, "set filter for listen socket fd=%" FMT_SOCKET_T + " ip=%s:%d", ctx->sock, + ctx->ip.local_ip, ctx->ip.local_port); out: if(result) { @@ -1873,65 +2237,16 @@ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, return result; } -static void set_accepted_remote_ip(struct Curl_cfilter *cf, - struct Curl_easy *data) +bool Curl_conn_is_tcp_listen(struct Curl_easy *data, + int sockindex) { - struct cf_socket_ctx *ctx = cf->ctx; -#ifdef HAVE_GETPEERNAME - char buffer[STRERROR_LEN]; - struct Curl_sockaddr_storage ssrem; - curl_socklen_t plen; - - ctx->r_ip[0] = 0; - ctx->r_port = 0; - plen = sizeof(ssrem); - memset(&ssrem, 0, plen); - if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) { - int error = SOCKERRNO; - failf(data, "getpeername() failed with errno %d: %s", - error, Curl_strerror(error, buffer, sizeof(buffer))); - return; + struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; + while(cf) { + if(cf->cft == &Curl_cft_tcp_accept) + return TRUE; + cf = cf->next; } - if(!Curl_addr2string((struct sockaddr*)&ssrem, plen, - ctx->r_ip, &ctx->r_port)) { - failf(data, "ssrem inet_ntop() failed with errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - return; - } -#else - ctx->r_ip[0] = 0; - ctx->r_port = 0; - (void)data; -#endif -} - -CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, curl_socket_t *s) -{ - struct Curl_cfilter *cf = NULL; - struct cf_socket_ctx *ctx = NULL; - - cf = conn->cfilter[sockindex]; - if(!cf || cf->cft != &Curl_cft_tcp_accept) - return CURLE_FAILED_INIT; - - ctx = cf->ctx; - /* discard the listen socket */ - socket_close(data, conn, TRUE, ctx->sock); - ctx->sock = *s; - conn->sock[sockindex] = ctx->sock; - set_accepted_remote_ip(cf, data); - set_local_ip(cf, data); - ctx->active = TRUE; - ctx->accepted = TRUE; - ctx->connected_at = Curl_now(); - cf->connected = TRUE; - CURL_TRC_CF(data, cf, "accepted_set(sock=%" CURL_FORMAT_SOCKET_T - ", remote=%s port=%d)", - ctx->sock, ctx->r_ip, ctx->r_port); - - return CURLE_OK; + return FALSE; } /** @@ -1949,9 +2264,9 @@ CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf, struct Curl_easy *data, curl_socket_t *psock, const struct Curl_sockaddr_ex **paddr, - const char **pr_ip_str, int *pr_port, - const char **pl_ip_str, int *pl_port) + struct ip_quadruple *pip) { + (void)data; if(cf_is_socket(cf) && cf->ctx) { struct cf_socket_ctx *ctx = cf->ctx; @@ -1959,17 +2274,8 @@ CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf, *psock = ctx->sock; if(paddr) *paddr = &ctx->addr; - if(pr_ip_str) - *pr_ip_str = ctx->r_ip; - if(pr_port) - *pr_port = ctx->r_port; - if(pl_port ||pl_ip_str) { - set_local_ip(cf, data); - if(pl_ip_str) - *pl_ip_str = ctx->l_ip; - if(pl_port) - *pl_port = ctx->l_port; - } + if(pip) + *pip = ctx->ip; return CURLE_OK; } return CURLE_FAILED_INIT; diff --git a/deps/curl/lib/cf-socket.h b/deps/curl/lib/cf-socket.h index 1d40df737fc..651034901cd 100644 --- a/deps/curl/lib/cf-socket.h +++ b/deps/curl/lib/cf-socket.h @@ -33,23 +33,7 @@ struct Curl_cfilter; struct Curl_easy; struct connectdata; struct Curl_sockaddr_ex; - -#ifndef SIZEOF_CURL_SOCKET_T -/* configure and cmake check and set the define */ -# ifdef _WIN64 -# define SIZEOF_CURL_SOCKET_T 8 -# else -/* default guess */ -# define SIZEOF_CURL_SOCKET_T 4 -# endif -#endif - -#if SIZEOF_CURL_SOCKET_T < 8 -# define CURL_FORMAT_SOCKET_T "d" -#else -# define CURL_FORMAT_SOCKET_T "qd" -#endif - +struct ip_quadruple; /* * The Curl_sockaddr_ex structure is basically libcurl's external API @@ -68,8 +52,13 @@ struct Curl_sockaddr_ex { struct Curl_sockaddr_storage buff; } _sa_ex_u; }; -#define sa_addr _sa_ex_u.addr +#define curl_sa_addr _sa_ex_u.addr +/* + * Parse interface option, and return the interface name and the host part. +*/ +CURLcode Curl_parse_interface(const char *input, + char **dev, char **iface, char **host); /* * Create a socket based on info from 'conn' and 'ai'. @@ -97,9 +86,9 @@ int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn, Buffer Size */ -void Curl_sndbufset(curl_socket_t sockfd); +void Curl_sndbuf_init(curl_socket_t sockfd); #else -#define Curl_sndbufset(y) Curl_nop_stmt +#define Curl_sndbuf_init(y) Curl_nop_stmt #endif /** @@ -158,30 +147,25 @@ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, curl_socket_t *s); /** - * Replace the listen socket with the accept()ed one. + * Return TRUE iff the last filter at `sockindex` was set via + * Curl_conn_tcp_listen_set(). */ -CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - curl_socket_t *s); +bool Curl_conn_is_tcp_listen(struct Curl_easy *data, + int sockindex); /** * Peek at the socket and remote ip/port the socket filter is using. * The filter owns all returned values. * @param psock pointer to hold socket descriptor or NULL * @param paddr pointer to hold addr reference or NULL - * @param pr_ip_str pointer to hold remote addr as string or NULL - * @param pr_port pointer to hold remote port number or NULL - * @param pl_ip_str pointer to hold local addr as string or NULL - * @param pl_port pointer to hold local port number or NULL + * @param pip pointer to get IP quadruple or NULL * Returns error if the filter is of invalid type. */ CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf, struct Curl_easy *data, curl_socket_t *psock, const struct Curl_sockaddr_ex **paddr, - const char **pr_ip_str, int *pr_port, - const char **pl_ip_str, int *pl_port); + struct ip_quadruple *pip); extern struct Curl_cftype Curl_cft_tcp; extern struct Curl_cftype Curl_cft_udp; diff --git a/deps/curl/lib/cfilters.c b/deps/curl/lib/cfilters.c index f74eb400397..ca1d7b33558 100644 --- a/deps/curl/lib/cfilters.c +++ b/deps/curl/lib/cfilters.c @@ -33,6 +33,7 @@ #include "sockaddr.h" /* required for Curl_sockaddr_storage */ #include "multiif.h" #include "progress.h" +#include "select.h" #include "warnless.h" /* The last 3 #include files should be in this order */ @@ -44,7 +45,10 @@ #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) #endif -#ifdef DEBUGBUILD +static void cf_cntrl_update_info(struct Curl_easy *data, + struct connectdata *conn); + +#ifdef UNITTESTS /* used by unit2600.c */ void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -54,6 +58,15 @@ void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data) } #endif +CURLcode Curl_cf_def_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + (void)cf; + (void)data; + *done = TRUE; + return CURLE_OK; +} + static void conn_report_connect_stats(struct Curl_easy *data, struct connectdata *conn); @@ -66,37 +79,40 @@ void Curl_cf_def_get_host(struct Curl_cfilter *cf, struct Curl_easy *data, else { *phost = cf->conn->host.name; *pdisplay_host = cf->conn->host.dispname; - *pport = cf->conn->port; + *pport = cf->conn->primary.remote_port; } } -int Curl_cf_def_get_select_socks(struct Curl_cfilter *cf, +void Curl_cf_def_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) + struct easy_pollset *ps) { - return cf->next? - cf->next->cft->get_select_socks(cf->next, data, socks) : 0; + /* NOP */ + (void)cf; + (void)data; + (void)ps; } bool Curl_cf_def_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - return cf->next? + return cf->next ? cf->next->cft->has_data_pending(cf->next, data) : FALSE; } ssize_t Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { - return cf->next? - cf->next->cft->do_send(cf->next, data, buf, len, err) : + return cf->next ? + cf->next->cft->do_send(cf->next, data, buf, len, eos, err) : CURLE_RECV_ERROR; } ssize_t Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { - return cf->next? + return cf->next ? cf->next->cft->do_recv(cf->next, data, buf, len, err) : CURLE_SEND_ERROR; } @@ -105,7 +121,7 @@ bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data, bool *input_pending) { - return cf->next? + return cf->next ? cf->next->cft->is_alive(cf->next, data, input_pending) : FALSE; /* pessimistic in absence of data */ } @@ -113,7 +129,7 @@ bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf, CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf, struct Curl_easy *data) { - return cf->next? + return cf->next ? cf->next->cft->keep_alive(cf->next, data) : CURLE_OK; } @@ -122,7 +138,7 @@ CURLcode Curl_cf_def_query(struct Curl_cfilter *cf, struct Curl_easy *data, int query, int *pres1, void *pres2) { - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -163,40 +179,105 @@ void Curl_conn_close(struct Curl_easy *data, int index) if(cf) { cf->cft->do_close(cf, data); } + Curl_shutdown_clear(data, index); } -ssize_t Curl_conn_recv(struct Curl_easy *data, int num, char *buf, - size_t len, CURLcode *code) +CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + timediff_t timeout_ms; + struct curltime now; + + DEBUGASSERT(data->conn); + /* Get the first connected filter that is not shut down already. */ + cf = data->conn->cfilter[sockindex]; + while(cf && (!cf->connected || cf->shutdown)) + cf = cf->next; + + if(!cf) { + *done = TRUE; + return CURLE_OK; + } + + *done = FALSE; + now = Curl_now(); + if(!Curl_shutdown_started(data, sockindex)) { + DEBUGF(infof(data, "shutdown start on%s connection", + sockindex ? " secondary" : "")); + Curl_shutdown_start(data, sockindex, &now); + } + else { + timeout_ms = Curl_shutdown_timeleft(data->conn, sockindex, &now); + if(timeout_ms < 0) { + /* info message, since this might be regarded as acceptable */ + infof(data, "shutdown timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + } + + while(cf) { + if(!cf->shutdown) { + bool cfdone = FALSE; + result = cf->cft->do_shutdown(cf, data, &cfdone); + if(result) { + CURL_TRC_CF(data, cf, "shut down failed with %d", result); + return result; + } + else if(!cfdone) { + CURL_TRC_CF(data, cf, "shut down not done yet"); + return CURLE_OK; + } + CURL_TRC_CF(data, cf, "shut down successfully"); + cf->shutdown = TRUE; + } + cf = cf->next; + } + *done = (!result); + return result; +} + +ssize_t Curl_cf_recv(struct Curl_easy *data, int num, char *buf, + size_t len, CURLcode *code) { struct Curl_cfilter *cf; DEBUGASSERT(data); DEBUGASSERT(data->conn); + *code = CURLE_OK; cf = data->conn->cfilter[num]; while(cf && !cf->connected) { cf = cf->next; } if(cf) { - return cf->cft->do_recv(cf, data, buf, len, code); + ssize_t nread = cf->cft->do_recv(cf, data, buf, len, code); + DEBUGASSERT(nread >= 0 || *code); + DEBUGASSERT(nread < 0 || !*code); + return nread; } failf(data, "recv: no filter connected"); *code = CURLE_FAILED_INIT; return -1; } -ssize_t Curl_conn_send(struct Curl_easy *data, int num, - const void *mem, size_t len, CURLcode *code) +ssize_t Curl_cf_send(struct Curl_easy *data, int num, + const void *mem, size_t len, bool eos, + CURLcode *code) { struct Curl_cfilter *cf; DEBUGASSERT(data); DEBUGASSERT(data->conn); + *code = CURLE_OK; cf = data->conn->cfilter[num]; while(cf && !cf->connected) { cf = cf->next; } if(cf) { - return cf->cft->do_send(cf, data, mem, len, code); + ssize_t nwritten = cf->cft->do_send(cf, data, mem, len, eos, code); + DEBUGASSERT(nwritten >= 0 || *code); + DEBUGASSERT(nwritten < 0 || !*code || !len); + return nwritten; } failf(data, "send: no filter connected"); DEBUGASSERT(0); @@ -212,7 +293,7 @@ CURLcode Curl_cf_create(struct Curl_cfilter **pcf, CURLcode result = CURLE_OUT_OF_MEMORY; DEBUGASSERT(cft); - cf = calloc(sizeof(*cf), 1); + cf = calloc(1, sizeof(*cf)); if(!cf) goto out; @@ -303,20 +384,12 @@ void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) cf->cft->do_close(cf, data); } -int Curl_conn_cf_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) -{ - if(cf) - return cf->cft->get_select_socks(cf, data, socks); - return 0; -} - ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { if(cf) - return cf->cft->do_send(cf, data, buf, len, err); + return cf->cft->do_send(cf, data, buf, len, eos, err); *err = CURLE_SEND_ERROR; return -1; } @@ -343,16 +416,29 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, cf = data->conn->cfilter[sockindex]; DEBUGASSERT(cf); - if(!cf) + if(!cf) { + *done = FALSE; return CURLE_FAILED_INIT; + } *done = cf->connected; if(!*done) { + if(Curl_conn_needs_flush(data, sockindex)) { + DEBUGF(infof(data, "Curl_conn_connect(index=%d), flush", sockindex)); + result = Curl_conn_flush(data, sockindex); + if(result && (result != CURLE_AGAIN)) + return result; + } + result = cf->cft->do_connect(cf, data, blocking, done); if(!result && *done) { - Curl_conn_ev_update_info(data, data->conn); + /* Now that the complete filter chain is connected, let all filters + * persist information at the connection. E.g. cf-socket sets the + * socket and ip related information. */ + cf_cntrl_update_info(data, data->conn); conn_report_connect_stats(data, data->conn); data->conn->keepalive = Curl_now(); + Curl_verboseconnect(data, data->conn, sockindex); } else if(result) { conn_report_connect_stats(data, data->conn); @@ -398,12 +484,12 @@ bool Curl_conn_cf_is_ssl(struct Curl_cfilter *cf) bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex) { - return conn? Curl_conn_cf_is_ssl(conn->cfilter[sockindex]) : FALSE; + return conn ? Curl_conn_cf_is_ssl(conn->cfilter[sockindex]) : FALSE; } bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex) { - struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + struct Curl_cfilter *cf = conn ? conn->cfilter[sockindex] : NULL; for(; cf; cf = cf->next) { if(cf->cft->flags & CF_TYPE_MULTIPLEX) @@ -433,22 +519,85 @@ bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex) return FALSE; } -int Curl_conn_get_select_socks(struct Curl_easy *data, int sockindex, - curl_socket_t *socks) +bool Curl_conn_cf_needs_flush(struct Curl_cfilter *cf, + struct Curl_easy *data) { - struct Curl_cfilter *cf; + CURLcode result; + int pending = 0; + result = cf ? cf->cft->query(cf, data, CF_QUERY_NEED_FLUSH, + &pending, NULL) : CURLE_UNKNOWN_OPTION; + return (result || !pending) ? FALSE : TRUE; +} + +bool Curl_conn_needs_flush(struct Curl_easy *data, int sockindex) +{ + return Curl_conn_cf_needs_flush(data->conn->cfilter[sockindex], data); +} + +void Curl_conn_cf_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + /* Get the lowest not-connected filter, if there are any */ + while(cf && !cf->connected && cf->next && !cf->next->connected) + cf = cf->next; + /* Skip all filters that have already shut down */ + while(cf && cf->shutdown) + cf = cf->next; + /* From there on, give all filters a chance to adjust the pollset. + * Lower filters are called later, so they may override */ + while(cf) { + cf->cft->adjust_pollset(cf, data, ps); + cf = cf->next; + } +} + +void Curl_conn_adjust_pollset(struct Curl_easy *data, + struct easy_pollset *ps) +{ + int i; DEBUGASSERT(data); DEBUGASSERT(data->conn); - cf = data->conn->cfilter[sockindex]; + for(i = 0; i < 2; ++i) { + Curl_conn_cf_adjust_pollset(data->conn->cfilter[i], data, ps); + } +} - /* if the next one is not yet connected, that's the one we want */ - while(cf && cf->next && !cf->next->connected) - cf = cf->next; - if(cf) { - return cf->cft->get_select_socks(cf, data, socks); +int Curl_conn_cf_poll(struct Curl_cfilter *cf, + struct Curl_easy *data, + timediff_t timeout_ms) +{ + struct easy_pollset ps; + struct pollfd pfds[MAX_SOCKSPEREASYHANDLE]; + unsigned int i, npfds = 0; + + DEBUGASSERT(cf); + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + memset(&ps, 0, sizeof(ps)); + memset(pfds, 0, sizeof(pfds)); + + Curl_conn_cf_adjust_pollset(cf, data, &ps); + DEBUGASSERT(ps.num <= MAX_SOCKSPEREASYHANDLE); + for(i = 0; i < ps.num; ++i) { + short events = 0; + if(ps.actions[i] & CURL_POLL_IN) { + events |= POLLIN; + } + if(ps.actions[i] & CURL_POLL_OUT) { + events |= POLLOUT; + } + if(events) { + pfds[npfds].fd = ps.sockets[i]; + pfds[npfds].events = events; + ++npfds; + } } - return GETSOCK_BLANK; + + if(!npfds) + DEBUGF(infof(data, "no sockets to poll!")); + return Curl_poll(pfds, npfds, timeout_ms); } void Curl_conn_get_host(struct Curl_easy *data, int sockindex, @@ -511,17 +660,38 @@ curl_socket_t Curl_conn_cf_get_socket(struct Curl_cfilter *cf, return CURL_SOCKET_BAD; } +CURLcode Curl_conn_cf_get_ip_info(struct Curl_cfilter *cf, + struct Curl_easy *data, + int *is_ipv6, struct ip_quadruple *ipquad) +{ + if(cf) + return cf->cft->query(cf, data, CF_QUERY_IP_INFO, is_ipv6, ipquad); + return CURLE_UNKNOWN_OPTION; +} + curl_socket_t Curl_conn_get_socket(struct Curl_easy *data, int sockindex) { struct Curl_cfilter *cf; - cf = data->conn? data->conn->cfilter[sockindex] : NULL; + cf = data->conn ? data->conn->cfilter[sockindex] : NULL; /* if the top filter has not connected, ask it (and its sub-filters) * for the socket. Otherwise conn->sock[sockindex] should have it. */ if(cf && !cf->connected) return Curl_conn_cf_get_socket(cf, data); - return data->conn? data->conn->sock[sockindex] : CURL_SOCKET_BAD; + return data->conn ? data->conn->sock[sockindex] : CURL_SOCKET_BAD; +} + +void Curl_conn_forget_socket(struct Curl_easy *data, int sockindex) +{ + if(data->conn) { + struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; + if(cf) + (void)Curl_conn_cf_cntrl(cf, data, TRUE, + CF_CTRL_FORGET_SOCKET, 0, NULL); + fake_sclose(data->conn->sock[sockindex]); + data->conn->sock[sockindex] = CURL_SOCKET_BAD; + } } static CURLcode cf_cntrl_all(struct connectdata *conn, @@ -565,9 +735,16 @@ CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data) CF_CTRL_DATA_IDLE, 0, NULL); } + +CURLcode Curl_conn_flush(struct Curl_easy *data, int sockindex) +{ + return Curl_conn_cf_cntrl(data->conn->cfilter[sockindex], data, FALSE, + CF_CTRL_FLUSH, 0, NULL); +} + /** * Notify connection filters that the transfer represented by `data` - * is donw with sending data (e.g. has uploaded everything). + * is done with sending data (e.g. has uploaded everything). */ void Curl_conn_ev_data_done_send(struct Curl_easy *data) { @@ -589,8 +766,8 @@ CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause) CF_CTRL_DATA_PAUSE, do_pause, NULL); } -void Curl_conn_ev_update_info(struct Curl_easy *data, - struct connectdata *conn) +static void cf_cntrl_update_info(struct Curl_easy *data, + struct connectdata *conn) { cf_cntrl_all(conn, data, TRUE, CF_CTRL_CONN_INFO_UPDATE, 0, NULL); } @@ -631,7 +808,7 @@ CURLcode Curl_conn_keep_alive(struct Curl_easy *data, int sockindex) { struct Curl_cfilter *cf = conn->cfilter[sockindex]; - return cf? cf->cft->keep_alive(cf, data) : CURLE_OK; + return cf ? cf->cft->keep_alive(cf, data) : CURLE_OK; } size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, @@ -642,7 +819,203 @@ size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, int n = 0; struct Curl_cfilter *cf = conn->cfilter[sockindex]; - result = cf? cf->cft->query(cf, data, CF_QUERY_MAX_CONCURRENT, - &n, NULL) : CURLE_UNKNOWN_OPTION; - return (result || n <= 0)? 1 : (size_t)n; + result = cf ? cf->cft->query(cf, data, CF_QUERY_MAX_CONCURRENT, + &n, NULL) : CURLE_UNKNOWN_OPTION; + return (result || n <= 0) ? 1 : (size_t)n; +} + +int Curl_conn_get_stream_error(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + CURLcode result; + int n = 0; + + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + result = cf ? cf->cft->query(cf, data, CF_QUERY_STREAM_ERROR, + &n, NULL) : CURLE_UNKNOWN_OPTION; + return (result || n < 0) ? 0 : n; +} + +int Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd) +{ + if(data && data->conn && + sockfd != CURL_SOCKET_BAD && sockfd == data->conn->sock[SECONDARYSOCKET]) + return SECONDARYSOCKET; + return FIRSTSOCKET; +} + +CURLcode Curl_conn_recv(struct Curl_easy *data, int sockindex, + char *buf, size_t blen, ssize_t *n) +{ + CURLcode result = CURLE_OK; + ssize_t nread; + + DEBUGASSERT(data->conn); + nread = data->conn->recv[sockindex](data, sockindex, buf, blen, &result); + DEBUGASSERT(nread >= 0 || result); + DEBUGASSERT(nread < 0 || !result); + *n = (nread >= 0) ? (size_t)nread : 0; + return result; +} + +CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex, + const void *buf, size_t blen, bool eos, + size_t *pnwritten) +{ + size_t write_len = blen; + ssize_t nwritten; + CURLcode result = CURLE_OK; + struct connectdata *conn; + + DEBUGASSERT(sockindex >= 0 && sockindex < 2); + DEBUGASSERT(pnwritten); + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + conn = data->conn; +#ifdef DEBUGBUILD + { + /* Allow debug builds to override this logic to force short sends + */ + char *p = getenv("CURL_SMALLSENDS"); + if(p) { + size_t altsize = (size_t)strtoul(p, NULL, 10); + if(altsize) + write_len = CURLMIN(write_len, altsize); + } + } +#endif + if(write_len != blen) + eos = FALSE; + nwritten = conn->send[sockindex](data, sockindex, buf, write_len, eos, + &result); + DEBUGASSERT((nwritten >= 0) || result); + *pnwritten = (nwritten < 0) ? 0 : (size_t)nwritten; + return result; +} + +void Curl_pollset_reset(struct Curl_easy *data, + struct easy_pollset *ps) +{ + size_t i; + (void)data; + memset(ps, 0, sizeof(*ps)); + for(i = 0; i < MAX_SOCKSPEREASYHANDLE; i++) + ps->sockets[i] = CURL_SOCKET_BAD; +} + +/** + * + */ +void Curl_pollset_change(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + int add_flags, int remove_flags) +{ + unsigned int i; + + (void)data; + DEBUGASSERT(VALID_SOCK(sock)); + if(!VALID_SOCK(sock)) + return; + + DEBUGASSERT(add_flags <= (CURL_POLL_IN|CURL_POLL_OUT)); + DEBUGASSERT(remove_flags <= (CURL_POLL_IN|CURL_POLL_OUT)); + DEBUGASSERT((add_flags&remove_flags) == 0); /* no overlap */ + for(i = 0; i < ps->num; ++i) { + if(ps->sockets[i] == sock) { + ps->actions[i] &= (unsigned char)(~remove_flags); + ps->actions[i] |= (unsigned char)add_flags; + /* all gone? remove socket */ + if(!ps->actions[i]) { + if((i + 1) < ps->num) { + memmove(&ps->sockets[i], &ps->sockets[i + 1], + (ps->num - (i + 1)) * sizeof(ps->sockets[0])); + memmove(&ps->actions[i], &ps->actions[i + 1], + (ps->num - (i + 1)) * sizeof(ps->actions[0])); + } + --ps->num; + } + return; + } + } + /* not present */ + if(add_flags) { + /* Having more SOCKETS per easy handle than what is defined + * is a programming error. This indicates that we need + * to raise this limit, making easy_pollset larger. + * Since we use this in tight loops, we do not want to make + * the pollset dynamic unnecessarily. + * The current maximum in practise is HTTP/3 eyeballing where + * we have up to 4 sockets involved in connection setup. + */ + DEBUGASSERT(i < MAX_SOCKSPEREASYHANDLE); + if(i < MAX_SOCKSPEREASYHANDLE) { + ps->sockets[i] = sock; + ps->actions[i] = (unsigned char)add_flags; + ps->num = i + 1; + } + } +} + +void Curl_pollset_set(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + bool do_in, bool do_out) +{ + Curl_pollset_change(data, ps, sock, + (do_in ? CURL_POLL_IN : 0)| + (do_out ? CURL_POLL_OUT : 0), + (!do_in ? CURL_POLL_IN : 0)| + (!do_out ? CURL_POLL_OUT : 0)); +} + +static void ps_add(struct Curl_easy *data, struct easy_pollset *ps, + int bitmap, curl_socket_t *socks) +{ + if(bitmap) { + int i; + for(i = 0; i < MAX_SOCKSPEREASYHANDLE; ++i) { + if(!(bitmap & GETSOCK_MASK_RW(i)) || !VALID_SOCK((socks[i]))) { + break; + } + if(bitmap & GETSOCK_READSOCK(i)) { + if(bitmap & GETSOCK_WRITESOCK(i)) + Curl_pollset_add_inout(data, ps, socks[i]); + else + /* is READ, since we checked MASK_RW above */ + Curl_pollset_add_in(data, ps, socks[i]); + } + else + Curl_pollset_add_out(data, ps, socks[i]); + } + } +} + +void Curl_pollset_add_socks(struct Curl_easy *data, + struct easy_pollset *ps, + int (*get_socks_cb)(struct Curl_easy *data, + curl_socket_t *socks)) +{ + curl_socket_t socks[MAX_SOCKSPEREASYHANDLE]; + int bitmap; + + bitmap = get_socks_cb(data, socks); + ps_add(data, ps, bitmap, socks); +} + +void Curl_pollset_check(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + bool *pwant_read, bool *pwant_write) +{ + unsigned int i; + + (void)data; + DEBUGASSERT(VALID_SOCK(sock)); + for(i = 0; i < ps->num; ++i) { + if(ps->sockets[i] == sock) { + *pwant_read = !!(ps->actions[i] & CURL_POLL_IN); + *pwant_write = !!(ps->actions[i] & CURL_POLL_OUT); + return; + } + } + *pwant_read = *pwant_write = FALSE; } diff --git a/deps/curl/lib/cfilters.h b/deps/curl/lib/cfilters.h index 2c65264d983..af696f52a57 100644 --- a/deps/curl/lib/cfilters.h +++ b/deps/curl/lib/cfilters.h @@ -24,11 +24,13 @@ * ***************************************************************************/ +#include "timediff.h" struct Curl_cfilter; struct Curl_easy; struct Curl_dns_entry; struct connectdata; +struct ip_quadruple; /* Callback to destroy resources held by this filter instance. * Implementations MUST NOT chain calls to cf->next. @@ -36,9 +38,17 @@ struct connectdata; typedef void Curl_cft_destroy_this(struct Curl_cfilter *cf, struct Curl_easy *data); +/* Callback to close the connection immediately. */ typedef void Curl_cft_close(struct Curl_cfilter *cf, struct Curl_easy *data); +/* Callback to close the connection filter gracefully, non-blocking. + * Implementations MUST NOT chain calls to cf->next. + */ +typedef CURLcode Curl_cft_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done); + typedef CURLcode Curl_cft_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool blocking, bool *done); @@ -60,14 +70,34 @@ typedef void Curl_cft_get_host(struct Curl_cfilter *cf, const char **pdisplay_host, int *pport); -/* Filters may return sockets and fdset flags they are waiting for. - * The passes array has room for up to MAX_SOCKSPEREASYHANDLE sockets. - * @return read/write fdset for index in socks - * or GETSOCK_BLANK when nothing to wait on +struct easy_pollset; + +/* Passing in an easy_pollset for monitoring of sockets, let + * filters add or remove sockets actions (CURL_POLL_OUT, CURL_POLL_IN). + * This may add a socket or, in case no actions remain, remove + * a socket from the set. + * + * Filter implementations need to call filters "below" *after* they have + * made their adjustments. This allows lower filters to override "upper" + * actions. If a "lower" filter is unable to write, it needs to be able + * to disallow POLL_OUT. + * + * A filter without own restrictions/preferences should not modify + * the pollset. Filters, whose filter "below" is not connected, should + * also do no adjustments. + * + * Examples: a TLS handshake, while ongoing, might remove POLL_IN when it + * needs to write, or vice versa. An HTTP/2 filter might remove POLL_OUT when + * a stream window is exhausted and a WINDOW_UPDATE needs to be received first + * and add instead POLL_IN. + * + * @param cf the filter to ask + * @param data the easy handle the pollset is about + * @param ps the pollset (inout) for the easy handle */ -typedef int Curl_cft_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks); +typedef void Curl_cft_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps); typedef bool Curl_cft_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data); @@ -76,6 +106,7 @@ typedef ssize_t Curl_cft_send(struct Curl_cfilter *cf, struct Curl_easy *data, /* transfer */ const void *buf, /* data to write */ size_t len, /* amount to write */ + bool eos, /* last chunk */ CURLcode *err); /* error to return */ typedef ssize_t Curl_cft_recv(struct Curl_cfilter *cf, @@ -110,6 +141,8 @@ typedef CURLcode Curl_cft_conn_keep_alive(struct Curl_cfilter *cf, #define CF_CTRL_DATA_DONE_SEND 8 /* 0 NULL ignored */ /* update conn info at connection and data */ #define CF_CTRL_CONN_INFO_UPDATE (256+0) /* 0 NULL ignored */ +#define CF_CTRL_FORGET_SOCKET (256+1) /* 0 NULL ignored */ +#define CF_CTRL_FLUSH (256+2) /* 0 NULL first fail */ /** * Handle event/control for the filter. @@ -132,6 +165,9 @@ typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf, * were received. * -1 if not determined yet. * - CF_QUERY_SOCKET: the socket used by the filter chain + * - CF_QUERY_NEED_FLUSH: TRUE iff any of the filters have unsent data + * - CF_QUERY_IP_INFO: res1 says if connection used IPv6, res2 is the + * ip quadruple */ /* query res1 res2 */ #define CF_QUERY_MAX_CONCURRENT 1 /* number - */ @@ -139,6 +175,9 @@ typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf, #define CF_QUERY_SOCKET 3 /* - curl_socket_t */ #define CF_QUERY_TIMER_CONNECT 4 /* - struct curltime */ #define CF_QUERY_TIMER_APPCONNECT 5 /* - struct curltime */ +#define CF_QUERY_STREAM_ERROR 6 /* error code - */ +#define CF_QUERY_NEED_FLUSH 7 /* TRUE/FALSE - */ +#define CF_QUERY_IP_INFO 8 /* TRUE/FALSE struct ip_quadruple */ /** * Query the cfilter for properties. Filters ignorant of a query will @@ -157,10 +196,12 @@ typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf, * connection, etc. * CF_TYPE_SSL: provide SSL/TLS * CF_TYPE_MULTIPLEX: provides multiplexing of easy handles + * CF_TYPE_PROXY provides proxying */ #define CF_TYPE_IP_CONNECT (1 << 0) #define CF_TYPE_SSL (1 << 1) #define CF_TYPE_MULTIPLEX (1 << 2) +#define CF_TYPE_PROXY (1 << 3) /* A connection filter type, e.g. specific implementation. */ struct Curl_cftype { @@ -170,8 +211,9 @@ struct Curl_cftype { Curl_cft_destroy_this *destroy; /* destroy resources of this cf */ Curl_cft_connect *do_connect; /* establish connection */ Curl_cft_close *do_close; /* close conn */ + Curl_cft_shutdown *do_shutdown; /* shutdown conn */ Curl_cft_get_host *get_host; /* host filter talks to */ - Curl_cft_get_select_socks *get_select_socks;/* sockets to select on */ + Curl_cft_adjust_pollset *adjust_pollset; /* adjust transfer poll set */ Curl_cft_data_pending *has_data_pending;/* conn has data pending */ Curl_cft_send *do_send; /* send data */ Curl_cft_recv *do_recv; /* receive data */ @@ -189,6 +231,7 @@ struct Curl_cfilter { struct connectdata *conn; /* the connection this filter belongs to */ int sockindex; /* the index the filter is installed at */ BIT(connected); /* != 0 iff this filter is connected */ + BIT(shutdown); /* != 0 iff this filter has shut down */ }; /* Default implementations for the type functions, implementing nop. */ @@ -200,13 +243,14 @@ void Curl_cf_def_destroy_this(struct Curl_cfilter *cf, void Curl_cf_def_get_host(struct Curl_cfilter *cf, struct Curl_easy *data, const char **phost, const char **pdisplay_host, int *pport); -int Curl_cf_def_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks); +void Curl_cf_def_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps); bool Curl_cf_def_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data); ssize_t Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err); + const void *buf, size_t len, bool eos, + CURLcode *err); ssize_t Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err); CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf, @@ -220,6 +264,8 @@ CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf, CURLcode Curl_cf_def_query(struct Curl_cfilter *cf, struct Curl_easy *data, int query, int *pres1, void *pres2); +CURLcode Curl_cf_def_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done); /** * Create a new filter instance, unattached to the filter chain. @@ -279,11 +325,9 @@ CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool blocking, bool *done); void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data); -int Curl_conn_cf_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks); ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err); + const void *buf, size_t len, bool eos, + CURLcode *err); ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err); CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf, @@ -304,6 +348,12 @@ bool Curl_conn_cf_is_ssl(struct Curl_cfilter *cf); curl_socket_t Curl_conn_cf_get_socket(struct Curl_cfilter *cf, struct Curl_easy *data); +CURLcode Curl_conn_cf_get_ip_info(struct Curl_cfilter *cf, + struct Curl_easy *data, + int *is_ipv6, struct ip_quadruple *ipquad); + +bool Curl_conn_cf_needs_flush(struct Curl_cfilter *cf, + struct Curl_easy *data); #define CURL_CF_SSL_DEFAULT -1 #define CURL_CF_SSL_DISABLE 0 @@ -350,6 +400,13 @@ bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex); */ void Curl_conn_close(struct Curl_easy *data, int sockindex); +/** + * Shutdown the connection at `sockindex` non-blocking, using timeout + * from `data->set.shutdowntimeout`, default DEFAULT_SHUTDOWN_TIMEOUT_MS. + * Will return CURLE_OK and *done == FALSE if not finished. + */ +CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done); + /** * Return if data is pending in some connection filter at chain * `sockindex` for connection `data->conn`. @@ -357,6 +414,17 @@ void Curl_conn_close(struct Curl_easy *data, int sockindex); bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex); +/** + * Return TRUE if any of the connection filters at chain `sockindex` + * have data still to send. + */ +bool Curl_conn_needs_flush(struct Curl_easy *data, int sockindex); + +/** + * Flush any pending data on the connection filters at chain `sockindex`. + */ +CURLcode Curl_conn_flush(struct Curl_easy *data, int sockindex); + /** * Return the socket used on data's connection for the index. * Returns CURL_SOCKET_BAD if not available. @@ -364,20 +432,40 @@ bool Curl_conn_data_pending(struct Curl_easy *data, curl_socket_t Curl_conn_get_socket(struct Curl_easy *data, int sockindex); /** - * Get any select fd flags and the socket filters at chain `sockindex` - * at connection `conn` might be waiting for. + * Tell filters to forget about the socket at sockindex. */ -int Curl_conn_get_select_socks(struct Curl_easy *data, int sockindex, - curl_socket_t *socks); +void Curl_conn_forget_socket(struct Curl_easy *data, int sockindex); + +/** + * Adjust the pollset for the filter chain startgin at `cf`. + */ +void Curl_conn_cf_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps); + +/** + * Adjust pollset from filters installed at transfer's connection. + */ +void Curl_conn_adjust_pollset(struct Curl_easy *data, + struct easy_pollset *ps); + +/** + * Curl_poll() the filter chain at `cf` with timeout `timeout_ms`. + * Returns 0 on timeout, negative on error or number of sockets + * with requested poll events. + */ +int Curl_conn_cf_poll(struct Curl_cfilter *cf, + struct Curl_easy *data, + timediff_t timeout_ms); /** * Receive data through the filter chain at `sockindex` for connection * `data->conn`. Copy at most `len` bytes into `buf`. Return the - * actuel number of bytes copied or a negative value on error. + * actual number of bytes copied or a negative value on error. * The error code is placed into `*code`. */ -ssize_t Curl_conn_recv(struct Curl_easy *data, int sockindex, char *buf, - size_t len, CURLcode *code); +ssize_t Curl_cf_recv(struct Curl_easy *data, int sockindex, char *buf, + size_t len, CURLcode *code); /** * Send `len` bytes of data from `buf` through the filter chain `sockindex` @@ -385,8 +473,8 @@ ssize_t Curl_conn_recv(struct Curl_easy *data, int sockindex, char *buf, * or a negative value on error. * The error code is placed into `*code`. */ -ssize_t Curl_conn_send(struct Curl_easy *data, int sockindex, - const void *buf, size_t len, CURLcode *code); +ssize_t Curl_cf_send(struct Curl_easy *data, int sockindex, + const void *buf, size_t len, bool eos, CURLcode *code); /** * The easy handle `data` is being attached to `conn`. This does @@ -420,7 +508,7 @@ CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data); /** * Notify connection filters that the transfer represented by `data` - * is donw with sending data (e.g. has uploaded everything). + * is done with sending data (e.g. has uploaded everything). */ void Curl_conn_ev_data_done_send(struct Curl_easy *data); @@ -435,12 +523,6 @@ void Curl_conn_ev_data_done(struct Curl_easy *data, bool premature); */ CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause); -/** - * Inform connection filters to update their info in `conn`. - */ -void Curl_conn_ev_update_info(struct Curl_easy *data, - struct connectdata *conn); - /** * Check if FIRSTSOCKET's cfilter chain deems connection alive. */ @@ -454,7 +536,9 @@ CURLcode Curl_conn_keep_alive(struct Curl_easy *data, struct connectdata *conn, int sockindex); +#ifdef UNITTESTS void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data); +#endif void Curl_conn_get_host(struct Curl_easy *data, int sockindex, const char **phost, const char **pdisplay_host, int *pport); @@ -467,6 +551,79 @@ size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, struct connectdata *conn, int sockindex); +/** + * Get the underlying error code for a transfer stream or 0 if not known. + */ +int Curl_conn_get_stream_error(struct Curl_easy *data, + struct connectdata *conn, + int sockindex); + +/** + * Get the index of the given socket in the connection's sockets. + * Useful in calling `Curl_conn_send()/Curl_conn_recv()` with the + * correct socket index. + */ +int Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd); + +/* + * Receive data on the connection, using FIRSTSOCKET/SECONDARYSOCKET. + * Will return CURLE_AGAIN iff blocked on receiving. + */ +CURLcode Curl_conn_recv(struct Curl_easy *data, int sockindex, + char *buf, size_t buffersize, + ssize_t *pnread); + +/* + * Send data on the connection, using FIRSTSOCKET/SECONDARYSOCKET. + * Will return CURLE_AGAIN iff blocked on sending. + */ +CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex, + const void *buf, size_t blen, bool eos, + size_t *pnwritten); + + +void Curl_pollset_reset(struct Curl_easy *data, + struct easy_pollset *ps); + +/* Change the poll flags (CURL_POLL_IN/CURL_POLL_OUT) to the poll set for + * socket `sock`. If the socket is not already part of the poll set, it + * will be added. + * If the socket is present and all poll flags are cleared, it will be removed. + */ +void Curl_pollset_change(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + int add_flags, int remove_flags); + +void Curl_pollset_set(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + bool do_in, bool do_out); + +#define Curl_pollset_add_in(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), CURL_POLL_IN, 0) +#define Curl_pollset_add_out(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), CURL_POLL_OUT, 0) +#define Curl_pollset_add_inout(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), \ + CURL_POLL_IN|CURL_POLL_OUT, 0) +#define Curl_pollset_set_in_only(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), \ + CURL_POLL_IN, CURL_POLL_OUT) +#define Curl_pollset_set_out_only(data, ps, sock) \ + Curl_pollset_change((data), (ps), (sock), \ + CURL_POLL_OUT, CURL_POLL_IN) + +void Curl_pollset_add_socks(struct Curl_easy *data, + struct easy_pollset *ps, + int (*get_socks_cb)(struct Curl_easy *data, + curl_socket_t *socks)); + +/** + * Check if the pollset, as is, wants to read and/or write regarding + * the given socket. + */ +void Curl_pollset_check(struct Curl_easy *data, + struct easy_pollset *ps, curl_socket_t sock, + bool *pwant_read, bool *pwant_write); /** * Types and macros used to keep the current easy handle in filter calls, diff --git a/deps/curl/lib/config-amigaos.h b/deps/curl/lib/config-amigaos.h index a6a518abb32..11e5999493d 100644 --- a/deps/curl/lib/config-amigaos.h +++ b/deps/curl/lib/config-amigaos.h @@ -32,7 +32,6 @@ #define HAVE_ARPA_INET_H 1 #define HAVE_CLOSESOCKET_CAMEL 1 -#define HAVE_INTTYPES_H 1 #define HAVE_IOCTLSOCKET_CAMEL 1 #define HAVE_IOCTLSOCKET_CAMEL_FIONBIO 1 #define HAVE_LONGLONG 1 @@ -41,15 +40,12 @@ #define HAVE_NET_IF_H 1 #define HAVE_PWD_H 1 #define HAVE_SELECT 1 -#define HAVE_SETJMP_H 1 #define HAVE_SIGNAL 1 -#define HAVE_SIGNAL_H 1 #define HAVE_SOCKET 1 #define HAVE_STRCASECMP 1 #define HAVE_STRDUP 1 #define HAVE_STRICMP 1 #define HAVE_STRINGS_H 1 -#define HAVE_STRING_H 1 #define HAVE_STRUCT_TIMEVAL 1 #define HAVE_SYS_PARAM_H 1 #define HAVE_SYS_SOCKET_H 1 @@ -57,7 +53,6 @@ #define HAVE_SYS_STAT_H 1 #define HAVE_SYS_TIME_H 1 #define HAVE_SYS_TYPES_H 1 -#define HAVE_TIME_H 1 #define HAVE_UNISTD_H 1 #define HAVE_UTIME 1 #define HAVE_UTIME_H 1 @@ -76,8 +71,8 @@ #define USE_MANUAL 1 #define CURL_DISABLE_LDAP 1 -#ifndef OS -#define OS "AmigaOS" +#ifndef CURL_OS +#define CURL_OS "AmigaOS" #endif #define PACKAGE "curl" @@ -96,7 +91,6 @@ #endif #define STDC_HEADERS 1 -#define TIME_WITH_SYS_TIME 1 #define in_addr_t int diff --git a/deps/curl/lib/config-dos.h b/deps/curl/lib/config-dos.h index 05c1a81407d..4e9724726e5 100644 --- a/deps/curl/lib/config-dos.h +++ b/deps/curl/lib/config-dos.h @@ -29,13 +29,13 @@ /* lib/config-dos.h - Hand crafted config file for DOS */ /* ================================================================ */ -#ifndef OS +#ifndef CURL_OS #if defined(DJGPP) - #define OS "MSDOS/djgpp" + #define CURL_OS "MSDOS/djgpp" #elif defined(__HIGHC__) - #define OS "MSDOS/HighC" + #define CURL_OS "MSDOS/HighC" #else - #define OS "MSDOS/?" + #define CURL_OS "MSDOS/?" #endif #endif @@ -61,7 +61,6 @@ #define HAVE_RECV 1 #define HAVE_SELECT 1 #define HAVE_SEND 1 -#define HAVE_SETJMP_H 1 #define HAVE_SETLOCALE 1 #define HAVE_SETMODE 1 #define HAVE_SIGNAL 1 @@ -74,7 +73,6 @@ #define HAVE_SYS_SOCKET_H 1 #define HAVE_SYS_STAT_H 1 #define HAVE_SYS_TYPES_H 1 -#define HAVE_TIME_H 1 #define HAVE_UNISTD_H 1 #define NEED_MALLOC_H 1 @@ -84,7 +82,6 @@ #define SIZEOF_SIZE_T 4 #define SIZEOF_CURL_OFF_T 8 #define STDC_HEADERS 1 -#define TIME_WITH_SYS_TIME 1 /* Qualifiers for send() and recv() */ @@ -125,7 +122,6 @@ #define HAVE_SIGSETJMP 1 #define HAVE_SYS_TIME_H 1 #define HAVE_TERMIOS_H 1 - #define HAVE_VARIADIC_MACROS_GCC 1 #elif defined(__HIGHC__) #define HAVE_SYS_TIME_H 1 diff --git a/deps/curl/lib/config-mac.h b/deps/curl/lib/config-mac.h index ff6fdb25ae7..8d6210c77e8 100644 --- a/deps/curl/lib/config-mac.h +++ b/deps/curl/lib/config-mac.h @@ -27,11 +27,11 @@ /* =================================================================== */ /* Hand crafted config file for Mac OS 9 */ /* =================================================================== */ -/* On Mac OS X you must run configure to generate curl_config.h file */ +/* On macOS you must run configure to generate curl_config.h file */ /* =================================================================== */ -#ifndef OS -#define OS "mac" +#ifndef CURL_OS +#define CURL_OS "mac" #endif #include @@ -52,15 +52,10 @@ #define HAVE_GETTIMEOFDAY 1 #define HAVE_FCNTL_H 1 #define HAVE_SYS_STAT_H 1 -#define HAVE_STDLIB_H 1 -#define HAVE_TIME_H 1 #define HAVE_UTIME_H 1 #define HAVE_SYS_TIME_H 1 #define HAVE_SYS_UTIME_H 1 #define HAVE_SYS_IOCTL_H 1 - -#define TIME_WITH_SYS_TIME 1 - #define HAVE_ALARM 1 #define HAVE_FTRUNCATE 1 #define HAVE_UTIME 1 @@ -69,7 +64,6 @@ #define HAVE_STRUCT_TIMEVAL 1 #define HAVE_SIGACTION 1 -#define HAVE_SIGNAL_H 1 #ifdef MACOS_SSL_SUPPORT # define USE_OPENSSL 1 diff --git a/deps/curl/lib/config-os400.h b/deps/curl/lib/config-os400.h index 2d0291d16dd..88259d95f4b 100644 --- a/deps/curl/lib/config-os400.h +++ b/deps/curl/lib/config-os400.h @@ -36,8 +36,8 @@ #undef VERSION /* Define cpu-machine-OS */ -#ifndef OS -#define OS "OS/400" +#ifndef CURL_OS +#define CURL_OS "OS/400" #endif /* OS400 supports a 3-argument ASCII version of gethostbyaddr_r(), but its @@ -57,7 +57,7 @@ #undef NEED_REENTRANT /* Define if you want to enable IPv6 support */ -#define ENABLE_IPV6 +#define USE_IPV6 /* Define if struct sockaddr_in6 has the sin6_scope_id member */ #define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 @@ -65,9 +65,6 @@ /* Define this to 'int' if ssize_t is not an available typedefed type */ #undef ssize_t -/* Define this as a suitable file to read random data from */ -#undef RANDOM_FILE - /* Define to 1 if you have the alarm function. */ #define HAVE_ALARM 1 @@ -104,27 +101,15 @@ /* Define if you have the `timeval' struct. */ #define HAVE_STRUCT_TIMEVAL -/* Define if you have the header file. */ -#define HAVE_INTTYPES_H - /* Define if you have the header file. */ #undef HAVE_IO_H -/* Define if you have the `socket' library (-lsocket). */ -#undef HAVE_LIBSOCKET - /* Define if you have GSS API. */ #define HAVE_GSSAPI /* Define if you have the GNU gssapi libraries */ #undef HAVE_GSSGNU -/* Define if you have the Heimdal gssapi libraries */ -#define HAVE_GSSHEIMDAL - -/* Define if you have the MIT gssapi libraries */ -#undef HAVE_GSSMIT - /* Define if you need the malloc.h header file even with stdlib.h */ /* #define NEED_MALLOC_H 1 */ @@ -149,18 +134,9 @@ /* Define if you have the `signal' function. */ #undef HAVE_SIGNAL -/* Define if you have the header file. */ -#define HAVE_SIGNAL_H - /* Define if you have the `socket' function. */ #define HAVE_SOCKET -/* Define if you have the header file. */ -#undef HAVE_STDINT_H - -/* Define if you have the header file. */ -#define HAVE_STDLIB_H - /* The following define is needed on OS400 to enable strcmpi(), stricmp() and strdup(). */ @@ -181,9 +157,6 @@ /* Define if you have the header file. */ #define HAVE_STRINGS_H -/* Define if you have the header file. */ -#define HAVE_STRING_H - /* Define if you have the header file. */ #undef HAVE_STROPTS_H @@ -226,9 +199,6 @@ /* Define if you have the header file. */ #undef HAVE_TERMIO_H -/* Define if you have the header file. */ -#define HAVE_TIME_H - /* Define if you have the header file. */ #define HAVE_UNISTD_H @@ -259,12 +229,9 @@ /* Define if you have the ANSI C header files. */ #define STDC_HEADERS -/* Define if you can safely include both and . */ -#define TIME_WITH_SYS_TIME - -/* Define to enable HTTP3 support (experimental, requires NGTCP2, QUICHE or +/* Define to enable HTTP3 support (experimental, requires NGTCP2, quiche or MSH3) */ -#undef ENABLE_QUIC +#undef USE_HTTP3 /* Version number of package */ #undef VERSION diff --git a/deps/curl/lib/config-plan9.h b/deps/curl/lib/config-plan9.h index 54f89b06cfb..699291a1edd 100644 --- a/deps/curl/lib/config-plan9.h +++ b/deps/curl/lib/config-plan9.h @@ -28,12 +28,12 @@ #define CURL_CA_BUNDLE "/sys/lib/tls/ca.pem" #define CURL_CA_PATH "/sys/lib/tls" #define CURL_STATICLIB 1 -#define ENABLE_IPV6 1 +#define USE_IPV6 1 #define CURL_DISABLE_LDAP 1 #define NEED_REENTRANT 1 -#ifndef OS -#define OS "plan9" +#ifndef CURL_OS +#define CURL_OS "plan9" #endif #define PACKAGE "curl" #define PACKAGE_NAME "curl" @@ -41,7 +41,6 @@ #define PACKAGE_STRING "curl -" #define PACKAGE_TARNAME "curl" #define PACKAGE_VERSION "-" -#define RANDOM_FILE "/dev/random" #define VERSION "0.0.0" /* TODO */ #define STDC_HEADERS 1 @@ -91,7 +90,6 @@ #define HAVE_GMTIME_R 1 #define HAVE_INET_NTOP 1 #define HAVE_INET_PTON 1 -#define HAVE_INTTYPES_H 1 #define HAVE_LIBGEN_H 1 #define HAVE_LIBZ 1 #define HAVE_LOCALE_H 1 @@ -105,25 +103,20 @@ #define USE_OPENSSL 1 #define HAVE_PIPE 1 -#define HAVE_POLL_FINE 1 +#define HAVE_POLL 1 #define HAVE_POLL_H 1 #define HAVE_PTHREAD_H 1 -#define HAVE_SETJMP_H 1 #define HAVE_SETLOCALE 1 #define HAVE_SIGACTION 1 #define HAVE_SIGNAL 1 -#define HAVE_SIGNAL_H 1 #define HAVE_SIGSETJMP 1 #define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 #define HAVE_SOCKET 1 #define HAVE_SSL_GET_SHUTDOWN 1 #define HAVE_STDBOOL_H 1 -#define HAVE_STDINT_H 1 -#define HAVE_STDLIB_H 1 #define HAVE_STRCASECMP 1 #define HAVE_STRDUP 1 -#define HAVE_STRING_H 1 #define HAVE_STRTOK_R 1 #define HAVE_STRTOLL 1 #define HAVE_STRUCT_TIMEVAL 1 @@ -136,15 +129,12 @@ #define HAVE_SYS_TYPES_H 1 #define HAVE_SYS_UN_H 1 #define HAVE_TERMIOS_H 1 -#define HAVE_TIME_H 1 #define HAVE_UNISTD_H 1 #define HAVE_UTIME 1 #define HAVE_UTIME_H 1 #define HAVE_POSIX_STRERROR_R 1 #define HAVE_STRERROR_R 1 - -#define TIME_WITH_SYS_TIME 1 #define USE_MANUAL 1 #define __attribute__(x) diff --git a/deps/curl/lib/config-riscos.h b/deps/curl/lib/config-riscos.h index 94946187d8c..35b28cc5345 100644 --- a/deps/curl/lib/config-riscos.h +++ b/deps/curl/lib/config-riscos.h @@ -35,8 +35,8 @@ #undef VERSION /* Define cpu-machine-OS */ -#ifndef OS -#define OS "ARM-RISC OS" +#ifndef CURL_OS +#define CURL_OS "ARM-RISC OS" #endif /* Define if you want the built-in manual */ @@ -55,7 +55,7 @@ #undef NEED_REENTRANT /* Define if you want to enable IPv6 support */ -#undef ENABLE_IPV6 +#undef USE_IPV6 /* Define if struct sockaddr_in6 has the sin6_scope_id member */ #define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 @@ -63,12 +63,6 @@ /* Define this to 'int' if ssize_t is not an available typedefed type */ #undef ssize_t -/* Define this as a suitable file to read random data from */ -#undef RANDOM_FILE - -/* Define if you want to enable IPv6 support */ -#undef ENABLE_IPV6 - /* Define if you have the alarm function. */ #define HAVE_ALARM @@ -108,15 +102,9 @@ /* Define if you have the `timeval' struct. */ #define HAVE_STRUCT_TIMEVAL -/* Define if you have the header file. */ -#define HAVE_INTTYPES_H - /* Define if you have the header file. */ #undef HAVE_IO_H -/* Define if you have the `socket' library (-lsocket). */ -#undef HAVE_LIBSOCKET - /* Define if you need the malloc.h header file even with stdlib.h */ /* #define NEED_MALLOC_H 1 */ @@ -141,18 +129,9 @@ /* Define if you have the `signal' function. */ #define HAVE_SIGNAL -/* Define if you have the header file. */ -#define HAVE_SIGNAL_H - /* Define if you have the `socket' function. */ #define HAVE_SOCKET -/* Define if you have the header file. */ -#undef HAVE_STDINT_H - -/* Define if you have the header file. */ -#define HAVE_STDLIB_H - /* Define if you have the `strcasecmp' function. */ #undef HAVE_STRCASECMP @@ -168,9 +147,6 @@ /* Define if you have the header file. */ #undef HAVE_STRINGS_H -/* Define if you have the header file. */ -#define HAVE_STRING_H - /* Define if you have the `strtok_r' function. */ #undef HAVE_STRTOK_R @@ -204,9 +180,6 @@ /* Define if you have the header file. */ #undef HAVE_TERMIO_H -/* Define if you have the header file. */ -#undef HAVE_TIME_H - /* Define if you have the header file. */ #define HAVE_UNISTD_H @@ -225,19 +198,9 @@ /* Define if you have the ANSI C header files. */ #undef STDC_HEADERS -/* Define if you can safely include both and . */ -#undef TIME_WITH_SYS_TIME - /* Version number of package */ #undef VERSION -/* Define if on AIX 3. - System headers sometimes define this. - We just want to avoid a redefinition error message. */ -#ifndef _ALL_SOURCE -# undef _ALL_SOURCE -#endif - /* Number of bits in a file offset, on hosts where this is settable. */ #undef _FILE_OFFSET_BITS diff --git a/deps/curl/lib/config-win32.h b/deps/curl/lib/config-win32.h index fd2503668f4..e6abf062151 100644 --- a/deps/curl/lib/config-win32.h +++ b/deps/curl/lib/config-win32.h @@ -38,19 +38,6 @@ /* Define if you have the header file. */ #define HAVE_FCNTL_H 1 -/* Define to 1 if you have the header file. */ -#if defined(__MINGW32__) || \ - (defined(_MSC_VER) && (_MSC_VER >= 1800)) -#define HAVE_INTTYPES_H 1 -#endif - -/* Define to 1 if you have the header file. */ -#if defined(__MINGW32__) || defined(__POCC__) || \ - (defined(_MSC_VER) && (_MSC_VER >= 1600)) || \ - (defined(__BORLANDC__) && (__BORLANDC__ >= 0x0582)) -#define HAVE_STDINT_H 1 -#endif - /* Define if you have the header file. */ #define HAVE_IO_H 1 @@ -58,9 +45,7 @@ #define HAVE_LOCALE_H 1 /* Define if you need header even with header file. */ -#if !defined(__SALFORDC__) && !defined(__POCC__) #define NEED_MALLOC_H 1 -#endif /* Define if you have the header file. */ /* #define HAVE_NETDB_H 1 */ @@ -68,20 +53,15 @@ /* Define if you have the header file. */ /* #define HAVE_NETINET_IN_H 1 */ -/* Define if you have the header file. */ -#define HAVE_SIGNAL_H 1 - /* Define to 1 if you have the header file. */ -#if (defined(_MSC_VER) && (_MSC_VER >= 1800)) || \ - defined(__MINGW64_VERSION_MAJOR) +#if (defined(_MSC_VER) && (_MSC_VER >= 1800)) || defined(__MINGW32__) #define HAVE_STDBOOL_H 1 #endif -/* Define if you have the header file. */ -#define HAVE_STDLIB_H 1 - /* Define if you have the header file. */ -/* #define HAVE_SYS_PARAM_H 1 */ +#if defined(__MINGW32__) +#define HAVE_SYS_PARAM_H 1 +#endif /* Define if you have the header file. */ /* #define HAVE_SYS_SELECT_H 1 */ @@ -96,15 +76,15 @@ #define HAVE_SYS_STAT_H 1 /* Define if you have the header file. */ -/* #define HAVE_SYS_TIME_H 1 */ +#if defined(__MINGW32__) +#define HAVE_SYS_TIME_H 1 +#endif /* Define if you have the header file. */ #define HAVE_SYS_TYPES_H 1 /* Define if you have the header file. */ -#ifndef __BORLANDC__ #define HAVE_SYS_UTIME_H 1 -#endif /* Define if you have the header file. */ /* #define HAVE_TERMIO_H 1 */ @@ -112,35 +92,13 @@ /* Define if you have the header file. */ /* #define HAVE_TERMIOS_H 1 */ -/* Define if you have the header file. */ -#define HAVE_TIME_H 1 - /* Define if you have the header file. */ -#if defined(__MINGW32__) || defined(__LCC__) || defined(__POCC__) +#if defined(__MINGW32__) #define HAVE_UNISTD_H 1 #endif -/* Define if you have the header file. */ -#define HAVE_WINDOWS_H 1 - -/* Define if you have the header file. */ -#ifndef __SALFORDC__ -#define HAVE_WINSOCK2_H 1 -#endif - -/* Define if you have the header file. */ -#ifndef __SALFORDC__ -#define HAVE_WS2TCPIP_H 1 -#endif - -/* Define to 1 if you have the header file. */ -#define HAVE_SETJMP_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STRING_H 1 - /* Define to 1 if you have the header file. */ -#if defined(__MINGW64_VERSION_MAJOR) +#if defined(__MINGW32__) #define HAVE_LIBGEN_H 1 #endif @@ -151,12 +109,8 @@ /* Define if you have the ANSI C header files. */ #define STDC_HEADERS 1 -/* Define if you can safely include both and . */ -/* #define TIME_WITH_SYS_TIME 1 */ - /* Define to 1 if bool is an available type. */ -#if (defined(_MSC_VER) && (_MSC_VER >= 1800)) || \ - defined(__MINGW64_VERSION_MAJOR) +#if (defined(_MSC_VER) && (_MSC_VER >= 1800)) || defined(__MINGW32__) #define HAVE_BOOL_T 1 #endif @@ -168,7 +122,7 @@ #define HAVE_CLOSESOCKET 1 /* Define if you have the ftruncate function. */ -#if defined(__MINGW64_VERSION_MAJOR) +#if defined(__MINGW32__) #define HAVE_FTRUNCATE 1 #endif @@ -182,7 +136,9 @@ #define HAVE_GETHOSTNAME 1 /* Define if you have the gettimeofday function. */ -/* #define HAVE_GETTIMEOFDAY 1 */ +#if defined(__MINGW32__) +#define HAVE_GETTIMEOFDAY 1 +#endif /* Define if you have the ioctlsocket function. */ #define HAVE_IOCTLSOCKET 1 @@ -199,11 +155,14 @@ /* Define if you have the setmode function. */ #define HAVE_SETMODE 1 +/* Define if you have the _setmode function. */ +#define HAVE__SETMODE 1 + /* Define if you have the socket function. */ #define HAVE_SOCKET 1 /* Define if you have the strcasecmp function. */ -#ifdef __MINGW32__ +#if defined(__MINGW32__) #define HAVE_STRCASECMP 1 #endif @@ -214,15 +173,12 @@ #define HAVE_STRICMP 1 /* Define if you have the strtoll function. */ -#if defined(__MINGW32__) || defined(__POCC__) || \ - (defined(_MSC_VER) && (_MSC_VER >= 1800)) +#if (defined(_MSC_VER) && (_MSC_VER >= 1800)) || defined(__MINGW32__) #define HAVE_STRTOLL 1 #endif /* Define if you have the utime function. */ -#ifndef __BORLANDC__ #define HAVE_UTIME 1 -#endif /* Define if you have the recv function. */ #define HAVE_RECV 1 @@ -264,7 +220,7 @@ #define SEND_TYPE_RETV int /* Define to 1 if you have the snprintf function. */ -#if defined(_MSC_VER) && (_MSC_VER >= 1900) +#if (defined(_MSC_VER) && (_MSC_VER >= 1900)) || defined(__MINGW32__) #define HAVE_SNPRINTF 1 #endif @@ -276,12 +232,12 @@ #endif /* Define to 1 if you have the `basename' function. */ -#if defined(__MINGW64_VERSION_MAJOR) +#if defined(__MINGW32__) #define HAVE_BASENAME 1 #endif /* Define to 1 if you have the strtok_r function. */ -#if defined(__MINGW64_VERSION_MAJOR) +#if defined(__MINGW32__) #define HAVE_STRTOK_R 1 #endif @@ -297,7 +253,7 @@ /* Define if ssize_t is not an available 'typedefed' type. */ #ifndef _SSIZE_T_DEFINED -# if defined(__POCC__) || defined(__MINGW32__) +# if defined(__MINGW32__) # elif defined(_WIN64) # define _SSIZE_T_DEFINED # define ssize_t __int64 @@ -330,56 +286,6 @@ /* Define to the size of `curl_off_t', as computed by sizeof. */ #define SIZEOF_CURL_OFF_T 8 -/* ---------------------------------------------------------------- */ -/* BSD-style lwIP TCP/IP stack SPECIFIC */ -/* ---------------------------------------------------------------- */ - -/* Define to use BSD-style lwIP TCP/IP stack. */ -/* #define USE_LWIPSOCK 1 */ - -#ifdef USE_LWIPSOCK -# undef USE_WINSOCK -# undef HAVE_WINSOCK2_H -# undef HAVE_WS2TCPIP_H -# undef HAVE_GETHOSTNAME -# undef LWIP_POSIX_SOCKETS_IO_NAMES -# undef RECV_TYPE_ARG1 -# undef RECV_TYPE_ARG3 -# undef SEND_TYPE_ARG1 -# undef SEND_TYPE_ARG3 -# define HAVE_FREEADDRINFO -# define HAVE_GETADDRINFO -# define HAVE_GETHOSTBYNAME_R -# define HAVE_GETHOSTBYNAME_R_6 -# define LWIP_POSIX_SOCKETS_IO_NAMES 0 -# define RECV_TYPE_ARG1 int -# define RECV_TYPE_ARG3 size_t -# define SEND_TYPE_ARG1 int -# define SEND_TYPE_ARG3 size_t -#endif - -/* ---------------------------------------------------------------- */ -/* Watt-32 tcp/ip SPECIFIC */ -/* ---------------------------------------------------------------- */ - -#ifdef USE_WATT32 - #include - #undef byte - #undef word - #undef USE_WINSOCK - #undef HAVE_WINSOCK2_H - #undef HAVE_WS2TCPIP_H - #define HAVE_GETADDRINFO - #define HAVE_SYS_IOCTL_H - #define HAVE_SYS_SOCKET_H - #define HAVE_NETINET_IN_H - #define HAVE_NETDB_H - #define HAVE_ARPA_INET_H - #define HAVE_FREEADDRINFO - #define SOCKET int -#endif - - /* ---------------------------------------------------------------- */ /* COMPILER SPECIFIC */ /* ---------------------------------------------------------------- */ @@ -393,15 +299,8 @@ /* Windows should not have HAVE_GMTIME_R defined */ /* #undef HAVE_GMTIME_R */ -/* Define if the compiler supports C99 variadic macro style. */ -#if defined(_MSC_VER) && (_MSC_VER >= 1400) -#define HAVE_VARIADIC_MACROS_C99 1 -#endif - /* Define if the compiler supports the 'long long' data type. */ -#if defined(__MINGW32__) || \ - (defined(_MSC_VER) && (_MSC_VER >= 1310)) || \ - (defined(__BORLANDC__) && (__BORLANDC__ >= 0x561)) +#if (defined(_MSC_VER) && (_MSC_VER >= 1310)) || defined(__MINGW32__) #define HAVE_LONGLONG 1 #endif @@ -411,11 +310,9 @@ #define _CRT_NONSTDC_NO_DEPRECATE 1 #endif -/* mingw-w64, mingw using >= MSVCR80, and visual studio >= 2005 (MSVCR80) +/* mingw-w64 and visual studio >= 2005 (MSVCR80) all default to 64-bit time_t unless _USE_32BIT_TIME_T is defined */ -#if defined(__MINGW64_VERSION_MAJOR) || \ - (defined(__MINGW32__) && (__MSVCRT_VERSION__ >= 0x0800)) || \ - (defined(_MSC_VER) && (_MSC_VER >= 1400)) +#if (defined(_MSC_VER) && (_MSC_VER >= 1400)) || defined(__MINGW32__) # ifndef _USE_32BIT_TIME_T # define SIZEOF_TIME_T 8 # else @@ -485,53 +382,17 @@ Vista # endif #endif -/* When no build target is specified Pelles C 5.00 and later default build - target is Windows Vista. We override default target to be Windows 2000. */ -#if defined(__POCC__) && (__POCC__ >= 500) -# ifndef _WIN32_WINNT -# define _WIN32_WINNT 0x0500 -# endif -# ifndef WINVER -# define WINVER 0x0500 -# endif -#endif - -/* Availability of freeaddrinfo, getaddrinfo, and if_nametoindex - functions is quite convoluted, compiler dependent and even build target - dependent. */ -#if defined(HAVE_WS2TCPIP_H) -# if defined(__POCC__) -# define HAVE_FREEADDRINFO 1 -# define HAVE_GETADDRINFO 1 -# define HAVE_GETADDRINFO_THREADSAFE 1 -# elif defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501) -# define HAVE_FREEADDRINFO 1 -# define HAVE_GETADDRINFO 1 -# define HAVE_GETADDRINFO_THREADSAFE 1 -# elif defined(_MSC_VER) && (_MSC_VER >= 1200) -# define HAVE_FREEADDRINFO 1 -# define HAVE_GETADDRINFO 1 -# define HAVE_GETADDRINFO_THREADSAFE 1 -# endif -#endif - -#if defined(__POCC__) -# ifndef _MSC_VER -# error Microsoft extensions /Ze compiler option is required -# endif -# ifndef __POCC__OLDNAMES -# error Compatibility names /Go compiler option is required -# endif -#endif +/* Windows XP is required for freeaddrinfo, getaddrinfo */ +#define HAVE_FREEADDRINFO 1 +#define HAVE_GETADDRINFO 1 +#define HAVE_GETADDRINFO_THREADSAFE 1 /* ---------------------------------------------------------------- */ /* STRUCT RELATED */ /* ---------------------------------------------------------------- */ /* Define if you have struct sockaddr_storage. */ -#if !defined(__SALFORDC__) && !defined(__BORLANDC__) #define HAVE_STRUCT_SOCKADDR_STORAGE 1 -#endif /* Define if you have struct timeval. */ #define HAVE_STRUCT_TIMEVAL 1 @@ -555,23 +416,23 @@ Vista # define USE_WIN32_LARGE_FILES #endif -#if defined(__POCC__) -# undef USE_WIN32_LARGE_FILES -#endif - #if !defined(USE_WIN32_LARGE_FILES) && !defined(USE_WIN32_SMALL_FILES) # define USE_WIN32_SMALL_FILES #endif /* Number of bits in a file offset, on hosts where this is settable. */ -#if defined(USE_WIN32_LARGE_FILES) && defined(__MINGW64_VERSION_MAJOR) +#if defined(USE_WIN32_LARGE_FILES) && defined(__MINGW32__) # ifndef _FILE_OFFSET_BITS # define _FILE_OFFSET_BITS 64 # endif #endif +#ifdef USE_WIN32_LARGE_FILES +#define HAVE__FSEEKI64 +#endif + /* Define to the size of `off_t', as computed by sizeof. */ -#if defined(__MINGW64_VERSION_MAJOR) && \ +#if defined(__MINGW32__) && \ defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS == 64) # define SIZEOF_OFF_T 8 #else @@ -616,12 +477,8 @@ Vista #define USE_WIN32_LDAP 1 #endif -#if defined(__POCC__) && defined(USE_WIN32_LDAP) -# define CURL_DISABLE_LDAP 1 -#endif - /* Define to use the Windows crypto library. */ -#if !defined(CURL_WINDOWS_APP) +#if !defined(CURL_WINDOWS_UWP) #define USE_WIN32_CRYPTO #endif @@ -633,19 +490,19 @@ Vista /* ---------------------------------------------------------------- */ /* Define cpu-machine-OS */ -#ifndef OS +#ifndef CURL_OS #if defined(_M_IX86) || defined(__i386__) /* x86 (MSVC or gcc) */ -#define OS "i386-pc-win32" +#define CURL_OS "i386-pc-win32" #elif defined(_M_X64) || defined(__x86_64__) /* x86_64 (MSVC >=2005 or gcc) */ -#define OS "x86_64-pc-win32" +#define CURL_OS "x86_64-pc-win32" #elif defined(_M_IA64) || defined(__ia64__) /* Itanium */ -#define OS "ia64-pc-win32" +#define CURL_OS "ia64-pc-win32" #elif defined(_M_ARM_NT) || defined(__arm__) /* ARMv7-Thumb2 (Windows RT) */ -#define OS "thumbv7a-pc-win32" +#define CURL_OS "thumbv7a-pc-win32" #elif defined(_M_ARM64) || defined(__aarch64__) /* ARM64 (Windows 10) */ -#define OS "aarch64-pc-win32" +#define CURL_OS "aarch64-pc-win32" #else -#define OS "unknown-pc-win32" +#define CURL_OS "unknown-pc-win32" #endif #endif @@ -655,8 +512,4 @@ Vista /* If you want to build curl with the built-in manual */ #define USE_MANUAL 1 -#if defined(__POCC__) || defined(USE_IPV6) -# define ENABLE_IPV6 1 -#endif - #endif /* HEADER_CURL_CONFIG_WIN32_H */ diff --git a/deps/curl/lib/config-win32ce.h b/deps/curl/lib/config-win32ce.h index 2f9a5305242..80765f1f655 100644 --- a/deps/curl/lib/config-win32ce.h +++ b/deps/curl/lib/config-win32ce.h @@ -25,7 +25,7 @@ ***************************************************************************/ /* ================================================================ */ -/* lib/config-win32ce.h - Hand crafted config file for windows ce */ +/* lib/config-win32ce.h - Hand crafted config file for Windows CE */ /* ================================================================ */ /* ---------------------------------------------------------------- */ @@ -50,12 +50,6 @@ /* Define if you have the header file. */ /* #define HAVE_NETINET_IN_H 1 */ -/* Define if you have the header file. */ -#define HAVE_SIGNAL_H 1 - -/* Define if you have the header file. */ -#define HAVE_STDLIB_H 1 - /* Define if you have the header file. */ /* #define HAVE_SYS_PARAM_H 1 */ @@ -86,23 +80,11 @@ /* Define if you have the header file. */ /* #define HAVE_TERMIOS_H 1 */ -/* Define if you have the header file. */ -#define HAVE_TIME_H 1 - /* Define if you have the header file. */ -#if defined(__MINGW32__) || defined(__LCC__) +#if defined(__MINGW32__) #define HAVE_UNISTD_H 1 #endif -/* Define if you have the header file. */ -#define HAVE_WINDOWS_H 1 - -/* Define if you have the header file. */ -#define HAVE_WINSOCK2_H 1 - -/* Define if you have the header file. */ -#define HAVE_WS2TCPIP_H 1 - /* ---------------------------------------------------------------- */ /* OTHER HEADER INFO */ /* ---------------------------------------------------------------- */ @@ -110,9 +92,6 @@ /* Define if you have the ANSI C header files. */ #define STDC_HEADERS 1 -/* Define if you can safely include both and . */ -/* #define TIME_WITH_SYS_TIME 1 */ - /* ---------------------------------------------------------------- */ /* FUNCTIONS */ /* ---------------------------------------------------------------- */ @@ -202,8 +181,7 @@ #define in_addr_t unsigned long /* Define ssize_t if it is not an available 'typedefed' type */ -#if defined(__POCC__) -#elif defined(_WIN64) +#if defined(_WIN64) #define ssize_t __int64 #else #define ssize_t int @@ -293,15 +271,15 @@ /* ---------------------------------------------------------------- */ /* Define cpu-machine-OS */ -#ifndef OS -#define OS "i386-pc-win32ce" +#ifndef CURL_OS +#define CURL_OS "i386-pc-win32ce" #endif /* Name of package */ #define PACKAGE "curl" /* ---------------------------------------------------------------- */ -/* WinCE */ +/* Windows CE */ /* ---------------------------------------------------------------- */ #ifndef UNICODE diff --git a/deps/curl/lib/conncache.c b/deps/curl/lib/conncache.c index a21409c13f1..589205bc152 100644 --- a/deps/curl/lib/conncache.c +++ b/deps/curl/lib/conncache.c @@ -29,13 +29,17 @@ #include "urldata.h" #include "url.h" +#include "cfilters.h" #include "progress.h" #include "multiif.h" #include "sendf.h" #include "conncache.h" +#include "http_negotiate.h" +#include "http_ntlm.h" #include "share.h" #include "sigpipe.h" #include "connect.h" +#include "select.h" #include "strcase.h" /* The last 3 #include files should be in this order */ @@ -43,390 +47,483 @@ #include "curl_memory.h" #include "memdebug.h" -#define HASHKEY_SIZE 128 -static CURLcode bundle_create(struct connectbundle **bundlep) +#define CPOOL_IS_LOCKED(c) ((c) && (c)->locked) + +#define CPOOL_LOCK(c) \ + do { \ + if((c)) { \ + if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ + Curl_share_lock(((c)->idata), CURL_LOCK_DATA_CONNECT, \ + CURL_LOCK_ACCESS_SINGLE); \ + DEBUGASSERT(!(c)->locked); \ + (c)->locked = TRUE; \ + } \ + } while(0) + +#define CPOOL_UNLOCK(c) \ + do { \ + if((c)) { \ + DEBUGASSERT((c)->locked); \ + (c)->locked = FALSE; \ + if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ + Curl_share_unlock((c)->idata, CURL_LOCK_DATA_CONNECT); \ + } \ + } while(0) + + +/* A list of connections to the same destination. */ +struct cpool_bundle { + struct Curl_llist conns; /* connections in the bundle */ + size_t dest_len; /* total length of destination, including NUL */ + char *dest[1]; /* destination of bundle, allocated to keep dest_len bytes */ +}; + + +static void cpool_discard_conn(struct cpool *cpool, + struct Curl_easy *data, + struct connectdata *conn, + bool aborted); +static void cpool_close_and_destroy(struct cpool *cpool, + struct connectdata *conn, + struct Curl_easy *data, + bool do_shutdown); +static void cpool_run_conn_shutdown(struct Curl_easy *data, + struct connectdata *conn, + bool *done); +static void cpool_run_conn_shutdown_handler(struct Curl_easy *data, + struct connectdata *conn); +static CURLMcode cpool_update_shutdown_ev(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn); +static void cpool_shutdown_all(struct cpool *cpool, + struct Curl_easy *data, int timeout_ms); +static void cpool_close_and_destroy_all(struct cpool *cpool); +static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool); + +static struct cpool_bundle *cpool_bundle_create(const char *dest, + size_t dest_len) { - DEBUGASSERT(*bundlep == NULL); - *bundlep = malloc(sizeof(struct connectbundle)); - if(!*bundlep) - return CURLE_OUT_OF_MEMORY; - - (*bundlep)->num_connections = 0; - (*bundlep)->multiuse = BUNDLE_UNKNOWN; - - Curl_llist_init(&(*bundlep)->conn_list, NULL); - return CURLE_OK; + struct cpool_bundle *bundle; + bundle = calloc(1, sizeof(*bundle) + dest_len); + if(!bundle) + return NULL; + Curl_llist_init(&bundle->conns, NULL); + bundle->dest_len = dest_len; + memcpy(bundle->dest, dest, dest_len); + return bundle; } -static void bundle_destroy(struct connectbundle *bundle) +static void cpool_bundle_destroy(struct cpool_bundle *bundle) { + DEBUGASSERT(!Curl_llist_count(&bundle->conns)); free(bundle); } /* Add a connection to a bundle */ -static void bundle_add_conn(struct connectbundle *bundle, - struct connectdata *conn) +static void cpool_bundle_add(struct cpool_bundle *bundle, + struct connectdata *conn) { - Curl_llist_insert_next(&bundle->conn_list, bundle->conn_list.tail, conn, - &conn->bundle_node); - conn->bundle = bundle; - bundle->num_connections++; + DEBUGASSERT(!Curl_node_llist(&conn->cpool_node)); + Curl_llist_append(&bundle->conns, conn, &conn->cpool_node); + conn->bits.in_cpool = TRUE; } /* Remove a connection from a bundle */ -static int bundle_remove_conn(struct connectbundle *bundle, - struct connectdata *conn) +static void cpool_bundle_remove(struct cpool_bundle *bundle, + struct connectdata *conn) { - struct Curl_llist_element *curr; - - curr = bundle->conn_list.head; - while(curr) { - if(curr->ptr == conn) { - Curl_llist_remove(&bundle->conn_list, curr, NULL); - bundle->num_connections--; - conn->bundle = NULL; - return 1; /* we removed a handle */ - } - curr = curr->next; - } - DEBUGASSERT(0); - return 0; + (void)bundle; + DEBUGASSERT(Curl_node_llist(&conn->cpool_node) == &bundle->conns); + Curl_node_remove(&conn->cpool_node); + conn->bits.in_cpool = FALSE; } -static void free_bundle_hash_entry(void *freethis) +static void cpool_bundle_free_entry(void *freethis) { - struct connectbundle *b = (struct connectbundle *) freethis; - - bundle_destroy(b); + cpool_bundle_destroy((struct cpool_bundle *)freethis); } -int Curl_conncache_init(struct conncache *connc, int size) +int Curl_cpool_init(struct cpool *cpool, + Curl_cpool_disconnect_cb *disconnect_cb, + struct Curl_multi *multi, + struct Curl_share *share, + size_t size) { + DEBUGASSERT(!!multi != !!share); /* either one */ + Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str, + Curl_str_key_compare, cpool_bundle_free_entry); + Curl_llist_init(&cpool->shutdowns, NULL); + + DEBUGASSERT(disconnect_cb); + if(!disconnect_cb) + return 1; + /* allocate a new easy handle to use when closing cached connections */ - connc->closure_handle = curl_easy_init(); - if(!connc->closure_handle) + cpool->idata = curl_easy_init(); + if(!cpool->idata) return 1; /* bad */ + cpool->idata->state.internal = TRUE; + /* TODO: this is quirky. We need an internal handle for certain + * operations, but we do not add it to the multi (if there is one). + * But we give it the multi so that socket event operations can work. + * Probably better to have an internal handle owned by the multi that + * can be used for cpool operations. */ + cpool->idata->multi = multi; +#ifdef DEBUGBUILD + if(getenv("CURL_DEBUG")) + cpool->idata->set.verbose = TRUE; +#endif - Curl_hash_init(&connc->hash, size, Curl_hash_str, - Curl_str_key_compare, free_bundle_hash_entry); - connc->closure_handle->state.conn_cache = connc; + cpool->disconnect_cb = disconnect_cb; + cpool->idata->multi = cpool->multi = multi; + cpool->idata->share = cpool->share = share; return 0; /* good */ } -void Curl_conncache_destroy(struct conncache *connc) +void Curl_cpool_destroy(struct cpool *cpool) { - if(connc) - Curl_hash_destroy(&connc->hash); + if(cpool) { + if(cpool->idata) { + cpool_close_and_destroy_all(cpool); + /* The internal closure handle is special and we need to + * disconnect it from multi/share before closing it down. */ + cpool->idata->multi = NULL; + cpool->idata->share = NULL; + Curl_close(&cpool->idata); + } + Curl_hash_destroy(&cpool->dest2bundle); + cpool->multi = NULL; + } } -/* creates a key to find a bundle for this connection */ -static void hashkey(struct connectdata *conn, char *buf, size_t len) +static struct cpool *cpool_get_instance(struct Curl_easy *data) { - const char *hostname; - long port = conn->remote_port; - DEBUGASSERT(len >= HASHKEY_SIZE); -#ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { - hostname = conn->http_proxy.host.name; - port = conn->port; + if(data) { + if(CURL_SHARE_KEEP_CONNECT(data->share)) + return &data->share->cpool; + else if(data->multi_easy) + return &data->multi_easy->cpool; + else if(data->multi) + return &data->multi->cpool; } - else -#endif - if(conn->bits.conn_to_host) - hostname = conn->conn_to_host.name; - else - hostname = conn->host.name; - - /* put the numbers first so that the hostname gets cut off if too long */ -#ifdef ENABLE_IPV6 - msnprintf(buf, len, "%u/%ld/%s", conn->scope_id, port, hostname); -#else - msnprintf(buf, len, "%ld/%s", port, hostname); -#endif - Curl_strntolower(buf, buf, len); + return NULL; } -/* Returns number of connections currently held in the connection cache. - Locks/unlocks the cache itself! -*/ -size_t Curl_conncache_size(struct Curl_easy *data) +void Curl_cpool_xfer_init(struct Curl_easy *data) { - size_t num; - CONNCACHE_LOCK(data); - num = data->state.conn_cache->num_conn; - CONNCACHE_UNLOCK(data); - return num; + struct cpool *cpool = cpool_get_instance(data); + + DEBUGASSERT(cpool); + if(cpool) { + CPOOL_LOCK(cpool); + /* the identifier inside the connection cache */ + data->id = cpool->next_easy_id++; + if(cpool->next_easy_id <= 0) + cpool->next_easy_id = 0; + data->state.lastconnect_id = -1; + + /* The closure handle only ever has default timeouts set. To improve the + state somewhat we clone the timeouts from each added handle so that the + closure handle always has the same timeouts as the most recently added + easy handle. */ + cpool->idata->set.timeout = data->set.timeout; + cpool->idata->set.server_response_timeout = + data->set.server_response_timeout; + cpool->idata->set.no_signal = data->set.no_signal; + + CPOOL_UNLOCK(cpool); + } + else { + /* We should not get here, but in a non-debug build, do something */ + data->id = 0; + data->state.lastconnect_id = -1; + } } -/* Look up the bundle with all the connections to the same host this - connectdata struct is setup to use. +static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool, + struct connectdata *conn) +{ + return Curl_hash_pick(&cpool->dest2bundle, + conn->destination, conn->destination_len); +} - **NOTE**: When it returns, it holds the connection cache lock! */ -struct connectbundle * -Curl_conncache_find_bundle(struct Curl_easy *data, - struct connectdata *conn, - struct conncache *connc) +static struct cpool_bundle * +cpool_add_bundle(struct cpool *cpool, struct connectdata *conn) { - struct connectbundle *bundle = NULL; - CONNCACHE_LOCK(data); - if(connc) { - char key[HASHKEY_SIZE]; - hashkey(conn, key, sizeof(key)); - bundle = Curl_hash_pick(&connc->hash, key, strlen(key)); - } + struct cpool_bundle *bundle; + + bundle = cpool_bundle_create(conn->destination, conn->destination_len); + if(!bundle) + return NULL; + if(!Curl_hash_add(&cpool->dest2bundle, + bundle->dest, bundle->dest_len, bundle)) { + cpool_bundle_destroy(bundle); + return NULL; + } return bundle; } -static void *conncache_add_bundle(struct conncache *connc, - char *key, - struct connectbundle *bundle) +static void cpool_remove_bundle(struct cpool *cpool, + struct cpool_bundle *bundle) { - return Curl_hash_add(&connc->hash, key, strlen(key), bundle); -} + if(!cpool) + return; -static void conncache_remove_bundle(struct conncache *connc, - struct connectbundle *bundle) -{ - struct Curl_hash_iterator iter; - struct Curl_hash_element *he; + Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len); +} - if(!connc) - return; +static struct connectdata * +cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle); - Curl_hash_start_iterate(&connc->hash, &iter); +int Curl_cpool_check_limits(struct Curl_easy *data, + struct connectdata *conn) +{ + struct cpool *cpool = cpool_get_instance(data); + struct cpool_bundle *bundle; + size_t dest_limit = 0; + size_t total_limit = 0; + int result = CPOOL_LIMIT_OK; + + if(!cpool) + return CPOOL_LIMIT_OK; + + if(data && data->multi) { + dest_limit = data->multi->max_host_connections; + total_limit = data->multi->max_total_connections; + } - he = Curl_hash_next_element(&iter); - while(he) { - if(he->ptr == bundle) { - /* The bundle is destroyed by the hash destructor function, - free_bundle_hash_entry() */ - Curl_hash_delete(&connc->hash, he->key, he->key_len); - return; + if(!dest_limit && !total_limit) + return CPOOL_LIMIT_OK; + + CPOOL_LOCK(cpool); + if(dest_limit) { + bundle = cpool_find_bundle(cpool, conn); + while(bundle && (Curl_llist_count(&bundle->conns) >= dest_limit)) { + struct connectdata *oldest_idle = NULL; + /* The bundle is full. Extract the oldest connection that may + * be removed now, if there is one. */ + oldest_idle = cpool_bundle_get_oldest_idle(bundle); + if(!oldest_idle) + break; + /* disconnect the old conn and continue */ + DEBUGF(infof(data, "Discarding connection #%" + FMT_OFF_T " from %zu to reach destination " + "limit of %zu", oldest_idle->connection_id, + Curl_llist_count(&bundle->conns), dest_limit)); + Curl_cpool_disconnect(data, oldest_idle, FALSE); + + /* in case the bundle was destroyed in disconnect, look it up again */ + bundle = cpool_find_bundle(cpool, conn); + } + if(bundle && (Curl_llist_count(&bundle->conns) >= dest_limit)) { + result = CPOOL_LIMIT_DEST; + goto out; } + } - he = Curl_hash_next_element(&iter); + if(total_limit) { + while(cpool->num_conn >= total_limit) { + struct connectdata *oldest_idle = cpool_get_oldest_idle(cpool); + if(!oldest_idle) + break; + /* disconnect the old conn and continue */ + DEBUGF(infof(data, "Discarding connection #%" + FMT_OFF_T " from %zu to reach total " + "limit of %zu", + oldest_idle->connection_id, cpool->num_conn, total_limit)); + Curl_cpool_disconnect(data, oldest_idle, FALSE); + } + if(cpool->num_conn >= total_limit) { + result = CPOOL_LIMIT_TOTAL; + goto out; + } } + +out: + CPOOL_UNLOCK(cpool); + return result; } -CURLcode Curl_conncache_add_conn(struct Curl_easy *data) +CURLcode Curl_cpool_add_conn(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result = CURLE_OK; - struct connectbundle *bundle = NULL; - struct connectdata *conn = data->conn; - struct conncache *connc = data->state.conn_cache; + struct cpool_bundle *bundle = NULL; + struct cpool *cpool = cpool_get_instance(data); DEBUGASSERT(conn); - /* *find_bundle() locks the connection cache */ - bundle = Curl_conncache_find_bundle(data, conn, data->state.conn_cache); - if(!bundle) { - char key[HASHKEY_SIZE]; - - result = bundle_create(&bundle); - if(result) { - goto unlock; - } - - hashkey(conn, key, sizeof(key)); + DEBUGASSERT(cpool); + if(!cpool) + return CURLE_FAILED_INIT; - if(!conncache_add_bundle(data->state.conn_cache, key, bundle)) { - bundle_destroy(bundle); + CPOOL_LOCK(cpool); + bundle = cpool_find_bundle(cpool, conn); + if(!bundle) { + bundle = cpool_add_bundle(cpool, conn); + if(!bundle) { result = CURLE_OUT_OF_MEMORY; - goto unlock; + goto out; } } - bundle_add_conn(bundle, conn); - conn->connection_id = connc->next_connection_id++; - connc->num_conn++; - - DEBUGF(infof(data, "Added connection %ld. " + cpool_bundle_add(bundle, conn); + conn->connection_id = cpool->next_connection_id++; + cpool->num_conn++; + DEBUGF(infof(data, "Added connection %" FMT_OFF_T ". " "The cache now contains %zu members", - conn->connection_id, connc->num_conn)); - -unlock: - CONNCACHE_UNLOCK(data); + conn->connection_id, cpool->num_conn)); +out: + CPOOL_UNLOCK(cpool); return result; } -/* - * Removes the connectdata object from the connection cache, but the transfer - * still owns this connection. - * - * Pass TRUE/FALSE in the 'lock' argument depending on if the parent function - * already holds the lock or not. - */ -void Curl_conncache_remove_conn(struct Curl_easy *data, - struct connectdata *conn, bool lock) +static void cpool_remove_conn(struct cpool *cpool, + struct connectdata *conn) { - struct connectbundle *bundle = conn->bundle; - struct conncache *connc = data->state.conn_cache; - - /* The bundle pointer can be NULL, since this function can be called - due to a failed connection attempt, before being added to a bundle */ - if(bundle) { - if(lock) { - CONNCACHE_LOCK(data); + struct Curl_llist *list = Curl_node_llist(&conn->cpool_node); + DEBUGASSERT(cpool); + if(list) { + /* The connection is certainly in the pool, but where? */ + struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn); + if(bundle && (list == &bundle->conns)) { + cpool_bundle_remove(bundle, conn); + if(!Curl_llist_count(&bundle->conns)) + cpool_remove_bundle(cpool, bundle); + conn->bits.in_cpool = FALSE; + cpool->num_conn--; } - bundle_remove_conn(bundle, conn); - if(bundle->num_connections == 0) - conncache_remove_bundle(connc, bundle); - conn->bundle = NULL; /* removed from it */ - if(connc) { - connc->num_conn--; - DEBUGF(infof(data, "The cache now contains %zu members", - connc->num_conn)); - } - if(lock) { - CONNCACHE_UNLOCK(data); + else { + /* Not in a bundle, already in the shutdown list? */ + DEBUGASSERT(list == &cpool->shutdowns); } } } -/* This function iterates the entire connection cache and calls the function +/* This function iterates the entire connection pool and calls the function func() with the connection pointer as the first argument and the supplied 'param' argument as the other. - The conncache lock is still held when the callback is called. It needs it, + The cpool lock is still held when the callback is called. It needs it, so that it can safely continue traversing the lists once the callback returns. - Returns 1 if the loop was aborted due to the callback's return code. + Returns TRUE if the loop was aborted due to the callback's return code. Return 0 from func() to continue the loop, return 1 to abort it. */ -bool Curl_conncache_foreach(struct Curl_easy *data, - struct conncache *connc, - void *param, - int (*func)(struct Curl_easy *data, - struct connectdata *conn, void *param)) +static bool cpool_foreach(struct Curl_easy *data, + struct cpool *cpool, + void *param, + int (*func)(struct Curl_easy *data, + struct connectdata *conn, void *param)) { struct Curl_hash_iterator iter; - struct Curl_llist_element *curr; struct Curl_hash_element *he; - if(!connc) + if(!cpool) return FALSE; - CONNCACHE_LOCK(data); - Curl_hash_start_iterate(&connc->hash, &iter); + Curl_hash_start_iterate(&cpool->dest2bundle, &iter); he = Curl_hash_next_element(&iter); while(he) { - struct connectbundle *bundle; - - bundle = he->ptr; + struct Curl_llist_node *curr; + struct cpool_bundle *bundle = he->ptr; he = Curl_hash_next_element(&iter); - curr = bundle->conn_list.head; + curr = Curl_llist_head(&bundle->conns); while(curr) { /* Yes, we need to update curr before calling func(), because func() might decide to remove the connection */ - struct connectdata *conn = curr->ptr; - curr = curr->next; + struct connectdata *conn = Curl_node_elem(curr); + curr = Curl_node_next(curr); if(1 == func(data, conn, param)) { - CONNCACHE_UNLOCK(data); return TRUE; } } } - CONNCACHE_UNLOCK(data); return FALSE; } -/* Return the first connection found in the cache. Used when closing all - connections. - - NOTE: no locking is done here as this is presumably only done when cleaning - up a cache! -*/ -static struct connectdata * -conncache_find_first_connection(struct conncache *connc) +/* Return a live connection in the pool or NULL. */ +static struct connectdata *cpool_get_live_conn(struct cpool *cpool) { struct Curl_hash_iterator iter; struct Curl_hash_element *he; - struct connectbundle *bundle; - - Curl_hash_start_iterate(&connc->hash, &iter); + struct cpool_bundle *bundle; + struct Curl_llist_node *conn_node; - he = Curl_hash_next_element(&iter); - while(he) { - struct Curl_llist_element *curr; + Curl_hash_start_iterate(&cpool->dest2bundle, &iter); + for(he = Curl_hash_next_element(&iter); he; + he = Curl_hash_next_element(&iter)) { bundle = he->ptr; - - curr = bundle->conn_list.head; - if(curr) { - return curr->ptr; - } - - he = Curl_hash_next_element(&iter); + conn_node = Curl_llist_head(&bundle->conns); + if(conn_node) + return Curl_node_elem(conn_node); } - return NULL; } /* - * Give ownership of a connection back to the connection cache. Might - * disconnect the oldest existing in there to make space. + * A connection (already in the pool) has become idle. Do any + * cleanups in regard to the pool's limits. * - * Return TRUE if stored, FALSE if closed. + * Return TRUE if idle connection kept in pool, FALSE if closed. */ -bool Curl_conncache_return_conn(struct Curl_easy *data, - struct connectdata *conn) +bool Curl_cpool_conn_now_idle(struct Curl_easy *data, + struct connectdata *conn) { - /* data->multi->maxconnects can be negative, deal with it. */ - size_t maxconnects = - (data->multi->maxconnects < 0) ? data->multi->num_easy * 4: - data->multi->maxconnects; - struct connectdata *conn_candidate = NULL; + unsigned int maxconnects = !data->multi->maxconnects ? + data->multi->num_easy * 4 : data->multi->maxconnects; + struct connectdata *oldest_idle = NULL; + struct cpool *cpool = cpool_get_instance(data); + bool kept = TRUE; conn->lastused = Curl_now(); /* it was used up until now */ - if(maxconnects > 0 && - Curl_conncache_size(data) > maxconnects) { - infof(data, "Connection cache is full, closing the oldest one"); - - conn_candidate = Curl_conncache_extract_oldest(data); - if(conn_candidate) { - /* the winner gets the honour of being disconnected */ - Curl_disconnect(data, conn_candidate, /* dead_connection */ FALSE); + if(cpool && maxconnects) { + /* may be called form a callback already under lock */ + bool do_lock = !CPOOL_IS_LOCKED(cpool); + if(do_lock) + CPOOL_LOCK(cpool); + if(cpool->num_conn > maxconnects) { + infof(data, "Connection pool is full, closing the oldest one"); + + oldest_idle = cpool_get_oldest_idle(cpool); + kept = (oldest_idle != conn); + if(oldest_idle) { + Curl_cpool_disconnect(cpool->idata, oldest_idle, FALSE); + } } + if(do_lock) + CPOOL_UNLOCK(cpool); } - return (conn_candidate == conn) ? FALSE : TRUE; - + return kept; } /* * This function finds the connection in the connection bundle that has been * unused for the longest time. - * - * Does not lock the connection cache! - * - * Returns the pointer to the oldest idle connection, or NULL if none was - * found. */ -struct connectdata * -Curl_conncache_extract_bundle(struct Curl_easy *data, - struct connectbundle *bundle) +static struct connectdata * +cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle) { - struct Curl_llist_element *curr; + struct Curl_llist_node *curr; timediff_t highscore = -1; timediff_t score; struct curltime now; - struct connectdata *conn_candidate = NULL; + struct connectdata *oldest_idle = NULL; struct connectdata *conn; - (void)data; - now = Curl_now(); - - curr = bundle->conn_list.head; + curr = Curl_llist_head(&bundle->conns); while(curr) { - conn = curr->ptr; + conn = Curl_node_elem(curr); if(!CONN_INUSE(conn)) { /* Set higher score for the age passed since the connection was used */ @@ -434,145 +531,836 @@ Curl_conncache_extract_bundle(struct Curl_easy *data, if(score > highscore) { highscore = score; - conn_candidate = conn; + oldest_idle = conn; } } - curr = curr->next; - } - if(conn_candidate) { - /* remove it to prevent another thread from nicking it */ - bundle_remove_conn(bundle, conn_candidate); - data->state.conn_cache->num_conn--; - DEBUGF(infof(data, "The cache now contains %zu members", - data->state.conn_cache->num_conn)); + curr = Curl_node_next(curr); } - - return conn_candidate; + return oldest_idle; } -/* - * This function finds the connection in the connection cache that has been - * unused for the longest time and extracts that from the bundle. - * - * Returns the pointer to the connection, or NULL if none was found. - */ -struct connectdata * -Curl_conncache_extract_oldest(struct Curl_easy *data) +static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool) { - struct conncache *connc = data->state.conn_cache; struct Curl_hash_iterator iter; - struct Curl_llist_element *curr; + struct Curl_llist_node *curr; struct Curl_hash_element *he; + struct connectdata *oldest_idle = NULL; + struct cpool_bundle *bundle; + struct curltime now; timediff_t highscore =- 1; timediff_t score; - struct curltime now; - struct connectdata *conn_candidate = NULL; - struct connectbundle *bundle; - struct connectbundle *bundle_candidate = NULL; now = Curl_now(); + Curl_hash_start_iterate(&cpool->dest2bundle, &iter); - CONNCACHE_LOCK(data); - Curl_hash_start_iterate(&connc->hash, &iter); - - he = Curl_hash_next_element(&iter); - while(he) { + for(he = Curl_hash_next_element(&iter); he; + he = Curl_hash_next_element(&iter)) { struct connectdata *conn; - bundle = he->ptr; - curr = bundle->conn_list.head; - while(curr) { - conn = curr->ptr; - - if(!CONN_INUSE(conn) && !conn->bits.close && - !conn->connect_only) { - /* Set higher score for the age passed since the connection was used */ - score = Curl_timediff(now, conn->lastused); - - if(score > highscore) { - highscore = score; - conn_candidate = conn; - bundle_candidate = bundle; - } + for(curr = Curl_llist_head(&bundle->conns); curr; + curr = Curl_node_next(curr)) { + conn = Curl_node_elem(curr); + if(CONN_INUSE(conn) || conn->bits.close || conn->connect_only) + continue; + /* Set higher score for the age passed since the connection was used */ + score = Curl_timediff(now, conn->lastused); + if(score > highscore) { + highscore = score; + oldest_idle = conn; } - curr = curr->next; } + } + return oldest_idle; +} - he = Curl_hash_next_element(&iter); +bool Curl_cpool_find(struct Curl_easy *data, + const char *destination, size_t dest_len, + Curl_cpool_conn_match_cb *conn_cb, + Curl_cpool_done_match_cb *done_cb, + void *userdata) +{ + struct cpool *cpool = cpool_get_instance(data); + struct cpool_bundle *bundle; + bool result = FALSE; + + DEBUGASSERT(cpool); + DEBUGASSERT(conn_cb); + if(!cpool) + return FALSE; + + CPOOL_LOCK(cpool); + bundle = Curl_hash_pick(&cpool->dest2bundle, (void *)destination, dest_len); + if(bundle) { + struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns); + while(curr) { + struct connectdata *conn = Curl_node_elem(curr); + /* Get next node now. callback might discard current */ + curr = Curl_node_next(curr); + + if(conn_cb(conn, userdata)) { + result = TRUE; + break; + } + } } - if(conn_candidate) { - /* remove it to prevent another thread from nicking it */ - bundle_remove_conn(bundle_candidate, conn_candidate); - connc->num_conn--; - DEBUGF(infof(data, "The cache now contains %zu members", - connc->num_conn)); + + if(done_cb) { + result = done_cb(result, userdata); } - CONNCACHE_UNLOCK(data); + CPOOL_UNLOCK(cpool); + return result; +} - return conn_candidate; +static void cpool_shutdown_discard_all(struct cpool *cpool) +{ + struct Curl_llist_node *e = Curl_llist_head(&cpool->shutdowns); + struct connectdata *conn; + + if(!e) + return; + + DEBUGF(infof(cpool->idata, "cpool_shutdown_discard_all")); + while(e) { + conn = Curl_node_elem(e); + Curl_node_remove(e); + DEBUGF(infof(cpool->idata, "discard connection #%" FMT_OFF_T, + conn->connection_id)); + cpool_close_and_destroy(cpool, conn, NULL, FALSE); + e = Curl_llist_head(&cpool->shutdowns); + } } -void Curl_conncache_close_all_connections(struct conncache *connc) +static void cpool_close_and_destroy_all(struct cpool *cpool) { struct connectdata *conn; - char buffer[READBUFFER_MIN + 1]; + int timeout_ms = 0; SIGPIPE_VARIABLE(pipe_st); - if(!connc->closure_handle) - return; - connc->closure_handle->state.buffer = buffer; - connc->closure_handle->set.buffer_size = READBUFFER_MIN; - conn = conncache_find_first_connection(connc); + DEBUGASSERT(cpool); + /* Move all connections to the shutdown list */ + sigpipe_init(&pipe_st); + CPOOL_LOCK(cpool); + conn = cpool_get_live_conn(cpool); while(conn) { - sigpipe_ignore(connc->closure_handle, &pipe_st); - /* This will remove the connection from the cache */ + cpool_remove_conn(cpool, conn); + sigpipe_apply(cpool->idata, &pipe_st); connclose(conn, "kill all"); - Curl_conncache_remove_conn(connc->closure_handle, conn, TRUE); - Curl_disconnect(connc->closure_handle, conn, FALSE); - sigpipe_restore(&pipe_st); + cpool_discard_conn(cpool, cpool->idata, conn, FALSE); - conn = conncache_find_first_connection(connc); + conn = cpool_get_live_conn(cpool); } + CPOOL_UNLOCK(cpool); + + /* Just for testing, run graceful shutdown */ +#ifdef DEBUGBUILD + { + char *p = getenv("CURL_GRACEFUL_SHUTDOWN"); + if(p) { + long l = strtol(p, NULL, 10); + if(l > 0 && l < INT_MAX) + timeout_ms = (int)l; + } + } +#endif + sigpipe_apply(cpool->idata, &pipe_st); + cpool_shutdown_all(cpool, cpool->idata, timeout_ms); - connc->closure_handle->state.buffer = NULL; - sigpipe_ignore(connc->closure_handle, &pipe_st); + /* discard all connections in the shutdown list */ + cpool_shutdown_discard_all(cpool); - Curl_hostcache_clean(connc->closure_handle, - connc->closure_handle->dns.hostcache); - Curl_close(&connc->closure_handle); + Curl_hostcache_clean(cpool->idata, cpool->idata->dns.hostcache); sigpipe_restore(&pipe_st); } + +static void cpool_shutdown_destroy_oldest(struct cpool *cpool) +{ + struct Curl_llist_node *e; + struct connectdata *conn; + + e = Curl_llist_head(&cpool->shutdowns); + if(e) { + SIGPIPE_VARIABLE(pipe_st); + conn = Curl_node_elem(e); + Curl_node_remove(e); + sigpipe_init(&pipe_st); + sigpipe_apply(cpool->idata, &pipe_st); + cpool_close_and_destroy(cpool, conn, NULL, FALSE); + sigpipe_restore(&pipe_st); + } +} + +static void cpool_discard_conn(struct cpool *cpool, + struct Curl_easy *data, + struct connectdata *conn, + bool aborted) +{ + bool done = FALSE; + + DEBUGASSERT(data); + DEBUGASSERT(cpool); + DEBUGASSERT(!conn->bits.in_cpool); + + /* + * If this connection is not marked to force-close, leave it open if there + * are other users of it + */ + if(CONN_INUSE(conn) && !aborted) { + DEBUGF(infof(data, "[CCACHE] not discarding #%" FMT_OFF_T + " still in use by %zu transfers", conn->connection_id, + CONN_INUSE(conn))); + return; + } + + /* treat the connection as aborted in CONNECT_ONLY situations, we do + * not know what the APP did with it. */ + if(conn->connect_only) + aborted = TRUE; + conn->bits.aborted = aborted; + + /* We do not shutdown dead connections. The term 'dead' can be misleading + * here, as we also mark errored connections/transfers as 'dead'. + * If we do a shutdown for an aborted transfer, the server might think + * it was successful otherwise (for example an ftps: upload). This is + * not what we want. */ + if(aborted) + done = TRUE; + if(!done) { + /* Attempt to shutdown the connection right away. */ + Curl_attach_connection(data, conn); + cpool_run_conn_shutdown(data, conn, &done); + DEBUGF(infof(data, "[CCACHE] shutdown #%" FMT_OFF_T ", done=%d", + conn->connection_id, done)); + Curl_detach_connection(data); + } + + if(done) { + cpool_close_and_destroy(cpool, conn, data, FALSE); + return; + } + + /* Add the connection to our shutdown list for non-blocking shutdown + * during multi processing. */ + if(data->multi && data->multi->max_shutdown_connections > 0 && + (data->multi->max_shutdown_connections >= + (long)Curl_llist_count(&cpool->shutdowns))) { + DEBUGF(infof(data, "[CCACHE] discarding oldest shutdown connection " + "due to limit of %ld", + data->multi->max_shutdown_connections)); + cpool_shutdown_destroy_oldest(cpool); + } + + if(data->multi && data->multi->socket_cb) { + DEBUGASSERT(cpool == &data->multi->cpool); + /* Start with an empty shutdown pollset, so out internal closure handle + * is added to the sockets. */ + memset(&conn->shutdown_poll, 0, sizeof(conn->shutdown_poll)); + if(cpool_update_shutdown_ev(data->multi, cpool->idata, conn)) { + DEBUGF(infof(data, "[CCACHE] update events for shutdown failed, " + "discarding #%" FMT_OFF_T, + conn->connection_id)); + cpool_close_and_destroy(cpool, conn, data, FALSE); + return; + } + } + + Curl_llist_append(&cpool->shutdowns, conn, &conn->cpool_node); + DEBUGF(infof(data, "[CCACHE] added #%" FMT_OFF_T + " to shutdown list of length %zu", conn->connection_id, + Curl_llist_count(&cpool->shutdowns))); +} + +void Curl_cpool_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool aborted) +{ + struct cpool *cpool = cpool_get_instance(data); + bool do_lock; + + DEBUGASSERT(cpool); + DEBUGASSERT(data && !data->conn); + if(!cpool) + return; + + /* If this connection is not marked to force-close, leave it open if there + * are other users of it */ + if(CONN_INUSE(conn) && !aborted) { + DEBUGASSERT(0); /* does this ever happen? */ + DEBUGF(infof(data, "Curl_disconnect when inuse: %zu", CONN_INUSE(conn))); + return; + } + + /* This method may be called while we are under lock, e.g. from a + * user callback in find. */ + do_lock = !CPOOL_IS_LOCKED(cpool); + if(do_lock) + CPOOL_LOCK(cpool); + + if(conn->bits.in_cpool) { + cpool_remove_conn(cpool, conn); + DEBUGASSERT(!conn->bits.in_cpool); + } + + /* Run the callback to let it clean up anything it wants to. */ + aborted = cpool->disconnect_cb(data, conn, aborted); + + if(data->multi) { + /* Add it to the multi's cpool for shutdown handling */ + infof(data, "%s connection #%" FMT_OFF_T, + aborted ? "closing" : "shutting down", conn->connection_id); + cpool_discard_conn(&data->multi->cpool, data, conn, aborted); + } + else { + /* No multi available. Make a best-effort shutdown + close */ + infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id); + cpool_close_and_destroy(NULL, conn, data, !aborted); + } + + if(do_lock) + CPOOL_UNLOCK(cpool); +} + +static void cpool_run_conn_shutdown_handler(struct Curl_easy *data, + struct connectdata *conn) +{ + if(!conn->bits.shutdown_handler) { + if(conn->dns_entry) + Curl_resolv_unlink(data, &conn->dns_entry); + + /* Cleanup NTLM connection-related data */ + Curl_http_auth_cleanup_ntlm(conn); + + /* Cleanup NEGOTIATE connection-related data */ + Curl_http_auth_cleanup_negotiate(conn); + + if(conn->handler && conn->handler->disconnect) { + /* This is set if protocol-specific cleanups should be made */ + DEBUGF(infof(data, "connection #%" FMT_OFF_T + ", shutdown protocol handler (aborted=%d)", + conn->connection_id, conn->bits.aborted)); + + conn->handler->disconnect(data, conn, conn->bits.aborted); + } + + /* possible left-overs from the async name resolvers */ + Curl_resolver_cancel(data); + + conn->bits.shutdown_handler = TRUE; + } +} + +static void cpool_run_conn_shutdown(struct Curl_easy *data, + struct connectdata *conn, + bool *done) +{ + CURLcode r1, r2; + bool done1, done2; + + /* We expect to be attached when called */ + DEBUGASSERT(data->conn == conn); + + cpool_run_conn_shutdown_handler(data, conn); + + if(conn->bits.shutdown_filters) { + *done = TRUE; + return; + } + + if(!conn->connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET)) + r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1); + else { + r1 = CURLE_OK; + done1 = TRUE; + } + + if(!conn->connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET)) + r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2); + else { + r2 = CURLE_OK; + done2 = TRUE; + } + + /* we are done when any failed or both report success */ + *done = (r1 || r2 || (done1 && done2)); + if(*done) + conn->bits.shutdown_filters = TRUE; +} + +static CURLcode cpool_add_pollfds(struct cpool *cpool, + struct curl_pollfds *cpfds) +{ + CURLcode result = CURLE_OK; + + if(Curl_llist_head(&cpool->shutdowns)) { + struct Curl_llist_node *e; + struct easy_pollset ps; + struct connectdata *conn; + + for(e = Curl_llist_head(&cpool->shutdowns); e; + e = Curl_node_next(e)) { + conn = Curl_node_elem(e); + memset(&ps, 0, sizeof(ps)); + Curl_attach_connection(cpool->idata, conn); + Curl_conn_adjust_pollset(cpool->idata, &ps); + Curl_detach_connection(cpool->idata); + + result = Curl_pollfds_add_ps(cpfds, &ps); + if(result) { + Curl_pollfds_cleanup(cpfds); + goto out; + } + } + } +out: + return result; +} + +CURLcode Curl_cpool_add_pollfds(struct cpool *cpool, + struct curl_pollfds *cpfds) +{ + CURLcode result; + CPOOL_LOCK(cpool); + result = cpool_add_pollfds(cpool, cpfds); + CPOOL_UNLOCK(cpool); + return result; +} + +CURLcode Curl_cpool_add_waitfds(struct cpool *cpool, + struct curl_waitfds *cwfds) +{ + CURLcode result = CURLE_OK; + + CPOOL_LOCK(cpool); + if(Curl_llist_head(&cpool->shutdowns)) { + struct Curl_llist_node *e; + struct easy_pollset ps; + struct connectdata *conn; + + for(e = Curl_llist_head(&cpool->shutdowns); e; + e = Curl_node_next(e)) { + conn = Curl_node_elem(e); + memset(&ps, 0, sizeof(ps)); + Curl_attach_connection(cpool->idata, conn); + Curl_conn_adjust_pollset(cpool->idata, &ps); + Curl_detach_connection(cpool->idata); + + result = Curl_waitfds_add_ps(cwfds, &ps); + if(result) + goto out; + } + } +out: + CPOOL_UNLOCK(cpool); + return result; +} + +static void cpool_perform(struct cpool *cpool) +{ + struct Curl_easy *data = cpool->idata; + struct Curl_llist_node *e = Curl_llist_head(&cpool->shutdowns); + struct Curl_llist_node *enext; + struct connectdata *conn; + struct curltime *nowp = NULL; + struct curltime now; + timediff_t next_from_now_ms = 0, ms; + bool done; + + if(!e) + return; + + DEBUGASSERT(data); + DEBUGF(infof(data, "[CCACHE] perform, %zu connections being shutdown", + Curl_llist_count(&cpool->shutdowns))); + while(e) { + enext = Curl_node_next(e); + conn = Curl_node_elem(e); + Curl_attach_connection(data, conn); + cpool_run_conn_shutdown(data, conn, &done); + DEBUGF(infof(data, "[CCACHE] shutdown #%" FMT_OFF_T ", done=%d", + conn->connection_id, done)); + Curl_detach_connection(data); + if(done) { + Curl_node_remove(e); + cpool_close_and_destroy(cpool, conn, NULL, FALSE); + } + else { + /* Not done, when does this connection time out? */ + if(!nowp) { + now = Curl_now(); + nowp = &now; + } + ms = Curl_conn_shutdown_timeleft(conn, nowp); + if(ms && ms < next_from_now_ms) + next_from_now_ms = ms; + } + e = enext; + } + + if(next_from_now_ms) + Curl_expire(data, next_from_now_ms, EXPIRE_RUN_NOW); +} + +void Curl_cpool_multi_perform(struct Curl_multi *multi) +{ + CPOOL_LOCK(&multi->cpool); + cpool_perform(&multi->cpool); + CPOOL_UNLOCK(&multi->cpool); +} + + +/* + * Close and destroy the connection. Run the shutdown sequence once, + * of so requested. + */ +static void cpool_close_and_destroy(struct cpool *cpool, + struct connectdata *conn, + struct Curl_easy *data, + bool do_shutdown) +{ + bool done; + + /* there must be a connection to close */ + DEBUGASSERT(conn); + /* it must be removed from the connection pool */ + DEBUGASSERT(!conn->bits.in_cpool); + /* there must be an associated transfer */ + DEBUGASSERT(data || cpool); + if(!data) + data = cpool->idata; + + /* the transfer must be detached from the connection */ + DEBUGASSERT(data && !data->conn); + + Curl_attach_connection(data, conn); + + cpool_run_conn_shutdown_handler(data, conn); + if(do_shutdown) { + /* Make a last attempt to shutdown handlers and filters, if + * not done so already. */ + cpool_run_conn_shutdown(data, conn, &done); + } + + if(cpool) + DEBUGF(infof(data, "[CCACHE] closing #%" FMT_OFF_T, + conn->connection_id)); + else + DEBUGF(infof(data, "closing connection #%" FMT_OFF_T, + conn->connection_id)); + Curl_conn_close(data, SECONDARYSOCKET); + Curl_conn_close(data, FIRSTSOCKET); + Curl_detach_connection(data); + + Curl_conn_free(data, conn); +} + + +static CURLMcode cpool_update_shutdown_ev(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn) +{ + struct easy_pollset ps; + CURLMcode mresult; + + DEBUGASSERT(data); + DEBUGASSERT(multi); + DEBUGASSERT(multi->socket_cb); + + memset(&ps, 0, sizeof(ps)); + Curl_attach_connection(data, conn); + Curl_conn_adjust_pollset(data, &ps); + Curl_detach_connection(data); + + mresult = Curl_multi_pollset_ev(multi, data, &ps, &conn->shutdown_poll); + + if(!mresult) /* Remember for next time */ + memcpy(&conn->shutdown_poll, &ps, sizeof(ps)); + return mresult; +} + +void Curl_cpool_multi_socket(struct Curl_multi *multi, + curl_socket_t s, int ev_bitmask) +{ + struct cpool *cpool = &multi->cpool; + struct Curl_easy *data = cpool->idata; + struct Curl_llist_node *e; + struct connectdata *conn; + bool done; + + (void)ev_bitmask; + DEBUGASSERT(multi->socket_cb); + CPOOL_LOCK(cpool); + e = Curl_llist_head(&cpool->shutdowns); + while(e) { + conn = Curl_node_elem(e); + if(s == conn->sock[FIRSTSOCKET] || s == conn->sock[SECONDARYSOCKET]) { + Curl_attach_connection(data, conn); + cpool_run_conn_shutdown(data, conn, &done); + DEBUGF(infof(data, "[CCACHE] shutdown #%" FMT_OFF_T ", done=%d", + conn->connection_id, done)); + Curl_detach_connection(data); + if(done || cpool_update_shutdown_ev(multi, data, conn)) { + Curl_node_remove(e); + cpool_close_and_destroy(cpool, conn, NULL, FALSE); + } + break; + } + e = Curl_node_next(e); + } + CPOOL_UNLOCK(cpool); +} + +#define NUM_POLLS_ON_STACK 10 + +static CURLcode cpool_shutdown_wait(struct cpool *cpool, int timeout_ms) +{ + struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; + struct curl_pollfds cpfds; + CURLcode result; + + Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK); + + result = cpool_add_pollfds(cpool, &cpfds); + if(result) + goto out; + + Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000)); + +out: + Curl_pollfds_cleanup(&cpfds); + return result; +} + +static void cpool_shutdown_all(struct cpool *cpool, + struct Curl_easy *data, int timeout_ms) +{ + struct connectdata *conn; + struct curltime started = Curl_now(); + + if(!data) + return; + (void)data; + + DEBUGF(infof(data, "cpool shutdown all")); + + /* Move all connections into the shutdown queue */ + for(conn = cpool_get_live_conn(cpool); conn; + conn = cpool_get_live_conn(cpool)) { + /* Move conn from live set to shutdown or destroy right away */ + DEBUGF(infof(data, "moving connection #%" FMT_OFF_T + " to shutdown queue", conn->connection_id)); + cpool_remove_conn(cpool, conn); + cpool_discard_conn(cpool, data, conn, FALSE); + } + + while(Curl_llist_head(&cpool->shutdowns)) { + timediff_t timespent; + int remain_ms; + + cpool_perform(cpool); + + if(!Curl_llist_head(&cpool->shutdowns)) { + DEBUGF(infof(data, "cpool shutdown ok")); + break; + } + + /* wait for activity, timeout or "nothing" */ + timespent = Curl_timediff(Curl_now(), started); + if(timespent >= (timediff_t)timeout_ms) { + DEBUGF(infof(data, "cpool shutdown %s", + (timeout_ms > 0) ? "timeout" : "best effort done")); + break; + } + + remain_ms = timeout_ms - (int)timespent; + if(cpool_shutdown_wait(cpool, remain_ms)) { + DEBUGF(infof(data, "cpool shutdown all, abort")); + break; + } + } + + /* Due to errors/timeout, we might come here without being done. */ + cpool_shutdown_discard_all(cpool); +} + +struct cpool_reaper_ctx { + struct curltime now; +}; + +static int cpool_reap_dead_cb(struct Curl_easy *data, + struct connectdata *conn, void *param) +{ + struct cpool_reaper_ctx *rctx = param; + if(Curl_conn_seems_dead(conn, data, &rctx->now)) { + /* stop the iteration here, pass back the connection that was pruned */ + Curl_cpool_disconnect(data, conn, FALSE); + return 1; + } + return 0; /* continue iteration */ +} + +/* + * This function scans the data's connection pool for half-open/dead + * connections, closes and removes them. + * The cleanup is done at most once per second. + * + * When called, this transfer has no connection attached. + */ +void Curl_cpool_prune_dead(struct Curl_easy *data) +{ + struct cpool *cpool = cpool_get_instance(data); + struct cpool_reaper_ctx rctx; + timediff_t elapsed; + + if(!cpool) + return; + + rctx.now = Curl_now(); + CPOOL_LOCK(cpool); + elapsed = Curl_timediff(rctx.now, cpool->last_cleanup); + + if(elapsed >= 1000L) { + while(cpool_foreach(data, cpool, &rctx, cpool_reap_dead_cb)) + ; + cpool->last_cleanup = rctx.now; + } + CPOOL_UNLOCK(cpool); +} + +static int conn_upkeep(struct Curl_easy *data, + struct connectdata *conn, + void *param) +{ + struct curltime *now = param; + /* TODO, shall we reap connections that return an error here? */ + Curl_conn_upkeep(data, conn, now); + return 0; /* continue iteration */ +} + +CURLcode Curl_cpool_upkeep(void *data) +{ + struct cpool *cpool = cpool_get_instance(data); + struct curltime now = Curl_now(); + + if(!cpool) + return CURLE_OK; + + CPOOL_LOCK(cpool); + cpool_foreach(data, cpool, &now, conn_upkeep); + CPOOL_UNLOCK(cpool); + return CURLE_OK; +} + +struct cpool_find_ctx { + curl_off_t id; + struct connectdata *conn; +}; + +static int cpool_find_conn(struct Curl_easy *data, + struct connectdata *conn, void *param) +{ + struct cpool_find_ctx *fctx = param; + (void)data; + if(conn->connection_id == fctx->id) { + fctx->conn = conn; + return 1; + } + return 0; +} + +struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, + curl_off_t conn_id) +{ + struct cpool *cpool = cpool_get_instance(data); + struct cpool_find_ctx fctx; + + if(!cpool) + return NULL; + fctx.id = conn_id; + fctx.conn = NULL; + CPOOL_LOCK(cpool); + cpool_foreach(cpool->idata, cpool, &fctx, cpool_find_conn); + CPOOL_UNLOCK(cpool); + return fctx.conn; +} + +struct cpool_do_conn_ctx { + curl_off_t id; + Curl_cpool_conn_do_cb *cb; + void *cbdata; +}; + +static int cpool_do_conn(struct Curl_easy *data, + struct connectdata *conn, void *param) +{ + struct cpool_do_conn_ctx *dctx = param; + (void)data; + if(conn->connection_id == dctx->id) { + dctx->cb(conn, data, dctx->cbdata); + return 1; + } + return 0; +} + +void Curl_cpool_do_by_id(struct Curl_easy *data, curl_off_t conn_id, + Curl_cpool_conn_do_cb *cb, void *cbdata) +{ + struct cpool *cpool = cpool_get_instance(data); + struct cpool_do_conn_ctx dctx; + + if(!cpool) + return; + dctx.id = conn_id; + dctx.cb = cb; + dctx.cbdata = cbdata; + CPOOL_LOCK(cpool); + cpool_foreach(data, cpool, &dctx, cpool_do_conn); + CPOOL_UNLOCK(cpool); +} + +void Curl_cpool_do_locked(struct Curl_easy *data, + struct connectdata *conn, + Curl_cpool_conn_do_cb *cb, void *cbdata) +{ + struct cpool *cpool = cpool_get_instance(data); + if(cpool) { + CPOOL_LOCK(cpool); + cb(conn, data, cbdata); + CPOOL_UNLOCK(cpool); + } + else + cb(conn, data, cbdata); +} + #if 0 -/* Useful for debugging the connection cache */ -void Curl_conncache_print(struct conncache *connc) +/* Useful for debugging the connection pool */ +void Curl_cpool_print(struct cpool *cpool) { struct Curl_hash_iterator iter; - struct Curl_llist_element *curr; + struct Curl_llist_node *curr; struct Curl_hash_element *he; - if(!connc) + if(!cpool) return; fprintf(stderr, "=Bundle cache=\n"); - Curl_hash_start_iterate(connc->hash, &iter); + Curl_hash_start_iterate(cpool->dest2bundle, &iter); he = Curl_hash_next_element(&iter); while(he) { - struct connectbundle *bundle; + struct cpool_bundle *bundle; struct connectdata *conn; bundle = he->ptr; fprintf(stderr, "%s -", he->key); - curr = bundle->conn_list->head; + curr = Curl_llist_head(bundle->conns); while(curr) { - conn = curr->ptr; + conn = Curl_node_elem(curr); - fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse); - curr = curr->next; + fprintf(stderr, " [%p %d]", (void *)conn, conn->refcount); + curr = Curl_node_next(curr); } fprintf(stderr, "\n"); diff --git a/deps/curl/lib/conncache.h b/deps/curl/lib/conncache.h index c60f8449eeb..a379ee747de 100644 --- a/deps/curl/lib/conncache.h +++ b/deps/curl/lib/conncache.h @@ -25,98 +25,177 @@ * ***************************************************************************/ -/* - * All accesses to struct fields and changing of data in the connection cache - * and connectbundles must be done with the conncache LOCKED. The cache might - * be shared. - */ - #include #include "timeval.h" struct connectdata; +struct Curl_easy; +struct curl_pollfds; +struct curl_waitfds; +struct Curl_multi; +struct Curl_share; -struct conncache { - struct Curl_hash hash; +/** + * Callback invoked when disconnecting connections. + * @param data transfer last handling the connection, not attached + * @param conn the connection to discard + * @param aborted if the connection is being aborted + * @return if the connection is being aborted, e.g. should NOT perform + * a shutdown and just close. + **/ +typedef bool Curl_cpool_disconnect_cb(struct Curl_easy *data, + struct connectdata *conn, + bool aborted); + +struct cpool { + /* the pooled connections, bundled per destination */ + struct Curl_hash dest2bundle; size_t num_conn; curl_off_t next_connection_id; curl_off_t next_easy_id; struct curltime last_cleanup; - /* handle used for closing cached connections */ - struct Curl_easy *closure_handle; + struct Curl_llist shutdowns; /* The connections being shut down */ + struct Curl_easy *idata; /* internal handle used for discard */ + struct Curl_multi *multi; /* != NULL iff pool belongs to multi */ + struct Curl_share *share; /* != NULL iff pool belongs to share */ + Curl_cpool_disconnect_cb *disconnect_cb; + BIT(locked); }; -#define BUNDLE_NO_MULTIUSE -1 -#define BUNDLE_UNKNOWN 0 /* initial value */ -#define BUNDLE_MULTIPLEX 2 - -#ifdef CURLDEBUG -/* the debug versions of these macros make extra certain that the lock is - never doubly locked or unlocked */ -#define CONNCACHE_LOCK(x) \ - do { \ - if((x)->share) { \ - Curl_share_lock((x), CURL_LOCK_DATA_CONNECT, \ - CURL_LOCK_ACCESS_SINGLE); \ - DEBUGASSERT(!(x)->state.conncache_lock); \ - (x)->state.conncache_lock = TRUE; \ - } \ - } while(0) - -#define CONNCACHE_UNLOCK(x) \ - do { \ - if((x)->share) { \ - DEBUGASSERT((x)->state.conncache_lock); \ - (x)->state.conncache_lock = FALSE; \ - Curl_share_unlock((x), CURL_LOCK_DATA_CONNECT); \ - } \ - } while(0) -#else -#define CONNCACHE_LOCK(x) if((x)->share) \ - Curl_share_lock((x), CURL_LOCK_DATA_CONNECT, CURL_LOCK_ACCESS_SINGLE) -#define CONNCACHE_UNLOCK(x) if((x)->share) \ - Curl_share_unlock((x), CURL_LOCK_DATA_CONNECT) -#endif - -struct connectbundle { - int multiuse; /* supports multi-use */ - size_t num_connections; /* Number of connections in the bundle */ - struct Curl_llist conn_list; /* The connectdata members of the bundle */ -}; +/* Init the pool, pass multi only if pool is owned by it. + * returns 1 on error, 0 is fine. + */ +int Curl_cpool_init(struct cpool *cpool, + Curl_cpool_disconnect_cb *disconnect_cb, + struct Curl_multi *multi, + struct Curl_share *share, + size_t size); + +/* Destroy all connections and free all members */ +void Curl_cpool_destroy(struct cpool *connc); + +/* Init the transfer to be used within its connection pool. + * Assigns `data->id`. */ +void Curl_cpool_xfer_init(struct Curl_easy *data); + +/** + * Get the connection with the given id from the transfer's pool. + */ +struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, + curl_off_t conn_id); + +CURLcode Curl_cpool_add_conn(struct Curl_easy *data, + struct connectdata *conn) WARN_UNUSED_RESULT; + +/** + * Return if the pool has reached its configured limits for adding + * the given connection. Will try to discard the oldest, idle + * connections to make space. + */ +#define CPOOL_LIMIT_OK 0 +#define CPOOL_LIMIT_DEST 1 +#define CPOOL_LIMIT_TOTAL 2 +int Curl_cpool_check_limits(struct Curl_easy *data, + struct connectdata *conn); + +/* Return of conn is suitable. If so, stops iteration. */ +typedef bool Curl_cpool_conn_match_cb(struct connectdata *conn, + void *userdata); + +/* Act on the result of the find, may override it. */ +typedef bool Curl_cpool_done_match_cb(bool result, void *userdata); + +/** + * Find a connection in the pool matching `destination`. + * All callbacks are invoked while the pool's lock is held. + * @param data current transfer + * @param destination match agaonst `conn->destination` in pool + * @param dest_len destination length, including terminating NUL + * @param conn_cb must be present, called for each connection in the + * bundle until it returns TRUE + * @param result_cb if not NULL, is called at the end with the result + * of the `conn_cb` or FALSE if never called. + * @return combined result of last conn_db and result_cb or FALSE if no + connections were present. + */ +bool Curl_cpool_find(struct Curl_easy *data, + const char *destination, size_t dest_len, + Curl_cpool_conn_match_cb *conn_cb, + Curl_cpool_done_match_cb *done_cb, + void *userdata); + +/* + * A connection (already in the pool) is now idle. Do any + * cleanups in regard to the pool's limits. + * + * Return TRUE if idle connection kept in pool, FALSE if closed. + */ +bool Curl_cpool_conn_now_idle(struct Curl_easy *data, + struct connectdata *conn); + +/** + * Remove the connection from the pool and tear it down. + * If `aborted` is FALSE, the connection will be shut down first + * before closing and destroying it. + * If the shutdown is not immediately complete, the connection + * will be placed into the pool's shutdown queue. + */ +void Curl_cpool_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool aborted); + +/** + * This function scans the data's connection pool for half-open/dead + * connections, closes and removes them. + * The cleanup is done at most once per second. + * + * When called, this transfer has no connection attached. + */ +void Curl_cpool_prune_dead(struct Curl_easy *data); + +/** + * Perform upkeep actions on connections in the transfer's pool. + */ +CURLcode Curl_cpool_upkeep(void *data); + +typedef void Curl_cpool_conn_do_cb(struct connectdata *conn, + struct Curl_easy *data, + void *cbdata); + +/** + * Invoke the callback on the pool's connection with the + * given connection id (if it exists). + */ +void Curl_cpool_do_by_id(struct Curl_easy *data, + curl_off_t conn_id, + Curl_cpool_conn_do_cb *cb, void *cbdata); + +/** + * Invoked the callback for the given data + connection under the + * connection pool's lock. + * The callback is always invoked, even if the transfer has no connection + * pool associated. + */ +void Curl_cpool_do_locked(struct Curl_easy *data, + struct connectdata *conn, + Curl_cpool_conn_do_cb *cb, void *cbdata); + +/** + * Add sockets and POLLIN/OUT flags for connections handled by the pool. + */ +CURLcode Curl_cpool_add_pollfds(struct cpool *connc, + struct curl_pollfds *cpfds); +CURLcode Curl_cpool_add_waitfds(struct cpool *connc, + struct curl_waitfds *cwfds); + +/** + * Perform maintenance on connections in the pool. Specifically, + * progress the shutdown of connections in the queue. + */ +void Curl_cpool_multi_perform(struct Curl_multi *multi); + +void Curl_cpool_multi_socket(struct Curl_multi *multi, + curl_socket_t s, int ev_bitmask); -/* returns 1 on error, 0 is fine */ -int Curl_conncache_init(struct conncache *, int size); -void Curl_conncache_destroy(struct conncache *connc); - -/* return the correct bundle, to a host or a proxy */ -struct connectbundle *Curl_conncache_find_bundle(struct Curl_easy *data, - struct connectdata *conn, - struct conncache *connc); -/* returns number of connections currently held in the connection cache */ -size_t Curl_conncache_size(struct Curl_easy *data); - -bool Curl_conncache_return_conn(struct Curl_easy *data, - struct connectdata *conn); -CURLcode Curl_conncache_add_conn(struct Curl_easy *data) WARN_UNUSED_RESULT; -void Curl_conncache_remove_conn(struct Curl_easy *data, - struct connectdata *conn, - bool lock); -bool Curl_conncache_foreach(struct Curl_easy *data, - struct conncache *connc, - void *param, - int (*func)(struct Curl_easy *data, - struct connectdata *conn, - void *param)); - -struct connectdata * -Curl_conncache_find_first_connection(struct conncache *connc); - -struct connectdata * -Curl_conncache_extract_bundle(struct Curl_easy *data, - struct connectbundle *bundle); -struct connectdata * -Curl_conncache_extract_oldest(struct Curl_easy *data); -void Curl_conncache_close_all_connections(struct conncache *connc); -void Curl_conncache_print(struct conncache *connc); #endif /* HEADER_CURL_CONNCACHE_H */ diff --git a/deps/curl/lib/connect.c b/deps/curl/lib/connect.c index 033fb7b17d6..ee3b4c8a692 100644 --- a/deps/curl/lib/connect.c +++ b/deps/curl/lib/connect.c @@ -84,31 +84,26 @@ #include "curl_memory.h" #include "memdebug.h" +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif /* * Curl_timeleft() returns the amount of milliseconds left allowed for the - * transfer/connection. If the value is 0, there's no timeout (ie there's + * transfer/connection. If the value is 0, there is no timeout (ie there is * infinite time left). If the value is negative, the timeout time has already * elapsed. - * - * If 'nowp' is non-NULL, it points to the current time. - * 'duringconnect' is FALSE if not during a connect, as then of course the - * connect timeout is not taken into account! - * + * @param data the transfer to check on + * @param nowp timestamp to use for calculation, NULL to use Curl_now() + * @param duringconnect TRUE iff connect timeout is also taken into account. * @unittest: 1303 */ - -#define TIMEOUT_CONNECT 1 -#define TIMEOUT_MAXTIME 2 - timediff_t Curl_timeleft(struct Curl_easy *data, struct curltime *nowp, bool duringconnect) { - unsigned int timeout_set = 0; - timediff_t connect_timeout_ms = 0; - timediff_t maxtime_timeout_ms = 0; - timediff_t timeout_ms = 0; + timediff_t timeleft_ms = 0; + timediff_t ctimeleft_ms = 0; struct curltime now; /* The duration of a connect and the total transfer are calculated from two @@ -116,61 +111,101 @@ timediff_t Curl_timeleft(struct Curl_easy *data, before the connect timeout expires and we must acknowledge whichever timeout that is reached first. The total timeout is set per entire operation, while the connect timeout is set per connect. */ + if(data->set.timeout <= 0 && !duringconnect) + return 0; /* no timeout in place or checked, return "no limit" */ + + if(!nowp) { + now = Curl_now(); + nowp = &now; + } if(data->set.timeout > 0) { - timeout_set = TIMEOUT_MAXTIME; - maxtime_timeout_ms = data->set.timeout; + timeleft_ms = data->set.timeout - + Curl_timediff(*nowp, data->progress.t_startop); + if(!timeleft_ms) + timeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */ + if(!duringconnect) + return timeleft_ms; /* no connect check, this is it */ } + if(duringconnect) { - timeout_set |= TIMEOUT_CONNECT; - connect_timeout_ms = (data->set.connecttimeout > 0) ? + timediff_t ctimeout_ms = (data->set.connecttimeout > 0) ? data->set.connecttimeout : DEFAULT_CONNECT_TIMEOUT; + ctimeleft_ms = ctimeout_ms - + Curl_timediff(*nowp, data->progress.t_startsingle); + if(!ctimeleft_ms) + ctimeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */ + if(!timeleft_ms) + return ctimeleft_ms; /* no general timeout, this is it */ } - if(!timeout_set) - /* no timeout */ - return 0; + /* return minimal time left or max amount already expired */ + return (ctimeleft_ms < timeleft_ms) ? ctimeleft_ms : timeleft_ms; +} + +void Curl_shutdown_start(struct Curl_easy *data, int sockindex, + struct curltime *nowp) +{ + struct curltime now; + DEBUGASSERT(data->conn); if(!nowp) { now = Curl_now(); nowp = &now; } + data->conn->shutdown.start[sockindex] = *nowp; + data->conn->shutdown.timeout_ms = (data->set.shutdowntimeout > 0) ? + data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS; +} - if(timeout_set & TIMEOUT_MAXTIME) { - maxtime_timeout_ms -= Curl_timediff(*nowp, data->progress.t_startop); - timeout_ms = maxtime_timeout_ms; - } +timediff_t Curl_shutdown_timeleft(struct connectdata *conn, int sockindex, + struct curltime *nowp) +{ + struct curltime now; + timediff_t left_ms; - if(timeout_set & TIMEOUT_CONNECT) { - connect_timeout_ms -= Curl_timediff(*nowp, data->progress.t_startsingle); + if(!conn->shutdown.start[sockindex].tv_sec || !conn->shutdown.timeout_ms) + return 0; /* not started or no limits */ - if(!(timeout_set & TIMEOUT_MAXTIME) || - (connect_timeout_ms < maxtime_timeout_ms)) - timeout_ms = connect_timeout_ms; + if(!nowp) { + now = Curl_now(); + nowp = &now; } + left_ms = conn->shutdown.timeout_ms - + Curl_timediff(*nowp, conn->shutdown.start[sockindex]); + return left_ms ? left_ms : -1; +} - if(!timeout_ms) - /* avoid returning 0 as that means no timeout! */ - return -1; +timediff_t Curl_conn_shutdown_timeleft(struct connectdata *conn, + struct curltime *nowp) +{ + timediff_t left_ms = 0, ms; + struct curltime now; + int i; - return timeout_ms; + for(i = 0; conn->shutdown.timeout_ms && (i < 2); ++i) { + if(!conn->shutdown.start[i].tv_sec) + continue; + if(!nowp) { + now = Curl_now(); + nowp = &now; + } + ms = Curl_shutdown_timeleft(conn, i, nowp); + if(ms && (!left_ms || ms < left_ms)) + left_ms = ms; + } + return left_ms; } -/* Copies connection info into the transfer handle to make it available when - the transfer handle is no longer associated with the connection. */ -void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn, - char *local_ip, int local_port) +void Curl_shutdown_clear(struct Curl_easy *data, int sockindex) { - memcpy(data->info.conn_primary_ip, conn->primary_ip, MAX_IPADR_LEN); - if(local_ip && local_ip[0]) - memcpy(data->info.conn_local_ip, local_ip, MAX_IPADR_LEN); - else - data->info.conn_local_ip[0] = 0; - data->info.conn_scheme = conn->handler->scheme; - /* conn_protocol can only provide "old" protocols */ - data->info.conn_protocol = (conn->handler->protocol) & CURLPROTO_MASK; - data->info.conn_primary_port = conn->port; - data->info.conn_remote_port = conn->remote_port; - data->info.conn_local_port = local_port; + struct curltime *pt = &data->conn->shutdown.start[sockindex]; + memset(pt, 0, sizeof(*pt)); +} + +bool Curl_shutdown_started(struct Curl_easy *data, int sockindex) +{ + struct curltime *pt = &data->conn->shutdown.start[sockindex]; + return (pt->tv_sec > 0) || (pt->tv_usec > 0); } static const struct Curl_addrinfo * @@ -201,7 +236,7 @@ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, char *addr, int *port) { struct sockaddr_in *si = NULL; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *si6 = NULL; #endif #if (defined(HAVE_SYS_UN_H) || defined(WIN32_SOCKADDR_UN)) && defined(AF_UNIX) @@ -220,7 +255,7 @@ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, return TRUE; } break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: si6 = (struct sockaddr_in6 *)(void *) sa; if(Curl_inet_ntop(sa->sa_family, &si6->sin6_addr, @@ -252,23 +287,6 @@ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, return FALSE; } -struct connfind { - curl_off_t id_tofind; - struct connectdata *found; -}; - -static int conn_is_conn(struct Curl_easy *data, - struct connectdata *conn, void *param) -{ - struct connfind *f = (struct connfind *)param; - (void)data; - if(conn->connection_id == f->id_tofind) { - f->found = conn; - return 1; - } - return 0; -} - /* * Used to extract socket and connectdata struct for the most recent * transfer on the given Curl_easy. @@ -285,30 +303,19 @@ curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, * - that is associated with a multi handle, and whose connection * was detached with CURLOPT_CONNECT_ONLY */ - if((data->state.lastconnect_id != -1) && (data->multi_easy || data->multi)) { - struct connectdata *c; - struct connfind find; - find.id_tofind = data->state.lastconnect_id; - find.found = NULL; - - Curl_conncache_foreach(data, - data->share && (data->share->specifier - & (1<< CURL_LOCK_DATA_CONNECT))? - &data->share->conn_cache: - data->multi_easy? - &data->multi_easy->conn_cache: - &data->multi->conn_cache, &find, conn_is_conn); - - if(!find.found) { + if(data->state.lastconnect_id != -1) { + struct connectdata *conn; + + conn = Curl_cpool_get_conn(data, data->state.lastconnect_id); + if(!conn) { data->state.lastconnect_id = -1; return CURL_SOCKET_BAD; } - c = find.found; if(connp) /* only store this if the caller cares for it */ - *connp = c; - return c->sock[FIRSTSOCKET]; + *connp = conn; + return conn->sock[FIRSTSOCKET]; } return CURL_SOCKET_BAD; } @@ -323,7 +330,7 @@ void Curl_conncontrol(struct connectdata *conn, #endif ) { - /* close if a connection, or a stream that isn't multiplexed. */ + /* close if a connection, or a stream that is not multiplexed. */ /* This function will be called both before and after this connection is associated with a transfer. */ bool closeit, is_multiplex; @@ -348,6 +355,7 @@ void Curl_conncontrol(struct connectdata *conn, */ struct eyeballer { const char *name; + const struct Curl_addrinfo *first; /* complete address list, not owned */ const struct Curl_addrinfo *addr; /* List of addresses to try, not owned */ int ai_family; /* matching address family only */ cf_ip_connect_create *cf_create; /* for creating cf */ @@ -359,9 +367,13 @@ struct eyeballer { expire_id timeout_id; /* ID for Curl_expire() */ CURLcode result; int error; + BIT(rewinded); /* if we rewinded the addr list */ BIT(has_started); /* attempts have started */ BIT(is_done); /* out of addresses/time */ BIT(connected); /* cf has connected */ + BIT(shutdown); /* cf has shutdown */ + BIT(inconclusive); /* connect was not a hard failure, we + * might talk to a restarting server */ }; @@ -398,21 +410,21 @@ static CURLcode eyeballer_new(struct eyeballer **pballer, struct eyeballer *baller; *pballer = NULL; - baller = calloc(1, sizeof(*baller) + 1000); + baller = calloc(1, sizeof(*baller)); if(!baller) return CURLE_OUT_OF_MEMORY; - baller->name = ((ai_family == AF_INET)? "ipv4" : ( -#ifdef ENABLE_IPV6 - (ai_family == AF_INET6)? "ipv6" : + baller->name = ((ai_family == AF_INET) ? "ipv4" : ( +#ifdef USE_IPV6 + (ai_family == AF_INET6) ? "ipv6" : #endif "ip")); baller->cf_create = cf_create; - baller->addr = addr; + baller->first = baller->addr = addr; baller->ai_family = ai_family; baller->primary = primary; baller->delay_ms = delay_ms; - baller->timeoutms = addr_next_match(baller->addr, baller->ai_family)? + baller->timeoutms = addr_next_match(baller->addr, baller->ai_family) ? USETIME(timeout_ms) : timeout_ms; baller->timeout_id = timeout_id; baller->result = CURLE_COULDNT_CONNECT; @@ -438,6 +450,13 @@ static void baller_free(struct eyeballer *baller, } } +static void baller_rewind(struct eyeballer *baller) +{ + baller->rewinded = TRUE; + baller->addr = baller->first; + baller->inconclusive = FALSE; +} + static void baller_next_addr(struct eyeballer *baller) { baller->addr = addr_next_match(baller->addr, baller->ai_family); @@ -459,7 +478,7 @@ static void baller_initiate(struct Curl_cfilter *cf, CURLcode result; - /* Don't close a previous cfilter yet to ensure that the next IP's + /* Do not close a previous cfilter yet to ensure that the next IP's socket gets a different file descriptor, which can prevent bugs when the curl_multi_socket_action interface is used with certain select() replacements such as kqueue. */ @@ -528,6 +547,12 @@ static CURLcode baller_start_next(struct Curl_cfilter *cf, { if(cf->sockindex == FIRSTSOCKET) { baller_next_addr(baller); + /* If we get inconclusive answers from the server(s), we start + * again until this whole thing times out. This allows us to + * connect to servers that are gracefully restarting and the + * packet routing to the new instance has not happened yet (e.g. QUIC). */ + if(!baller->addr && baller->inconclusive) + baller_rewind(baller); baller_start(cf, data, baller, timeoutms); } else { @@ -558,7 +583,7 @@ static CURLcode baller_connect(struct Curl_cfilter *cf, baller->is_done = TRUE; } else if(Curl_timediff(*now, baller->started) >= baller->timeoutms) { - infof(data, "%s connect timeout after %" CURL_FORMAT_TIMEDIFF_T + infof(data, "%s connect timeout after %" FMT_TIMEDIFF_T "ms, move on!", baller->name, baller->timeoutms); #if defined(ETIMEDOUT) baller->error = ETIMEDOUT; @@ -566,6 +591,8 @@ static CURLcode baller_connect(struct Curl_cfilter *cf, baller->result = CURLE_OPERATION_TIMEDOUT; } } + else if(baller->result == CURLE_WEIRD_SERVER_REPLY) + baller->inconclusive = TRUE; } return baller->result; } @@ -592,10 +619,10 @@ static CURLcode is_connected(struct Curl_cfilter *cf, * If transport is QUIC, we need to shutdown the ongoing 'other' * cot ballers in a QUIC appropriate way. */ evaluate: - *connected = FALSE; /* a very negative world view is best */ + *connected = FALSE; /* a negative world view is best */ now = Curl_now(); ongoing = not_started = 0; - for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; if(!baller || baller->is_done) @@ -634,6 +661,7 @@ static CURLcode is_connected(struct Curl_cfilter *cf, /* next attempt was started */ CURL_TRC_CF(data, cf, "%s trying next", baller->name); ++ongoing; + Curl_expire(data, 0, EXPIRE_RUN_NOW); } } } @@ -646,7 +674,7 @@ static CURLcode is_connected(struct Curl_cfilter *cf, /* Nothing connected, check the time before we might * start new ballers or return ok. */ if((ongoing || not_started) && Curl_timeleft(data, &now, TRUE) < 0) { - failf(data, "Connection timeout after %ld ms", + failf(data, "Connection timeout after %" FMT_OFF_T " ms", Curl_timediff(now, data->progress.t_startsingle)); return CURLE_OPERATION_TIMEDOUT; } @@ -655,7 +683,7 @@ static CURLcode is_connected(struct Curl_cfilter *cf, if(not_started > 0) { int added = 0; - for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; if(!baller || baller->has_started) @@ -669,8 +697,7 @@ static CURLcode is_connected(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "%s done", baller->name); } else { - CURL_TRC_CF(data, cf, "%s starting (timeout=%" - CURL_FORMAT_TIMEDIFF_T "ms)", + CURL_TRC_CF(data, cf, "%s starting (timeout=%" FMT_TIMEDIFF_T "ms)", baller->name, baller->timeoutms); ++ongoing; ++added; @@ -690,13 +717,13 @@ static CURLcode is_connected(struct Curl_cfilter *cf, /* all ballers have failed to connect. */ CURL_TRC_CF(data, cf, "all eyeballers failed"); result = CURLE_COULDNT_CONNECT; - for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; + if(!baller) + continue; CURL_TRC_CF(data, cf, "%s assess started=%d, result=%d", - baller?baller->name:NULL, - baller?baller->has_started:0, - baller?baller->result:0); - if(baller && baller->has_started && baller->result) { + baller->name, baller->has_started, baller->result); + if(baller->has_started && baller->result) { result = baller->result; break; } @@ -715,8 +742,8 @@ static CURLcode is_connected(struct Curl_cfilter *cf, hostname = conn->host.name; failf(data, "Failed to connect to %s port %u after " - "%" CURL_FORMAT_TIMEDIFF_T " ms: %s", - hostname, conn->port, + "%" FMT_TIMEDIFF_T " ms: %s", + hostname, conn->primary.remote_port, Curl_timediff(now, data->progress.t_startsingle), curl_easy_strerror(result)); @@ -732,7 +759,7 @@ static CURLcode is_connected(struct Curl_cfilter *cf, } /* - * Connect to the given host with timeout, proxy or remote doesn't matter. + * Connect to the given host with timeout, proxy or remote does not matter. * There might be more than one IP address to try out. */ static CURLcode start_connect(struct Curl_cfilter *cf, @@ -742,9 +769,9 @@ static CURLcode start_connect(struct Curl_cfilter *cf, struct cf_he_ctx *ctx = cf->ctx; struct connectdata *conn = cf->conn; CURLcode result = CURLE_COULDNT_CONNECT; - int ai_family0, ai_family1; + int ai_family0 = 0, ai_family1 = 0; timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - const struct Curl_addrinfo *addr0, *addr1; + const struct Curl_addrinfo *addr0 = NULL, *addr1 = NULL; if(timeout_ms < 0) { /* a precaution, no need to continue if time already is up */ @@ -763,33 +790,33 @@ static CURLcode start_connect(struct Curl_cfilter *cf, * the 2 connect attempt ballers to try different families, if possible. * */ - if(conn->ip_version == CURL_IPRESOLVE_WHATEVER) { - /* any IP version is allowed */ - ai_family0 = remotehost->addr? - remotehost->addr->ai_family : 0; -#ifdef ENABLE_IPV6 - ai_family1 = ai_family0 == AF_INET6 ? - AF_INET : AF_INET6; -#else - ai_family1 = AF_UNSPEC; + if(conn->ip_version == CURL_IPRESOLVE_V6) { +#ifdef USE_IPV6 + ai_family0 = AF_INET6; + addr0 = addr_first_match(remotehost->addr, ai_family0); #endif } + else if(conn->ip_version == CURL_IPRESOLVE_V4) { + ai_family0 = AF_INET; + addr0 = addr_first_match(remotehost->addr, ai_family0); + } else { - /* only one IP version is allowed */ - ai_family0 = (conn->ip_version == CURL_IPRESOLVE_V4) ? - AF_INET : -#ifdef ENABLE_IPV6 - AF_INET6; -#else - AF_UNSPEC; + /* no user preference, we try ipv6 always first when available */ +#ifdef USE_IPV6 + ai_family0 = AF_INET6; + addr0 = addr_first_match(remotehost->addr, ai_family0); #endif - ai_family1 = AF_UNSPEC; + /* next candidate is ipv4 */ + ai_family1 = AF_INET; + addr1 = addr_first_match(remotehost->addr, ai_family1); + /* no ip address families, probably AF_UNIX or something, use the + * address family given to us */ + if(!addr1 && !addr0 && remotehost->addr) { + ai_family0 = remotehost->addr->ai_family; + addr0 = addr_first_match(remotehost->addr, ai_family0); + } } - /* Get the first address in the list that matches the family, - * this might give NULL, if we do not have any matches. */ - addr0 = addr_first_match(remotehost->addr, ai_family0); - addr1 = addr_first_match(remotehost->addr, ai_family1); if(!addr0 && addr1) { /* switch around, so a single baller always uses addr0 */ addr0 = addr1; @@ -808,8 +835,7 @@ static CURLcode start_connect(struct Curl_cfilter *cf, timeout_ms, EXPIRE_DNS_PER_NAME); if(result) return result; - CURL_TRC_CF(data, cf, "created %s (timeout %" - CURL_FORMAT_TIMEDIFF_T "ms)", + CURL_TRC_CF(data, cf, "created %s (timeout %" FMT_TIMEDIFF_T "ms)", ctx->baller[0]->name, ctx->baller[0]->timeoutms); if(addr1) { /* second one gets a delayed start */ @@ -820,14 +846,12 @@ static CURLcode start_connect(struct Curl_cfilter *cf, timeout_ms, EXPIRE_DNS_PER_NAME2); if(result) return result; - CURL_TRC_CF(data, cf, "created %s (timeout %" - CURL_FORMAT_TIMEDIFF_T "ms)", + CURL_TRC_CF(data, cf, "created %s (timeout %" FMT_TIMEDIFF_T "ms)", ctx->baller[1]->name, ctx->baller[1]->timeoutms); + Curl_expire(data, data->set.happy_eyeballs_timeout, + EXPIRE_HAPPY_EYEBALLS); } - Curl_expire(data, data->set.happy_eyeballs_timeout, - EXPIRE_HAPPY_EYEBALLS); - return CURLE_OK; } @@ -838,7 +862,7 @@ static void cf_he_ctx_clear(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGASSERT(ctx); DEBUGASSERT(data); - for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { baller_free(ctx->baller[i], data); ctx->baller[i] = NULL; } @@ -846,35 +870,62 @@ static void cf_he_ctx_clear(struct Curl_cfilter *cf, struct Curl_easy *data) ctx->winner = NULL; } -static int cf_he_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) +static CURLcode cf_he_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) { struct cf_he_ctx *ctx = cf->ctx; - size_t i, s; - int wrc, rc = GETSOCK_BLANK; - curl_socket_t wsocks[MAX_SOCKSPEREASYHANDLE]; + size_t i; + CURLcode result = CURLE_OK; - if(cf->connected) - return cf->next->cft->get_select_socks(cf->next, data, socks); + DEBUGASSERT(data); + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } - for(i = s = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + /* shutdown all ballers that have not done so already. If one fails, + * continue shutting down others until all are shutdown. */ + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; - if(!baller || !baller->cf) + bool bdone = FALSE; + if(!baller || !baller->cf || baller->shutdown) continue; + baller->result = baller->cf->cft->do_shutdown(baller->cf, data, &bdone); + if(baller->result || bdone) + baller->shutdown = TRUE; /* treat a failed shutdown as done */ + } + + *done = TRUE; + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + if(ctx->baller[i] && !ctx->baller[i]->shutdown) + *done = FALSE; + } + if(*done) { + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + if(ctx->baller[i] && ctx->baller[i]->result) + result = ctx->baller[i]->result; + } + } + CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done); + return result; +} + +static void cf_he_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_he_ctx *ctx = cf->ctx; + size_t i; - wrc = Curl_conn_cf_get_select_socks(baller->cf, data, wsocks); - if(wrc) { - /* TODO: we assume we get at most one socket back */ - socks[s] = wsocks[0]; - if(wrc & GETSOCK_WRITESOCK(0)) - rc |= GETSOCK_WRITESOCK(s); - if(wrc & GETSOCK_READSOCK(0)) - rc |= GETSOCK_READSOCK(s); - s++; + if(!cf->connected) { + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + if(!baller || !baller->cf) + continue; + Curl_conn_cf_adjust_pollset(baller->cf, data, ps); } + CURL_TRC_CF(data, cf, "adjust_pollset -> %d socks", ps->num); } - return rc; } static CURLcode cf_he_connect(struct Curl_cfilter *cf, @@ -901,7 +952,7 @@ static CURLcode cf_he_connect(struct Curl_cfilter *cf, if(result) return result; ctx->state = SCFST_WAITING; - /* FALLTHROUGH */ + FALLTHROUGH(); case SCFST_WAITING: result = is_connected(cf, data, done); if(!result && *done) { @@ -915,12 +966,20 @@ static CURLcode cf_he_connect(struct Curl_cfilter *cf, cf->next = ctx->winner->cf; ctx->winner->cf = NULL; cf_he_ctx_clear(cf, data); - Curl_conn_cf_cntrl(cf->next, data, TRUE, - CF_CTRL_CONN_INFO_UPDATE, 0, NULL); if(cf->conn->handler->protocol & PROTO_FAMILY_SSH) - Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */ - Curl_verboseconnect(data, cf->conn); + Curl_pgrsTime(data, TIMER_APPCONNECT); /* we are connected already */ + if(Curl_trc_cf_is_verbose(cf, data)) { + struct ip_quadruple ipquad; + int is_ipv6; + if(!Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad)) { + const char *host, *disphost; + int port; + cf->next->cft->get_host(cf->next, data, &host, &disphost, &port); + CURL_TRC_CF(data, cf, "Connected to %s (%s) port %u", + disphost, ipquad.remote_ip, ipquad.remote_port); + } + } data->info.numconnects++; /* to track the # of connections made */ } break; @@ -956,7 +1015,7 @@ static bool cf_he_data_pending(struct Curl_cfilter *cf, if(cf->connected) return cf->next->cft->has_data_pending(cf->next, data); - for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; if(!baller || !baller->cf) continue; @@ -975,7 +1034,7 @@ static struct curltime get_max_baller_time(struct Curl_cfilter *cf, size_t i; memset(&tmax, 0, sizeof(tmax)); - for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; memset(&t, 0, sizeof(t)); @@ -1000,7 +1059,7 @@ static CURLcode cf_he_query(struct Curl_cfilter *cf, int reply_ms = -1; size_t i; - for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; int breply_ms; @@ -1030,7 +1089,7 @@ static CURLcode cf_he_query(struct Curl_cfilter *cf, } } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -1054,8 +1113,9 @@ struct Curl_cftype Curl_cft_happy_eyeballs = { cf_he_destroy, cf_he_connect, cf_he_close, + cf_he_shutdown, Curl_cf_def_get_host, - cf_he_get_select_socks, + cf_he_adjust_pollset, cf_he_data_pending, Curl_cf_def_send, Curl_cf_def_recv, @@ -1089,7 +1149,7 @@ cf_happy_eyeballs_create(struct Curl_cfilter **pcf, (void)data; (void)conn; *pcf = NULL; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -1114,21 +1174,21 @@ struct transport_provider { }; static -#ifndef DEBUGBUILD +#ifndef UNITTESTS const #endif struct transport_provider transport_providers[] = { { TRNSPRT_TCP, Curl_cf_tcp_create }, -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 { TRNSPRT_QUIC, Curl_cf_quic_create }, #endif +#ifndef CURL_DISABLE_TFTP { TRNSPRT_UDP, Curl_cf_udp_create }, +#endif +#ifdef USE_UNIX_SOCKETS { TRNSPRT_UNIX, Curl_cf_unix_create }, -}; - -#ifndef ARRAYSIZE -#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) #endif +}; static cf_ip_connect_create *get_cf_create(int transport) { @@ -1318,8 +1378,9 @@ struct Curl_cftype Curl_cft_setup = { cf_setup_destroy, cf_setup_connect, cf_setup_close, + Curl_cf_def_shutdown, Curl_cf_def_get_host, - Curl_cf_def_get_select_socks, + Curl_cf_def_adjust_pollset, Curl_cf_def_data_pending, Curl_cf_def_send, Curl_cf_def_recv, @@ -1340,7 +1401,7 @@ static CURLcode cf_setup_create(struct Curl_cfilter **pcf, CURLcode result = CURLE_OK; (void)data; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -1356,7 +1417,7 @@ static CURLcode cf_setup_create(struct Curl_cfilter **pcf, ctx = NULL; out: - *pcf = result? NULL : cf; + *pcf = result ? NULL : cf; free(ctx); return result; } @@ -1380,7 +1441,7 @@ static CURLcode cf_setup_add(struct Curl_easy *data, return result; } -#ifdef DEBUGBUILD +#ifdef UNITTESTS /* used by unit2600.c */ void Curl_debug_set_transport_provider(int transport, cf_ip_connect_create *cf_create) @@ -1393,7 +1454,7 @@ void Curl_debug_set_transport_provider(int transport, } } } -#endif /* DEBUGBUILD */ +#endif /* UNITTESTS */ CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, diff --git a/deps/curl/lib/connect.h b/deps/curl/lib/connect.h index 58264bdba48..160db9420fd 100644 --- a/deps/curl/lib/connect.h +++ b/deps/curl/lib/connect.h @@ -30,8 +30,9 @@ #include "timeval.h" struct Curl_dns_entry; +struct ip_quadruple; -/* generic function that returns how much time there's left to run, according +/* generic function that returns how much time there is left to run, according to the timeouts set */ timediff_t Curl_timeleft(struct Curl_easy *data, struct curltime *nowp, @@ -39,6 +40,26 @@ timediff_t Curl_timeleft(struct Curl_easy *data, #define DEFAULT_CONNECT_TIMEOUT 300000 /* milliseconds == five minutes */ +#define DEFAULT_SHUTDOWN_TIMEOUT_MS (2 * 1000) + +void Curl_shutdown_start(struct Curl_easy *data, int sockindex, + struct curltime *nowp); + +/* return how much time there is left to shutdown the connection at + * sockindex. Returns 0 if there is no limit or shutdown has not started. */ +timediff_t Curl_shutdown_timeleft(struct connectdata *conn, int sockindex, + struct curltime *nowp); + +/* return how much time there is left to shutdown the connection. + * Returns 0 if there is no limit or shutdown has not started. */ +timediff_t Curl_conn_shutdown_timeleft(struct connectdata *conn, + struct curltime *nowp); + +void Curl_shutdown_clear(struct Curl_easy *data, int sockindex); + +/* TRUE iff shutdown has been started */ +bool Curl_shutdown_started(struct Curl_easy *data, int sockindex); + /* * Used to extract socket and connectdata struct for the most recent * transfer on the given Curl_easy. @@ -51,9 +72,6 @@ curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, char *addr, int *port); -void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn, - char *local_ip, int local_port); - /* * Curl_conncontrol() marks the end of a connection/stream. The 'closeit' * argument specifies if it is the end of a connection or a stream. @@ -124,7 +142,7 @@ CURLcode Curl_conn_setup(struct Curl_easy *data, extern struct Curl_cftype Curl_cft_happy_eyeballs; extern struct Curl_cftype Curl_cft_setup; -#ifdef DEBUGBUILD +#ifdef UNITTESTS void Curl_debug_set_transport_provider(int transport, cf_ip_connect_create *cf_create); #endif diff --git a/deps/curl/lib/content_encoding.c b/deps/curl/lib/content_encoding.c index efbe7cb18d1..a4b16dda103 100644 --- a/deps/curl/lib/content_encoding.c +++ b/deps/curl/lib/content_encoding.c @@ -33,13 +33,13 @@ #endif #ifdef HAVE_BROTLI -#if defined(__GNUC__) +#if defined(__GNUC__) || defined(__clang__) /* Ignore -Wvla warnings in brotli headers */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wvla" #endif #include -#if defined(__GNUC__) +#if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop #endif #endif @@ -63,6 +63,9 @@ #ifndef CURL_DISABLE_HTTP +/* allow no more than 5 "chained" compression steps */ +#define MAX_ENCODE_STACK 5 + #define DSIZ CURL_MAX_WRITE_SIZE /* buffer size for decompressed data */ @@ -76,10 +79,10 @@ #define GZIP_MAGIC_1 0x8b /* gzip flag byte */ -#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ +#define ASCII_FLAG 0x01 /* bit 0 set: file probably ASCII text */ #define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ #define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ -#define ORIG_NAME 0x08 /* bit 3 set: original file name present */ +#define ORIG_NAME 0x08 /* bit 3 set: original filename present */ #define COMMENT 0x10 /* bit 4 set: file comment present */ #define RESERVED 0xE0 /* bits 5..7: reserved */ @@ -95,7 +98,7 @@ typedef enum { /* Deflate and gzip writer. */ struct zlib_writer { - struct contenc_writer super; + struct Curl_cwriter super; zlibInitState zlib_init; /* zlib init state */ uInt trailerlen; /* Remaining trailer byte count. */ z_stream z; /* State structure for zlib. */ @@ -151,7 +154,7 @@ static CURLcode process_trailer(struct Curl_easy *data, { z_stream *z = &zp->z; CURLcode result = CURLE_OK; - uInt len = z->avail_in < zp->trailerlen? z->avail_in: zp->trailerlen; + uInt len = z->avail_in < zp->trailerlen ? z->avail_in : zp->trailerlen; /* Consume expected trailer bytes. Terminate stream if exhausted. Issue an error if unexpected bytes follow. */ @@ -171,7 +174,7 @@ static CURLcode process_trailer(struct Curl_easy *data, } static CURLcode inflate_stream(struct Curl_easy *data, - struct contenc_writer *writer, + struct Curl_cwriter *writer, int type, zlibInitState started) { struct zlib_writer *zp = (struct zlib_writer *) writer; @@ -189,14 +192,14 @@ static CURLcode inflate_stream(struct Curl_easy *data, zp->zlib_init != ZLIB_GZIP_INFLATING) return exit_zlib(data, z, &zp->zlib_init, CURLE_WRITE_ERROR); - /* Dynamically allocate a buffer for decompression because it's uncommonly + /* Dynamically allocate a buffer for decompression because it is uncommonly large to hold on the stack */ decomp = malloc(DSIZ); if(!decomp) return exit_zlib(data, z, &zp->zlib_init, CURLE_OUT_OF_MEMORY); /* because the buffer size is fixed, iteratively decompress and transfer to - the client via downstream_write function. */ + the client via next_write function. */ while(!done) { int status; /* zlib status */ done = TRUE; @@ -217,7 +220,7 @@ static CURLcode inflate_stream(struct Curl_easy *data, if(z->avail_out != DSIZ) { if(status == Z_OK || status == Z_STREAM_END) { zp->zlib_init = started; /* Data started. */ - result = Curl_unencode_write(data, writer->downstream, decomp, + result = Curl_cwriter_write(data, writer->next, type, decomp, DSIZ - z->avail_out); if(result) { exit_zlib(data, z, &zp->zlib_init, result); @@ -243,7 +246,7 @@ static CURLcode inflate_stream(struct Curl_easy *data, to fix and continue anyway */ if(zp->zlib_init == ZLIB_INIT) { /* Do not use inflateReset2(): only available since zlib 1.2.3.4. */ - (void) inflateEnd(z); /* don't care about the return code */ + (void) inflateEnd(z); /* do not care about the return code */ if(inflateInit2(z, -MAX_WBITS) == Z_OK) { z->next_in = orig_in; z->avail_in = nread; @@ -263,7 +266,7 @@ static CURLcode inflate_stream(struct Curl_easy *data, } free(decomp); - /* We're about to leave this call so the `nread' data bytes won't be seen + /* We are about to leave this call so the `nread' data bytes will not be seen again. If we are in a state that would wrongly allow restart in raw mode at the next call, assume output has already started. */ if(nread && zp->zlib_init == ZLIB_INIT) @@ -274,15 +277,12 @@ static CURLcode inflate_stream(struct Curl_easy *data, /* Deflate handler. */ -static CURLcode deflate_init_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static CURLcode deflate_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ - if(!writer->downstream) - return CURLE_WRITE_ERROR; - /* Initialize zlib */ z->zalloc = (alloc_func) zalloc_cb; z->zfree = (free_func) zfree_cb; @@ -293,13 +293,16 @@ static CURLcode deflate_init_writer(struct Curl_easy *data, return CURLE_OK; } -static CURLcode deflate_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, +static CURLcode deflate_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes) { struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ + if(!(type & CLIENTWRITE_BODY) || !nbytes) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + /* Set the compressed input when this function is called */ z->next_in = (Bytef *) buf; z->avail_in = (uInt) nbytes; @@ -308,11 +311,11 @@ static CURLcode deflate_unencode_write(struct Curl_easy *data, return process_trailer(data, zp); /* Now uncompress the data */ - return inflate_stream(data, writer, ZLIB_INFLATING); + return inflate_stream(data, writer, type, ZLIB_INFLATING); } -static void deflate_close_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static void deflate_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ @@ -320,26 +323,23 @@ static void deflate_close_writer(struct Curl_easy *data, exit_zlib(data, z, &zp->zlib_init, CURLE_OK); } -static const struct content_encoding deflate_encoding = { +static const struct Curl_cwtype deflate_encoding = { "deflate", NULL, - deflate_init_writer, - deflate_unencode_write, - deflate_close_writer, + deflate_do_init, + deflate_do_write, + deflate_do_close, sizeof(struct zlib_writer) }; /* Gzip handler. */ -static CURLcode gzip_init_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static CURLcode gzip_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ - if(!writer->downstream) - return CURLE_WRITE_ERROR; - /* Initialize zlib */ z->zalloc = (alloc_func) zalloc_cb; z->zfree = (free_func) zfree_cb; @@ -365,11 +365,14 @@ static CURLcode gzip_init_writer(struct Curl_easy *data, #ifdef OLD_ZLIB_SUPPORT /* Skip over the gzip header */ -static enum { +typedef enum { GZIP_OK, GZIP_BAD, GZIP_UNDERFLOW -} check_gzip_header(unsigned char const *data, ssize_t len, ssize_t *headerlen) +} gzip_status; + +static gzip_status check_gzip_header(unsigned char const *data, ssize_t len, + ssize_t *headerlen) { int method, flags; const ssize_t totallen = len; @@ -385,7 +388,7 @@ static enum { flags = data[3]; if(method != Z_DEFLATED || (flags & RESERVED) != 0) { - /* Can't handle this compression method or unknown flag */ + /* cannot handle this compression method or unknown flag */ return GZIP_BAD; } @@ -409,7 +412,7 @@ static enum { } if(flags & ORIG_NAME) { - /* Skip over NUL-terminated file name */ + /* Skip over NUL-terminated filename */ while(len && *data) { --len; ++data; @@ -447,19 +450,22 @@ static enum { } #endif -static CURLcode gzip_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, +static CURLcode gzip_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes) { struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ + if(!(type & CLIENTWRITE_BODY) || !nbytes) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + if(zp->zlib_init == ZLIB_INIT_GZIP) { /* Let zlib handle the gzip decompression entirely */ z->next_in = (Bytef *) buf; z->avail_in = (uInt) nbytes; /* Now uncompress the data */ - return inflate_stream(data, writer, ZLIB_INIT_GZIP); + return inflate_stream(data, writer, type, ZLIB_INIT_GZIP); } #ifndef OLD_ZLIB_SUPPORT @@ -468,10 +474,10 @@ static CURLcode gzip_unencode_write(struct Curl_easy *data, return exit_zlib(data, z, &zp->zlib_init, CURLE_WRITE_ERROR); #else - /* This next mess is to get around the potential case where there isn't - * enough data passed in to skip over the gzip header. If that happens, we - * malloc a block and copy what we have then wait for the next call. If - * there still isn't enough (this is definitely a worst-case scenario), we + /* This next mess is to get around the potential case where there is not + * enough data passed in to skip over the gzip header. If that happens, we + * malloc a block and copy what we have then wait for the next call. If + * there still is not enough (this is definitely a worst-case scenario), we * make the block bigger, copy the next part in and keep waiting. * * This is only required with zlib versions < 1.2.0.4 as newer versions @@ -493,11 +499,11 @@ static CURLcode gzip_unencode_write(struct Curl_easy *data, break; case GZIP_UNDERFLOW: - /* We need more data so we can find the end of the gzip header. It's + /* We need more data so we can find the end of the gzip header. it is * possible that the memory block we malloc here will never be freed if - * the transfer abruptly aborts after this point. Since it's unlikely + * the transfer abruptly aborts after this point. Since it is unlikely * that circumstances will be right for this code path to be followed in - * the first place, and it's even more unlikely for a transfer to fail + * the first place, and it is even more unlikely for a transfer to fail * immediately afterwards, it should seldom be a problem. */ z->avail_in = (uInt) nbytes; @@ -507,7 +513,7 @@ static CURLcode gzip_unencode_write(struct Curl_easy *data, } memcpy(z->next_in, buf, z->avail_in); zp->zlib_init = ZLIB_GZIP_HEADER; /* Need more gzip header data state */ - /* We don't have any data to inflate yet */ + /* We do not have any data to inflate yet */ return CURLE_OK; case GZIP_BAD: @@ -530,18 +536,18 @@ static CURLcode gzip_unencode_write(struct Curl_easy *data, /* Append the new block of data to the previous one */ memcpy(z->next_in + z->avail_in - nbytes, buf, nbytes); - switch(check_gzip_header(z->next_in, z->avail_in, &hlen)) { + switch(check_gzip_header(z->next_in, (ssize_t)z->avail_in, &hlen)) { case GZIP_OK: /* This is the zlib stream data */ free(z->next_in); - /* Don't point into the malloced block since we just freed it */ + /* Do not point into the malloced block since we just freed it */ z->next_in = (Bytef *) buf + hlen + nbytes - z->avail_in; - z->avail_in = (uInt) (z->avail_in - hlen); + z->avail_in = z->avail_in - (uInt)hlen; zp->zlib_init = ZLIB_GZIP_INFLATING; /* Inflating stream state */ break; case GZIP_UNDERFLOW: - /* We still don't have any data to inflate! */ + /* We still do not have any data to inflate! */ return CURLE_OK; case GZIP_BAD: @@ -566,17 +572,17 @@ static CURLcode gzip_unencode_write(struct Curl_easy *data, } if(z->avail_in == 0) { - /* We don't have any data to inflate; wait until next time */ + /* We do not have any data to inflate; wait until next time */ return CURLE_OK; } - /* We've parsed the header, now uncompress the data */ - return inflate_stream(data, writer, ZLIB_GZIP_INFLATING); + /* We have parsed the header, now uncompress the data */ + return inflate_stream(data, writer, type, ZLIB_GZIP_INFLATING); #endif } -static void gzip_close_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static void gzip_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ @@ -584,12 +590,12 @@ static void gzip_close_writer(struct Curl_easy *data, exit_zlib(data, z, &zp->zlib_init, CURLE_OK); } -static const struct content_encoding gzip_encoding = { +static const struct Curl_cwtype gzip_encoding = { "gzip", "x-gzip", - gzip_init_writer, - gzip_unencode_write, - gzip_close_writer, + gzip_do_init, + gzip_do_write, + gzip_do_close, sizeof(struct zlib_writer) }; @@ -599,7 +605,7 @@ static const struct content_encoding gzip_encoding = { #ifdef HAVE_BROTLI /* Brotli writer. */ struct brotli_writer { - struct contenc_writer super; + struct Curl_cwriter super; BrotliDecoderState *br; /* State structure for brotli. */ }; @@ -641,21 +647,18 @@ static CURLcode brotli_map_error(BrotliDecoderErrorCode be) return CURLE_WRITE_ERROR; } -static CURLcode brotli_init_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static CURLcode brotli_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct brotli_writer *bp = (struct brotli_writer *) writer; (void) data; - if(!writer->downstream) - return CURLE_WRITE_ERROR; - bp->br = BrotliDecoderCreateInstance(NULL, NULL, NULL); - return bp->br? CURLE_OK: CURLE_OUT_OF_MEMORY; + return bp->br ? CURLE_OK : CURLE_OUT_OF_MEMORY; } -static CURLcode brotli_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, +static CURLcode brotli_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes) { struct brotli_writer *bp = (struct brotli_writer *) writer; @@ -666,6 +669,9 @@ static CURLcode brotli_unencode_write(struct Curl_easy *data, CURLcode result = CURLE_OK; BrotliDecoderResult r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + if(!(type & CLIENTWRITE_BODY) || !nbytes) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + if(!bp->br) return CURLE_WRITE_ERROR; /* Stream already ended. */ @@ -679,7 +685,7 @@ static CURLcode brotli_unencode_write(struct Curl_easy *data, dstleft = DSIZ; r = BrotliDecoderDecompressStream(bp->br, &nbytes, &src, &dstleft, &dst, NULL); - result = Curl_unencode_write(data, writer->downstream, + result = Curl_cwriter_write(data, writer->next, type, decomp, DSIZ - dstleft); if(result) break; @@ -702,8 +708,8 @@ static CURLcode brotli_unencode_write(struct Curl_easy *data, return result; } -static void brotli_close_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static void brotli_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct brotli_writer *bp = (struct brotli_writer *) writer; @@ -715,12 +721,12 @@ static void brotli_close_writer(struct Curl_easy *data, } } -static const struct content_encoding brotli_encoding = { +static const struct Curl_cwtype brotli_encoding = { "br", NULL, - brotli_init_writer, - brotli_unencode_write, - brotli_close_writer, + brotli_do_init, + brotli_do_write, + brotli_do_close, sizeof(struct brotli_writer) }; #endif @@ -729,28 +735,25 @@ static const struct content_encoding brotli_encoding = { #ifdef HAVE_ZSTD /* Zstd writer. */ struct zstd_writer { - struct contenc_writer super; + struct Curl_cwriter super; ZSTD_DStream *zds; /* State structure for zstd. */ void *decomp; }; -static CURLcode zstd_init_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static CURLcode zstd_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct zstd_writer *zp = (struct zstd_writer *) writer; (void)data; - if(!writer->downstream) - return CURLE_WRITE_ERROR; - zp->zds = ZSTD_createDStream(); zp->decomp = NULL; return zp->zds ? CURLE_OK : CURLE_OUT_OF_MEMORY; } -static CURLcode zstd_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, +static CURLcode zstd_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes) { CURLcode result = CURLE_OK; @@ -759,6 +762,9 @@ static CURLcode zstd_unencode_write(struct Curl_easy *data, ZSTD_outBuffer out; size_t errorCode; + if(!(type & CLIENTWRITE_BODY) || !nbytes) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + if(!zp->decomp) { zp->decomp = malloc(DSIZ); if(!zp->decomp) @@ -778,7 +784,7 @@ static CURLcode zstd_unencode_write(struct Curl_easy *data, return CURLE_BAD_CONTENT_ENCODING; } if(out.pos > 0) { - result = Curl_unencode_write(data, writer->downstream, + result = Curl_cwriter_write(data, writer->next, type, zp->decomp, out.pos); if(result) break; @@ -790,8 +796,8 @@ static CURLcode zstd_unencode_write(struct Curl_easy *data, return result; } -static void zstd_close_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static void zstd_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) { struct zstd_writer *zp = (struct zstd_writer *) writer; @@ -807,51 +813,30 @@ static void zstd_close_writer(struct Curl_easy *data, } } -static const struct content_encoding zstd_encoding = { +static const struct Curl_cwtype zstd_encoding = { "zstd", NULL, - zstd_init_writer, - zstd_unencode_write, - zstd_close_writer, + zstd_do_init, + zstd_do_write, + zstd_do_close, sizeof(struct zstd_writer) }; #endif /* Identity handler. */ -static CURLcode identity_init_writer(struct Curl_easy *data, - struct contenc_writer *writer) -{ - (void) data; - return writer->downstream? CURLE_OK: CURLE_WRITE_ERROR; -} - -static CURLcode identity_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes) -{ - return Curl_unencode_write(data, writer->downstream, buf, nbytes); -} - -static void identity_close_writer(struct Curl_easy *data, - struct contenc_writer *writer) -{ - (void) data; - (void) writer; -} - -static const struct content_encoding identity_encoding = { +static const struct Curl_cwtype identity_encoding = { "identity", "none", - identity_init_writer, - identity_unencode_write, - identity_close_writer, - sizeof(struct contenc_writer) + Curl_cwriter_def_init, + Curl_cwriter_def_write, + Curl_cwriter_def_close, + sizeof(struct Curl_cwriter) }; -/* supported content encodings table. */ -static const struct content_encoding * const encodings[] = { +/* supported general content decoders. */ +static const struct Curl_cwtype * const general_unencoders[] = { &identity_encoding, #ifdef HAVE_LIBZ &deflate_encoding, @@ -866,28 +851,39 @@ static const struct content_encoding * const encodings[] = { NULL }; +/* supported content decoders only for transfer encodings */ +static const struct Curl_cwtype * const transfer_unencoders[] = { +#ifndef CURL_DISABLE_HTTP + &Curl_httpchunk_unencoder, +#endif + NULL +}; -/* Return a list of comma-separated names of supported encodings. */ -char *Curl_all_content_encodings(void) +/* Provide a list of comma-separated names of supported encodings. +*/ +void Curl_all_content_encodings(char *buf, size_t blen) { size_t len = 0; - const struct content_encoding * const *cep; - const struct content_encoding *ce; - char *ace; + const struct Curl_cwtype * const *cep; + const struct Curl_cwtype *ce; - for(cep = encodings; *cep; cep++) { + DEBUGASSERT(buf); + DEBUGASSERT(blen); + buf[0] = 0; + + for(cep = general_unencoders; *cep; cep++) { ce = *cep; if(!strcasecompare(ce->name, CONTENT_ENCODING_DEFAULT)) len += strlen(ce->name) + 2; } - if(!len) - return strdup(CONTENT_ENCODING_DEFAULT); - - ace = malloc(len); - if(ace) { - char *p = ace; - for(cep = encodings; *cep; cep++) { + if(!len) { + if(blen >= sizeof(CONTENT_ENCODING_DEFAULT)) + strcpy(buf, CONTENT_ENCODING_DEFAULT); + } + else if(blen > len) { + char *p = buf; + for(cep = general_unencoders; *cep; cep++) { ce = *cep; if(!strcasecompare(ce->name, CONTENT_ENCODING_DEFAULT)) { strcpy(p, ce->name); @@ -898,150 +894,71 @@ char *Curl_all_content_encodings(void) } p[-2] = '\0'; } - - return ace; -} - - -/* Real client writer: no downstream. */ -static CURLcode client_init_writer(struct Curl_easy *data, - struct contenc_writer *writer) -{ - (void) data; - return writer->downstream? CURLE_WRITE_ERROR: CURLE_OK; } -static CURLcode client_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes) -{ - struct SingleRequest *k = &data->req; - - (void) writer; - - if(!nbytes || k->ignorebody) - return CURLE_OK; - - return Curl_client_write(data, CLIENTWRITE_BODY, (char *) buf, nbytes); -} - -static void client_close_writer(struct Curl_easy *data, - struct contenc_writer *writer) -{ - (void) data; - (void) writer; -} - -static const struct content_encoding client_encoding = { - NULL, - NULL, - client_init_writer, - client_unencode_write, - client_close_writer, - sizeof(struct contenc_writer) -}; - - /* Deferred error dummy writer. */ -static CURLcode error_init_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static CURLcode error_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) { - (void) data; - return writer->downstream? CURLE_OK: CURLE_WRITE_ERROR; + (void)data; + (void)writer; + return CURLE_OK; } -static CURLcode error_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, +static CURLcode error_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes) { - char *all = Curl_all_content_encodings(); - (void) writer; (void) buf; (void) nbytes; - if(!all) - return CURLE_OUT_OF_MEMORY; - failf(data, "Unrecognized content encoding type. " - "libcurl understands %s content encodings.", all); - free(all); + if(!(type & CLIENTWRITE_BODY) || !nbytes) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + else { + char all[256]; + (void)Curl_all_content_encodings(all, sizeof(all)); + failf(data, "Unrecognized content encoding type. " + "libcurl understands %s content encodings.", all); + } return CURLE_BAD_CONTENT_ENCODING; } -static void error_close_writer(struct Curl_easy *data, - struct contenc_writer *writer) +static void error_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) { (void) data; (void) writer; } -static const struct content_encoding error_encoding = { - NULL, +static const struct Curl_cwtype error_writer = { + "ce-error", NULL, - error_init_writer, - error_unencode_write, - error_close_writer, - sizeof(struct contenc_writer) + error_do_init, + error_do_write, + error_do_close, + sizeof(struct Curl_cwriter) }; -/* Create an unencoding writer stage using the given handler. */ -static struct contenc_writer * -new_unencoding_writer(struct Curl_easy *data, - const struct content_encoding *handler, - struct contenc_writer *downstream, - int order) +/* Find the content encoding by name. */ +static const struct Curl_cwtype *find_unencode_writer(const char *name, + size_t len, + Curl_cwriter_phase phase) { - struct contenc_writer *writer; - - DEBUGASSERT(handler->writersize >= sizeof(struct contenc_writer)); - writer = (struct contenc_writer *) calloc(1, handler->writersize); - - if(writer) { - writer->handler = handler; - writer->downstream = downstream; - writer->order = order; - if(handler->init_writer(data, writer)) { - free(writer); - writer = NULL; + const struct Curl_cwtype * const *cep; + + if(phase == CURL_CW_TRANSFER_DECODE) { + for(cep = transfer_unencoders; *cep; cep++) { + const struct Curl_cwtype *ce = *cep; + if((strncasecompare(name, ce->name, len) && !ce->name[len]) || + (ce->alias && strncasecompare(name, ce->alias, len) + && !ce->alias[len])) + return ce; } } - - return writer; -} - -/* Write data using an unencoding writer stack. "nbytes" is not - allowed to be 0. */ -CURLcode Curl_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes) -{ - if(!nbytes) - return CURLE_OK; - return writer->handler->unencode_write(data, writer, buf, nbytes); -} - -/* Close and clean-up the connection's writer stack. */ -void Curl_unencode_cleanup(struct Curl_easy *data) -{ - struct SingleRequest *k = &data->req; - struct contenc_writer *writer = k->writer_stack; - - while(writer) { - k->writer_stack = writer->downstream; - writer->handler->close_writer(data, writer); - free(writer); - writer = k->writer_stack; - } -} - -/* Find the content encoding by name. */ -static const struct content_encoding *find_encoding(const char *name, - size_t len) -{ - const struct content_encoding * const *cep; - - for(cep = encodings; *cep; cep++) { - const struct content_encoding *ce = *cep; + /* look among the general decoders */ + for(cep = general_unencoders; *cep; cep++) { + const struct Curl_cwtype *ce = *cep; if((strncasecompare(name, ce->name, len) && !ce->name[len]) || (ce->alias && strncasecompare(name, ce->alias, len) && !ce->alias[len])) return ce; @@ -1049,20 +966,19 @@ static const struct content_encoding *find_encoding(const char *name, return NULL; } -/* allow no more than 5 "chained" compression steps */ -#define MAX_ENCODE_STACK 5 - -/* Set-up the unencoding stack from the Content-Encoding header value. +/* Setup the unencoding stack from the Content-Encoding header value. * See RFC 7231 section 3.1.2.2. */ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, const char *enclist, int is_transfer) { - struct SingleRequest *k = &data->req; - unsigned int order = is_transfer? 2: 1; + Curl_cwriter_phase phase = is_transfer ? + CURL_CW_TRANSFER_DECODE : CURL_CW_CONTENT_DECODE; + CURLcode result; do { const char *name; size_t namelen; + bool is_chunked = FALSE; /* Parse a single encoding name. */ while(ISBLANK(*enclist) || *enclist == ',') @@ -1074,52 +990,70 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, if(!ISSPACE(*enclist)) namelen = enclist - name + 1; - /* Special case: chunked encoding is handled at the reader level. */ - if(is_transfer && namelen == 7 && strncasecompare(name, "chunked", 7)) { - k->chunk = TRUE; /* chunks coming our way. */ - Curl_httpchunk_init(data); /* init our chunky engine. */ - } - else if(namelen) { - const struct content_encoding *encoding; - struct contenc_writer *writer; - if(is_transfer && !data->set.http_transfer_encoding) + if(namelen) { + const struct Curl_cwtype *cwt; + struct Curl_cwriter *writer; + + CURL_TRC_WRITE(data, "looking for %s decoder: %.*s", + is_transfer ? "transfer" : "content", (int)namelen, name); + is_chunked = (is_transfer && (namelen == 7) && + strncasecompare(name, "chunked", 7)); + /* if we skip the decoding in this phase, do not look further. + * Exception is "chunked" transfer-encoding which always must happen */ + if((is_transfer && !data->set.http_transfer_encoding && !is_chunked) || + (!is_transfer && data->set.http_ce_skip)) { /* not requested, ignore */ + CURL_TRC_WRITE(data, "decoder not requested, ignored: %.*s", + (int)namelen, name); return CURLE_OK; - encoding = find_encoding(name, namelen); - - if(!k->writer_stack) { - k->writer_stack = new_unencoding_writer(data, &client_encoding, - NULL, 0); - - if(!k->writer_stack) - return CURLE_OUT_OF_MEMORY; } - if(!encoding) - encoding = &error_encoding; /* Defer error at stack use. */ - - if(k->writer_stack_depth++ >= MAX_ENCODE_STACK) { + if(Curl_cwriter_count(data, phase) + 1 >= MAX_ENCODE_STACK) { failf(data, "Reject response due to more than %u content encodings", MAX_ENCODE_STACK); return CURLE_BAD_CONTENT_ENCODING; } - /* Stack the unencoding stage. */ - if(order >= k->writer_stack->order) { - writer = new_unencoding_writer(data, encoding, - k->writer_stack, order); - if(!writer) - return CURLE_OUT_OF_MEMORY; - k->writer_stack = writer; + + cwt = find_unencode_writer(name, namelen, phase); + if(cwt && is_chunked && Curl_cwriter_get_by_type(data, cwt)) { + /* A 'chunked' transfer encoding has already been added. + * Ignore duplicates. See #13451. + * Also RFC 9112, ch. 6.1: + * "A sender MUST NOT apply the chunked transfer coding more than + * once to a message body." + */ + CURL_TRC_WRITE(data, "ignoring duplicate 'chunked' decoder"); + return CURLE_OK; } - else { - struct contenc_writer *w = k->writer_stack; - while(w->downstream && order < w->downstream->order) - w = w->downstream; - writer = new_unencoding_writer(data, encoding, - w->downstream, order); - if(!writer) - return CURLE_OUT_OF_MEMORY; - w->downstream = writer; + + if(is_transfer && !is_chunked && + Curl_cwriter_get_by_name(data, "chunked")) { + /* RFC 9112, ch. 6.1: + * "If any transfer coding other than chunked is applied to a + * response's content, the sender MUST either apply chunked as the + * final transfer coding or terminate the message by closing the + * connection." + * "chunked" must be the last added to be the first in its phase, + * reject this. + */ + failf(data, "Reject response due to 'chunked' not being the last " + "Transfer-Encoding"); + return CURLE_BAD_CONTENT_ENCODING; + } + + if(!cwt) + cwt = &error_writer; /* Defer error at use. */ + + result = Curl_cwriter_create(&writer, data, cwt, phase); + CURL_TRC_WRITE(data, "added %s decoder %s -> %d", + is_transfer ? "transfer" : "content", cwt->name, result); + if(result) + return result; + + result = Curl_cwriter_add(data, writer); + if(result) { + Curl_cwriter_free(data, writer); + return result; } } } while(*enclist); @@ -1138,25 +1072,15 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, return CURLE_NOT_BUILT_IN; } -CURLcode Curl_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes) +void Curl_all_content_encodings(char *buf, size_t blen) { - (void) data; - (void) writer; - (void) buf; - (void) nbytes; - return CURLE_NOT_BUILT_IN; -} - -void Curl_unencode_cleanup(struct Curl_easy *data) -{ - (void) data; + DEBUGASSERT(buf); + DEBUGASSERT(blen); + if(blen < sizeof(CONTENT_ENCODING_DEFAULT)) + buf[0] = 0; + else + strcpy(buf, CONTENT_ENCODING_DEFAULT); } -char *Curl_all_content_encodings(void) -{ - return strdup(CONTENT_ENCODING_DEFAULT); /* Satisfy caller. */ -} #endif /* CURL_DISABLE_HTTP */ diff --git a/deps/curl/lib/content_encoding.h b/deps/curl/lib/content_encoding.h index 56e7f97f704..1addf230bbf 100644 --- a/deps/curl/lib/content_encoding.h +++ b/deps/curl/lib/content_encoding.h @@ -25,33 +25,10 @@ ***************************************************************************/ #include "curl_setup.h" -struct contenc_writer { - const struct content_encoding *handler; /* Encoding handler. */ - struct contenc_writer *downstream; /* Downstream writer. */ - unsigned int order; /* Ordering within writer stack. */ -}; - -/* Content encoding writer. */ -struct content_encoding { - const char *name; /* Encoding name. */ - const char *alias; /* Encoding name alias. */ - CURLcode (*init_writer)(struct Curl_easy *data, - struct contenc_writer *writer); - CURLcode (*unencode_write)(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes); - void (*close_writer)(struct Curl_easy *data, - struct contenc_writer *writer); - size_t writersize; -}; +struct Curl_cwriter; +void Curl_all_content_encodings(char *buf, size_t blen); CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, const char *enclist, int is_transfer); -CURLcode Curl_unencode_write(struct Curl_easy *data, - struct contenc_writer *writer, - const char *buf, size_t nbytes); -void Curl_unencode_cleanup(struct Curl_easy *data); -char *Curl_all_content_encodings(void); - #endif /* HEADER_CURL_CONTENT_ENCODING_H */ diff --git a/deps/curl/lib/cookie.c b/deps/curl/lib/cookie.c index 4345a84c6fd..ca8c3c5967e 100644 --- a/deps/curl/lib/cookie.c +++ b/deps/curl/lib/cookie.c @@ -28,41 +28,27 @@ RECEIVING COOKIE INFORMATION ============================ -struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, - const char *file, struct CookieInfo *inc, bool newsession); +Curl_cookie_init() Inits a cookie struct to store data in a local file. This is always called before any cookies are set. -struct Cookie *Curl_cookie_add(struct Curl_easy *data, - struct CookieInfo *c, bool httpheader, bool noexpire, - char *lineptr, const char *domain, const char *path, - bool secure); - - The 'lineptr' parameter is a full "Set-cookie:" line as - received from a server. - - The function need to replace previously stored lines that this new - line supersedes. +Curl_cookie_add() - It may remove lines that are expired. - - It should return an indication of success/error. + Adds a cookie to the in-memory cookie jar. SENDING COOKIE INFORMATION ========================== -struct Cookies *Curl_cookie_getlist(struct CookieInfo *cookie, - char *host, char *path, bool secure); +Curl_cookie_getlist() For a given host and path, return a linked list of cookies that the client should send to the server if used now. The secure boolean informs the cookie if a secure connection is achieved or not. - It shall only return cookies that haven't expired. - + It shall only return cookies that have not expired. Example set of cookies: @@ -102,6 +88,7 @@ Example set of cookies: #include "rename.h" #include "fopen.h" #include "strdup.h" +#include "llist.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -112,14 +99,11 @@ static void strstore(char **str, const char *newstr, size_t len); static void freecookie(struct Cookie *co) { - free(co->expirestr); free(co->domain); free(co->path); free(co->spath); free(co->name); free(co->value); - free(co->maxage); - free(co->version); free(co); } @@ -153,7 +137,7 @@ static bool cookie_tailmatch(const char *cookie_domain, } /* - * matching cookie path and url path + * matching cookie path and URL path * RFC6265 5.1.4 Paths and Path-Match */ static bool pathmatch(const char *cookie_path, const char *request_uri) @@ -248,7 +232,7 @@ static const char *get_top_domain(const char * const domain, size_t *outlen) if(outlen) *outlen = len; - return first? first: domain; + return first ? first : domain; } /* Avoid C1001, an "internal error" with MSVC14 */ @@ -265,8 +249,9 @@ static size_t cookie_hash_domain(const char *domain, const size_t len) size_t h = 5381; while(domain < end) { + size_t j = (size_t)Curl_raw_toupper(*domain++); h += h << 5; - h ^= Curl_raw_toupper(*domain++); + h ^= j; } return (h % COOKIE_HASH_SIZE); @@ -333,21 +318,21 @@ static char *sanitize_cookie_path(const char *cookie_path) */ void Curl_cookie_loadfiles(struct Curl_easy *data) { - struct curl_slist *list = data->set.cookielist; + struct curl_slist *list = data->state.cookielist; if(list) { Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); while(list) { - struct CookieInfo *newcookies = + struct CookieInfo *ci = Curl_cookie_init(data, list->data, data->cookies, data->set.cookiesession); - if(!newcookies) + if(!ci) /* * Failure may be due to OOM or a bad cookie; both are ignored * but only the first should be */ infof(data, "ignoring failed cookie_init for %s", list->data); else - data->cookies = newcookies; + data->cookies = ci; list = list->next; } Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); @@ -368,9 +353,7 @@ static void strstore(char **str, const char *newstr, size_t len) DEBUGASSERT(newstr); DEBUGASSERT(str); free(*str); - *str = Curl_memdup(newstr, len + 1); - if(*str) - (*str)[len] = 0; + *str = Curl_memdup0(newstr, len); } /* @@ -378,59 +361,55 @@ static void strstore(char **str, const char *newstr, size_t len) * * Remove expired cookies from the hash by inspecting the expires timestamp on * each cookie in the hash, freeing and deleting any where the timestamp is in - * the past. If the cookiejar has recorded the next timestamp at which one or + * the past. If the cookiejar has recorded the next timestamp at which one or * more cookies expire, then processing will exit early in case this timestamp * is in the future. */ -static void remove_expired(struct CookieInfo *cookies) +static void remove_expired(struct CookieInfo *ci) { - struct Cookie *co, *nx; + struct Cookie *co; curl_off_t now = (curl_off_t)time(NULL); unsigned int i; /* * If the earliest expiration timestamp in the jar is in the future we can - * skip scanning the whole jar and instead exit early as there won't be any - * cookies to evict. If we need to evict however, reset the next_expiration - * counter in order to track the next one. In case the recorded first - * expiration is the max offset, then perform the safe fallback of checking - * all cookies. + * skip scanning the whole jar and instead exit early as there will not be + * any cookies to evict. If we need to evict however, reset the + * next_expiration counter in order to track the next one. In case the + * recorded first expiration is the max offset, then perform the safe + * fallback of checking all cookies. */ - if(now < cookies->next_expiration && - cookies->next_expiration != CURL_OFF_T_MAX) + if(now < ci->next_expiration && + ci->next_expiration != CURL_OFF_T_MAX) return; else - cookies->next_expiration = CURL_OFF_T_MAX; + ci->next_expiration = CURL_OFF_T_MAX; for(i = 0; i < COOKIE_HASH_SIZE; i++) { - struct Cookie *pv = NULL; - co = cookies->cookies[i]; - while(co) { - nx = co->next; + struct Curl_llist_node *n; + struct Curl_llist_node *e = NULL; + + for(n = Curl_llist_head(&ci->cookielist[i]); n; n = e) { + co = Curl_node_elem(n); + e = Curl_node_next(n); if(co->expires && co->expires < now) { - if(!pv) { - cookies->cookies[i] = co->next; - } - else { - pv->next = co->next; - } - cookies->numcookies--; + Curl_node_remove(n); freecookie(co); + ci->numcookies--; } else { /* - * If this cookie has an expiration timestamp earlier than what we've - * seen so far then record it for the next round of expirations. + * If this cookie has an expiration timestamp earlier than what we + * have seen so far then record it for the next round of expirations. */ - if(co->expires && co->expires < cookies->next_expiration) - cookies->next_expiration = co->expires; - pv = co; + if(co->expires && co->expires < ci->next_expiration) + ci->next_expiration = co->expires; } - co = nx; } } } +#ifndef USE_LIBPSL /* Make sure domain contains a dot or is localhost. */ static bool bad_domain(const char *domain, size_t len) { @@ -448,6 +427,7 @@ static bool bad_domain(const char *domain, size_t len) } return TRUE; } +#endif /* RFC 6265 section 4.1.1 says a server should accept this range: @@ -472,598 +452,543 @@ static int invalid_octets(const char *p) return (p[len] != '\0'); } -/* - * Curl_cookie_add - * - * Add a single cookie line to the cookie keeping object. Be aware that - * sometimes we get an IP-only host name, and that might also be a numerical - * IPv6 address. - * - * Returns NULL on out of memory or invalid cookie. This is suboptimal, - * as they should be treated separately. - */ -struct Cookie * -Curl_cookie_add(struct Curl_easy *data, - struct CookieInfo *c, - bool httpheader, /* TRUE if HTTP header-style line */ - bool noexpire, /* if TRUE, skip remove_expired() */ - char *lineptr, /* first character of the line */ - const char *domain, /* default domain */ - const char *path, /* full path used when this cookie is set, - used to get default path for the cookie - unless set */ - bool secure) /* TRUE if connection is over secure origin */ +#define CERR_OK 0 +#define CERR_TOO_LONG 1 /* input line too long */ +#define CERR_TAB 2 /* in a wrong place */ +#define CERR_TOO_BIG 3 /* name/value too large */ +#define CERR_BAD 4 /* deemed incorrect */ +#define CERR_NO_SEP 5 /* semicolon problem */ +#define CERR_NO_NAME_VALUE 6 /* name or value problem */ +#define CERR_INVALID_OCTET 7 /* bad content */ +#define CERR_BAD_SECURE 8 /* secure in a bad place */ +#define CERR_OUT_OF_MEMORY 9 +#define CERR_NO_TAILMATCH 10 +#define CERR_COMMENT 11 /* a commented line */ +#define CERR_RANGE 12 /* expire range problem */ +#define CERR_FIELDS 13 /* incomplete netscape line */ +#define CERR_PSL 14 /* a public suffix */ +#define CERR_LIVE_WINS 15 + +static int +parse_cookie_header(struct Curl_easy *data, + struct Cookie *co, + struct CookieInfo *ci, + const char *ptr, + const char *domain, /* default domain */ + const char *path, /* full path used when this cookie is + set, used to get default path for + the cookie unless set */ + bool secure) /* TRUE if connection is over secure + origin */ { - struct Cookie *clist; - struct Cookie *co; - struct Cookie *lastc = NULL; - struct Cookie *replace_co = NULL; - struct Cookie *replace_clist = NULL; - time_t now = time(NULL); - bool replace_old = FALSE; - bool badcookie = FALSE; /* cookies are good by default. mmmmm yummy */ - size_t myhash; - - DEBUGASSERT(data); - DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */ - if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT) - return NULL; - - /* First, alloc and init a new struct for it */ - co = calloc(1, sizeof(struct Cookie)); - if(!co) - return NULL; /* bail out if we're this low on memory */ - - if(httpheader) { - /* This line was read off an HTTP-header */ - const char *ptr; + /* This line was read off an HTTP-header */ + time_t now; + size_t linelength = strlen(ptr); + if(linelength > MAX_COOKIE_LINE) + /* discard overly long lines at once */ + return CERR_TOO_LONG; + + now = time(NULL); + do { + size_t vlen; + size_t nlen; + + while(*ptr && ISBLANK(*ptr)) + ptr++; + + /* we have a = pair or a stand-alone word here */ + nlen = strcspn(ptr, ";\t\r\n="); + if(nlen) { + bool done = FALSE; + bool sep = FALSE; + const char *namep = ptr; + const char *valuep; + + ptr += nlen; + + /* trim trailing spaces and tabs after name */ + while(nlen && ISBLANK(namep[nlen - 1])) + nlen--; + + if(*ptr == '=') { + vlen = strcspn(++ptr, ";\r\n"); + valuep = ptr; + sep = TRUE; + ptr = &valuep[vlen]; + + /* Strip off trailing whitespace from the value */ + while(vlen && ISBLANK(valuep[vlen-1])) + vlen--; + + /* Skip leading whitespace from the value */ + while(vlen && ISBLANK(*valuep)) { + valuep++; + vlen--; + } - size_t linelength = strlen(lineptr); - if(linelength > MAX_COOKIE_LINE) { - /* discard overly long lines at once */ - free(co); - return NULL; - } + /* Reject cookies with a TAB inside the value */ + if(memchr(valuep, '\t', vlen)) { + infof(data, "cookie contains TAB, dropping"); + return CERR_TAB; + } + } + else { + valuep = NULL; + vlen = 0; + } - ptr = lineptr; - do { - size_t vlen; - size_t nlen; + /* + * Check for too long individual name or contents, or too long + * combination of name + contents. Chrome and Firefox support 4095 or + * 4096 bytes combo + */ + if(nlen >= (MAX_NAME-1) || vlen >= (MAX_NAME-1) || + ((nlen + vlen) > MAX_NAME)) { + infof(data, "oversized cookie dropped, name/val %zu + %zu bytes", + nlen, vlen); + return CERR_TOO_BIG; + } - while(*ptr && ISBLANK(*ptr)) - ptr++; + /* + * Check if we have a reserved prefix set before anything else, as we + * otherwise have to test for the prefix in both the cookie name and + * "the rest". Prefixes must start with '__' and end with a '-', so + * only test for names where that can possibly be true. + */ + if(nlen >= 7 && namep[0] == '_' && namep[1] == '_') { + if(strncasecompare("__Secure-", namep, 9)) + co->prefix_secure = TRUE; + else if(strncasecompare("__Host-", namep, 7)) + co->prefix_host = TRUE; + } - /* we have a = pair or a stand-alone word here */ - nlen = strcspn(ptr, ";\t\r\n="); - if(nlen) { - bool done = FALSE; - bool sep = FALSE; - const char *namep = ptr; - const char *valuep; - - ptr += nlen; - - /* trim trailing spaces and tabs after name */ - while(nlen && ISBLANK(namep[nlen - 1])) - nlen--; - - if(*ptr == '=') { - vlen = strcspn(++ptr, ";\r\n"); - valuep = ptr; - sep = TRUE; - ptr = &valuep[vlen]; - - /* Strip off trailing whitespace from the value */ - while(vlen && ISBLANK(valuep[vlen-1])) - vlen--; - - /* Skip leading whitespace from the value */ - while(vlen && ISBLANK(*valuep)) { - valuep++; - vlen--; - } + /* + * Use strstore() below to properly deal with received cookie + * headers that have the same string property set more than once, + * and then we use the last one. + */ - /* Reject cookies with a TAB inside the value */ - if(memchr(valuep, '\t', vlen)) { - freecookie(co); - infof(data, "cookie contains TAB, dropping"); - return NULL; - } - } - else { - valuep = NULL; - vlen = 0; + if(!co->name) { + /* The very first name/value pair is the actual cookie name */ + if(!sep) + /* Bad name/value pair. */ + return CERR_NO_SEP; + + strstore(&co->name, namep, nlen); + strstore(&co->value, valuep, vlen); + done = TRUE; + if(!co->name || !co->value) + return CERR_NO_NAME_VALUE; + + if(invalid_octets(co->value) || invalid_octets(co->name)) { + infof(data, "invalid octets in name/value, cookie dropped"); + return CERR_INVALID_OCTET; } - + } + else if(!vlen) { /* - * Check for too long individual name or contents, or too long - * combination of name + contents. Chrome and Firefox support 4095 or - * 4096 bytes combo + * this was a "=" with no content, and we must allow + * 'secure' and 'httponly' specified this weirdly */ - if(nlen >= (MAX_NAME-1) || vlen >= (MAX_NAME-1) || - ((nlen + vlen) > MAX_NAME)) { - freecookie(co); - infof(data, "oversized cookie dropped, name/val %zu + %zu bytes", - nlen, vlen); - return NULL; - } - + done = TRUE; /* - * Check if we have a reserved prefix set before anything else, as we - * otherwise have to test for the prefix in both the cookie name and - * "the rest". Prefixes must start with '__' and end with a '-', so - * only test for names where that can possibly be true. + * secure cookies are only allowed to be set when the connection is + * using a secure protocol, or when the cookie is being set by + * reading from file */ - if(nlen >= 7 && namep[0] == '_' && namep[1] == '_') { - if(strncasecompare("__Secure-", namep, 9)) - co->prefix |= COOKIE_PREFIX__SECURE; - else if(strncasecompare("__Host-", namep, 7)) - co->prefix |= COOKIE_PREFIX__HOST; + if((nlen == 6) && strncasecompare("secure", namep, 6)) { + if(secure || !ci->running) { + co->secure = TRUE; + } + else { + return CERR_BAD_SECURE; + } } + else if((nlen == 8) && strncasecompare("httponly", namep, 8)) + co->httponly = TRUE; + else if(sep) + /* there was a '=' so we are not done parsing this field */ + done = FALSE; + } + if(done) + ; + else if((nlen == 4) && strncasecompare("path", namep, 4)) { + strstore(&co->path, valuep, vlen); + if(!co->path) + return CERR_OUT_OF_MEMORY; + free(co->spath); /* if this is set again */ + co->spath = sanitize_cookie_path(co->path); + if(!co->spath) + return CERR_OUT_OF_MEMORY; + } + else if((nlen == 6) && + strncasecompare("domain", namep, 6) && vlen) { + bool is_ip; /* - * Use strstore() below to properly deal with received cookie - * headers that have the same string property set more than once, - * and then we use the last one. + * Now, we make sure that our host is within the given domain, or + * the given domain is not valid and thus cannot be set. */ - if(!co->name) { - /* The very first name/value pair is the actual cookie name */ - if(!sep) { - /* Bad name/value pair. */ - badcookie = TRUE; - break; - } - strstore(&co->name, namep, nlen); - strstore(&co->value, valuep, vlen); - done = TRUE; - if(!co->name || !co->value) { - badcookie = TRUE; - break; - } - if(invalid_octets(co->value) || invalid_octets(co->name)) { - infof(data, "invalid octets in name/value, cookie dropped"); - badcookie = TRUE; - break; - } + if('.' == valuep[0]) { + valuep++; /* ignore preceding dot */ + vlen--; } - else if(!vlen) { - /* - * this was a "=" with no content, and we must allow - * 'secure' and 'httponly' specified this weirdly - */ - done = TRUE; - /* - * secure cookies are only allowed to be set when the connection is - * using a secure protocol, or when the cookie is being set by - * reading from file - */ - if((nlen == 6) && strncasecompare("secure", namep, 6)) { - if(secure || !c->running) { - co->secure = TRUE; - } - else { - badcookie = TRUE; - break; - } - } - else if((nlen == 8) && strncasecompare("httponly", namep, 8)) - co->httponly = TRUE; - else if(sep) - /* there was a '=' so we're not done parsing this field */ - done = FALSE; - } - if(done) - ; - else if((nlen == 4) && strncasecompare("path", namep, 4)) { - strstore(&co->path, valuep, vlen); - if(!co->path) { - badcookie = TRUE; /* out of memory bad */ - break; - } - free(co->spath); /* if this is set again */ - co->spath = sanitize_cookie_path(co->path); - if(!co->spath) { - badcookie = TRUE; /* out of memory bad */ - break; - } - } - else if((nlen == 6) && - strncasecompare("domain", namep, 6) && vlen) { - bool is_ip; - - /* - * Now, we make sure that our host is within the given domain, or - * the given domain is not valid and thus cannot be set. - */ - - if('.' == valuep[0]) { - valuep++; /* ignore preceding dot */ - vlen--; - } #ifndef USE_LIBPSL - /* - * Without PSL we don't know when the incoming cookie is set on a - * TLD or otherwise "protected" suffix. To reduce risk, we require a - * dot OR the exact host name being "localhost". - */ - if(bad_domain(valuep, vlen)) - domain = ":"; + /* + * Without PSL we do not know when the incoming cookie is set on a + * TLD or otherwise "protected" suffix. To reduce risk, we require a + * dot OR the exact hostname being "localhost". + */ + if(bad_domain(valuep, vlen)) + domain = ":"; #endif - is_ip = Curl_host_is_ipnum(domain ? domain : valuep); - - if(!domain - || (is_ip && !strncmp(valuep, domain, vlen) && - (vlen == strlen(domain))) - || (!is_ip && cookie_tailmatch(valuep, vlen, domain))) { - strstore(&co->domain, valuep, vlen); - if(!co->domain) { - badcookie = TRUE; - break; - } - if(!is_ip) - co->tailmatch = TRUE; /* we always do that if the domain name was - given */ - } - else { - /* - * We did not get a tailmatch and then the attempted set domain is - * not a domain to which the current host belongs. Mark as bad. - */ - badcookie = TRUE; - infof(data, "skipped cookie with bad tailmatch domain: %s", - valuep); - } - } - else if((nlen == 7) && strncasecompare("version", namep, 7)) { - strstore(&co->version, valuep, vlen); - if(!co->version) { - badcookie = TRUE; - break; - } + is_ip = Curl_host_is_ipnum(domain ? domain : valuep); + + if(!domain + || (is_ip && !strncmp(valuep, domain, vlen) && + (vlen == strlen(domain))) + || (!is_ip && cookie_tailmatch(valuep, vlen, domain))) { + strstore(&co->domain, valuep, vlen); + if(!co->domain) + return CERR_OUT_OF_MEMORY; + + if(!is_ip) + co->tailmatch = TRUE; /* we always do that if the domain name was + given */ } - else if((nlen == 7) && strncasecompare("max-age", namep, 7)) { + else { /* - * Defined in RFC2109: - * - * Optional. The Max-Age attribute defines the lifetime of the - * cookie, in seconds. The delta-seconds value is a decimal non- - * negative integer. After delta-seconds seconds elapse, the - * client should discard the cookie. A value of zero means the - * cookie should be discarded immediately. + * We did not get a tailmatch and then the attempted set domain is + * not a domain to which the current host belongs. Mark as bad. */ - strstore(&co->maxage, valuep, vlen); - if(!co->maxage) { - badcookie = TRUE; - break; - } - } - else if((nlen == 7) && strncasecompare("expires", namep, 7)) { - strstore(&co->expirestr, valuep, vlen); - if(!co->expirestr) { - badcookie = TRUE; - break; - } + infof(data, "skipped cookie with bad tailmatch domain: %s", + valuep); + return CERR_NO_TAILMATCH; } - - /* - * Else, this is the second (or more) name we don't know about! - */ } - else { - /* this is an "illegal" = pair */ + else if((nlen == 7) && strncasecompare("version", namep, 7)) { + /* just ignore */ } - - while(*ptr && ISBLANK(*ptr)) - ptr++; - if(*ptr == ';') - ptr++; - else - break; - } while(1); - - if(co->maxage) { - CURLofft offt; - offt = curlx_strtoofft((*co->maxage == '\"')? - &co->maxage[1]:&co->maxage[0], NULL, 10, - &co->expires); - switch(offt) { - case CURL_OFFT_FLOW: - /* overflow, used max value */ - co->expires = CURL_OFF_T_MAX; - break; - case CURL_OFFT_INVAL: - /* negative or otherwise bad, expire */ - co->expires = 1; - break; - case CURL_OFFT_OK: - if(!co->expires) - /* already expired */ - co->expires = 1; - else if(CURL_OFF_T_MAX - now < co->expires) - /* would overflow */ + else if((nlen == 7) && strncasecompare("max-age", namep, 7)) { + /* + * Defined in RFC2109: + * + * Optional. The Max-Age attribute defines the lifetime of the + * cookie, in seconds. The delta-seconds value is a decimal non- + * negative integer. After delta-seconds seconds elapse, the + * client should discard the cookie. A value of zero means the + * cookie should be discarded immediately. + */ + CURLofft offt; + const char *maxage = valuep; + offt = curlx_strtoofft((*maxage == '\"') ? + &maxage[1] : &maxage[0], NULL, 10, + &co->expires); + switch(offt) { + case CURL_OFFT_FLOW: + /* overflow, used max value */ co->expires = CURL_OFF_T_MAX; - else - co->expires += now; - break; + break; + case CURL_OFFT_INVAL: + /* negative or otherwise bad, expire */ + co->expires = 1; + break; + case CURL_OFFT_OK: + if(!co->expires) + /* already expired */ + co->expires = 1; + else if(CURL_OFF_T_MAX - now < co->expires) + /* would overflow */ + co->expires = CURL_OFF_T_MAX; + else + co->expires += now; + break; + } } - } - else if(co->expirestr) { - /* - * Note that if the date couldn't get parsed for whatever reason, the - * cookie will be treated as a session cookie - */ - co->expires = Curl_getdate_capped(co->expirestr); - - /* - * Session cookies have expires set to 0 so if we get that back from the - * date parser let's add a second to make it a non-session cookie - */ - if(co->expires == 0) - co->expires = 1; - else if(co->expires < 0) - co->expires = 0; - } + else if((nlen == 7) && strncasecompare("expires", namep, 7)) { + if(!co->expires) { + /* + * Let max-age have priority. + * + * If the date cannot get parsed for whatever reason, the cookie + * will be treated as a session cookie + */ + co->expires = Curl_getdate_capped(valuep); - if(!badcookie && !co->domain) { - if(domain) { - /* no domain was given in the header line, set the default */ - co->domain = strdup(domain); - if(!co->domain) - badcookie = TRUE; + /* + * Session cookies have expires set to 0 so if we get that back + * from the date parser let's add a second to make it a + * non-session cookie + */ + if(co->expires == 0) + co->expires = 1; + else if(co->expires < 0) + co->expires = 0; + } } - } - if(!badcookie && !co->path && path) { /* - * No path was given in the header line, set the default. Note that the - * passed-in path to this function MAY have a '?' and following part that - * MUST NOT be stored as part of the path. + * Else, this is the second (or more) name we do not know about! */ - char *queryp = strchr(path, '?'); - - /* - * queryp is where the interesting part of the path ends, so now we - * want to the find the last - */ - char *endslash; - if(!queryp) - endslash = strrchr(path, '/'); - else - endslash = memrchr(path, '/', (queryp - path)); - if(endslash) { - size_t pathlen = (endslash-path + 1); /* include end slash */ - co->path = malloc(pathlen + 1); /* one extra for the zero byte */ - if(co->path) { - memcpy(co->path, path, pathlen); - co->path[pathlen] = 0; /* null-terminate */ - co->spath = sanitize_cookie_path(co->path); - if(!co->spath) - badcookie = TRUE; /* out of memory bad */ - } - else - badcookie = TRUE; - } } - - /* - * If we didn't get a cookie name, or a bad one, the this is an illegal - * line so bail out. - */ - if(badcookie || !co->name) { - freecookie(co); - return NULL; + else { + /* this is an "illegal" = pair */ } - data->req.setcookies++; + + while(*ptr && ISBLANK(*ptr)) + ptr++; + if(*ptr == ';') + ptr++; + else + break; + } while(1); + + if(!co->domain && domain) { + /* no domain was given in the header line, set the default */ + co->domain = strdup(domain); + if(!co->domain) + return CERR_OUT_OF_MEMORY; } - else { - /* - * This line is NOT an HTTP header style line, we do offer support for - * reading the odd netscape cookies-file format here - */ - char *ptr; - char *firstptr; - char *tok_buf = NULL; - int fields; + if(!co->path && path) { /* - * IE introduced HTTP-only cookies to prevent XSS attacks. Cookies marked - * with httpOnly after the domain name are not accessible from javascripts, - * but since curl does not operate at javascript level, we include them - * anyway. In Firefox's cookie files, these lines are preceded with - * #HttpOnly_ and then everything is as usual, so we skip 10 characters of - * the line.. + * No path was given in the header line, set the default. Note that the + * passed-in path to this function MAY have a '?' and following part that + * MUST NOT be stored as part of the path. */ - if(strncmp(lineptr, "#HttpOnly_", 10) == 0) { - lineptr += 10; - co->httponly = TRUE; - } - - if(lineptr[0]=='#') { - /* don't even try the comments */ - free(co); - return NULL; - } - /* strip off the possible end-of-line characters */ - ptr = strchr(lineptr, '\r'); - if(ptr) - *ptr = 0; /* clear it */ - ptr = strchr(lineptr, '\n'); - if(ptr) - *ptr = 0; /* clear it */ - - firstptr = strtok_r(lineptr, "\t", &tok_buf); /* tokenize it on the TAB */ + char *queryp = strchr(path, '?'); /* - * Now loop through the fields and init the struct we already have - * allocated + * queryp is where the interesting part of the path ends, so now we + * want to the find the last */ - for(ptr = firstptr, fields = 0; ptr && !badcookie; - ptr = strtok_r(NULL, "\t", &tok_buf), fields++) { - switch(fields) { - case 0: - if(ptr[0]=='.') /* skip preceding dots */ - ptr++; - co->domain = strdup(ptr); - if(!co->domain) - badcookie = TRUE; - break; - case 1: - /* - * flag: A TRUE/FALSE value indicating if all machines within a given - * domain can access the variable. Set TRUE when the cookie says - * .domain.com and to false when the domain is complete www.domain.com - */ - co->tailmatch = strcasecompare(ptr, "TRUE")?TRUE:FALSE; - break; - case 2: - /* The file format allows the path field to remain not filled in */ - if(strcmp("TRUE", ptr) && strcmp("FALSE", ptr)) { - /* only if the path doesn't look like a boolean option! */ - co->path = strdup(ptr); - if(!co->path) - badcookie = TRUE; - else { - co->spath = sanitize_cookie_path(co->path); - if(!co->spath) { - badcookie = TRUE; /* out of memory bad */ - } - } - break; - } - /* this doesn't look like a path, make one up! */ - co->path = strdup("/"); - if(!co->path) - badcookie = TRUE; - co->spath = strdup("/"); + char *endslash; + if(!queryp) + endslash = strrchr(path, '/'); + else + endslash = memrchr(path, '/', (queryp - path)); + if(endslash) { + size_t pathlen = (endslash-path + 1); /* include end slash */ + co->path = Curl_memdup0(path, pathlen); + if(co->path) { + co->spath = sanitize_cookie_path(co->path); if(!co->spath) - badcookie = TRUE; - fields++; /* add a field and fall down to secure */ - /* FALLTHROUGH */ - case 3: - co->secure = FALSE; - if(strcasecompare(ptr, "TRUE")) { - if(secure || c->running) - co->secure = TRUE; - else - badcookie = TRUE; - } - break; - case 4: - if(curlx_strtoofft(ptr, NULL, 10, &co->expires)) - badcookie = TRUE; - break; - case 5: - co->name = strdup(ptr); - if(!co->name) - badcookie = TRUE; - else { - /* For Netscape file format cookies we check prefix on the name */ - if(strncasecompare("__Secure-", co->name, 9)) - co->prefix |= COOKIE_PREFIX__SECURE; - else if(strncasecompare("__Host-", co->name, 7)) - co->prefix |= COOKIE_PREFIX__HOST; - } - break; - case 6: - co->value = strdup(ptr); - if(!co->value) - badcookie = TRUE; - break; + return CERR_OUT_OF_MEMORY; } - } - if(6 == fields) { - /* we got a cookie with blank contents, fix it */ - co->value = strdup(""); - if(!co->value) - badcookie = TRUE; else - fields++; + return CERR_OUT_OF_MEMORY; } + } - if(!badcookie && (7 != fields)) - /* we did not find the sufficient number of fields */ - badcookie = TRUE; + /* + * If we did not get a cookie name, or a bad one, the this is an illegal + * line so bail out. + */ + if(!co->name) + return CERR_BAD; - if(badcookie) { - freecookie(co); - return NULL; - } + data->req.setcookies++; + return CERR_OK; +} - } +static int +parse_netscape(struct Cookie *co, + struct CookieInfo *ci, + const char *lineptr, + bool secure) /* TRUE if connection is over secure + origin */ +{ + /* + * This line is NOT an HTTP header style line, we do offer support for + * reading the odd netscape cookies-file format here + */ + char *ptr; + char *firstptr; + char *tok_buf = NULL; + int fields; - if(co->prefix & COOKIE_PREFIX__SECURE) { - /* The __Secure- prefix only requires that the cookie be set secure */ - if(!co->secure) { - freecookie(co); - return NULL; - } - } - if(co->prefix & COOKIE_PREFIX__HOST) { - /* - * The __Host- prefix requires the cookie to be secure, have a "/" path - * and not have a domain set. - */ - if(co->secure && co->path && strcmp(co->path, "/") == 0 && !co->tailmatch) - ; - else { - freecookie(co); - return NULL; - } + /* + * In 2008, Internet Explorer introduced HTTP-only cookies to prevent XSS + * attacks. Cookies marked httpOnly are not accessible to JavaScript. In + * Firefox's cookie files, they are prefixed #HttpOnly_ and the rest + * remains as usual, so we skip 10 characters of the line. + */ + if(strncmp(lineptr, "#HttpOnly_", 10) == 0) { + lineptr += 10; + co->httponly = TRUE; } - if(!c->running && /* read from a file */ - c->newsession && /* clean session cookies */ - !co->expires) { /* this is a session cookie since it doesn't expire! */ - freecookie(co); - return NULL; - } + if(lineptr[0]=='#') + /* do not even try the comments */ + return CERR_COMMENT; + + /* strip off the possible end-of-line characters */ + ptr = strchr(lineptr, '\r'); + if(ptr) + *ptr = 0; /* clear it */ + ptr = strchr(lineptr, '\n'); + if(ptr) + *ptr = 0; /* clear it */ - co->livecookie = c->running; - co->creationtime = ++c->lastct; + firstptr = strtok_r((char *)lineptr, "\t", &tok_buf); /* tokenize on TAB */ /* - * Now we have parsed the incoming line, we must now check if this supersedes - * an already existing cookie, which it may if the previous have the same - * domain and path as this. + * Now loop through the fields and init the struct we already have + * allocated */ + fields = 0; + for(ptr = firstptr; ptr; ptr = strtok_r(NULL, "\t", &tok_buf), fields++) { + switch(fields) { + case 0: + if(ptr[0]=='.') /* skip preceding dots */ + ptr++; + co->domain = strdup(ptr); + if(!co->domain) + return CERR_OUT_OF_MEMORY; + break; + case 1: + /* + * flag: A TRUE/FALSE value indicating if all machines within a given + * domain can access the variable. Set TRUE when the cookie says + * .domain.com and to false when the domain is complete www.domain.com + */ + co->tailmatch = !!strcasecompare(ptr, "TRUE"); + break; + case 2: + /* The file format allows the path field to remain not filled in */ + if(strcmp("TRUE", ptr) && strcmp("FALSE", ptr)) { + /* only if the path does not look like a boolean option! */ + co->path = strdup(ptr); + if(!co->path) + return CERR_OUT_OF_MEMORY; + else { + co->spath = sanitize_cookie_path(co->path); + if(!co->spath) + return CERR_OUT_OF_MEMORY; + } + break; + } + /* this does not look like a path, make one up! */ + co->path = strdup("/"); + if(!co->path) + return CERR_OUT_OF_MEMORY; + co->spath = strdup("/"); + if(!co->spath) + return CERR_OUT_OF_MEMORY; + fields++; /* add a field and fall down to secure */ + FALLTHROUGH(); + case 3: + co->secure = FALSE; + if(strcasecompare(ptr, "TRUE")) { + if(secure || ci->running) + co->secure = TRUE; + else + return CERR_BAD_SECURE; + } + break; + case 4: + if(curlx_strtoofft(ptr, NULL, 10, &co->expires)) + return CERR_RANGE; + break; + case 5: + co->name = strdup(ptr); + if(!co->name) + return CERR_OUT_OF_MEMORY; + else { + /* For Netscape file format cookies we check prefix on the name */ + if(strncasecompare("__Secure-", co->name, 9)) + co->prefix_secure = TRUE; + else if(strncasecompare("__Host-", co->name, 7)) + co->prefix_host = TRUE; + } + break; + case 6: + co->value = strdup(ptr); + if(!co->value) + return CERR_OUT_OF_MEMORY; + break; + } + } + if(6 == fields) { + /* we got a cookie with blank contents, fix it */ + co->value = strdup(""); + if(!co->value) + return CERR_OUT_OF_MEMORY; + else + fields++; + } - /* at first, remove expired cookies */ - if(!noexpire) - remove_expired(c); + if(7 != fields) + /* we did not find the sufficient number of fields */ + return CERR_FIELDS; + return CERR_OK; +} + +static int +is_public_suffix(struct Curl_easy *data, + struct Cookie *co, + const char *domain) +{ #ifdef USE_LIBPSL /* * Check if the domain is a Public Suffix and if yes, ignore the cookie. We - * must also check that the data handle isn't NULL since the psl code will + * must also check that the data handle is not NULL since the psl code will * dereference it. */ + DEBUGF(infof(data, "PSL check set-cookie '%s' for domain=%s in %s", + co->name, co->domain, domain)); if(data && (domain && co->domain && !Curl_host_is_ipnum(co->domain))) { - const psl_ctx_t *psl = Curl_psl_use(data); - int acceptable; - - if(psl) { - acceptable = psl_is_cookie_domain_acceptable(psl, domain, co->domain); - Curl_psl_release(data); + bool acceptable = FALSE; + char lcase[256]; + char lcookie[256]; + size_t dlen = strlen(domain); + size_t clen = strlen(co->domain); + if((dlen < sizeof(lcase)) && (clen < sizeof(lcookie))) { + const psl_ctx_t *psl = Curl_psl_use(data); + if(psl) { + /* the PSL check requires lowercase domain name and pattern */ + Curl_strntolower(lcase, domain, dlen + 1); + Curl_strntolower(lcookie, co->domain, clen + 1); + acceptable = psl_is_cookie_domain_acceptable(psl, lcase, lcookie); + Curl_psl_release(data); + } + else + infof(data, "libpsl problem, rejecting cookie for satety"); } - else - acceptable = !bad_domain(domain, strlen(domain)); if(!acceptable) { infof(data, "cookie '%s' dropped, domain '%s' must not " - "set cookies for '%s'", co->name, domain, co->domain); - freecookie(co); - return NULL; + "set cookies for '%s'", co->name, domain, co->domain); + return CERR_PSL; } } +#else + (void)data; + (void)co; + (void)domain; + DEBUGF(infof(data, "NO PSL to check set-cookie '%s' for domain=%s in %s", + co->name, co->domain, domain)); #endif + return CERR_OK; +} - /* A non-secure cookie may not overlay an existing secure cookie. */ - myhash = cookiehash(co->domain); - clist = c->cookies[myhash]; - while(clist) { +static int +replace_existing(struct Curl_easy *data, + struct Cookie *co, + struct CookieInfo *ci, + bool secure, + bool *replacep) +{ + bool replace_old = FALSE; + struct Curl_llist_node *replace_n = NULL; + struct Curl_llist_node *n; + size_t myhash = cookiehash(co->domain); + for(n = Curl_llist_head(&ci->cookielist[myhash]); n; n = Curl_node_next(n)) { + struct Cookie *clist = Curl_node_elem(n); if(strcasecompare(clist->name, co->name)) { /* the names are identical */ bool matching_domains = FALSE; @@ -1099,13 +1024,12 @@ Curl_cookie_add(struct Curl_easy *data, if(strncasecompare(clist->spath, co->spath, cllen)) { infof(data, "cookie '%s' for domain '%s' dropped, would " "overlay an existing cookie", co->name, co->domain); - freecookie(co); - return NULL; + return CERR_BAD_SECURE; } } } - if(!replace_co && strcasecompare(clist->name, co->name)) { + if(!replace_n && strcasecompare(clist->name, co->name)) { /* the names are identical */ if(clist->domain && co->domain) { @@ -1129,70 +1053,142 @@ Curl_cookie_add(struct Curl_easy *data, if(replace_old && !co->livecookie && clist->livecookie) { /* - * Both cookies matched fine, except that the already present cookie is - * "live", which means it was set from a header, while the new one was - * read from a file and thus isn't "live". "live" cookies are preferred - * so the new cookie is freed. + * Both cookies matched fine, except that the already present cookie + * is "live", which means it was set from a header, while the new one + * was read from a file and thus is not "live". "live" cookies are + * preferred so the new cookie is freed. */ - freecookie(co); - return NULL; - } - if(replace_old) { - replace_co = co; - replace_clist = clist; + return CERR_LIVE_WINS; } + if(replace_old) + replace_n = n; } - lastc = clist; - clist = clist->next; } - if(replace_co) { - co = replace_co; - clist = replace_clist; - co->next = clist->next; /* get the next-pointer first */ + if(replace_n) { + struct Cookie *repl = Curl_node_elem(replace_n); /* when replacing, creationtime is kept from old */ - co->creationtime = clist->creationtime; - - /* then free all the old pointers */ - free(clist->name); - free(clist->value); - free(clist->domain); - free(clist->path); - free(clist->spath); - free(clist->expirestr); - free(clist->version); - free(clist->maxage); - - *clist = *co; /* then store all the new data */ - - free(co); /* free the newly allocated memory */ - co = clist; + co->creationtime = repl->creationtime; + + /* unlink the old */ + Curl_node_remove(replace_n); + + /* free the old cookie */ + freecookie(repl); + } + *replacep = replace_old; + return CERR_OK; +} + +/* + * Curl_cookie_add + * + * Add a single cookie line to the cookie keeping object. Be aware that + * sometimes we get an IP-only hostname, and that might also be a numerical + * IPv6 address. + * + * Returns NULL on out of memory or invalid cookie. This is suboptimal, + * as they should be treated separately. + */ +struct Cookie * +Curl_cookie_add(struct Curl_easy *data, + struct CookieInfo *ci, + bool httpheader, /* TRUE if HTTP header-style line */ + bool noexpire, /* if TRUE, skip remove_expired() */ + const char *lineptr, /* first character of the line */ + const char *domain, /* default domain */ + const char *path, /* full path used when this cookie is set, + used to get default path for the cookie + unless set */ + bool secure) /* TRUE if connection is over secure origin */ +{ + struct Cookie *co; + size_t myhash; + int rc; + bool replaces = FALSE; + + DEBUGASSERT(data); + DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */ + if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT) + return NULL; + + /* First, alloc and init a new struct for it */ + co = calloc(1, sizeof(struct Cookie)); + if(!co) + return NULL; /* bail out if we are this low on memory */ + + if(httpheader) + rc = parse_cookie_header(data, co, ci, lineptr, domain, path, secure); + else + rc = parse_netscape(co, ci, lineptr, secure); + + if(rc) + goto fail; + + if(co->prefix_secure && !co->secure) + /* The __Secure- prefix only requires that the cookie be set secure */ + goto fail; + + if(co->prefix_host) { + /* + * The __Host- prefix requires the cookie to be secure, have a "/" path + * and not have a domain set. + */ + if(co->secure && co->path && strcmp(co->path, "/") == 0 && !co->tailmatch) + ; + else + goto fail; } - if(c->running) + if(!ci->running && /* read from a file */ + ci->newsession && /* clean session cookies */ + !co->expires) /* this is a session cookie since it does not expire */ + goto fail; + + co->livecookie = ci->running; + co->creationtime = ++ci->lastct; + + /* + * Now we have parsed the incoming line, we must now check if this supersedes + * an already existing cookie, which it may if the previous have the same + * domain and path as this. + */ + + /* remove expired cookies */ + if(!noexpire) + remove_expired(ci); + + if(is_public_suffix(data, co, domain)) + goto fail; + + if(replace_existing(data, co, ci, secure, &replaces)) + goto fail; + + /* add this cookie to the list */ + myhash = cookiehash(co->domain); + Curl_llist_append(&ci->cookielist[myhash], co, &co->node); + + if(ci->running) /* Only show this when NOT reading the cookies from a file */ infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, " - "expire %" CURL_FORMAT_CURL_OFF_T, - replace_old?"Replaced":"Added", co->name, co->value, + "expire %" FMT_OFF_T, + replaces ? "Replaced":"Added", co->name, co->value, co->domain, co->path, co->expires); - if(!replace_old) { - /* then make the last item point on this new one */ - if(lastc) - lastc->next = co; - else - c->cookies[myhash] = co; - c->numcookies++; /* one more cookie in the jar */ - } + if(!replaces) + ci->numcookies++; /* one more cookie in the jar */ /* - * Now that we've added a new cookie to the jar, update the expiration + * Now that we have added a new cookie to the jar, update the expiration * tracker in case it is the next one to expire. */ - if(co->expires && (co->expires < c->next_expiration)) - c->next_expiration = co->expires; + if(co->expires && (co->expires < ci->next_expiration)) + ci->next_expiration = co->expires; return co; +fail: + freecookie(co); + return NULL; } @@ -1212,36 +1208,34 @@ Curl_cookie_add(struct Curl_easy *data, */ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, const char *file, - struct CookieInfo *inc, + struct CookieInfo *ci, bool newsession) { - struct CookieInfo *c; - char *line = NULL; FILE *handle = NULL; - if(!inc) { - /* we didn't get a struct, create one */ - c = calloc(1, sizeof(struct CookieInfo)); - if(!c) + if(!ci) { + int i; + + /* we did not get a struct, create one */ + ci = calloc(1, sizeof(struct CookieInfo)); + if(!ci) return NULL; /* failed to get memory */ - c->filename = strdup(file?file:"none"); /* copy the name just in case */ - if(!c->filename) - goto fail; /* failed to get memory */ + + /* This does not use the destructor callback since we want to add + and remove to lists while keeping the cookie struct intact */ + for(i = 0; i < COOKIE_HASH_SIZE; i++) + Curl_llist_init(&ci->cookielist[i], NULL); /* - * Initialize the next_expiration time to signal that we don't have enough + * Initialize the next_expiration time to signal that we do not have enough * information yet. */ - c->next_expiration = CURL_OFF_T_MAX; - } - else { - /* we got an already existing one, use that */ - c = inc; + ci->next_expiration = CURL_OFF_T_MAX; } - c->newsession = newsession; /* new session? */ + ci->newsession = newsession; /* new session? */ if(data) { FILE *fp = NULL; - if(file) { + if(file && *file) { if(!strcmp(file, "-")) fp = stdin; else { @@ -1253,57 +1247,39 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, } } - c->running = FALSE; /* this is not running, this is init */ + ci->running = FALSE; /* this is not running, this is init */ if(fp) { - char *lineptr; - bool headerline; - - line = malloc(MAX_COOKIE_LINE); - if(!line) - goto fail; - while(Curl_get_line(line, MAX_COOKIE_LINE, fp)) { - if(checkprefix("Set-Cookie:", line)) { + struct dynbuf buf; + Curl_dyn_init(&buf, MAX_COOKIE_LINE); + while(Curl_get_line(&buf, fp)) { + char *lineptr = Curl_dyn_ptr(&buf); + bool headerline = FALSE; + if(checkprefix("Set-Cookie:", lineptr)) { /* This is a cookie line, get it! */ - lineptr = &line[11]; + lineptr += 11; headerline = TRUE; + while(*lineptr && ISBLANK(*lineptr)) + lineptr++; } - else { - lineptr = line; - headerline = FALSE; - } - while(*lineptr && ISBLANK(*lineptr)) - lineptr++; - Curl_cookie_add(data, c, headerline, TRUE, lineptr, NULL, NULL, TRUE); + Curl_cookie_add(data, ci, headerline, TRUE, lineptr, NULL, NULL, TRUE); } - free(line); /* free the line buffer */ + Curl_dyn_free(&buf); /* free the line buffer */ /* * Remove expired cookies from the hash. We must make sure to run this * after reading the file, and not on every cookie. */ - remove_expired(c); + remove_expired(ci); if(handle) fclose(handle); } data->state.cookie_engine = TRUE; - c->running = TRUE; /* now, we're running */ } + ci->running = TRUE; /* now, we are running */ - return c; - -fail: - free(line); - /* - * Only clean up if we allocated it here, as the original could still be in - * use by a share handle. - */ - if(!inc) - Curl_cookie_cleanup(c); - if(handle) - fclose(handle); - return NULL; /* out of memory */ + return ci; } /* @@ -1358,41 +1334,6 @@ static int cookie_sort_ct(const void *p1, const void *p2) return (c2->creationtime > c1->creationtime) ? 1 : -1; } -#define CLONE(field) \ - do { \ - if(src->field) { \ - d->field = strdup(src->field); \ - if(!d->field) \ - goto fail; \ - } \ - } while(0) - -static struct Cookie *dup_cookie(struct Cookie *src) -{ - struct Cookie *d = calloc(sizeof(struct Cookie), 1); - if(d) { - CLONE(expirestr); - CLONE(domain); - CLONE(path); - CLONE(spath); - CLONE(name); - CLONE(value); - CLONE(maxage); - CLONE(version); - d->expires = src->expires; - d->tailmatch = src->tailmatch; - d->secure = src->secure; - d->livecookie = src->livecookie; - d->httponly = src->httponly; - d->creationtime = src->creationtime; - } - return d; - -fail: - freecookie(d); - return NULL; -} - /* * Curl_cookie_getlist * @@ -1400,34 +1341,38 @@ static struct Cookie *dup_cookie(struct Cookie *src) * should send to the server if used now. The secure boolean informs the cookie * if a secure connection is achieved or not. * - * It shall only return cookies that haven't expired. + * It shall only return cookies that have not expired. + * + * Returns 0 when there is a list returned. Otherwise non-zero. */ -struct Cookie *Curl_cookie_getlist(struct Curl_easy *data, - struct CookieInfo *c, - const char *host, const char *path, - bool secure) +int Curl_cookie_getlist(struct Curl_easy *data, + struct CookieInfo *ci, + const char *host, const char *path, + bool secure, + struct Curl_llist *list) { - struct Cookie *newco; - struct Cookie *co; - struct Cookie *mainco = NULL; size_t matches = 0; bool is_ip; const size_t myhash = cookiehash(host); + struct Curl_llist_node *n; + + Curl_llist_init(list, NULL); - if(!c || !c->cookies[myhash]) - return NULL; /* no cookie struct or no cookies in the struct */ + if(!ci || !Curl_llist_count(&ci->cookielist[myhash])) + return 1; /* no cookie struct or no cookies in the struct */ /* at first, remove expired cookies */ - remove_expired(c); + remove_expired(ci); /* check if host is an IP(v4|v6) address */ is_ip = Curl_host_is_ipnum(host); - co = c->cookies[myhash]; + for(n = Curl_llist_head(&ci->cookielist[myhash]); + n; n = Curl_node_next(n)) { + struct Cookie *co = Curl_node_elem(n); - while(co) { - /* if the cookie requires we're secure we must only continue if we are! */ - if(co->secure?secure:TRUE) { + /* if the cookie requires we are secure we must only continue if we are! */ + if(co->secure ? secure : TRUE) { /* now check if the domain is correct */ if(!co->domain || @@ -1446,31 +1391,18 @@ struct Cookie *Curl_cookie_getlist(struct Curl_easy *data, if(!co->spath || pathmatch(co->spath, path) ) { /* - * and now, we know this is a match and we should create an - * entry for the return-linked-list + * This is a match and we add it to the return-linked-list */ - - newco = dup_cookie(co); - if(newco) { - /* then modify our next */ - newco->next = mainco; - - /* point the main to us */ - mainco = newco; - - matches++; - if(matches >= MAX_COOKIE_SEND_AMOUNT) { - infof(data, "Included max number of cookies (%zu) in request!", - matches); - break; - } + Curl_llist_append(list, co, &co->getnode); + matches++; + if(matches >= MAX_COOKIE_SEND_AMOUNT) { + infof(data, "Included max number of cookies (%zu) in request!", + matches); + break; } - else - goto fail; } } } - co = co->next; } if(matches) { @@ -1487,30 +1419,29 @@ struct Cookie *Curl_cookie_getlist(struct Curl_easy *data, if(!array) goto fail; - co = mainco; + n = Curl_llist_head(list); - for(i = 0; co; co = co->next) - array[i++] = co; + for(i = 0; n; n = Curl_node_next(n)) + array[i++] = Curl_node_elem(n); /* now sort the cookie pointers in path length order */ qsort(array, matches, sizeof(struct Cookie *), cookie_sort); /* remake the linked list order according to the new order */ + Curl_llist_destroy(list, NULL); - mainco = array[0]; /* start here */ - for(i = 0; inext = array[i + 1]; - array[matches-1]->next = NULL; /* terminate the list */ + for(i = 0; i < matches; i++) + Curl_llist_append(list, array[i], &array[i]->getnode); free(array); /* remove the temporary data again */ } - return mainco; /* return the new list */ + return 0; /* success */ fail: /* failure, clear up the allocated chain and return NULL */ - Curl_cookie_freelist(mainco); - return NULL; + Curl_llist_destroy(list, NULL); + return 2; /* error */ } /* @@ -1518,30 +1449,21 @@ struct Cookie *Curl_cookie_getlist(struct Curl_easy *data, * * Clear all existing cookies and reset the counter. */ -void Curl_cookie_clearall(struct CookieInfo *cookies) +void Curl_cookie_clearall(struct CookieInfo *ci) { - if(cookies) { + if(ci) { unsigned int i; for(i = 0; i < COOKIE_HASH_SIZE; i++) { - Curl_cookie_freelist(cookies->cookies[i]); - cookies->cookies[i] = NULL; + struct Curl_llist_node *n; + for(n = Curl_llist_head(&ci->cookielist[i]); n;) { + struct Cookie *c = Curl_node_elem(n); + struct Curl_llist_node *e = Curl_node_next(n); + Curl_node_remove(n); + freecookie(c); + n = e; + } } - cookies->numcookies = 0; - } -} - -/* - * Curl_cookie_freelist - * - * Free a list of cookies previously returned by Curl_cookie_getlist(); - */ -void Curl_cookie_freelist(struct Cookie *co) -{ - struct Cookie *next; - while(co) { - next = co->next; - freecookie(co); - co = next; + ci->numcookies = 0; } } @@ -1550,39 +1472,26 @@ void Curl_cookie_freelist(struct Cookie *co) * * Free all session cookies in the cookies list. */ -void Curl_cookie_clearsess(struct CookieInfo *cookies) +void Curl_cookie_clearsess(struct CookieInfo *ci) { - struct Cookie *first, *curr, *next, *prev = NULL; unsigned int i; - if(!cookies) + if(!ci) return; for(i = 0; i < COOKIE_HASH_SIZE; i++) { - if(!cookies->cookies[i]) - continue; - - first = curr = prev = cookies->cookies[i]; + struct Curl_llist_node *n = Curl_llist_head(&ci->cookielist[i]); + struct Curl_llist_node *e = NULL; - for(; curr; curr = next) { - next = curr->next; + for(; n; n = e) { + struct Cookie *curr = Curl_node_elem(n); + e = Curl_node_next(n); /* in case the node is removed, get it early */ if(!curr->expires) { - if(first == curr) - first = next; - - if(prev == curr) - prev = next; - else - prev->next = next; - + Curl_node_remove(n); freecookie(curr); - cookies->numcookies--; + ci->numcookies--; } - else - prev = curr; } - - cookies->cookies[i] = first; } } @@ -1591,14 +1500,11 @@ void Curl_cookie_clearsess(struct CookieInfo *cookies) * * Free a "cookie object" previous created with Curl_cookie_init(). */ -void Curl_cookie_cleanup(struct CookieInfo *c) +void Curl_cookie_cleanup(struct CookieInfo *ci) { - if(c) { - unsigned int i; - free(c->filename); - for(i = 0; i < COOKIE_HASH_SIZE; i++) - Curl_cookie_freelist(c->cookies[i]); - free(c); /* free the base struct as well */ + if(ci) { + Curl_cookie_clearall(ci); + free(ci); /* free the base struct as well */ } } @@ -1617,47 +1523,47 @@ static char *get_netscape_format(const struct Cookie *co) "%s\t" /* tailmatch */ "%s\t" /* path */ "%s\t" /* secure */ - "%" CURL_FORMAT_CURL_OFF_T "\t" /* expires */ + "%" FMT_OFF_T "\t" /* expires */ "%s\t" /* name */ "%s", /* value */ - co->httponly?"#HttpOnly_":"", + co->httponly ? "#HttpOnly_" : "", /* * Make sure all domains are prefixed with a dot if they allow * tailmatching. This is Mozilla-style. */ - (co->tailmatch && co->domain && co->domain[0] != '.')? ".":"", - co->domain?co->domain:"unknown", - co->tailmatch?"TRUE":"FALSE", - co->path?co->path:"/", - co->secure?"TRUE":"FALSE", + (co->tailmatch && co->domain && co->domain[0] != '.') ? "." : "", + co->domain ? co->domain : "unknown", + co->tailmatch ? "TRUE" : "FALSE", + co->path ? co->path : "/", + co->secure ? "TRUE" : "FALSE", co->expires, co->name, - co->value?co->value:""); + co->value ? co->value : ""); } /* * cookie_output() * * Writes all internally known cookies to the specified file. Specify - * "-" as file name to write to stdout. + * "-" as filename to write to stdout. * * The function returns non-zero on write failure. */ static CURLcode cookie_output(struct Curl_easy *data, - struct CookieInfo *c, const char *filename) + struct CookieInfo *ci, + const char *filename) { - struct Cookie *co; FILE *out = NULL; bool use_stdout = FALSE; char *tempstore = NULL; CURLcode error = CURLE_OK; - if(!c) + if(!ci) /* no cookie engine alive */ return CURLE_OK; /* at first, remove expired cookies */ - remove_expired(c); + remove_expired(ci); if(!strcmp("-", filename)) { /* use stdout */ @@ -1675,12 +1581,13 @@ static CURLcode cookie_output(struct Curl_easy *data, "# This file was generated by libcurl! Edit at your own risk.\n\n", out); - if(c->numcookies) { + if(ci->numcookies) { unsigned int i; size_t nvalid = 0; struct Cookie **array; + struct Curl_llist_node *n; - array = calloc(1, sizeof(struct Cookie *) * c->numcookies); + array = calloc(1, sizeof(struct Cookie *) * ci->numcookies); if(!array) { error = CURLE_OUT_OF_MEMORY; goto error; @@ -1688,7 +1595,9 @@ static CURLcode cookie_output(struct Curl_easy *data, /* only sort the cookies with a domain property */ for(i = 0; i < COOKIE_HASH_SIZE; i++) { - for(co = c->cookies[i]; co; co = co->next) { + for(n = Curl_llist_head(&ci->cookielist[i]); n; + n = Curl_node_next(n)) { + struct Cookie *co = Curl_node_elem(n); if(!co->domain) continue; array[nvalid++] = co; @@ -1740,15 +1649,17 @@ static struct curl_slist *cookie_list(struct Curl_easy *data) { struct curl_slist *list = NULL; struct curl_slist *beg; - struct Cookie *c; - char *line; unsigned int i; + struct Curl_llist_node *n; if(!data->cookies || (data->cookies->numcookies == 0)) return NULL; for(i = 0; i < COOKIE_HASH_SIZE; i++) { - for(c = data->cookies->cookies[i]; c; c = c->next) { + for(n = Curl_llist_head(&data->cookies->cookielist[i]); n; + n = Curl_node_next(n)) { + struct Cookie *c = Curl_node_elem(n); + char *line; if(!c->domain) continue; line = get_netscape_format(c); diff --git a/deps/curl/lib/cookie.h b/deps/curl/lib/cookie.h index b3c0063b2cf..c98f3602e44 100644 --- a/deps/curl/lib/cookie.h +++ b/deps/curl/lib/cookie.h @@ -27,26 +27,24 @@ #include +#include "llist.h" + struct Cookie { - struct Cookie *next; /* next in the chain */ - char *name; /* = value */ - char *value; /* name = */ + struct Curl_llist_node node; /* for the main cookie list */ + struct Curl_llist_node getnode; /* for getlist */ + char *name; /* = value */ + char *value; /* name = */ char *path; /* path = which is in Set-Cookie: */ char *spath; /* sanitized cookie path */ - char *domain; /* domain = */ - curl_off_t expires; /* expires = */ - char *expirestr; /* the plain text version */ - - /* RFC 2109 keywords. Version=1 means 2109-compliant cookie sending */ - char *version; /* Version = */ - char *maxage; /* Max-Age = */ - - bool tailmatch; /* whether we do tail-matching of the domain name */ - bool secure; /* whether the 'secure' keyword was used */ - bool livecookie; /* updated from a server, not a stored file */ - bool httponly; /* true if the httponly directive is present */ - int creationtime; /* time when the cookie was written */ - unsigned char prefix; /* bitmap fields indicating which prefix are set */ + char *domain; /* domain = */ + curl_off_t expires; /* expires = */ + int creationtime; /* time when the cookie was written */ + BIT(tailmatch); /* tail-match the domain name */ + BIT(secure); /* the 'secure' keyword was used */ + BIT(livecookie); /* updated from a server, not a stored file */ + BIT(httponly); /* the httponly directive is present */ + BIT(prefix_secure); /* secure prefix is set */ + BIT(prefix_host); /* host prefix is set */ }; /* @@ -56,17 +54,16 @@ struct Cookie { #define COOKIE_PREFIX__SECURE (1<<0) #define COOKIE_PREFIX__HOST (1<<1) -#define COOKIE_HASH_SIZE 256 +#define COOKIE_HASH_SIZE 63 struct CookieInfo { - /* linked list of cookies we know of */ - struct Cookie *cookies[COOKIE_HASH_SIZE]; - char *filename; /* file we read from/write to */ - long numcookies; /* number of cookies in the "jar" */ + /* linked lists of cookies we know of */ + struct Curl_llist cookielist[COOKIE_HASH_SIZE]; + curl_off_t next_expiration; /* the next time at which expiration happens */ + int numcookies; /* number of cookies in the "jar" */ + int lastct; /* last creation-time used in the jar */ bool running; /* state info, for cookie adding information */ bool newsession; /* new session, discard session cookies on load */ - int lastct; /* last creation-time used in the jar */ - curl_off_t next_expiration; /* the next time at which expiration happens */ }; /* The maximum sizes we accept for cookies. RFC 6265 section 6.1 says @@ -75,7 +72,6 @@ struct CookieInfo { - At least 4096 bytes per cookie (as measured by the sum of the length of the cookie's name, value, and attributes). - In the 6265bis draft document section 5.4 it is phrased even stronger: "If the sum of the lengths of the name string and the value string is more than 4096 octets, abort these steps and ignore the set-cookie-string entirely." @@ -83,7 +79,7 @@ struct CookieInfo { /** Limits for INCOMING cookies **/ -/* The longest we allow a line to be when reading a cookie from a HTTP header +/* The longest we allow a line to be when reading a cookie from an HTTP header or from a cookie jar */ #define MAX_COOKIE_LINE 5000 @@ -116,14 +112,14 @@ struct Curl_easy; struct Cookie *Curl_cookie_add(struct Curl_easy *data, struct CookieInfo *c, bool header, - bool noexpiry, char *lineptr, + bool noexpiry, const char *lineptr, const char *domain, const char *path, bool secure); -struct Cookie *Curl_cookie_getlist(struct Curl_easy *data, - struct CookieInfo *c, const char *host, - const char *path, bool secure); -void Curl_cookie_freelist(struct Cookie *cookies); +int Curl_cookie_getlist(struct Curl_easy *data, + struct CookieInfo *c, const char *host, + const char *path, bool secure, + struct Curl_llist *list); void Curl_cookie_clearall(struct CookieInfo *cookies); void Curl_cookie_clearsess(struct CookieInfo *cookies); diff --git a/deps/curl/lib/curl_addrinfo.c b/deps/curl/lib/curl_addrinfo.c index f9211d3f576..a08caf3328c 100644 --- a/deps/curl/lib/curl_addrinfo.c +++ b/deps/curl/lib/curl_addrinfo.c @@ -95,7 +95,7 @@ Curl_freeaddrinfo(struct Curl_addrinfo *cahead) * the only difference that instead of returning a linked list of * addrinfo structs this one returns a linked list of Curl_addrinfo * ones. The memory allocated by this function *MUST* be free'd with - * Curl_freeaddrinfo(). For each successful call to this function + * Curl_freeaddrinfo(). For each successful call to this function * there must be an associated call later to Curl_freeaddrinfo(). * * There should be no single call to system's getaddrinfo() in the @@ -130,7 +130,7 @@ Curl_getaddrinfo_ex(const char *nodename, /* settle family-specific sockaddr structure size. */ if(ai->ai_family == AF_INET) ss_size = sizeof(struct sockaddr_in); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else if(ai->ai_family == AF_INET6) ss_size = sizeof(struct sockaddr_in6); #endif @@ -221,7 +221,7 @@ Curl_getaddrinfo_ex(const char *nodename, * stack, but usable also for IPv4, all hosts and environments. * * The memory allocated by this function *MUST* be free'd later on calling - * Curl_freeaddrinfo(). For each successful call to this function there + * Curl_freeaddrinfo(). For each successful call to this function there * must be an associated call later to Curl_freeaddrinfo(). * * Curl_addrinfo defined in "lib/curl_addrinfo.h" @@ -252,6 +252,7 @@ Curl_getaddrinfo_ex(const char *nodename, * #define h_addr h_addr_list[0] */ +#if !(defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE)) struct Curl_addrinfo * Curl_he2ai(const struct hostent *he, int port) { @@ -259,7 +260,7 @@ Curl_he2ai(const struct hostent *he, int port) struct Curl_addrinfo *prevai = NULL; struct Curl_addrinfo *firstai = NULL; struct sockaddr_in *addr; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif CURLcode result = CURLE_OK; @@ -275,7 +276,7 @@ Curl_he2ai(const struct hostent *he, int port) for(i = 0; (curr = he->h_addr_list[i]) != NULL; i++) { size_t ss_size; size_t namelen = strlen(he->h_name) + 1; /* include null-terminator */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(he->h_addrtype == AF_INET6) ss_size = sizeof(struct sockaddr_in6); else @@ -317,16 +318,24 @@ Curl_he2ai(const struct hostent *he, int port) addr = (void *)ai->ai_addr; /* storage area for this info */ memcpy(&addr->sin_addr, curr, sizeof(struct in_addr)); +#ifdef __MINGW32__ + addr->sin_family = (short)(he->h_addrtype); +#else addr->sin_family = (CURL_SA_FAMILY_T)(he->h_addrtype); +#endif addr->sin_port = htons((unsigned short)port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: addr6 = (void *)ai->ai_addr; /* storage area for this info */ memcpy(&addr6->sin6_addr, curr, sizeof(struct in6_addr)); +#ifdef __MINGW32__ + addr6->sin6_family = (short)(he->h_addrtype); +#else addr6->sin6_family = (CURL_SA_FAMILY_T)(he->h_addrtype); +#endif addr6->sin6_port = htons((unsigned short)port); break; #endif @@ -342,24 +351,12 @@ Curl_he2ai(const struct hostent *he, int port) return firstai; } - - -struct namebuff { - struct hostent hostentry; - union { - struct in_addr ina4; -#ifdef ENABLE_IPV6 - struct in6_addr ina6; #endif - } addrentry; - char *h_addr_list[2]; -}; - /* * Curl_ip2addr() * - * This function takes an internet address, in binary form, as input parameter + * This function takes an Internet address, in binary form, as input parameter * along with its address family and the string version of the address, and it * returns a Curl_addrinfo chain filled in correctly with information for the * given address/host @@ -369,71 +366,68 @@ struct Curl_addrinfo * Curl_ip2addr(int af, const void *inaddr, const char *hostname, int port) { struct Curl_addrinfo *ai; - -#if defined(__VMS) && \ - defined(__INITIAL_POINTER_SIZE) && (__INITIAL_POINTER_SIZE == 64) -#pragma pointer_size save -#pragma pointer_size short -#pragma message disable PTRMISMATCH -#endif - - struct hostent *h; - struct namebuff *buf; - char *addrentry; - char *hoststr; size_t addrsize; + size_t namelen; + struct sockaddr_in *addr; +#ifdef USE_IPV6 + struct sockaddr_in6 *addr6; +#endif DEBUGASSERT(inaddr && hostname); - buf = malloc(sizeof(struct namebuff)); - if(!buf) + namelen = strlen(hostname) + 1; + + if(af == AF_INET) + addrsize = sizeof(struct sockaddr_in); +#ifdef USE_IPV6 + else if(af == AF_INET6) + addrsize = sizeof(struct sockaddr_in6); +#endif + else return NULL; - hoststr = strdup(hostname); - if(!hoststr) { - free(buf); + /* allocate memory to hold the struct, the address and the name */ + ai = calloc(1, sizeof(struct Curl_addrinfo) + addrsize + namelen); + if(!ai) return NULL; - } + /* put the address after the struct */ + ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo)); + /* then put the name after the address */ + ai->ai_canonname = (char *)ai->ai_addr + addrsize; + memcpy(ai->ai_canonname, hostname, namelen); + ai->ai_family = af; + ai->ai_socktype = SOCK_STREAM; + ai->ai_addrlen = (curl_socklen_t)addrsize; + /* leave the rest of the struct filled with zero */ switch(af) { case AF_INET: - addrsize = sizeof(struct in_addr); - addrentry = (void *)&buf->addrentry.ina4; - memcpy(addrentry, inaddr, sizeof(struct in_addr)); + addr = (void *)ai->ai_addr; /* storage area for this info */ + + memcpy(&addr->sin_addr, inaddr, sizeof(struct in_addr)); +#ifdef __MINGW32__ + addr->sin_family = (short)af; +#else + addr->sin_family = (CURL_SA_FAMILY_T)af; +#endif + addr->sin_port = htons((unsigned short)port); break; -#ifdef ENABLE_IPV6 + +#ifdef USE_IPV6 case AF_INET6: - addrsize = sizeof(struct in6_addr); - addrentry = (void *)&buf->addrentry.ina6; - memcpy(addrentry, inaddr, sizeof(struct in6_addr)); + addr6 = (void *)ai->ai_addr; /* storage area for this info */ + + memcpy(&addr6->sin6_addr, inaddr, sizeof(struct in6_addr)); +#ifdef __MINGW32__ + addr6->sin6_family = (short)af; +#else + addr6->sin6_family = (CURL_SA_FAMILY_T)af; +#endif + addr6->sin6_port = htons((unsigned short)port); break; #endif - default: - free(hoststr); - free(buf); - return NULL; } - h = &buf->hostentry; - h->h_name = hoststr; - h->h_aliases = NULL; - h->h_addrtype = (short)af; - h->h_length = (short)addrsize; - h->h_addr_list = &buf->h_addr_list[0]; - h->h_addr_list[0] = addrentry; - h->h_addr_list[1] = NULL; /* terminate list of entries */ - -#if defined(__VMS) && \ - defined(__INITIAL_POINTER_SIZE) && (__INITIAL_POINTER_SIZE == 64) -#pragma pointer_size restore -#pragma message enable PTRMISMATCH -#endif - - ai = Curl_he2ai(h, port); - - free(hoststr); - free(buf); - return ai; } @@ -447,7 +441,7 @@ struct Curl_addrinfo *Curl_str2addr(char *address, int port) if(Curl_inet_pton(AF_INET, address, &in) > 0) /* This is a dotted IP address 123.123.123.123-style */ return Curl_ip2addr(AF_INET, &in, address, port); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 { struct in6_addr in6; if(Curl_inet_pton(AF_INET6, address, &in6) > 0) @@ -511,7 +505,7 @@ struct Curl_addrinfo *Curl_unix2addr(const char *path, bool *longpath, * * This is strictly for memory tracing and are using the same style as the * family otherwise present in memdebug.c. I put these ones here since they - * require a bunch of structs I didn't want to include in memdebug.c + * require a bunch of structs I did not want to include in memdebug.c */ void @@ -535,7 +529,7 @@ curl_dbg_freeaddrinfo(struct addrinfo *freethis, * * This is strictly for memory tracing and are using the same style as the * family otherwise present in memdebug.c. I put these ones here since they - * require a bunch of structs I didn't want to include in memdebug.c + * require a bunch of structs I did not want to include in memdebug.c */ int @@ -563,14 +557,14 @@ curl_dbg_getaddrinfo(const char *hostname, #if defined(HAVE_GETADDRINFO) && defined(USE_RESOLVE_ON_IPS) /* - * Work-arounds the sin6_port is always zero bug on iOS 9.3.2 and Mac OS X + * Work-arounds the sin6_port is always zero bug on iOS 9.3.2 and macOS * 10.11.5. */ void Curl_addrinfo_set_port(struct Curl_addrinfo *addrinfo, int port) { struct Curl_addrinfo *ca; struct sockaddr_in *addr; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif for(ca = addrinfo; ca != NULL; ca = ca->ai_next) { @@ -580,7 +574,7 @@ void Curl_addrinfo_set_port(struct Curl_addrinfo *addrinfo, int port) addr->sin_port = htons((unsigned short)port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: addr6 = (void *)ca->ai_addr; /* storage area for this info */ addr6->sin6_port = htons((unsigned short)port); diff --git a/deps/curl/lib/curl_addrinfo.h b/deps/curl/lib/curl_addrinfo.h index c757c49c5cb..2303e95e314 100644 --- a/deps/curl/lib/curl_addrinfo.h +++ b/deps/curl/lib/curl_addrinfo.h @@ -44,9 +44,9 @@ /* * Curl_addrinfo is our internal struct definition that we use to allow - * consistent internal handling of this data. We use this even when the - * system provides an addrinfo structure definition. And we use this for - * all sorts of IPv4 and IPV6 builds. + * consistent internal handling of this data. We use this even when the system + * provides an addrinfo structure definition. We use this for all sorts of + * IPv4 and IPV6 builds. */ struct Curl_addrinfo { @@ -71,8 +71,10 @@ Curl_getaddrinfo_ex(const char *nodename, struct Curl_addrinfo **result); #endif +#if !(defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE)) struct Curl_addrinfo * Curl_he2ai(const struct hostent *he, int port); +#endif struct Curl_addrinfo * Curl_ip2addr(int af, const void *inaddr, const char *hostname, int port); diff --git a/deps/curl/lib/curl_config.h.cmake b/deps/curl/lib/curl_config.h.cmake index 65901a2b1dd..7ee0a400f7f 100644 --- a/deps/curl/lib/curl_config.h.cmake +++ b/deps/curl/lib/curl_config.h.cmake @@ -21,7 +21,6 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -/* lib/curl_config.h.in. Generated somehow by cmake. */ /* Location of default ca bundle */ #cmakedefine CURL_CA_BUNDLE "${CURL_CA_BUNDLE}" @@ -56,7 +55,7 @@ /* disables negotiate authentication */ #cmakedefine CURL_DISABLE_NEGOTIATE_AUTH 1 -/* disables AWS-SIG4 */ +/* disables aws-sigv4 */ #cmakedefine CURL_DISABLE_AWS 1 /* disables DICT */ @@ -74,15 +73,24 @@ /* disables FTP */ #cmakedefine CURL_DISABLE_FTP 1 +/* disables curl_easy_options API for existing options to curl_easy_setopt */ +#cmakedefine CURL_DISABLE_GETOPTIONS 1 + /* disables GOPHER */ #cmakedefine CURL_DISABLE_GOPHER 1 +/* disables headers-api support */ +#cmakedefine CURL_DISABLE_HEADERS_API 1 + /* disables HSTS support */ #cmakedefine CURL_DISABLE_HSTS 1 /* disables HTTP */ #cmakedefine CURL_DISABLE_HTTP 1 +/* disabled all HTTP authentication methods */ +#cmakedefine CURL_DISABLE_HTTP_AUTH 1 + /* disables IMAP */ #cmakedefine CURL_DISABLE_IMAP 1 @@ -98,6 +106,9 @@ /* disables MIME support */ #cmakedefine CURL_DISABLE_MIME 1 +/* disables local binding support */ +#cmakedefine CURL_DISABLE_BINDLOCAL 1 + /* disables MQTT */ #cmakedefine CURL_DISABLE_MQTT 1 @@ -119,15 +130,27 @@ /* disables proxies */ #cmakedefine CURL_DISABLE_PROXY 1 +/* disables IPFS from the curl tool */ +#cmakedefine CURL_DISABLE_IPFS 1 + /* disables RTSP */ #cmakedefine CURL_DISABLE_RTSP 1 +/* disables SHA-512/256 hash algorithm */ +#cmakedefine CURL_DISABLE_SHA512_256 1 + +/* disabled shuffle DNS feature */ +#cmakedefine CURL_DISABLE_SHUFFLE_DNS 1 + /* disables SMB */ #cmakedefine CURL_DISABLE_SMB 1 /* disables SMTP */ #cmakedefine CURL_DISABLE_SMTP 1 +/* disabled WebSockets */ +#cmakedefine CURL_DISABLE_WEBSOCKETS 1 + /* disables use of socketpair for curl_multi_poll */ #cmakedefine CURL_DISABLE_SOCKETPAIR 1 @@ -140,6 +163,12 @@ /* disables verbose strings */ #cmakedefine CURL_DISABLE_VERBOSE_STRINGS 1 +/* disables unsafe CA bundle search on Windows from the curl tool */ +#cmakedefine CURL_DISABLE_CA_SEARCH 1 + +/* safe CA bundle search (within the curl tool directory) on Windows */ +#cmakedefine CURL_CA_SEARCH_SAFE 1 + /* to make a symbol visible */ #cmakedefine CURL_EXTERN_SYMBOL ${CURL_EXTERN_SYMBOL} /* Ensure using CURL_EXTERN_SYMBOL is possible */ @@ -154,22 +183,22 @@ #cmakedefine USE_WIN32_LDAP 1 /* Define if you want to enable IPv6 support */ -#cmakedefine ENABLE_IPV6 1 +#cmakedefine USE_IPV6 1 /* Define to 1 if you have the alarm function. */ #cmakedefine HAVE_ALARM 1 +/* Define to 1 if you have the arc4random function. */ +#cmakedefine HAVE_ARC4RANDOM 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_ARPA_INET_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_ARPA_TFTP_H 1 - /* Define to 1 if you have _Atomic support. */ #cmakedefine HAVE_ATOMIC 1 -/* Define to 1 if you have the `fchmod' function. */ -#cmakedefine HAVE_FCHMOD 1 +/* Define to 1 if you have the `fnmatch' function. */ +#cmakedefine HAVE_FNMATCH 1 /* Define to 1 if you have the `basename' function. */ #cmakedefine HAVE_BASENAME 1 @@ -183,9 +212,22 @@ /* Define to 1 if you have the clock_gettime function and monotonic timer. */ #cmakedefine HAVE_CLOCK_GETTIME_MONOTONIC 1 +/* Define to 1 if you have the clock_gettime function and raw monotonic timer. + */ +#cmakedefine HAVE_CLOCK_GETTIME_MONOTONIC_RAW 1 + /* Define to 1 if you have the `closesocket' function. */ #cmakedefine HAVE_CLOSESOCKET 1 +/* Define to 1 if you have the `CloseSocket' function. */ +#cmakedefine HAVE_CLOSESOCKET_CAMEL 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_DIRENT_H 1 + +/* Define to 1 if you have the `opendir' function. */ +#cmakedefine HAVE_OPENDIR 1 + /* Define to 1 if you have the fcntl function. */ #cmakedefine HAVE_FCNTL 1 @@ -198,6 +240,15 @@ /* Define to 1 if you have the freeaddrinfo function. */ #cmakedefine HAVE_FREEADDRINFO 1 +/* Define to 1 if you have the fseeko function. */ +#cmakedefine HAVE_FSEEKO 1 + +/* Define to 1 if you have the fseeko declaration. */ +#cmakedefine HAVE_DECL_FSEEKO 1 + +/* Define to 1 if you have the _fseeki64 function. */ +#cmakedefine HAVE__FSEEKI64 1 + /* Define to 1 if you have the ftruncate function. */ #cmakedefine HAVE_FTRUNCATE 1 @@ -234,9 +285,6 @@ /* Define to 1 if you have the `getpass_r' function. */ #cmakedefine HAVE_GETPASS_R 1 -/* Define to 1 if you have the `getppid' function. */ -#cmakedefine HAVE_GETPPID 1 - /* Define to 1 if you have the `getpeername' function. */ #cmakedefine HAVE_GETPEERNAME 1 @@ -273,21 +321,9 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_GSSAPI_GSSAPI_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_GSSAPI_GSSAPI_KRB5_H 1 - /* if you have the GNU gssapi libraries */ #cmakedefine HAVE_GSSGNU 1 -/* if you have the Heimdal gssapi libraries */ -#cmakedefine HAVE_GSSHEIMDAL 1 - -/* if you have the MIT gssapi libraries */ -#cmakedefine HAVE_GSSMIT 1 - -/* Define to 1 if you have the `idna_strerror' function. */ -#cmakedefine HAVE_IDNA_STRERROR 1 - /* Define to 1 if you have the header file. */ #cmakedefine HAVE_IFADDRS_H 1 @@ -303,9 +339,6 @@ /* Define to 1 if symbol `ADDRESS_FAMILY' exists */ #cmakedefine HAVE_ADDRESS_FAMILY 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_INTTYPES_H 1 - /* Define to 1 if you have the ioctlsocket function. */ #cmakedefine HAVE_IOCTLSOCKET 1 @@ -331,9 +364,6 @@ /* Define to 1 if you have the lber.h header file. */ #cmakedefine HAVE_LBER_H 1 -/* Define to 1 if you have the ldap.h header file. */ -#cmakedefine HAVE_LDAP_H 1 - /* Use LDAPS implementation */ #cmakedefine HAVE_LDAP_SSL 1 @@ -352,12 +382,6 @@ /* Define to 1 if you have the idn2.h header file. */ #cmakedefine HAVE_IDN2_H 1 -/* Define to 1 if you have the `socket' library (-lsocket). */ -#cmakedefine HAVE_LIBSOCKET 1 - -/* Define to 1 if you have the `ssh2' library (-lssh2). */ -#cmakedefine HAVE_LIBSSH2 1 - /* if zlib is available */ #cmakedefine HAVE_LIBZ 1 @@ -373,6 +397,9 @@ /* Define to 1 if the compiler supports the 'long long' data type. */ #cmakedefine HAVE_LONGLONG 1 +/* Define to 1 if you have the 'suseconds_t' data type. */ +#cmakedefine HAVE_SUSECONDS_T 1 + /* Define to 1 if you have the MSG_NOSIGNAL flag. */ #cmakedefine HAVE_MSG_NOSIGNAL 1 @@ -382,9 +409,15 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_NETINET_IN_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETINET_IN6_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_NETINET_TCP_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETINET_UDP_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_LINUX_TCP_H 1 @@ -397,8 +430,11 @@ /* Define to 1 if you have the `pipe' function. */ #cmakedefine HAVE_PIPE 1 -/* If you have a fine poll */ -#cmakedefine HAVE_POLL_FINE 1 +/* Define to 1 if you have the `eventfd' function. */ +#cmakedefine HAVE_EVENTFD 1 + +/* If you have poll */ +#cmakedefine HAVE_POLL 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_POLL_H 1 @@ -421,9 +457,18 @@ /* Define to 1 if you have the select function. */ #cmakedefine HAVE_SELECT 1 +/* Define to 1 if you have the sched_yield function. */ +#cmakedefine HAVE_SCHED_YIELD 1 + /* Define to 1 if you have the send function. */ #cmakedefine HAVE_SEND 1 +/* Define to 1 if you have the sendmsg function. */ +#cmakedefine HAVE_SENDMSG 1 + +/* Define to 1 if you have the sendmmsg function. */ +#cmakedefine HAVE_SENDMMSG 1 + /* Define to 1 if you have the 'fsetxattr' function. */ #cmakedefine HAVE_FSETXATTR 1 @@ -433,15 +478,15 @@ /* fsetxattr() takes 6 args */ #cmakedefine HAVE_FSETXATTR_6 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_SETJMP_H 1 - /* Define to 1 if you have the `setlocale' function. */ #cmakedefine HAVE_SETLOCALE 1 /* Define to 1 if you have the `setmode' function. */ #cmakedefine HAVE_SETMODE 1 +/* Define to 1 if you have the `_setmode' function. */ +#cmakedefine HAVE__SETMODE 1 + /* Define to 1 if you have the `setrlimit' function. */ #cmakedefine HAVE_SETRLIMIT 1 @@ -457,14 +502,11 @@ /* Define to 1 if you have the signal function. */ #cmakedefine HAVE_SIGNAL 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_SIGNAL_H 1 - /* Define to 1 if you have the sigsetjmp function or macro. */ #cmakedefine HAVE_SIGSETJMP 1 /* Define to 1 if you have the `snprintf' function. */ -#cmakedefine HAVE_SNPRINTF +#cmakedefine HAVE_SNPRINTF 1 /* Define to 1 if struct sockaddr_in6 has the sin6_scope_id member */ #cmakedefine HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 @@ -472,6 +514,9 @@ /* Define to 1 if you have the `socket' function. */ #cmakedefine HAVE_SOCKET 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PROTO_BSDSOCKET_H 1 + /* Define to 1 if you have the socketpair function. */ #cmakedefine HAVE_SOCKETPAIR 1 @@ -481,12 +526,6 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STDBOOL_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_STDINT_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_STDLIB_H 1 - /* Define to 1 if you have the strcasecmp function. */ #cmakedefine HAVE_STRCASECMP 1 @@ -505,9 +544,6 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STRINGS_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_STRING_H 1 - /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STROPTS_H 1 @@ -517,15 +553,24 @@ /* Define to 1 if you have the strtoll function. */ #cmakedefine HAVE_STRTOLL 1 +/* Define to 1 if you have the memrchr function. */ +#cmakedefine HAVE_MEMRCHR 1 + /* if struct sockaddr_storage is defined */ #cmakedefine HAVE_STRUCT_SOCKADDR_STORAGE 1 /* Define to 1 if you have the timeval struct. */ #cmakedefine HAVE_STRUCT_TIMEVAL 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_EVENTFD_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_FILIO_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_WAIT_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_IOCTL_H 1 @@ -568,9 +613,6 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_TERMIO_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_TIME_H 1 - /* Define to 1 if you have the header file. */ #cmakedefine HAVE_UNISTD_H 1 @@ -583,27 +625,9 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_UTIME_H 1 -/* Define to 1 if compiler supports C99 variadic macro style. */ -#cmakedefine HAVE_VARIADIC_MACROS_C99 1 - -/* Define to 1 if compiler supports old gcc variadic macro style. */ -#cmakedefine HAVE_VARIADIC_MACROS_GCC 1 - -/* Define to 1 if you have the windows.h header file. */ -#cmakedefine HAVE_WINDOWS_H 1 - -/* Define to 1 if you have the winsock2.h header file. */ -#cmakedefine HAVE_WINSOCK2_H 1 - /* Define this symbol if your OS supports changing the contents of argv */ #cmakedefine HAVE_WRITABLE_ARGV 1 -/* Define to 1 if you have the ws2tcpip.h header file. */ -#cmakedefine HAVE_WS2TCPIP_H 1 - -/* Define to 1 if you need the lber.h header file even with ldap.h */ -#cmakedefine NEED_LBER_H 1 - /* Define to 1 if you need the malloc.h header file even with stdlib.h */ #cmakedefine NEED_MALLOC_H 1 @@ -611,7 +635,7 @@ #cmakedefine NEED_REENTRANT 1 /* cpu-machine-OS */ -#cmakedefine OS ${OS} +#cmakedefine CURL_OS ${CURL_OS} /* Name of package */ #cmakedefine PACKAGE ${PACKAGE} @@ -631,9 +655,6 @@ /* Define to the version of this package. */ #cmakedefine PACKAGE_VERSION ${PACKAGE_VERSION} -/* a suitable file to read random data from */ -#cmakedefine RANDOM_FILE "${RANDOM_FILE}" - /* Note: SIZEOF_* variables are fetched with CMake through check_type_size(). As per CMake documentation on CheckTypeSize, C preprocessor code is @@ -658,6 +679,9 @@ ${SIZEOF_OFF_T_CODE} /* The size of `curl_off_t', as computed by sizeof. */ ${SIZEOF_CURL_OFF_T_CODE} +/* The size of `curl_socket_t', as computed by sizeof. */ +${SIZEOF_CURL_SOCKET_T_CODE} + /* The size of `size_t', as computed by sizeof. */ ${SIZEOF_SIZE_T_CODE} @@ -667,16 +691,13 @@ ${SIZEOF_TIME_T_CODE} /* Define to 1 if you have the ANSI C header files. */ #cmakedefine STDC_HEADERS 1 -/* Define to 1 if you can safely include both and . */ -#cmakedefine TIME_WITH_SYS_TIME 1 - /* Define if you want to enable c-ares support */ #cmakedefine USE_ARES 1 /* Define if you want to enable POSIX threaded DNS lookup */ #cmakedefine USE_THREADS_POSIX 1 -/* Define if you want to enable WIN32 threaded DNS lookup */ +/* Define if you want to enable Win32 threaded DNS lookup */ #cmakedefine USE_THREADS_WIN32 1 /* if GnuTLS is enabled */ @@ -691,20 +712,32 @@ ${SIZEOF_TIME_T_CODE} /* if BearSSL is enabled */ #cmakedefine USE_BEARSSL 1 -/* if WolfSSL is enabled */ +/* if Rustls is enabled */ +#cmakedefine USE_RUSTLS 1 + +/* if wolfSSL is enabled */ #cmakedefine USE_WOLFSSL 1 -/* if libSSH is in use */ +/* if wolfSSL has the wolfSSL_DES_ecb_encrypt function. */ +#cmakedefine HAVE_WOLFSSL_DES_ECB_ENCRYPT 1 + +/* if wolfSSL has the wolfSSL_BIO_new function. */ +#cmakedefine HAVE_WOLFSSL_BIO 1 + +/* if wolfSSL has the wolfSSL_BIO_set_shutdown function. */ +#cmakedefine HAVE_WOLFSSL_FULL_BIO 1 + +/* if libssh is in use */ #cmakedefine USE_LIBSSH 1 -/* if libSSH2 is in use */ +/* if libssh2 is in use */ #cmakedefine USE_LIBSSH2 1 -/* if libPSL is in use */ -#cmakedefine USE_LIBPSL 1 +/* if wolfssh is in use */ +#cmakedefine USE_WOLFSSH 1 -/* If you want to build curl with the built-in manual */ -#cmakedefine USE_MANUAL 1 +/* if libpsl is in use */ +#cmakedefine USE_LIBPSL 1 /* if you want to use OpenLDAP code instead of legacy ldap implementation */ #cmakedefine USE_OPENLDAP 1 @@ -712,7 +745,19 @@ ${SIZEOF_TIME_T_CODE} /* if OpenSSL is in use */ #cmakedefine USE_OPENSSL 1 -/* Define to 1 if you don't want the OpenSSL configuration to be loaded +/* if librtmp/rtmpdump is in use */ +#cmakedefine USE_LIBRTMP 1 + +/* if GSASL is in use */ +#cmakedefine USE_GSASL 1 + +/* if libuv is in use */ +#cmakedefine USE_LIBUV 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UV_H 1 + +/* Define to 1 if you do not want the OpenSSL configuration to be loaded automatically */ #cmakedefine CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG 1 @@ -728,6 +773,9 @@ ${SIZEOF_TIME_T_CODE} /* to enable quiche */ #cmakedefine USE_QUICHE 1 +/* to enable openssl + nghttp3 */ +#cmakedefine USE_OPENSSL_QUIC 1 + /* Define to 1 if you have the quiche_conn_set_qlog_fd function. */ #cmakedefine HAVE_QUICHE_CONN_SET_QLOG_FD 1 @@ -735,7 +783,7 @@ ${SIZEOF_TIME_T_CODE} #cmakedefine USE_MSH3 1 /* if Unix domain sockets are enabled */ -#cmakedefine USE_UNIX_SOCKETS +#cmakedefine USE_UNIX_SOCKETS 1 /* Define to 1 if you are building a Windows target with large file support. */ #cmakedefine USE_WIN32_LARGE_FILES 1 @@ -752,11 +800,6 @@ ${SIZEOF_TIME_T_CODE} /* Version number of package */ #cmakedefine VERSION ${VERSION} -/* Define to 1 if OS is AIX. */ -#ifndef _ALL_SOURCE -# undef _ALL_SOURCE -#endif - /* Number of bits in a file offset, on hosts where this is settable. */ #cmakedefine _FILE_OFFSET_BITS ${_FILE_OFFSET_BITS} @@ -772,12 +815,6 @@ ${SIZEOF_TIME_T_CODE} /* Type to use in place of in_addr_t when system does not provide it. */ #cmakedefine in_addr_t ${in_addr_t} -/* Define to `__inline__' or `__inline' if that's what the C compiler - calls it, or to nothing if 'inline' is not supported under any name. */ -#ifndef __cplusplus -#undef inline -#endif - /* Define to `unsigned int' if does not define. */ #cmakedefine size_t ${size_t} @@ -790,5 +827,20 @@ ${SIZEOF_TIME_T_CODE} /* to enable Windows IDN */ #cmakedefine USE_WIN32_IDN 1 -/* Define to 1 to enable websocket support. */ -#cmakedefine USE_WEBSOCKETS 1 +/* to enable Apple IDN */ +#cmakedefine USE_APPLE_IDN 1 + +/* Define to 1 if OpenSSL has the SSL_CTX_set_srp_username function. */ +#cmakedefine HAVE_OPENSSL_SRP 1 + +/* Define to 1 if GnuTLS has the gnutls_srp_verifier function. */ +#cmakedefine HAVE_GNUTLS_SRP 1 + +/* Define to 1 to enable TLS-SRP support. */ +#cmakedefine USE_TLS_SRP 1 + +/* Define to 1 to query for HTTPSRR when using DoH */ +#cmakedefine USE_HTTPSRR 1 + +/* if ECH support is available */ +#cmakedefine USE_ECH 1 diff --git a/deps/curl/lib/curl_config.h.in b/deps/curl/lib/curl_config.h.in index 7d1b9328820..e0dd88e7809 100644 --- a/deps/curl/lib/curl_config.h.in +++ b/deps/curl/lib/curl_config.h.in @@ -1,17 +1,23 @@ /* lib/curl_config.h.in. Generated from configure.ac by autoheader. */ +/* Ignore c-ares deprecation warnings */ +#undef CARES_NO_DEPRECATED + /* to enable curl debug memory tracking */ #undef CURLDEBUG /* Location of default ca bundle */ #undef CURL_CA_BUNDLE -/* define "1" to use built in CA store of SSL library */ +/* define "1" to use built-in CA store of SSL library */ #undef CURL_CA_FALLBACK /* Location of default ca path */ #undef CURL_CA_PATH +/* If safe CA bundle search is enabled */ +#undef CURL_CA_SEARCH_SAFE + /* Default SSL backend */ #undef CURL_DEFAULT_SSL_BACKEND @@ -30,6 +36,9 @@ /* disable local binding support */ #undef CURL_DISABLE_BINDLOCAL +/* If unsafe CA bundle search in PATH on Windows is disabled */ +#undef CURL_DISABLE_CA_SEARCH + /* to disable cookies support */ #undef CURL_DISABLE_COOKIES @@ -72,6 +81,9 @@ /* to disable IMAP */ #undef CURL_DISABLE_IMAP +/* to disable IPFS */ +#undef CURL_DISABLE_IPFS + /* to disable kerberos authentication */ #undef CURL_DISABLE_KERBEROS_AUTH @@ -117,6 +129,9 @@ /* to disable RTSP */ #undef CURL_DISABLE_RTSP +/* disable SHA-512/256 hash algorithm */ +#undef CURL_DISABLE_SHA512_256 + /* disable DNS shuffling */ #undef CURL_DISABLE_SHUFFLE_DNS @@ -138,9 +153,15 @@ /* to disable verbose strings */ #undef CURL_DISABLE_VERBOSE_STRINGS +/* disable WebSockets */ +#undef CURL_DISABLE_WEBSOCKETS + /* Definition to make a library symbol externally visible. */ #undef CURL_EXTERN_SYMBOL +/* cpu-machine-OS */ +#undef CURL_OS + /* IP address type in sockaddr */ #undef CURL_SA_FAMILY_T @@ -150,9 +171,6 @@ /* enable debug build options */ #undef DEBUGBUILD -/* Define if you want to enable IPv6 support */ -#undef ENABLE_IPV6 - /* Define to the type of arg 2 for gethostname. */ #undef GETHOSTNAME_TYPE_ARG2 @@ -165,24 +183,15 @@ /* Define to 1 if you have the header file. */ #undef HAVE_ARPA_INET_H -/* Define to 1 if you have the header file. */ -#undef HAVE_ARPA_TFTP_H - /* Define to 1 if you have _Atomic support. */ #undef HAVE_ATOMIC -/* Define to 1 if using AWS-LC. */ -#undef HAVE_AWSLC - /* Define to 1 if you have the basename function. */ #undef HAVE_BASENAME /* Define to 1 if bool is an available type. */ #undef HAVE_BOOL_T -/* Define to 1 if using BoringSSL. */ -#undef HAVE_BORINGSSL - /* if BROTLI is in use */ #undef HAVE_BROTLI @@ -205,12 +214,12 @@ /* Define to 1 if you have the CloseSocket camel case function. */ #undef HAVE_CLOSESOCKET_CAMEL -/* Define to 1 if you have the connect function. */ -#undef HAVE_CONNECT - /* Define to 1 if you have the header file. */ #undef HAVE_CRYPTO_H +/* Define to 1 if you have the fseeko declaration */ +#undef HAVE_DECL_FSEEKO + /* Define to 1 if you have the declaration of `getpwuid_r', and to 0 if you don't. */ #undef HAVE_DECL_GETPWUID_R @@ -218,14 +227,17 @@ /* "Set if getpwuid_r() declaration is missing" */ #undef HAVE_DECL_GETPWUID_R_MISSING +/* if you have */ +#undef HAVE_DIRENT_H + /* Define to 1 if you have the header file. */ #undef HAVE_DLFCN_H /* Define to 1 if you have the header file. */ #undef HAVE_ERR_H -/* Define to 1 if you have the `fchmod' function. */ -#undef HAVE_FCHMOD +/* Define to 1 if you have the `eventfd' function. */ +#undef HAVE_EVENTFD /* Define to 1 if you have the fcntl function. */ #undef HAVE_FCNTL @@ -239,12 +251,12 @@ /* Define to 1 if you have the `fnmatch' function. */ #undef HAVE_FNMATCH -/* Define to 1 if you have the `fork' function. */ -#undef HAVE_FORK - /* Define to 1 if you have the freeaddrinfo function. */ #undef HAVE_FREEADDRINFO +/* Define to 1 if you have the `fseeko' function. */ +#undef HAVE_FSEEKO + /* Define to 1 if you have the fsetxattr function. */ #undef HAVE_FSETXATTR @@ -335,12 +347,6 @@ /* if you have GNU GSS */ #undef HAVE_GSSGNU -/* if you have Heimdal */ -#undef HAVE_GSSHEIMDAL - -/* if you have MIT Kerberos */ -#undef HAVE_GSSMIT - /* Define to 1 if you have the header file. */ #undef HAVE_HYPER_H @@ -362,6 +368,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H +/* Define to 1 if you have the ioctl function. */ +#undef HAVE_IOCTL + /* Define to 1 if you have the ioctlsocket function. */ #undef HAVE_IOCTLSOCKET @@ -375,6 +384,12 @@ /* Define to 1 if you have a working ioctlsocket FIONBIO function. */ #undef HAVE_IOCTLSOCKET_FIONBIO +/* Define to 1 if you have a working ioctl FIONBIO function. */ +#undef HAVE_IOCTL_FIONBIO + +/* Define to 1 if you have a working ioctl SIOCGIFADDR function. */ +#undef HAVE_IOCTL_SIOCGIFADDR + /* Define to 1 if you have the header file. */ #undef HAVE_IO_H @@ -405,7 +420,10 @@ /* Define to 1 if you have the `idn2' library (-lidn2). */ #undef HAVE_LIBIDN2 -/* Define to 1 if using libressl. */ +/* Define to 1 if you have the header file. */ +#undef HAVE_LIBPSL_H + +/* Define to 1 if using LibreSSL. */ #undef HAVE_LIBRESSL /* Define to 1 if you have the header file. */ @@ -484,8 +502,8 @@ */ #undef HAVE_OLD_GSSMIT -/* Define to 1 if using OpenSSL 3 or later. */ -#undef HAVE_OPENSSL3 +/* if you have opendir */ +#undef HAVE_OPENDIR /* Define to 1 if you have the header file. */ #undef HAVE_OPENSSL_CRYPTO_H @@ -515,8 +533,8 @@ /* Define to 1 if you have the `pipe' function. */ #undef HAVE_PIPE -/* If you have a fine poll */ -#undef HAVE_POLL_FINE +/* Define to 1 if you have the `poll' function. */ +#undef HAVE_POLL /* Define to 1 if you have the header file. */ #undef HAVE_POLL_H @@ -554,6 +572,9 @@ /* Define to 1 if you have the send function. */ #undef HAVE_SEND +/* Define to 1 if you have the `sendmmsg' function. */ +#undef HAVE_SENDMMSG + /* Define to 1 if you have the `sendmsg' function. */ #undef HAVE_SENDMSG @@ -578,9 +599,6 @@ /* Define to 1 if you have the signal function. */ #undef HAVE_SIGNAL -/* Define to 1 if you have the header file. */ -#undef HAVE_SIGNAL_H - /* Define to 1 if you have the sigsetjmp function or macro. */ #undef HAVE_SIGSETJMP @@ -596,11 +614,8 @@ /* Define to 1 if you have the socketpair function. */ #undef HAVE_SOCKETPAIR -/* Define to 1 if you have the header file. */ -#undef HAVE_SOCKET_H - -/* Define to 1 if you have the `SSL_get_ech_status' function. */ -#undef HAVE_SSL_GET_ECH_STATUS +/* Define to 1 if you have the `SSL_ech_set1_echconfig' function. */ +#undef HAVE_SSL_ECH_SET1_ECHCONFIG /* Define to 1 if you have the header file. */ #undef HAVE_SSL_H @@ -608,6 +623,13 @@ /* Define to 1 if you have the `SSL_set0_wbio' function. */ #undef HAVE_SSL_SET0_WBIO +/* Define to 1 if you have the `SSL_set1_ech_config_list' function. */ +#undef HAVE_SSL_SET1_ECH_CONFIG_LIST + +/* Define to 1 if you have the `SSL_set_quic_use_legacy_codepoint' function. + */ +#undef HAVE_SSL_SET_QUIC_USE_LEGACY_CODEPOINT + /* Define to 1 if you have the header file. */ #undef HAVE_STDATOMIC_H @@ -644,6 +666,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H +/* Define to 1 if you have the header file. */ +#undef HAVE_STROPTS_H + /* Define to 1 if you have the strtok_r function. */ #undef HAVE_STRTOK_R @@ -659,6 +684,9 @@ /* Define to 1 if suseconds_t is an available type. */ #undef HAVE_SUSECONDS_T +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_EVENTFD_H + /* Define to 1 if you have the header file. */ #undef HAVE_SYS_FILIO_H @@ -692,9 +720,6 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H -/* Define to 1 if you have the header file. */ -#undef HAVE_SYS_UIO_H - /* Define to 1 if you have the header file. */ #undef HAVE_SYS_UN_H @@ -716,6 +741,9 @@ /* Define this if time_t is unsigned */ #undef HAVE_TIME_T_UNSIGNED +/* Define to 1 if you have the header file. */ +#undef HAVE_UNICODE_UIDNA_H + /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H @@ -728,21 +756,18 @@ /* Define to 1 if you have the header file. */ #undef HAVE_UTIME_H -/* Define to 1 if compiler supports C99 variadic macro style. */ -#undef HAVE_VARIADIC_MACROS_C99 - -/* Define to 1 if compiler supports old gcc variadic macro style. */ -#undef HAVE_VARIADIC_MACROS_GCC - -/* Define to 1 if you have the windows.h header file. */ -#undef HAVE_WINDOWS_H - -/* Define to 1 if you have the winsock2.h header file. */ -#undef HAVE_WINSOCK2_H +/* Define to 1 if you have the header file. */ +#undef HAVE_UV_H /* Define to 1 if you have the header file. */ #undef HAVE_WOLFSSH_SSH_H +/* if you have wolfSSL_BIO_new */ +#undef HAVE_WOLFSSL_BIO + +/* Define to 1 if you have the `wolfSSL_CTX_GenerateEchConfig' function. */ +#undef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG + /* if you have wolfSSL_DES_ecb_encrypt */ #undef HAVE_WOLFSSL_DES_ECB_ENCRYPT @@ -758,9 +783,6 @@ /* Define this symbol if your OS supports changing the contents of argv */ #undef HAVE_WRITABLE_ARGV -/* Define to 1 if you have the ws2tcpip.h header file. */ -#undef HAVE_WS2TCPIP_H - /* Define to 1 if you have the header file. */ #undef HAVE_X509_H @@ -770,6 +792,12 @@ /* Define to 1 if you have the header file. */ #undef HAVE_ZSTD_H +/* Define to 1 if you have the `_fseeki64' function. */ +#undef HAVE__FSEEKI64 + +/* Define to 1 if you have the `_setmode' function. */ +#undef HAVE__SETMODE + /* Define to the sub-directory where libtool stores uninstalled libraries. */ #undef LT_OBJDIR @@ -782,15 +810,6 @@ /* Define to 1 if _THREAD_SAFE preprocessor symbol must be defined. */ #undef NEED_THREAD_SAFE -/* Define to enable NTLM delegation to winbind's ntlm_auth helper. */ -#undef NTLM_WB_ENABLED - -/* Define absolute filename for winbind's ntlm_auth helper. */ -#undef NTLM_WB_FILE - -/* cpu-machine-OS */ -#undef OS - /* Name of package */ #undef PACKAGE @@ -812,9 +831,6 @@ /* Define to the version of this package. */ #undef PACKAGE_VERSION -/* a suitable file to read random data from */ -#undef RANDOM_FILE - /* Size of curl_off_t in number of bytes */ #undef SIZEOF_CURL_OFF_T @@ -847,6 +863,9 @@ /* if AmiSSL is in use */ #undef USE_AMISSL +/* if AppleIDN */ +#undef USE_APPLE_IDN + /* Define to enable c-ares support */ #undef USE_ARES @@ -862,23 +881,29 @@ /* GSASL support enabled */ #undef USE_GSASL +/* force HTTPS RR support for ECH */ +#undef USE_HTTPSRR + /* if hyper is in use */ #undef USE_HYPER -/* PSL support enabled */ +/* Define if you want to enable IPv6 support */ +#undef USE_IPV6 + +/* if libpsl is in use */ #undef USE_LIBPSL /* if librtmp is in use */ #undef USE_LIBRTMP -/* if libSSH is in use */ +/* if libssh is in use */ #undef USE_LIBSSH -/* if libSSH2 is in use */ +/* if libssh2 is in use */ #undef USE_LIBSSH2 -/* If you want to build curl with the built-in manual */ -#undef USE_MANUAL +/* if libuv is in use */ +#undef USE_LIBUV /* if mbedTLS is enabled */ #undef USE_MBEDTLS @@ -895,6 +920,9 @@ /* if ngtcp2 is in use */ #undef USE_NGTCP2 +/* if ngtcp2_crypto_boringssl is in use */ +#undef USE_NGTCP2_CRYPTO_BORINGSSL + /* if ngtcp2_crypto_gnutls is in use */ #undef USE_NGTCP2_CRYPTO_GNUTLS @@ -904,16 +932,25 @@ /* if ngtcp2_crypto_wolfssl is in use */ #undef USE_NGTCP2_CRYPTO_WOLFSSL +/* if ngtcp2 + nghttp3 is in use */ +#undef USE_NGTCP2_H3 + /* Use OpenLDAP-specific code */ #undef USE_OPENLDAP /* if OpenSSL is in use */ #undef USE_OPENSSL +/* if openssl quic + nghttp3 is in use */ +#undef USE_OPENSSL_H3 + +/* if openssl QUIC is in use */ +#undef USE_OPENSSL_QUIC + /* if quiche is in use */ #undef USE_QUICHE -/* if rustls is enabled */ +/* if Rustls is enabled */ #undef USE_RUSTLS /* to enable Windows native SSL/TLS support */ @@ -934,9 +971,6 @@ /* Use Unix domain sockets */ #undef USE_UNIX_SOCKETS -/* enable websockets support */ -#undef USE_WEBSOCKETS - /* Define to 1 if you are building a Windows target with crypto API support. */ #undef USE_WIN32_CRYPTO @@ -984,12 +1018,6 @@ /* Type to use in place of in_addr_t when system does not provide it. */ #undef in_addr_t -/* Define to `__inline__' or `__inline' if that's what the C compiler - calls it, or to nothing if 'inline' is not supported under any name. */ -#ifndef __cplusplus -#undef inline -#endif - /* Define to `unsigned int' if does not define. */ #undef size_t diff --git a/deps/curl/lib/curl_ctype.h b/deps/curl/lib/curl_ctype.h index 1d1d60c28d1..7f0d0cc2916 100644 --- a/deps/curl/lib/curl_ctype.h +++ b/deps/curl/lib/curl_ctype.h @@ -43,5 +43,9 @@ #define ISDIGIT(x) (((x) >= '0') && ((x) <= '9')) #define ISBLANK(x) (((x) == ' ') || ((x) == '\t')) #define ISSPACE(x) (ISBLANK(x) || (((x) >= 0xa) && ((x) <= 0x0d))) +#define ISURLPUNTCS(x) (((x) == '-') || ((x) == '.') || ((x) == '_') || \ + ((x) == '~')) +#define ISUNRESERVED(x) (ISALNUM(x) || ISURLPUNTCS(x)) + #endif /* HEADER_CURL_CTYPE_H */ diff --git a/deps/curl/lib/curl_des.c b/deps/curl/lib/curl_des.c index b77763f2684..15836f58b97 100644 --- a/deps/curl/lib/curl_des.c +++ b/deps/curl/lib/curl_des.c @@ -24,10 +24,10 @@ #include "curl_setup.h" -#if defined(USE_CURL_NTLM_CORE) && !defined(USE_WOLFSSL) && \ - (defined(USE_GNUTLS) || \ - defined(USE_SECTRANSP) || \ - defined(USE_OS400CRYPTO) || \ +#if defined(USE_CURL_NTLM_CORE) && \ + (defined(USE_GNUTLS) || \ + defined(USE_SECTRANSP) || \ + defined(USE_OS400CRYPTO) || \ defined(USE_WIN32_CRYPTO)) #include "curl_des.h" @@ -36,7 +36,7 @@ * Curl_des_set_odd_parity() * * This is used to apply odd parity to the given byte array. It is typically - * used by when a cryptography engines doesn't have it's own version. + * used by when a cryptography engine does not have its own version. * * The function is a port of the Java based oddParity() function over at: * diff --git a/deps/curl/lib/curl_des.h b/deps/curl/lib/curl_des.h index 66525ab4363..2dd498da245 100644 --- a/deps/curl/lib/curl_des.h +++ b/deps/curl/lib/curl_des.h @@ -26,10 +26,10 @@ #include "curl_setup.h" -#if defined(USE_CURL_NTLM_CORE) && !defined(USE_WOLFSSL) && \ - (defined(USE_GNUTLS) || \ - defined(USE_SECTRANSP) || \ - defined(USE_OS400CRYPTO) || \ +#if defined(USE_CURL_NTLM_CORE) && \ + (defined(USE_GNUTLS) || \ + defined(USE_SECTRANSP) || \ + defined(USE_OS400CRYPTO) || \ defined(USE_WIN32_CRYPTO)) /* Applies odd parity to the given byte array */ diff --git a/deps/curl/lib/curl_endian.c b/deps/curl/lib/curl_endian.c index 11c662a4c7e..d982e31269f 100644 --- a/deps/curl/lib/curl_endian.c +++ b/deps/curl/lib/curl_endian.c @@ -30,7 +30,7 @@ * Curl_read16_le() * * This function converts a 16-bit integer from the little endian format, as - * used in the incoming package to whatever endian format we're using + * used in the incoming package to whatever endian format we are using * natively. * * Parameters: @@ -49,7 +49,7 @@ unsigned short Curl_read16_le(const unsigned char *buf) * Curl_read32_le() * * This function converts a 32-bit integer from the little endian format, as - * used in the incoming package to whatever endian format we're using + * used in the incoming package to whatever endian format we are using * natively. * * Parameters: @@ -68,7 +68,7 @@ unsigned int Curl_read32_le(const unsigned char *buf) * Curl_read16_be() * * This function converts a 16-bit integer from the big endian format, as - * used in the incoming package to whatever endian format we're using + * used in the incoming package to whatever endian format we are using * natively. * * Parameters: diff --git a/deps/curl/lib/curl_fnmatch.c b/deps/curl/lib/curl_fnmatch.c index 5f9ca4f1be3..ffac8048f67 100644 --- a/deps/curl/lib/curl_fnmatch.c +++ b/deps/curl/lib/curl_fnmatch.c @@ -80,7 +80,7 @@ static int parsekeyword(unsigned char **pattern, unsigned char *charset) unsigned char *p = *pattern; bool found = FALSE; for(i = 0; !found; i++) { - char c = *p++; + char c = (char)*p++; if(i >= KEYLEN) return SETCHARSET_FAIL; switch(state) { @@ -161,7 +161,7 @@ static void setcharorrange(unsigned char **pp, unsigned char *charset) } } -/* returns 1 (true) if pattern is OK, 0 if is bad ("p" is pattern pointer) */ +/* returns 1 (TRUE) if pattern is OK, 0 if is bad ("p" is pattern pointer) */ static int setcharset(unsigned char **p, unsigned char *charset) { setcharset_state state = CURLFNM_SCHS_DEFAULT; @@ -293,7 +293,7 @@ static int loop(const unsigned char *pattern, const unsigned char *string, p++; break; case '\0': - return *s? CURL_FNMATCH_NOMATCH: CURL_FNMATCH_MATCH; + return *s ? CURL_FNMATCH_NOMATCH : CURL_FNMATCH_MATCH; case '\\': if(p[1]) p++; @@ -303,7 +303,7 @@ static int loop(const unsigned char *pattern, const unsigned char *string, case '[': pp = p + 1; /* Copy in case of syntax error in set. */ if(setcharset(&pp, charset)) { - int found = FALSE; + bool found = FALSE; if(!*s) return CURL_FNMATCH_NOMATCH; if(charset[(unsigned int)*s]) diff --git a/deps/curl/lib/curl_fnmatch.h b/deps/curl/lib/curl_fnmatch.h index 595646ff0d2..b8c2a4353c6 100644 --- a/deps/curl/lib/curl_fnmatch.h +++ b/deps/curl/lib/curl_fnmatch.h @@ -31,7 +31,7 @@ /* default pattern matching function * ================================= * Implemented with recursive backtracking, if you want to use Curl_fnmatch, - * please note that there is not implemented UTF/UNICODE support. + * please note that there is not implemented UTF/Unicode support. * * Implemented features: * '?' notation, does not match UTF characters diff --git a/deps/curl/lib/curl_get_line.c b/deps/curl/lib/curl_get_line.c index 686abe7511b..100207331d8 100644 --- a/deps/curl/lib/curl_get_line.c +++ b/deps/curl/lib/curl_get_line.c @@ -33,14 +33,16 @@ #include "memdebug.h" /* - * Curl_get_line() makes sure to only return complete whole lines that fit in - * 'len' bytes and end with a newline. + * Curl_get_line() makes sure to only return complete whole lines that end + * newlines. */ -char *Curl_get_line(char *buf, int len, FILE *input) +int Curl_get_line(struct dynbuf *buf, FILE *input) { - bool partial = FALSE; + CURLcode result; + char buffer[128]; + Curl_dyn_reset(buf); while(1) { - char *b = fgets(buf, len, input); + char *b = fgets(buffer, sizeof(buffer), input); if(b) { size_t rlen = strlen(b); @@ -48,39 +50,28 @@ char *Curl_get_line(char *buf, int len, FILE *input) if(!rlen) break; - if(b[rlen-1] == '\n') { - /* b is \n terminated */ - if(partial) { - partial = FALSE; - continue; - } - return b; - } - else if(feof(input)) { - if(partial) - /* Line is already too large to return, ignore rest */ - break; + result = Curl_dyn_addn(buf, b, rlen); + if(result) + /* too long line or out of memory */ + return 0; /* error */ - if(rlen + 1 < (size_t) len) { - /* b is EOF terminated, insert missing \n */ - b[rlen] = '\n'; - b[rlen + 1] = '\0'; - return b; - } - else - /* Maximum buffersize reached + EOF - * This line is impossible to add a \n to so we'll ignore it - */ - break; + else if(b[rlen-1] == '\n') + /* end of the line */ + return 1; /* all good */ + + else if(feof(input)) { + /* append a newline */ + result = Curl_dyn_addn(buf, "\n", 1); + if(result) + /* too long line or out of memory */ + return 0; /* error */ + return 1; /* all good */ } - else - /* Maximum buffersize reached */ - partial = TRUE; } else break; } - return NULL; + return 0; } #endif /* if not disabled */ diff --git a/deps/curl/lib/curl_get_line.h b/deps/curl/lib/curl_get_line.h index 0ff32c5c2cb..7907cde8808 100644 --- a/deps/curl/lib/curl_get_line.h +++ b/deps/curl/lib/curl_get_line.h @@ -24,8 +24,9 @@ * ***************************************************************************/ -/* get_line() makes sure to only return complete whole lines that fit in 'len' - * bytes and end with a newline. */ -char *Curl_get_line(char *buf, int len, FILE *input); +#include "dynbuf.h" + +/* Curl_get_line() returns complete lines that end with a newline. */ +int Curl_get_line(struct dynbuf *buf, FILE *input); #endif /* HEADER_CURL_GET_LINE_H */ diff --git a/deps/curl/lib/curl_gethostname.c b/deps/curl/lib/curl_gethostname.c index 706b2e68928..617a8ad52f6 100644 --- a/deps/curl/lib/curl_gethostname.c +++ b/deps/curl/lib/curl_gethostname.c @@ -28,26 +28,17 @@ /* * Curl_gethostname() is a wrapper around gethostname() which allows - * overriding the host name that the function would normally return. + * overriding the hostname that the function would normally return. * This capability is used by the test suite to verify exact matching * of NTLM authentication, which exercises libcurl's MD4 and DES code * as well as by the SMTP module when a hostname is not provided. * - * For libcurl debug enabled builds host name overriding takes place + * For libcurl debug enabled builds hostname overriding takes place * when environment variable CURL_GETHOSTNAME is set, using the value - * held by the variable to override returned host name. + * held by the variable to override returned hostname. * * Note: The function always returns the un-qualified hostname rather * than being provider dependent. - * - * For libcurl shared library release builds the test suite preloads - * another shared library named libhostname using the LD_PRELOAD - * mechanism which intercepts, and might override, the gethostname() - * function call. In this case a given platform must support the - * LD_PRELOAD mechanism and additionally have environment variable - * CURL_GETHOSTNAME set in order to override the returned host name. - * - * For libcurl static library release builds no overriding takes place. */ int Curl_gethostname(char * const name, GETHOSTNAME_TYPE_ARG2 namelen) @@ -65,10 +56,13 @@ int Curl_gethostname(char * const name, GETHOSTNAME_TYPE_ARG2 namelen) #ifdef DEBUGBUILD - /* Override host name when environment variable CURL_GETHOSTNAME is set */ + /* Override hostname when environment variable CURL_GETHOSTNAME is set */ const char *force_hostname = getenv("CURL_GETHOSTNAME"); if(force_hostname) { - strncpy(name, force_hostname, namelen); + if(strlen(force_hostname) < (size_t)namelen) + strcpy(name, force_hostname); + else + return 1; /* can't do it */ err = 0; } else { @@ -78,9 +72,6 @@ int Curl_gethostname(char * const name, GETHOSTNAME_TYPE_ARG2 namelen) #else /* DEBUGBUILD */ - /* The call to system's gethostname() might get intercepted by the - libhostname library when libcurl is built as a non-debug shared - library when running the test suite. */ name[0] = '\0'; err = gethostname(name, namelen); diff --git a/deps/curl/lib/curl_gssapi.c b/deps/curl/lib/curl_gssapi.c index c6fe1256b2c..b7e774cea01 100644 --- a/deps/curl/lib/curl_gssapi.c +++ b/deps/curl/lib/curl_gssapi.c @@ -35,7 +35,7 @@ #include "memdebug.h" #if defined(__GNUC__) -#define CURL_ALIGN8 __attribute__ ((aligned(8))) +#define CURL_ALIGN8 __attribute__((aligned(8))) #else #define CURL_ALIGN8 #endif diff --git a/deps/curl/lib/curl_hmac.h b/deps/curl/lib/curl_hmac.h index 9438ca782a7..ed5035ca632 100644 --- a/deps/curl/lib/curl_hmac.h +++ b/deps/curl/lib/curl_hmac.h @@ -24,37 +24,36 @@ * ***************************************************************************/ -#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) \ - || !defined(CURL_DISABLE_AWS) +#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) \ + || !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) \ + || defined(USE_LIBSSH2) #include #define HMAC_MD5_LENGTH 16 -typedef CURLcode (* HMAC_hinit_func)(void *context); -typedef void (* HMAC_hupdate_func)(void *context, - const unsigned char *data, - unsigned int len); -typedef void (* HMAC_hfinal_func)(unsigned char *result, void *context); - +typedef CURLcode (*HMAC_hinit)(void *context); +typedef void (*HMAC_hupdate)(void *context, + const unsigned char *data, + unsigned int len); +typedef void (*HMAC_hfinal)(unsigned char *result, void *context); /* Per-hash function HMAC parameters. */ struct HMAC_params { - HMAC_hinit_func - hmac_hinit; /* Initialize context procedure. */ - HMAC_hupdate_func hmac_hupdate; /* Update context with data. */ - HMAC_hfinal_func hmac_hfinal; /* Get final result procedure. */ - unsigned int hmac_ctxtsize; /* Context structure size. */ - unsigned int hmac_maxkeylen; /* Maximum key length (bytes). */ - unsigned int hmac_resultlen; /* Result length (bytes). */ + HMAC_hinit hinit; /* Initialize context procedure. */ + HMAC_hupdate hupdate; /* Update context with data. */ + HMAC_hfinal hfinal; /* Get final result procedure. */ + unsigned int ctxtsize; /* Context structure size. */ + unsigned int maxkeylen; /* Maximum key length (bytes). */ + unsigned int resultlen; /* Result length (bytes). */ }; /* HMAC computation context. */ struct HMAC_context { - const struct HMAC_params *hmac_hash; /* Hash function definition. */ - void *hmac_hashctxt1; /* Hash function context 1. */ - void *hmac_hashctxt2; /* Hash function context 2. */ + const struct HMAC_params *hash; /* Hash function definition. */ + void *hashctxt1; /* Hash function context 1. */ + void *hashctxt2; /* Hash function context 2. */ }; diff --git a/deps/curl/lib/curl_md5.h b/deps/curl/lib/curl_md5.h index 61671c306a6..ec27503b144 100644 --- a/deps/curl/lib/curl_md5.h +++ b/deps/curl/lib/curl_md5.h @@ -31,11 +31,11 @@ #define MD5_DIGEST_LEN 16 -typedef CURLcode (* Curl_MD5_init_func)(void *context); -typedef void (* Curl_MD5_update_func)(void *context, - const unsigned char *data, - unsigned int len); -typedef void (* Curl_MD5_final_func)(unsigned char *result, void *context); +typedef CURLcode (*Curl_MD5_init_func)(void *context); +typedef void (*Curl_MD5_update_func)(void *context, + const unsigned char *data, + unsigned int len); +typedef void (*Curl_MD5_final_func)(unsigned char *result, void *context); struct MD5_params { Curl_MD5_init_func md5_init_func; /* Initialize context procedure */ @@ -50,8 +50,8 @@ struct MD5_context { void *md5_hashctx; /* Hash function context */ }; -extern const struct MD5_params Curl_DIGEST_MD5[1]; -extern const struct HMAC_params Curl_HMAC_MD5[1]; +extern const struct MD5_params Curl_DIGEST_MD5; +extern const struct HMAC_params Curl_HMAC_MD5; CURLcode Curl_md5it(unsigned char *output, const unsigned char *input, const size_t len); diff --git a/deps/curl/lib/curl_memory.h b/deps/curl/lib/curl_memory.h index b8c46d79395..7f110dab7de 100644 --- a/deps/curl/lib/curl_memory.h +++ b/deps/curl/lib/curl_memory.h @@ -68,7 +68,7 @@ #undef send #undef recv -#ifdef WIN32 +#ifdef _WIN32 # ifdef UNICODE # undef wcsdup # undef _wcsdup @@ -84,6 +84,7 @@ #undef socketpair #endif +#ifndef CURL_NO_GETADDRINFO_OVERRIDE #ifdef HAVE_GETADDRINFO #if defined(getaddrinfo) && defined(__osf__) #undef ogetaddrinfo @@ -95,6 +96,7 @@ #ifdef HAVE_FREEADDRINFO #undef freeaddrinfo #endif /* HAVE_FREEADDRINFO */ +#endif /* !CURL_NO_GETADDRINFO_OVERRIDE */ /* sclose is probably already defined, redefine it! */ #undef sclose @@ -134,7 +136,7 @@ extern curl_free_callback Curl_cfree; extern curl_realloc_callback Curl_crealloc; extern curl_strdup_callback Curl_cstrdup; extern curl_calloc_callback Curl_ccalloc; -#if defined(WIN32) && defined(UNICODE) +#if defined(_WIN32) && defined(UNICODE) extern curl_wcsdup_callback Curl_cwcsdup; #endif @@ -160,7 +162,7 @@ extern curl_wcsdup_callback Curl_cwcsdup; #undef free #define free(ptr) Curl_cfree(ptr) -#ifdef WIN32 +#ifdef _WIN32 # ifdef UNICODE # undef wcsdup # define wcsdup(ptr) Curl_cwcsdup(ptr) diff --git a/deps/curl/lib/curl_memrchr.c b/deps/curl/lib/curl_memrchr.c index 3f3dc6de163..c6d55f10423 100644 --- a/deps/curl/lib/curl_memrchr.c +++ b/deps/curl/lib/curl_memrchr.c @@ -33,6 +33,9 @@ #include "memdebug.h" #ifndef HAVE_MEMRCHR +#if (!defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)) || \ + defined(USE_OPENSSL) || \ + defined(USE_SCHANNEL) /* * Curl_memrchr() @@ -61,4 +64,5 @@ Curl_memrchr(const void *s, int c, size_t n) return NULL; } +#endif #endif /* HAVE_MEMRCHR */ diff --git a/deps/curl/lib/curl_memrchr.h b/deps/curl/lib/curl_memrchr.h index a1a4ba09273..67a21ef3617 100644 --- a/deps/curl/lib/curl_memrchr.h +++ b/deps/curl/lib/curl_memrchr.h @@ -28,19 +28,21 @@ #ifdef HAVE_MEMRCHR -#ifdef HAVE_STRING_H -# include -#endif +#include #ifdef HAVE_STRINGS_H # include #endif #else /* HAVE_MEMRCHR */ +#if (!defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)) || \ + defined(USE_OPENSSL) || \ + defined(USE_SCHANNEL) void *Curl_memrchr(const void *s, int c, size_t n); #define memrchr(x,y,z) Curl_memrchr((x),(y),(z)) +#endif #endif /* HAVE_MEMRCHR */ #endif /* HEADER_CURL_MEMRCHR_H */ diff --git a/deps/curl/lib/curl_multibyte.c b/deps/curl/lib/curl_multibyte.c index 522ea34e827..86ac74ff4b0 100644 --- a/deps/curl/lib/curl_multibyte.c +++ b/deps/curl/lib/curl_multibyte.c @@ -32,7 +32,7 @@ #include "curl_setup.h" -#if defined(WIN32) +#if defined(_WIN32) #include "curl_multibyte.h" @@ -84,7 +84,7 @@ char *curlx_convert_wchar_to_UTF8(const wchar_t *str_w) return str_utf8; } -#endif /* WIN32 */ +#endif /* _WIN32 */ #if defined(USE_WIN32_LARGE_FILES) || defined(USE_WIN32_SMALL_FILES) @@ -159,21 +159,4 @@ int curlx_win32_stat(const char *path, struct_stat *buffer) #endif } -int curlx_win32_access(const char *path, int mode) -{ -#if defined(_UNICODE) - int result = -1; - wchar_t *path_w = curlx_convert_UTF8_to_wchar(path); - if(path_w) { - result = _waccess(path_w, mode); - curlx_unicodefree(path_w); - } - else - errno = EINVAL; - return result; -#else - return _access(path, mode); -#endif -} - #endif /* USE_WIN32_LARGE_FILES || USE_WIN32_SMALL_FILES */ diff --git a/deps/curl/lib/curl_multibyte.h b/deps/curl/lib/curl_multibyte.h index ddac1f63821..dec384e2fe2 100644 --- a/deps/curl/lib/curl_multibyte.h +++ b/deps/curl/lib/curl_multibyte.h @@ -25,7 +25,7 @@ ***************************************************************************/ #include "curl_setup.h" -#if defined(WIN32) +#if defined(_WIN32) /* * MultiByte conversions using Windows kernel32 library. @@ -33,28 +33,29 @@ wchar_t *curlx_convert_UTF8_to_wchar(const char *str_utf8); char *curlx_convert_wchar_to_UTF8(const wchar_t *str_w); -#endif /* WIN32 */ +#endif /* _WIN32 */ /* * Macros curlx_convert_UTF8_to_tchar(), curlx_convert_tchar_to_UTF8() * and curlx_unicodefree() main purpose is to minimize the number of * preprocessor conditional directives needed by code using these - * to differentiate UNICODE from non-UNICODE builds. + * to differentiate Unicode from non-Unicode builds. * - * In the case of a non-UNICODE build the tchar strings are char strings that + * In the case of a non-Unicode build the tchar strings are char strings that * are duplicated via strdup and remain in whatever the passed in encoding is, * which is assumed to be UTF-8 but may be other encoding. Therefore the - * significance of the conversion functions is primarily for UNICODE builds. + * significance of the conversion functions is primarily for Unicode builds. * * Allocated memory should be free'd with curlx_unicodefree(). * * Note: Because these are curlx functions their memory usage is not tracked - * by the curl memory tracker memdebug. You'll notice that curlx function-like - * macros call free and strdup in parentheses, eg (strdup)(ptr), and that's to - * ensure that the curl memdebug override macros do not replace them. + * by the curl memory tracker memdebug. you will notice that curlx + * function-like macros call free and strdup in parentheses, eg (strdup)(ptr), + * and that is to ensure that the curl memdebug override macros do not replace + * them. */ -#if defined(UNICODE) && defined(WIN32) +#if defined(UNICODE) && defined(_WIN32) #define curlx_convert_UTF8_to_tchar(ptr) curlx_convert_UTF8_to_wchar((ptr)) #define curlx_convert_tchar_to_UTF8(ptr) curlx_convert_wchar_to_UTF8((ptr)) @@ -78,7 +79,7 @@ typedef union { const unsigned char *const_tbyte_ptr; } xcharp_u; -#endif /* UNICODE && WIN32 */ +#endif /* UNICODE && _WIN32 */ #define curlx_unicodefree(ptr) \ do { \ diff --git a/deps/curl/lib/curl_ntlm_core.c b/deps/curl/lib/curl_ntlm_core.c index cc0ed91672a..3cc885e03bd 100644 --- a/deps/curl/lib/curl_ntlm_core.c +++ b/deps/curl/lib/curl_ntlm_core.c @@ -57,9 +57,14 @@ #if !defined(OPENSSL_NO_DES) && !defined(OPENSSL_NO_DEPRECATED_3_0) #define USE_OPENSSL_DES #endif +#elif defined(USE_WOLFSSL) + #include + #if !defined(NO_DES3) + #define USE_OPENSSL_DES + #endif #endif -#if defined(USE_OPENSSL_DES) || defined(USE_WOLFSSL) +#if defined(USE_OPENSSL_DES) #if defined(USE_OPENSSL) # include @@ -67,7 +72,6 @@ # include # include #else -# include # include # include # include @@ -110,7 +114,8 @@ #elif defined(USE_WIN32_CRYPTO) # include #else -# error "Can't compile NTLM support without a crypto library with DES." +# error "cannot compile NTLM support without a crypto library with DES." +# define CURL_NTLM_NOT_SUPPORTED #endif #include "urldata.h" @@ -130,24 +135,26 @@ #define NTLMv2_BLOB_SIGNATURE "\x01\x01\x00\x00" #define NTLMv2_BLOB_LEN (44 -16 + ntlm->target_info_len + 4) +#if !defined(CURL_NTLM_NOT_SUPPORTED) /* * Turns a 56-bit key into being 64-bit wide. */ static void extend_key_56_to_64(const unsigned char *key_56, char *key) { - key[0] = key_56[0]; - key[1] = (unsigned char)(((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1)); - key[2] = (unsigned char)(((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2)); - key[3] = (unsigned char)(((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3)); - key[4] = (unsigned char)(((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4)); - key[5] = (unsigned char)(((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5)); - key[6] = (unsigned char)(((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6)); - key[7] = (unsigned char) ((key_56[6] << 1) & 0xFF); + key[0] = (char)key_56[0]; + key[1] = (char)(((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1)); + key[2] = (char)(((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2)); + key[3] = (char)(((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3)); + key[4] = (char)(((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4)); + key[5] = (char)(((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5)); + key[6] = (char)(((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6)); + key[7] = (char) ((key_56[6] << 1) & 0xFF); } +#endif -#if defined(USE_OPENSSL_DES) || defined(USE_WOLFSSL) +#if defined(USE_OPENSSL_DES) /* - * Turns a 56 bit key into the 64 bit, odd parity key and sets the key. The + * Turns a 56-bit key into a 64-bit, odd parity key and sets the key. The * key schedule ks is also set. */ static void setup_des_key(const unsigned char *key_56, @@ -155,7 +162,7 @@ static void setup_des_key(const unsigned char *key_56, { DES_cblock key; - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, (char *) &key); /* Set the key parity to odd */ @@ -172,7 +179,7 @@ static void setup_des_key(const unsigned char *key_56, { char key[8]; - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, key); /* Set the key parity to odd */ @@ -190,7 +197,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, mbedtls_des_context ctx; char key[8]; - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, key); /* Set the key parity to odd */ @@ -211,7 +218,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, size_t out_len; CCCryptorStatus err; - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, key); /* Set the key parity to odd */ @@ -237,7 +244,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, ctl.Func_ID = ENCRYPT_ONLY; ctl.Data_Len = sizeof(key); - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, ctl.Crypto_Key); /* Set the key parity to odd */ @@ -275,7 +282,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, blob.hdr.aiKeyAlg = CALG_DES; blob.len = sizeof(blob.key); - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, blob.key); /* Set the key parity to odd */ @@ -310,7 +317,7 @@ void Curl_ntlm_core_lm_resp(const unsigned char *keys, const unsigned char *plaintext, unsigned char *results) { -#if defined(USE_OPENSSL_DES) || defined(USE_WOLFSSL) +#if defined(USE_OPENSSL_DES) DES_key_schedule ks; setup_des_key(keys, DESKEY(ks)); @@ -337,6 +344,10 @@ void Curl_ntlm_core_lm_resp(const unsigned char *keys, encrypt_des(plaintext, results, keys); encrypt_des(plaintext, results + 8, keys + 7); encrypt_des(plaintext, results + 16, keys + 14); +#else + (void)keys; + (void)plaintext; + (void)results; #endif } @@ -347,9 +358,11 @@ CURLcode Curl_ntlm_core_mk_lm_hash(const char *password, unsigned char *lmbuffer /* 21 bytes */) { unsigned char pw[14]; +#if !defined(CURL_NTLM_NOT_SUPPORTED) static const unsigned char magic[] = { 0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 /* i.e. KGS!@#$% */ }; +#endif size_t len = CURLMIN(strlen(password), 14); Curl_strntoupper((char *)pw, password, len); @@ -358,7 +371,7 @@ CURLcode Curl_ntlm_core_mk_lm_hash(const char *password, { /* Create LanManager hashed password. */ -#if defined(USE_OPENSSL_DES) || defined(USE_WOLFSSL) +#if defined(USE_OPENSSL_DES) DES_key_schedule ks; setup_des_key(pw, DESKEY(ks)); @@ -457,20 +470,20 @@ static void time2filetime(struct ms_filetime *ft, time_t t) unsigned int r, s; unsigned int i; - ft->dwLowDateTime = t & 0xFFFFFFFF; + ft->dwLowDateTime = (unsigned int)t & 0xFFFFFFFF; ft->dwHighDateTime = 0; # ifndef HAVE_TIME_T_UNSIGNED /* Extend sign if needed. */ if(ft->dwLowDateTime & 0x80000000) - ft->dwHighDateTime = ~0; + ft->dwHighDateTime = ~(unsigned int)0; # endif /* Bias seconds to Jan 1, 1601. 134774 days = 11644473600 seconds = 0x2B6109100 */ r = ft->dwLowDateTime; ft->dwLowDateTime = (ft->dwLowDateTime + 0xB6109100U) & 0xFFFFFFFF; - ft->dwHighDateTime += ft->dwLowDateTime < r? 0x03: 0x02; + ft->dwHighDateTime += ft->dwLowDateTime < r ? 0x03 : 0x02; /* Convert to tenths of microseconds. */ ft->dwHighDateTime *= 10000000; @@ -515,7 +528,7 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_hash(const char *user, size_t userlen, ascii_uppercase_to_unicode_le(identity, user, userlen); ascii_to_unicode_le(identity + (userlen << 1), domain, domlen); - result = Curl_hmacit(Curl_HMAC_MD5, ntlmhash, 16, identity, identity_len, + result = Curl_hmacit(&Curl_HMAC_MD5, ntlmhash, 16, identity, identity_len, ntlmv2hash); free(identity); @@ -525,13 +538,13 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_hash(const char *user, size_t userlen, /* * Curl_ntlm_core_mk_ntlmv2_resp() * - * This creates the NTLMv2 response as set in the ntlm type-3 message. + * This creates the NTLMv2 response as set in the NTLM type-3 message. * * Parameters: * - * ntlmv2hash [in] - The ntlmv2 hash (16 bytes) + * ntlmv2hash [in] - The NTLMv2 hash (16 bytes) * challenge_client [in] - The client nonce (8 bytes) - * ntlm [in] - The ntlm data struct being used to read TargetInfo + * ntlm [in] - The NTLM data struct being used to read TargetInfo and Server challenge received in the type-2 message * ntresp [out] - The address where a pointer to newly allocated * memory holding the NTLMv2 response. @@ -600,7 +613,7 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_resp(unsigned char *ntlmv2hash, /* Concatenate the Type 2 challenge with the BLOB and do HMAC MD5 */ memcpy(ptr + 8, &ntlm->nonce[0], 8); - result = Curl_hmacit(Curl_HMAC_MD5, ntlmv2hash, HMAC_MD5_LENGTH, ptr + 8, + result = Curl_hmacit(&Curl_HMAC_MD5, ntlmv2hash, HMAC_MD5_LENGTH, ptr + 8, NTLMv2_BLOB_LEN + 8, hmac_output); if(result) { free(ptr); @@ -620,11 +633,11 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_resp(unsigned char *ntlmv2hash, /* * Curl_ntlm_core_mk_lmv2_resp() * - * This creates the LMv2 response as used in the ntlm type-3 message. + * This creates the LMv2 response as used in the NTLM type-3 message. * * Parameters: * - * ntlmv2hash [in] - The ntlmv2 hash (16 bytes) + * ntlmv2hash [in] - The NTLMv2 hash (16 bytes) * challenge_client [in] - The client nonce (8 bytes) * challenge_client [in] - The server challenge (8 bytes) * lmresp [out] - The LMv2 response (24 bytes) @@ -643,12 +656,12 @@ CURLcode Curl_ntlm_core_mk_lmv2_resp(unsigned char *ntlmv2hash, memcpy(&data[0], challenge_server, 8); memcpy(&data[8], challenge_client, 8); - result = Curl_hmacit(Curl_HMAC_MD5, ntlmv2hash, 16, &data[0], 16, + result = Curl_hmacit(&Curl_HMAC_MD5, ntlmv2hash, 16, &data[0], 16, hmac_output); if(result) return result; - /* Concatenate the HMAC MD5 output with the client nonce */ + /* Concatenate the HMAC MD5 output with the client nonce */ memcpy(lmresp, hmac_output, 16); memcpy(lmresp + 16, challenge_client, 8); diff --git a/deps/curl/lib/curl_ntlm_core.h b/deps/curl/lib/curl_ntlm_core.h index 0c62ee052ab..e2e4b1bd43a 100644 --- a/deps/curl/lib/curl_ntlm_core.h +++ b/deps/curl/lib/curl_ntlm_core.h @@ -28,13 +28,6 @@ #if defined(USE_CURL_NTLM_CORE) -#if defined(USE_OPENSSL) -# include -#elif defined(USE_WOLFSSL) -# include -# include -#endif - /* Helpers to generate function byte arguments in little endian order */ #define SHORTPAIR(x) ((int)((x) & 0xff)), ((int)(((x) >> 8) & 0xff)) #define LONGQUARTET(x) ((int)((x) & 0xff)), ((int)(((x) >> 8) & 0xff)), \ diff --git a/deps/curl/lib/curl_ntlm_wb.c b/deps/curl/lib/curl_ntlm_wb.c deleted file mode 100644 index a10e2a1b090..00000000000 --- a/deps/curl/lib/curl_ntlm_wb.c +++ /dev/null @@ -1,500 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 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.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. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) - -/* - * NTLM details: - * - * https://davenport.sourceforge.net/ntlm.html - * https://www.innovation.ch/java/ntlm.html - */ - -#define DEBUG_ME 0 - -#ifdef HAVE_SYS_WAIT_H -#include -#endif -#ifdef HAVE_SIGNAL_H -#include -#endif -#ifdef HAVE_PWD_H -#include -#endif - -#include "urldata.h" -#include "sendf.h" -#include "select.h" -#include "vauth/ntlm.h" -#include "curl_ntlm_core.h" -#include "curl_ntlm_wb.h" -#include "url.h" -#include "strerror.h" -#include "strdup.h" -#include "strcase.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -#if DEBUG_ME -# define DEBUG_OUT(x) x -#else -# define DEBUG_OUT(x) Curl_nop_stmt -#endif - -/* Portable 'sclose_nolog' used only in child process instead of 'sclose' - to avoid fooling the socket leak detector */ -#if defined(HAVE_CLOSESOCKET) -# define sclose_nolog(x) closesocket((x)) -#elif defined(HAVE_CLOSESOCKET_CAMEL) -# define sclose_nolog(x) CloseSocket((x)) -#else -# define sclose_nolog(x) close((x)) -#endif - -static void ntlm_wb_cleanup(struct ntlmdata *ntlm) -{ - if(ntlm->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) { - sclose(ntlm->ntlm_auth_hlpr_socket); - ntlm->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; - } - - if(ntlm->ntlm_auth_hlpr_pid) { - int i; - for(i = 0; i < 4; i++) { - pid_t ret = waitpid(ntlm->ntlm_auth_hlpr_pid, NULL, WNOHANG); - if(ret == ntlm->ntlm_auth_hlpr_pid || errno == ECHILD) - break; - switch(i) { - case 0: - kill(ntlm->ntlm_auth_hlpr_pid, SIGTERM); - break; - case 1: - /* Give the process another moment to shut down cleanly before - bringing down the axe */ - Curl_wait_ms(1); - break; - case 2: - kill(ntlm->ntlm_auth_hlpr_pid, SIGKILL); - break; - case 3: - break; - } - } - ntlm->ntlm_auth_hlpr_pid = 0; - } - - Curl_safefree(ntlm->challenge); - Curl_safefree(ntlm->response); -} - -static CURLcode ntlm_wb_init(struct Curl_easy *data, struct ntlmdata *ntlm, - const char *userp) -{ - curl_socket_t sockfds[2]; - pid_t child_pid; - const char *username; - char *slash, *domain = NULL; - const char *ntlm_auth = NULL; - char *ntlm_auth_alloc = NULL; -#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) - struct passwd pw, *pw_res; - char pwbuf[1024]; -#endif - char buffer[STRERROR_LEN]; - -#if defined(CURL_DISABLE_VERBOSE_STRINGS) - (void) data; -#endif - - /* Return if communication with ntlm_auth already set up */ - if(ntlm->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD || - ntlm->ntlm_auth_hlpr_pid) - return CURLE_OK; - - username = userp; - /* The real ntlm_auth really doesn't like being invoked with an - empty username. It won't make inferences for itself, and expects - the client to do so (mostly because it's really designed for - servers like squid to use for auth, and client support is an - afterthought for it). So try hard to provide a suitable username - if we don't already have one. But if we can't, provide the - empty one anyway. Perhaps they have an implementation of the - ntlm_auth helper which *doesn't* need it so we might as well try */ - if(!username || !username[0]) { - username = getenv("NTLMUSER"); - if(!username || !username[0]) - username = getenv("LOGNAME"); - if(!username || !username[0]) - username = getenv("USER"); -#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) - if((!username || !username[0]) && - !getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) && - pw_res) { - username = pw.pw_name; - } -#endif - if(!username || !username[0]) - username = userp; - } - slash = strpbrk(username, "\\/"); - if(slash) { - domain = strdup(username); - if(!domain) - return CURLE_OUT_OF_MEMORY; - slash = domain + (slash - username); - *slash = '\0'; - username = username + (slash - domain) + 1; - } - - /* For testing purposes, when DEBUGBUILD is defined and environment - variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform - NTLM challenge/response which only accepts commands and output - strings pre-written in test case definitions */ -#ifdef DEBUGBUILD - ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE"); - if(ntlm_auth_alloc) - ntlm_auth = ntlm_auth_alloc; - else -#endif - ntlm_auth = NTLM_WB_FILE; - - if(access(ntlm_auth, X_OK) != 0) { - failf(data, "Could not access ntlm_auth: %s errno %d: %s", - ntlm_auth, errno, Curl_strerror(errno, buffer, sizeof(buffer))); - goto done; - } - - if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) { - failf(data, "Could not open socket pair. errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - goto done; - } - - child_pid = fork(); - if(child_pid == -1) { - sclose(sockfds[0]); - sclose(sockfds[1]); - failf(data, "Could not fork. errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - goto done; - } - else if(!child_pid) { - /* - * child process - */ - - /* Don't use sclose in the child since it fools the socket leak detector */ - sclose_nolog(sockfds[0]); - if(dup2(sockfds[1], STDIN_FILENO) == -1) { - failf(data, "Could not redirect child stdin. errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - exit(1); - } - - if(dup2(sockfds[1], STDOUT_FILENO) == -1) { - failf(data, "Could not redirect child stdout. errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - exit(1); - } - - if(domain) - execl(ntlm_auth, ntlm_auth, - "--helper-protocol", "ntlmssp-client-1", - "--use-cached-creds", - "--username", username, - "--domain", domain, - NULL); - else - execl(ntlm_auth, ntlm_auth, - "--helper-protocol", "ntlmssp-client-1", - "--use-cached-creds", - "--username", username, - NULL); - - sclose_nolog(sockfds[1]); - failf(data, "Could not execl(). errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - exit(1); - } - - sclose(sockfds[1]); - ntlm->ntlm_auth_hlpr_socket = sockfds[0]; - ntlm->ntlm_auth_hlpr_pid = child_pid; - free(domain); - free(ntlm_auth_alloc); - return CURLE_OK; - -done: - free(domain); - free(ntlm_auth_alloc); - return CURLE_REMOTE_ACCESS_DENIED; -} - -/* if larger than this, something is seriously wrong */ -#define MAX_NTLM_WB_RESPONSE 100000 - -static CURLcode ntlm_wb_response(struct Curl_easy *data, struct ntlmdata *ntlm, - const char *input, curlntlm state) -{ - size_t len_in = strlen(input), len_out = 0; - struct dynbuf b; - char *ptr = NULL; - unsigned char *buf = (unsigned char *)data->state.buffer; - Curl_dyn_init(&b, MAX_NTLM_WB_RESPONSE); - - while(len_in > 0) { - ssize_t written = swrite(ntlm->ntlm_auth_hlpr_socket, input, len_in); - if(written == -1) { - /* Interrupted by a signal, retry it */ - if(errno == EINTR) - continue; - /* write failed if other errors happen */ - goto done; - } - input += written; - len_in -= written; - } - /* Read one line */ - while(1) { - ssize_t size = - sread(ntlm->ntlm_auth_hlpr_socket, buf, data->set.buffer_size); - if(size == -1) { - if(errno == EINTR) - continue; - goto done; - } - else if(size == 0) - goto done; - - if(Curl_dyn_addn(&b, buf, size)) - goto done; - - len_out = Curl_dyn_len(&b); - ptr = Curl_dyn_ptr(&b); - if(len_out && ptr[len_out - 1] == '\n') { - ptr[len_out - 1] = '\0'; - break; /* done! */ - } - /* loop */ - } - - /* Samba/winbind installed but not configured */ - if(state == NTLMSTATE_TYPE1 && - len_out == 3 && - ptr[0] == 'P' && ptr[1] == 'W') - goto done; - /* invalid response */ - if(len_out < 4) - goto done; - if(state == NTLMSTATE_TYPE1 && - (ptr[0]!='Y' || ptr[1]!='R' || ptr[2]!=' ')) - goto done; - if(state == NTLMSTATE_TYPE2 && - (ptr[0]!='K' || ptr[1]!='K' || ptr[2]!=' ') && - (ptr[0]!='A' || ptr[1]!='F' || ptr[2]!=' ')) - goto done; - - ntlm->response = strdup(ptr + 3); - Curl_dyn_free(&b); - if(!ntlm->response) - return CURLE_OUT_OF_MEMORY; - return CURLE_OK; -done: - Curl_dyn_free(&b); - return CURLE_REMOTE_ACCESS_DENIED; -} - -CURLcode Curl_input_ntlm_wb(struct Curl_easy *data, - struct connectdata *conn, - bool proxy, - const char *header) -{ - struct ntlmdata *ntlm = proxy ? &conn->proxyntlm : &conn->ntlm; - curlntlm *state = proxy ? &conn->proxy_ntlm_state : &conn->http_ntlm_state; - - (void) data; /* In case it gets unused by nop log macros. */ - - if(!checkprefix("NTLM", header)) - return CURLE_BAD_CONTENT_ENCODING; - - header += strlen("NTLM"); - while(*header && ISSPACE(*header)) - header++; - - if(*header) { - ntlm->challenge = strdup(header); - if(!ntlm->challenge) - return CURLE_OUT_OF_MEMORY; - - *state = NTLMSTATE_TYPE2; /* We got a type-2 message */ - } - else { - if(*state == NTLMSTATE_LAST) { - infof(data, "NTLM auth restarted"); - Curl_http_auth_cleanup_ntlm_wb(conn); - } - else if(*state == NTLMSTATE_TYPE3) { - infof(data, "NTLM handshake rejected"); - Curl_http_auth_cleanup_ntlm_wb(conn); - *state = NTLMSTATE_NONE; - return CURLE_REMOTE_ACCESS_DENIED; - } - else if(*state >= NTLMSTATE_TYPE1) { - infof(data, "NTLM handshake failure (internal error)"); - return CURLE_REMOTE_ACCESS_DENIED; - } - - *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */ - } - - return CURLE_OK; -} - -/* - * This is for creating ntlm header output by delegating challenge/response - * to Samba's winbind daemon helper ntlm_auth. - */ -CURLcode Curl_output_ntlm_wb(struct Curl_easy *data, struct connectdata *conn, - bool proxy) -{ - /* point to the address of the pointer that holds the string to send to the - server, which is for a plain host or for an HTTP proxy */ - char **allocuserpwd; - /* point to the name and password for this */ - const char *userp; - struct ntlmdata *ntlm; - curlntlm *state; - struct auth *authp; - - CURLcode res = CURLE_OK; - - DEBUGASSERT(conn); - DEBUGASSERT(data); - - if(proxy) { -#ifndef CURL_DISABLE_PROXY - allocuserpwd = &data->state.aptr.proxyuserpwd; - userp = conn->http_proxy.user; - ntlm = &conn->proxyntlm; - state = &conn->proxy_ntlm_state; - authp = &data->state.authproxy; -#else - return CURLE_NOT_BUILT_IN; -#endif - } - else { - allocuserpwd = &data->state.aptr.userpwd; - userp = conn->user; - ntlm = &conn->ntlm; - state = &conn->http_ntlm_state; - authp = &data->state.authhost; - } - authp->done = FALSE; - - /* not set means empty */ - if(!userp) - userp = ""; - - switch(*state) { - case NTLMSTATE_TYPE1: - default: - /* Use Samba's 'winbind' daemon to support NTLM authentication, - * by delegating the NTLM challenge/response protocol to a helper - * in ntlm_auth. - * https://web.archive.org/web/20190925164737 - * /devel.squid-cache.org/ntlm/squid_helper_protocol.html - * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html - * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html - * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this - * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute - * filename of ntlm_auth helper. - * If NTLM authentication using winbind fails, go back to original - * request handling process. - */ - /* Create communication with ntlm_auth */ - res = ntlm_wb_init(data, ntlm, userp); - if(res) - return res; - res = ntlm_wb_response(data, ntlm, "YR\n", *state); - if(res) - return res; - - free(*allocuserpwd); - *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", - proxy ? "Proxy-" : "", - ntlm->response); - DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd)); - Curl_safefree(ntlm->response); - if(!*allocuserpwd) - return CURLE_OUT_OF_MEMORY; - break; - - case NTLMSTATE_TYPE2: { - char *input = aprintf("TT %s\n", ntlm->challenge); - if(!input) - return CURLE_OUT_OF_MEMORY; - res = ntlm_wb_response(data, ntlm, input, *state); - free(input); - if(res) - return res; - - free(*allocuserpwd); - *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", - proxy ? "Proxy-" : "", - ntlm->response); - DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd)); - *state = NTLMSTATE_TYPE3; /* we sent a type-3 */ - authp->done = TRUE; - Curl_http_auth_cleanup_ntlm_wb(conn); - if(!*allocuserpwd) - return CURLE_OUT_OF_MEMORY; - break; - } - case NTLMSTATE_TYPE3: - /* connection is already authenticated, - * don't send a header in future requests */ - *state = NTLMSTATE_LAST; - /* FALLTHROUGH */ - case NTLMSTATE_LAST: - Curl_safefree(*allocuserpwd); - authp->done = TRUE; - break; - } - - return CURLE_OK; -} - -void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn) -{ - ntlm_wb_cleanup(&conn->ntlm); - ntlm_wb_cleanup(&conn->proxyntlm); -} - -#endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */ diff --git a/deps/curl/lib/curl_printf.h b/deps/curl/lib/curl_printf.h index 46ef344f76e..e851b14a507 100644 --- a/deps/curl/lib/curl_printf.h +++ b/deps/curl/lib/curl_printf.h @@ -29,14 +29,21 @@ * *rintf() functions. */ +#ifndef CURL_TEMP_PRINTF +#error "CURL_TEMP_PRINTF must be set before including curl/mprintf.h" +#endif + #include +#define MERR_OK 0 +#define MERR_MEM 1 +#define MERR_TOO_LARGE 2 + # undef printf # undef fprintf # undef msnprintf # undef vprintf # undef vfprintf -# undef vsnprintf # undef mvsnprintf # undef aprintf # undef vaprintf diff --git a/deps/curl/lib/curl_range.c b/deps/curl/lib/curl_range.c index d499953c9ed..49fb5f07783 100644 --- a/deps/curl/lib/curl_range.c +++ b/deps/curl/lib/curl_range.c @@ -55,15 +55,13 @@ CURLcode Curl_range(struct Curl_easy *data) if((to_t == CURL_OFFT_INVAL) && !from_t) { /* X - */ data->state.resume_from = from; - DEBUGF(infof(data, "RANGE %" CURL_FORMAT_CURL_OFF_T " to end of file", - from)); + DEBUGF(infof(data, "RANGE %" FMT_OFF_T " to end of file", from)); } else if((from_t == CURL_OFFT_INVAL) && !to_t) { /* -Y */ data->req.maxdownload = to; data->state.resume_from = -to; - DEBUGF(infof(data, "RANGE the last %" CURL_FORMAT_CURL_OFF_T " bytes", - to)); + DEBUGF(infof(data, "RANGE the last %" FMT_OFF_T " bytes", to)); } else { /* X-Y */ @@ -79,13 +77,12 @@ CURLcode Curl_range(struct Curl_easy *data) data->req.maxdownload = totalsize + 1; /* include last byte */ data->state.resume_from = from; - DEBUGF(infof(data, "RANGE from %" CURL_FORMAT_CURL_OFF_T - " getting %" CURL_FORMAT_CURL_OFF_T " bytes", + DEBUGF(infof(data, "RANGE from %" FMT_OFF_T + " getting %" FMT_OFF_T " bytes", from, data->req.maxdownload)); } - DEBUGF(infof(data, "range-download from %" CURL_FORMAT_CURL_OFF_T - " to %" CURL_FORMAT_CURL_OFF_T ", totally %" - CURL_FORMAT_CURL_OFF_T " bytes", + DEBUGF(infof(data, "range-download from %" FMT_OFF_T + " to %" FMT_OFF_T ", totally %" FMT_OFF_T " bytes", from, to, data->req.maxdownload)); } else diff --git a/deps/curl/lib/curl_rtmp.c b/deps/curl/lib/curl_rtmp.c index 406fb42ac0f..59fcc4e1ffc 100644 --- a/deps/curl/lib/curl_rtmp.c +++ b/deps/curl/lib/curl_rtmp.c @@ -35,11 +35,13 @@ #include "warnless.h" #include #include + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" #include "curl_memory.h" -/* The last #include file should be: */ #include "memdebug.h" -#if defined(WIN32) && !defined(USE_LWIPSOCK) +#if defined(_WIN32) && !defined(USE_LWIPSOCK) #define setsockopt(a,b,c,d,e) (setsockopt)(a,b,c,(const char *)d,(int)e) #define SET_RCVTIMEO(tv,s) int tv = s*1000 #elif defined(LWIP_SO_SNDRCVTIMEO_NONSTANDARD) @@ -66,7 +68,7 @@ static Curl_send rtmp_send; */ const struct Curl_handler Curl_handler_rtmp = { - "RTMP", /* scheme */ + "rtmp", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -79,7 +81,8 @@ const struct Curl_handler Curl_handler_rtmp = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMP, /* defport */ @@ -89,7 +92,7 @@ const struct Curl_handler Curl_handler_rtmp = { }; const struct Curl_handler Curl_handler_rtmpt = { - "RTMPT", /* scheme */ + "rtmpt", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -102,7 +105,8 @@ const struct Curl_handler Curl_handler_rtmpt = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPT, /* defport */ @@ -112,7 +116,7 @@ const struct Curl_handler Curl_handler_rtmpt = { }; const struct Curl_handler Curl_handler_rtmpe = { - "RTMPE", /* scheme */ + "rtmpe", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -125,7 +129,8 @@ const struct Curl_handler Curl_handler_rtmpe = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMP, /* defport */ @@ -135,7 +140,7 @@ const struct Curl_handler Curl_handler_rtmpe = { }; const struct Curl_handler Curl_handler_rtmpte = { - "RTMPTE", /* scheme */ + "rtmpte", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -148,7 +153,8 @@ const struct Curl_handler Curl_handler_rtmpte = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPT, /* defport */ @@ -158,7 +164,7 @@ const struct Curl_handler Curl_handler_rtmpte = { }; const struct Curl_handler Curl_handler_rtmps = { - "RTMPS", /* scheme */ + "rtmps", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -171,7 +177,8 @@ const struct Curl_handler Curl_handler_rtmps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPS, /* defport */ @@ -181,7 +188,7 @@ const struct Curl_handler Curl_handler_rtmps = { }; const struct Curl_handler Curl_handler_rtmpts = { - "RTMPTS", /* scheme */ + "rtmpts", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -194,7 +201,8 @@ const struct Curl_handler Curl_handler_rtmpts = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPS, /* defport */ @@ -228,7 +236,7 @@ static CURLcode rtmp_connect(struct Curl_easy *data, bool *done) r->m_sb.sb_socket = (int)conn->sock[FIRSTSOCKET]; - /* We have to know if it's a write before we send the + /* We have to know if it is a write before we send the * connect request packet */ if(data->state.upload) @@ -247,7 +255,7 @@ static CURLcode rtmp_connect(struct Curl_easy *data, bool *done) return CURLE_FAILED_INIT; /* Clients must send a periodic BytesReceived report to the server */ - r->m_bSendCounter = true; + r->m_bSendCounter = TRUE; *done = TRUE; conn->recv[FIRSTSOCKET] = rtmp_recv; @@ -265,10 +273,10 @@ static CURLcode rtmp_do(struct Curl_easy *data, bool *done) if(data->state.upload) { Curl_pgrsSetUploadSize(data, data->state.infilesize); - Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); } else - Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); *done = TRUE; return CURLE_OK; } @@ -321,13 +329,14 @@ static ssize_t rtmp_recv(struct Curl_easy *data, int sockindex, char *buf, } static ssize_t rtmp_send(struct Curl_easy *data, int sockindex, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, CURLcode *err) { struct connectdata *conn = data->conn; RTMP *r = conn->proto.rtmp; ssize_t num; (void)sockindex; /* unused */ + (void)eos; /* unused */ num = RTMP_Write(r, (char *)buf, curlx_uztosi(len)); if(num < 0) @@ -335,4 +344,20 @@ static ssize_t rtmp_send(struct Curl_easy *data, int sockindex, return num; } + +void Curl_rtmp_version(char *version, size_t len) +{ + char suff[2]; + if(RTMP_LIB_VERSION & 0xff) { + suff[0] = (RTMP_LIB_VERSION & 0xff) + 'a' - 1; + suff[1] = '\0'; + } + else + suff[0] = '\0'; + + msnprintf(version, len, "librtmp/%d.%d%s", + RTMP_LIB_VERSION >> 16, (RTMP_LIB_VERSION >> 8) & 0xff, + suff); +} + #endif /* USE_LIBRTMP */ diff --git a/deps/curl/lib/curl_rtmp.h b/deps/curl/lib/curl_rtmp.h index 9b93ee060b7..339d3a4384d 100644 --- a/deps/curl/lib/curl_rtmp.h +++ b/deps/curl/lib/curl_rtmp.h @@ -30,6 +30,8 @@ extern const struct Curl_handler Curl_handler_rtmpe; extern const struct Curl_handler Curl_handler_rtmpte; extern const struct Curl_handler Curl_handler_rtmps; extern const struct Curl_handler Curl_handler_rtmpts; + +void Curl_rtmp_version(char *version, size_t len); #endif #endif /* HEADER_CURL_RTMP_H */ diff --git a/deps/curl/lib/curl_sasl.c b/deps/curl/lib/curl_sasl.c index 91ddf106223..24f8c8c53c1 100644 --- a/deps/curl/lib/curl_sasl.c +++ b/deps/curl/lib/curl_sasl.c @@ -205,18 +205,23 @@ void Curl_sasl_init(struct SASL *sasl, struct Curl_easy *data, sasl->force_ir = FALSE; /* Respect external option */ if(auth != CURLAUTH_BASIC) { - sasl->resetprefs = FALSE; - sasl->prefmech = SASL_AUTH_NONE; + unsigned short mechs = SASL_AUTH_NONE; + + /* If some usable http authentication options have been set, determine + new defaults from them. */ if(auth & CURLAUTH_BASIC) - sasl->prefmech |= SASL_MECH_PLAIN | SASL_MECH_LOGIN; + mechs |= SASL_MECH_PLAIN | SASL_MECH_LOGIN; if(auth & CURLAUTH_DIGEST) - sasl->prefmech |= SASL_MECH_DIGEST_MD5; + mechs |= SASL_MECH_DIGEST_MD5; if(auth & CURLAUTH_NTLM) - sasl->prefmech |= SASL_MECH_NTLM; + mechs |= SASL_MECH_NTLM; if(auth & CURLAUTH_BEARER) - sasl->prefmech |= SASL_MECH_OAUTHBEARER | SASL_MECH_XOAUTH2; + mechs |= SASL_MECH_OAUTHBEARER | SASL_MECH_XOAUTH2; if(auth & CURLAUTH_GSSAPI) - sasl->prefmech |= SASL_MECH_GSSAPI; + mechs |= SASL_MECH_GSSAPI; + + if(mechs != SASL_AUTH_NONE) + sasl->prefmech = mechs; } } @@ -262,6 +267,8 @@ static void sasl_state(struct SASL *sasl, struct Curl_easy *data, sasl->state = newstate; } +#if defined(USE_NTLM) || defined(USE_GSASL) || defined(USE_KERBEROS5) || \ + !defined(CURL_DISABLE_DIGEST_AUTH) /* Get the SASL server message and convert it to binary. */ static CURLcode get_server_message(struct SASL *sasl, struct Curl_easy *data, struct bufref *out) @@ -284,6 +291,7 @@ static CURLcode get_server_message(struct SASL *sasl, struct Curl_easy *data, } return result; } +#endif /* Encode the outgoing SASL message. */ static CURLcode build_message(struct SASL *sasl, struct bufref *msg) @@ -320,7 +328,7 @@ bool Curl_sasl_can_authenticate(struct SASL *sasl, struct Curl_easy *data) if(data->state.aptr.user) return TRUE; - /* EXTERNAL can authenticate without a user name and/or password */ + /* EXTERNAL can authenticate without a username and/or password */ if(sasl->authmechs & sasl->prefmech & SASL_MECH_EXTERNAL) return TRUE; @@ -368,7 +376,7 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct Curl_easy *data, sasl->authused = SASL_MECH_EXTERNAL; if(force_ir || data->set.sasl_ir) - result = Curl_auth_create_external_message(conn->user, &resp); + Curl_auth_create_external_message(conn->user, &resp); } else if(data->state.aptr.user) { #if defined(USE_KERBEROS5) @@ -490,7 +498,7 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct Curl_easy *data, sasl->authused = SASL_MECH_LOGIN; if(force_ir || data->set.sasl_ir) - result = Curl_auth_create_login_message(conn->user, &resp); + Curl_auth_create_login_message(conn->user, &resp); } } @@ -568,14 +576,14 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, conn->user, conn->passwd, &resp); break; case SASL_LOGIN: - result = Curl_auth_create_login_message(conn->user, &resp); + Curl_auth_create_login_message(conn->user, &resp); newstate = SASL_LOGIN_PASSWD; break; case SASL_LOGIN_PASSWD: - result = Curl_auth_create_login_message(conn->passwd, &resp); + Curl_auth_create_login_message(conn->passwd, &resp); break; case SASL_EXTERNAL: - result = Curl_auth_create_external_message(conn->user, &resp); + Curl_auth_create_external_message(conn->user, &resp); break; #ifdef USE_GSASL case SASL_GSASL: diff --git a/deps/curl/lib/curl_setup.h b/deps/curl/lib/curl_setup.h index b43714da741..cfab1408504 100644 --- a/deps/curl/lib/curl_setup.h +++ b/deps/curl/lib/curl_setup.h @@ -28,11 +28,60 @@ #define CURL_NO_OLDIES #endif -/* define mingw version macros, eg __MINGW{32,64}_{MINOR,MAJOR}_VERSION */ +/* Tell "curl/curl.h" not to include "curl/mprintf.h" */ +#define CURL_SKIP_INCLUDE_MPRINTF + +/* FIXME: Delete this once the warnings have been fixed. */ +#if !defined(CURL_WARN_SIGN_CONVERSION) +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif +#endif + +/* Set default _WIN32_WINNT */ #ifdef __MINGW32__ #include <_mingw.h> #endif +/* Workaround for Homebrew gcc 12.4.0, 13.3.0, 14.1.0 and newer (as of 14.1.0) + that started advertising the `availability` attribute, which then gets used + by Apple SDK, but, in a way incompatible with gcc, resulting in misc errors + inside SDK headers, e.g.: + error: attributes should be specified before the declarator in a function + definition + error: expected ',' or '}' before + Followed by missing declarations. + Fix it by overriding the built-in feature-check macro used by the headers + to enable the problematic attributes. This makes the feature check fail. */ +#if defined(__APPLE__) && \ + !defined(__clang__) && \ + defined(__GNUC__) && __GNUC__ >= 12 && \ + defined(__has_attribute) +#define availability curl_pp_attribute_disabled +#endif + +#if defined(__APPLE__) +#include +#include +/* Fixup faulty target macro initialization in macOS SDK since v14.4 (as of + 15.0 beta). The SDK target detection in `TargetConditionals.h` correctly + detects macOS, but fails to set the macro's old name `TARGET_OS_OSX`, then + continues to set it to a default value of 0. Other parts of the SDK still + rely on the old name, and with this inconsistency our builds fail due to + missing declarations. It happens when using mainline llvm older than v18. + Later versions fixed it by predefining these target macros, avoiding the + faulty dynamic detection. gcc is not affected (for now) because it lacks + the necessary dynamic detection features, so the SDK falls back to + a codepath that sets both the old and new macro to 1. */ +#if defined(TARGET_OS_MAC) && TARGET_OS_MAC && \ + defined(TARGET_OS_OSX) && !TARGET_OS_OSX && \ + (!defined(TARGET_OS_IPHONE) || !TARGET_OS_IPHONE) && \ + (!defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR) +#undef TARGET_OS_OSX +#define TARGET_OS_OSX TARGET_OS_MAC +#endif +#endif + /* * Disable Visual Studio warnings: * 4127 "conditional expression is constant" @@ -41,17 +90,9 @@ #pragma warning(disable:4127) #endif +#ifdef _WIN32 /* - * Define WIN32 when build target is Win32 API - */ - -#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) -#define WIN32 -#endif - -#ifdef WIN32 -/* - * Don't include unneeded stuff in Windows headers to avoid compiler + * Do not include unneeded stuff in Windows headers to avoid compiler * warnings and macro clashes. * Make sure to define this macro before including any Windows headers. */ @@ -68,11 +109,16 @@ # include # if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && \ !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) -# define CURL_WINDOWS_APP +# define CURL_WINDOWS_UWP # endif # endif #endif +/* Compatibility */ +#if defined(ENABLE_IPV6) +# define USE_IPV6 1 +#endif + /* * Include configuration script results or hand-crafted * configuration file for platforms which lack config tool. @@ -87,7 +133,7 @@ #ifdef _WIN32_WCE # include "config-win32ce.h" #else -# ifdef WIN32 +# ifdef _WIN32 # include "config-win32.h" # endif #endif @@ -162,6 +208,11 @@ /* please, do it beyond the point further indicated in this file. */ /* ================================================================ */ +/* Give calloc a chance to be dragging in early, so we do not redefine */ +#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) +# include +#endif + /* * Disable other protocols when http is the only one desired. */ @@ -219,6 +270,23 @@ # define CURL_DISABLE_RTSP #endif +/* + * When HTTP is disabled, disable HTTP-only features + */ + +#if defined(CURL_DISABLE_HTTP) +# define CURL_DISABLE_ALTSVC 1 +# define CURL_DISABLE_COOKIES 1 +# define CURL_DISABLE_BASIC_AUTH 1 +# define CURL_DISABLE_BEARER_AUTH 1 +# define CURL_DISABLE_AWS 1 +# define CURL_DISABLE_DOH 1 +# define CURL_DISABLE_FORM_API 1 +# define CURL_DISABLE_HEADERS_API 1 +# define CURL_DISABLE_HSTS 1 +# define CURL_DISABLE_HTTP_AUTH 1 +#endif + /* ================================================================ */ /* No system header file shall be included in this file before this */ /* point. */ @@ -244,22 +312,72 @@ * Windows setup file includes some system headers. */ -#ifdef HAVE_WINDOWS_H +#ifdef _WIN32 # include "setup-win32.h" #endif #include +/* Helper macro to expand and concatenate two macros. + * Direct macros concatenation does not work because macros + * are not expanded before direct concatenation. + */ +#define CURL_CONC_MACROS_(A,B) A ## B +#define CURL_CONC_MACROS(A,B) CURL_CONC_MACROS_(A,B) + +/* curl uses its own printf() function internally. It understands the GNU + * format. Use this format, so that is matches the GNU format attribute we + * use with the MinGW compiler, allowing it to verify them at compile-time. + */ +#ifdef __MINGW32__ +# undef CURL_FORMAT_CURL_OFF_T +# undef CURL_FORMAT_CURL_OFF_TU +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +#endif + +/* based on logic in "curl/mprintf.h" */ + +#if (defined(__GNUC__) || defined(__clang__) || \ + defined(__IAR_SYSTEMS_ICC__)) && \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(CURL_NO_FMT_CHECKS) +#if defined(__MINGW32__) && !defined(__clang__) +#define CURL_PRINTF(fmt, arg) \ + __attribute__((format(gnu_printf, fmt, arg))) +#else +#define CURL_PRINTF(fmt, arg) \ + __attribute__((format(__printf__, fmt, arg))) +#endif +#else +#define CURL_PRINTF(fmt, arg) +#endif + +/* Override default printf mask check rules in "curl/mprintf.h" */ +#define CURL_TEMP_PRINTF CURL_PRINTF + +/* Workaround for mainline llvm v16 and earlier missing a built-in macro + expected by macOS SDK v14 / Xcode v15 (2023) and newer. + gcc (as of v14) is also missing it. */ +#if defined(__APPLE__) && \ + ((!defined(__apple_build_version__) && \ + defined(__clang__) && __clang_major__ < 17) || \ + (defined(__GNUC__) && __GNUC__ <= 14)) && \ + defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \ + !defined(__ENVIRONMENT_OS_VERSION_MIN_REQUIRED__) +#define __ENVIRONMENT_OS_VERSION_MIN_REQUIRED__ \ + __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ +#endif + /* * Use getaddrinfo to resolve the IPv4 address literal. If the current network - * interface doesn't support IPv4, but supports IPv6, NAT64, and DNS64, + * interface does not support IPv4, but supports IPv6, NAT64, and DNS64, * performing this task will result in a synthesized IPv6 address. */ #if defined(__APPLE__) && !defined(USE_ARES) -#include #define USE_RESOLVE_ON_IPS 1 # if TARGET_OS_MAC && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) && \ - defined(ENABLE_IPV6) + defined(USE_IPV6) # define CURL_MACOS_CALL_COPYPROXIES 1 # endif #endif @@ -336,25 +454,8 @@ #include #endif -#ifdef __POCC__ -# include -# include -# define sys_nerr EILSEQ -#endif - -/* - * Salford-C kludge section (mostly borrowed from wxWidgets). - */ -#ifdef __SALFORDC__ - #pragma suppress 353 /* Possible nested comments */ - #pragma suppress 593 /* Define not used */ - #pragma suppress 61 /* enum has no name */ - #pragma suppress 106 /* unnamed, unused parameter */ - #include -#endif - /* - * Large file (>2Gb) support using WIN32 functions. + * Large file (>2Gb) support using Win32 functions. */ #ifdef USE_WIN32_LARGE_FILES @@ -371,15 +472,13 @@ # define LSEEK_ERROR (__int64)-1 # define open curlx_win32_open # define fopen(fname,mode) curlx_win32_fopen(fname, mode) -# define access(fname,mode) curlx_win32_access(fname, mode) int curlx_win32_open(const char *filename, int oflag, ...); int curlx_win32_stat(const char *path, struct_stat *buffer); FILE *curlx_win32_fopen(const char *filename, const char *mode); - int curlx_win32_access(const char *path, int mode); #endif /* - * Small file (<2Gb) support using WIN32 functions. + * Small file (<2Gb) support using Win32 functions. */ #ifdef USE_WIN32_SMALL_FILES @@ -394,11 +493,9 @@ # define struct_stat struct _stat # define open curlx_win32_open # define fopen(fname,mode) curlx_win32_fopen(fname, mode) -# define access(fname,mode) curlx_win32_access(fname, mode) int curlx_win32_stat(const char *path, struct_stat *buffer); int curlx_win32_open(const char *filename, int oflag, ...); FILE *curlx_win32_fopen(const char *filename, const char *mode); - int curlx_win32_access(const char *path, int mode); # endif # define LSEEK_ERROR (long)-1 #endif @@ -412,12 +509,30 @@ #endif #ifndef SIZEOF_TIME_T -/* assume default size of time_t to be 32 bit */ +/* assume default size of time_t to be 32 bits */ #define SIZEOF_TIME_T 4 #endif +#ifndef SIZEOF_CURL_SOCKET_T +/* configure and cmake check and set the define */ +# ifdef _WIN64 +# define SIZEOF_CURL_SOCKET_T 8 +# else +/* default guess */ +# define SIZEOF_CURL_SOCKET_T 4 +# endif +#endif + +#if SIZEOF_CURL_SOCKET_T < 8 +# define FMT_SOCKET_T "d" +#elif defined(__MINGW32__) +# define FMT_SOCKET_T "zd" +#else +# define FMT_SOCKET_T "qd" +#endif + /* - * Default sizeof(off_t) in case it hasn't been defined in config file. + * Default sizeof(off_t) in case it has not been defined in config file. */ #ifndef SIZEOF_OFF_T @@ -451,6 +566,23 @@ #endif #define CURL_OFF_T_MIN (-CURL_OFF_T_MAX - CURL_OFF_T_C(1)) +#if (SIZEOF_CURL_OFF_T != 8) +# error "curl_off_t must be exactly 64 bits" +#else + typedef unsigned CURL_TYPEOF_CURL_OFF_T curl_uint64_t; + typedef CURL_TYPEOF_CURL_OFF_T curl_int64_t; +# ifndef CURL_SUFFIX_CURL_OFF_TU +# error "CURL_SUFFIX_CURL_OFF_TU must be defined" +# endif +# define CURL_UINT64_SUFFIX CURL_SUFFIX_CURL_OFF_TU +# define CURL_UINT64_C(val) CURL_CONC_MACROS(val,CURL_UINT64_SUFFIX) +# define FMT_PRId64 CURL_FORMAT_CURL_OFF_T +# define FMT_PRIu64 CURL_FORMAT_CURL_OFF_TU +#endif + +#define FMT_OFF_T CURL_FORMAT_CURL_OFF_T +#define FMT_OFF_TU CURL_FORMAT_CURL_OFF_TU + #if (SIZEOF_TIME_T == 4) # ifdef HAVE_TIME_T_UNSIGNED # define TIME_T_MAX UINT_MAX @@ -470,7 +602,7 @@ #endif #ifndef SIZE_T_MAX -/* some limits.h headers have this defined, some don't */ +/* some limits.h headers have this defined, some do not */ #if defined(SIZEOF_SIZE_T) && (SIZEOF_SIZE_T > 4) #define SIZE_T_MAX 18446744073709551615U #else @@ -479,7 +611,7 @@ #endif #ifndef SSIZE_T_MAX -/* some limits.h headers have this defined, some don't */ +/* some limits.h headers have this defined, some do not */ #if defined(SIZEOF_SIZE_T) && (SIZEOF_SIZE_T > 4) #define SSIZE_T_MAX 9223372036854775807 #else @@ -488,7 +620,7 @@ #endif /* - * Arg 2 type for gethostname in case it hasn't been defined in config file. + * Arg 2 type for gethostname in case it has not been defined in config file. */ #ifndef GETHOSTNAME_TYPE_ARG2 @@ -505,11 +637,11 @@ 5. set dir/file naming defines */ -#ifdef WIN32 +#ifdef _WIN32 # define DIR_CHAR "\\" -#else /* WIN32 */ +#else /* _WIN32 */ # ifdef MSDOS /* Watt-32 */ @@ -534,48 +666,19 @@ # define DIR_CHAR "/" -# ifndef fileno /* sunos 4 have this as a macro! */ - int fileno(FILE *stream); -# endif - -#endif /* WIN32 */ - -/* - * msvc 6.0 requires PSDK in order to have INET6_ADDRSTRLEN - * defined in ws2tcpip.h as well as to provide IPv6 support. - * Does not apply if lwIP is used. - */ - -#if defined(_MSC_VER) && !defined(__POCC__) && !defined(USE_LWIPSOCK) -# if !defined(HAVE_WS2TCPIP_H) || \ - ((_MSC_VER < 1300) && !defined(INET6_ADDRSTRLEN)) -# undef HAVE_GETADDRINFO_THREADSAFE -# undef HAVE_FREEADDRINFO -# undef HAVE_GETADDRINFO -# undef ENABLE_IPV6 -# endif -#endif +#endif /* _WIN32 */ /* ---------------------------------------------------------------- */ /* resolver specialty compile-time defines */ /* CURLRES_* defines to use in the host*.c sources */ /* ---------------------------------------------------------------- */ -/* - * lcc-win32 doesn't have _beginthreadex(), lacks threads support. - */ - -#if defined(__LCC__) && defined(WIN32) -# undef USE_THREADS_POSIX -# undef USE_THREADS_WIN32 -#endif - /* * MSVC threads support requires a multi-threaded runtime library. * _beginthreadex() is not available in single-threaded ones. */ -#if defined(_MSC_VER) && !defined(__POCC__) && !defined(_MT) +#if defined(_MSC_VER) && !defined(_MT) # undef USE_THREADS_POSIX # undef USE_THREADS_WIN32 #endif @@ -584,8 +687,11 @@ * Mutually exclusive CURLRES_* definitions. */ -#if defined(ENABLE_IPV6) && defined(HAVE_GETADDRINFO) +#if defined(USE_IPV6) && defined(HAVE_GETADDRINFO) # define CURLRES_IPV6 +#elif defined(USE_IPV6) && (defined(_WIN32) || defined(__CYGWIN__)) +/* assume on Windows that IPv6 without getaddrinfo is a broken build */ +# error "Unexpected build: IPv6 is enabled but getaddrinfo was not found." #else # define CURLRES_IPV4 #endif @@ -605,42 +711,14 @@ /* ---------------------------------------------------------------- */ -/* - * msvc 6.0 does not have struct sockaddr_storage and - * does not define IPPROTO_ESP in winsock2.h. But both - * are available if PSDK is properly installed. - */ - -#if defined(_MSC_VER) && !defined(__POCC__) -# if !defined(HAVE_WINSOCK2_H) || ((_MSC_VER < 1300) && !defined(IPPROTO_ESP)) -# undef HAVE_STRUCT_SOCKADDR_STORAGE -# endif -#endif - -/* - * Intentionally fail to build when using msvc 6.0 without PSDK installed. - * The brave of heart can circumvent this, defining ALLOW_MSVC6_WITHOUT_PSDK - * in lib/config-win32.h although absolutely discouraged and unsupported. - */ - -#if defined(_MSC_VER) && !defined(__POCC__) -# if !defined(HAVE_WINDOWS_H) || ((_MSC_VER < 1300) && !defined(_FILETIME_)) -# if !defined(ALLOW_MSVC6_WITHOUT_PSDK) -# error MSVC 6.0 requires "February 2003 Platform SDK" a.k.a. \ - "Windows Server 2003 PSDK" -# else -# define CURL_DISABLE_LDAP 1 -# endif -# endif -#endif - -#if defined(HAVE_LIBIDN2) && defined(HAVE_IDN2_H) && !defined(USE_WIN32_IDN) +#if defined(HAVE_LIBIDN2) && defined(HAVE_IDN2_H) && \ + !defined(USE_WIN32_IDN) && !defined(USE_APPLE_IDN) /* The lib and header are present */ #define USE_LIBIDN2 #endif -#if defined(USE_LIBIDN2) && defined(USE_WIN32_IDN) -#error "Both libidn2 and WinIDN are enabled, choose one." +#if defined(USE_LIBIDN2) && (defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN)) +#error "libidn2 cannot be enabled with WinIDN or AppleIDN, choose one." #endif #define LIBIDN_REQUIRED_VERSION "0.4.1" @@ -651,6 +729,11 @@ #define USE_SSL /* SSL support has been enabled */ #endif +#if defined(USE_WOLFSSL) && defined(USE_GNUTLS) +/* Avoid defining unprefixed wolfSSL SHA macros colliding with nettle ones */ +#define NO_OLD_WC_NAMES +#endif + /* Single point where USE_SPNEGO definition might be defined */ #if !defined(CURL_DISABLE_NEGOTIATE_AUTH) && \ (defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)) @@ -694,11 +777,42 @@ ((__GNUC__ == 2) && defined(__GNUC_MINOR__) && (__GNUC_MINOR__ >= 7))) # define UNUSED_PARAM __attribute__((__unused__)) # define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#elif defined(__IAR_SYSTEMS_ICC__) +# define UNUSED_PARAM __attribute__((__unused__)) +# if (__VER__ >= 9040001) +# define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define WARN_UNUSED_RESULT +# endif #else # define UNUSED_PARAM /* NOTHING */ # define WARN_UNUSED_RESULT #endif +/* noreturn attribute */ + +#if !defined(CURL_NORETURN) +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__) || \ + defined(__IAR_SYSTEMS_ICC__) +# define CURL_NORETURN __attribute__((__noreturn__)) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) +# define CURL_NORETURN __declspec(noreturn) +#else +# define CURL_NORETURN +#endif +#endif + +/* fallthrough attribute */ + +#if !defined(FALLTHROUGH) +#if (defined(__GNUC__) && __GNUC__ >= 7) || \ + (defined(__clang__) && __clang_major__ >= 10) +# define FALLTHROUGH() __attribute__((fallthrough)) +#else +# define FALLTHROUGH() do {} while (0) +#endif +#endif + /* * Include macros and defines that should only be processed once. */ @@ -720,16 +834,13 @@ */ #if defined(__LWIP_OPT_H__) || defined(LWIP_HDR_OPT_H) -# if defined(SOCKET) || \ - defined(USE_WINSOCK) || \ - defined(HAVE_WINSOCK2_H) || \ - defined(HAVE_WS2TCPIP_H) -# error "WinSock and lwIP TCP/IP stack definitions shall not coexist!" +# if defined(SOCKET) || defined(USE_WINSOCK) +# error "Winsock and lwIP TCP/IP stack definitions shall not coexist!" # endif #endif /* - * shutdown() flags for systems that don't define them + * shutdown() flags for systems that do not define them */ #ifndef SHUT_RD @@ -757,12 +868,12 @@ /* In Windows the default file mode is text but an application can override it. Therefore we specify it explicitly. https://github.com/curl/curl/pull/258 */ -#if defined(WIN32) || defined(MSDOS) +#if defined(_WIN32) || defined(MSDOS) #define FOPEN_READTEXT "rt" #define FOPEN_WRITETEXT "wt" #define FOPEN_APPENDTEXT "at" #elif defined(__CYGWIN__) -/* Cygwin has specific behavior we need to address when WIN32 is not defined. +/* Cygwin has specific behavior we need to address when _WIN32 is not defined. https://cygwin.com/cygwin-ug-net/using-textbinary.html For write we want our output to have line endings of LF and be compatible with other Cygwin utilities. For read we want to handle input that may have line @@ -777,7 +888,7 @@ endings either CRLF or LF so 't' is appropriate. #define FOPEN_APPENDTEXT "a" #endif -/* for systems that don't detect this in configure */ +/* for systems that do not detect this in configure */ #ifndef CURL_SA_FAMILY_T # if defined(HAVE_SA_FAMILY_T) # define CURL_SA_FAMILY_T sa_family_t @@ -806,33 +917,36 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result); #endif -#ifdef DEBUGBUILD +#ifdef UNITTESTS #define UNITTEST #else #define UNITTEST static #endif -#if defined(USE_NGHTTP2) || defined(USE_HYPER) +/* Hyper supports HTTP2 also, but Curl's integration with Hyper does not */ +#if defined(USE_NGHTTP2) #define USE_HTTP2 #endif #if (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || \ + (defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3)) || \ defined(USE_QUICHE) || defined(USE_MSH3) -#define ENABLE_QUIC + +#ifdef CURL_WITH_MULTI_SSL +#error "Multi-SSL combined with QUIC is not supported" +#endif + #define USE_HTTP3 #endif /* Certain Windows implementations are not aligned with what curl expects, so always use the local one on this platform. E.g. the mingw-w64 implementation can return wrong results for non-ASCII inputs. */ -#if defined(HAVE_BASENAME) && defined(WIN32) +#if defined(HAVE_BASENAME) && defined(_WIN32) #undef HAVE_BASENAME #endif -#if defined(USE_UNIX_SOCKETS) && defined(WIN32) -# if defined(__MINGW32__) && !defined(LUP_SECURE) - typedef u_short ADDRESS_FAMILY; /* Classic mingw, 11y+ old mingw-w64 */ -# endif +#if defined(USE_UNIX_SOCKETS) && defined(_WIN32) # if !defined(UNIX_PATH_MAX) /* Replicating logic present in afunix.h (distributed with newer Windows 10 SDK versions only) */ @@ -852,4 +966,26 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, #define OPENSSL_SUPPRESS_DEPRECATED #endif +#if defined(inline) + /* 'inline' is defined as macro and assumed to be correct */ + /* No need for 'inline' replacement */ +#elif defined(__cplusplus) + /* The code is compiled with C++ compiler. + C++ always supports 'inline'. */ + /* No need for 'inline' replacement */ +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901 + /* C99 (and later) supports 'inline' keyword */ + /* No need for 'inline' replacement */ +#elif defined(__GNUC__) && __GNUC__ >= 3 + /* GCC supports '__inline__' as an extension */ +# define inline __inline__ +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + /* MSC supports '__inline' from VS 2005 (or even earlier) */ +# define inline __inline +#else + /* Probably 'inline' is not supported by compiler. + Define to the empty string to be on the safe side. */ +# define inline /* empty */ +#endif + #endif /* HEADER_CURL_SETUP_H */ diff --git a/deps/curl/lib/curl_setup_once.h b/deps/curl/lib/curl_setup_once.h index c1ed0590709..1521e69f916 100644 --- a/deps/curl/lib/curl_setup_once.h +++ b/deps/curl/lib/curl_setup_once.h @@ -56,7 +56,7 @@ #include #endif -#ifdef WIN32 +#ifdef _WIN32 #include #include #endif @@ -70,11 +70,7 @@ #endif #ifdef USE_WOLFSSL -# if defined(HAVE_STDINT_H) -# include -# elif defined(HAVE_INTTYPES_H) -# include -# endif +#include #endif #ifdef USE_SCHANNEL @@ -110,7 +106,7 @@ #endif /* - * Definition of timeval struct for platforms that don't have it. + * Definition of timeval struct for platforms that do not have it. */ #ifndef HAVE_STRUCT_TIMEVAL @@ -134,7 +130,7 @@ struct timeval { #if defined(__minix) -/* Minix doesn't support recv on TCP sockets */ +/* Minix does not support recv on TCP sockets */ #define sread(x,y,z) (ssize_t)read((RECV_TYPE_ARG1)(x), \ (RECV_TYPE_ARG2)(y), \ (RECV_TYPE_ARG3)(z)) @@ -147,7 +143,7 @@ struct timeval { * * HAVE_RECV is defined if you have a function named recv() * which is used to read incoming data from sockets. If your - * function has another name then don't define HAVE_RECV. + * function has another name then do not define HAVE_RECV. * * If HAVE_RECV is defined then RECV_TYPE_ARG1, RECV_TYPE_ARG2, * RECV_TYPE_ARG3, RECV_TYPE_ARG4 and RECV_TYPE_RETV must also @@ -155,7 +151,7 @@ struct timeval { * * HAVE_SEND is defined if you have a function named send() * which is used to write outgoing data on a connected socket. - * If yours has another name then don't define HAVE_SEND. + * If yours has another name then do not define HAVE_SEND. * * If HAVE_SEND is defined then SEND_TYPE_ARG1, SEND_QUAL_ARG2, * SEND_TYPE_ARG2, SEND_TYPE_ARG3, SEND_TYPE_ARG4 and @@ -168,15 +164,13 @@ struct timeval { (RECV_TYPE_ARG4)(0)) #else /* HAVE_RECV */ #ifndef sread - /* */ - Error Missing_definition_of_macro_sread - /* */ +#error "Missing definition of macro sread!" #endif #endif /* HAVE_RECV */ #if defined(__minix) -/* Minix doesn't support send on TCP sockets */ +/* Minix does not support send on TCP sockets */ #define swrite(x,y,z) (ssize_t)write((SEND_TYPE_ARG1)(x), \ (SEND_TYPE_ARG2)(y), \ (SEND_TYPE_ARG3)(z)) @@ -188,9 +182,7 @@ struct timeval { (SEND_TYPE_ARG4)(SEND_4TH_ARG)) #else /* HAVE_SEND */ #ifndef swrite - /* */ - Error Missing_definition_of_macro_swrite - /* */ +#error "Missing definition of macro swrite!" #endif #endif /* HAVE_SEND */ @@ -234,7 +226,7 @@ struct timeval { /* * 'bool' exists on platforms with , i.e. C99 platforms. - * On non-C99 platforms there's no bool, so define an enum for that. + * On non-C99 platforms there is no bool, so define an enum for that. * On C99 platforms 'false' and 'true' also exist. Enum uses a * global namespace though, so use bool_false and bool_true. */ @@ -246,7 +238,7 @@ struct timeval { } bool; /* - * Use a define to let 'true' and 'false' use those enums. There + * Use a define to let 'true' and 'false' use those enums. There * are currently no use of true and false in libcurl proper, but * there are some in the examples. This will cater for any later * code happening to use true and false. diff --git a/deps/curl/lib/curl_sha256.h b/deps/curl/lib/curl_sha256.h index d99f958f90d..00e5b74c58d 100644 --- a/deps/curl/lib/curl_sha256.h +++ b/deps/curl/lib/curl_sha256.h @@ -31,15 +31,10 @@ #include #include "curl_hmac.h" -extern const struct HMAC_params Curl_HMAC_SHA256[1]; +extern const struct HMAC_params Curl_HMAC_SHA256; -#ifdef USE_WOLFSSL -/* SHA256_DIGEST_LENGTH is an enum value in wolfSSL. Need to import it from - * sha.h */ -#include -#include -#else -#define SHA256_DIGEST_LENGTH 32 +#ifndef CURL_SHA256_DIGEST_LENGTH +#define CURL_SHA256_DIGEST_LENGTH 32 /* fixed size */ #endif CURLcode Curl_sha256it(unsigned char *outbuffer, const unsigned char *input, diff --git a/deps/curl/lib/curl_sha512_256.c b/deps/curl/lib/curl_sha512_256.c new file mode 100644 index 00000000000..80fd8cf1291 --- /dev/null +++ b/deps/curl/lib/curl_sha512_256.c @@ -0,0 +1,857 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Evgeny Grin (Karlson2k), . + * + * 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.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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_DIGEST_AUTH) && !defined(CURL_DISABLE_SHA512_256) + +#include "curl_sha512_256.h" +#include "warnless.h" + +/* The recommended order of the TLS backends: + * * OpenSSL + * * GnuTLS + * * wolfSSL + * * Schannel SSPI + * * Secure Transport (Darwin) + * * mbedTLS + * * BearSSL + * * Rustls + * Skip the backend if it does not support the required algorithm */ + +#if defined(USE_OPENSSL) +# include +# if (!defined(LIBRESSL_VERSION_NUMBER) && \ + defined(OPENSSL_VERSION_NUMBER) && \ + (OPENSSL_VERSION_NUMBER >= 0x10101000L)) || \ + (defined(LIBRESSL_VERSION_NUMBER) && \ + (LIBRESSL_VERSION_NUMBER >= 0x3080000fL)) +# include +# if !defined(OPENSSL_NO_SHA) && !defined(OPENSSL_NO_SHA512) +# include +# define USE_OPENSSL_SHA512_256 1 +# define HAS_SHA512_256_IMPLEMENTATION 1 +# ifdef __NetBSD__ +/* Some NetBSD versions has a bug in SHA-512/256. + * See https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=58039 + * The problematic versions: + * - NetBSD before 9.4 + * - NetBSD 9 all development versions (9.99.x) + * - NetBSD 10 development versions (10.99.x) before 10.99.11 + * The bug was fixed in NetBSD 9.4 release, NetBSD 10.0 release, + * NetBSD 10.99.11 development. + * It is safe to apply the workaround even if the bug is not present, as + * the workaround just reduces performance slightly. */ +# include +# if __NetBSD_Version__ < 904000000 || \ + (__NetBSD_Version__ >= 999000000 && \ + __NetBSD_Version__ < 1000000000) || \ + (__NetBSD_Version__ >= 1099000000 && \ + __NetBSD_Version__ < 1099001100) +# define NEED_NETBSD_SHA512_256_WORKAROUND 1 +# include +# endif +# endif +# endif +# endif +#endif /* USE_OPENSSL */ + + +#if !defined(HAS_SHA512_256_IMPLEMENTATION) && defined(USE_GNUTLS) +# include +# if defined(SHA512_256_DIGEST_SIZE) +# define USE_GNUTLS_SHA512_256 1 +# define HAS_SHA512_256_IMPLEMENTATION 1 +# endif +#endif /* ! HAS_SHA512_256_IMPLEMENTATION && USE_GNUTLS */ + +#if defined(USE_OPENSSL_SHA512_256) + +/* OpenSSL does not provide macros for SHA-512/256 sizes */ + +/** + * Size of the SHA-512/256 single processing block in bytes. + */ +#define CURL_SHA512_256_BLOCK_SIZE 128 + +/** + * Size of the SHA-512/256 resulting digest in bytes. + * This is the final digest size, not intermediate hash. + */ +#define CURL_SHA512_256_DIGEST_SIZE CURL_SHA512_256_DIGEST_LENGTH + +/** + * Context type used for SHA-512/256 calculations + */ +typedef EVP_MD_CTX *Curl_sha512_256_ctx; + +/** + * Initialise structure for SHA-512/256 calculation. + * + * @param context the calculation context + * @return CURLE_OK if succeed, + * error code otherwise + */ +static CURLcode +Curl_sha512_256_init(void *context) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + *ctx = EVP_MD_CTX_create(); + if(!*ctx) + return CURLE_OUT_OF_MEMORY; + + if(EVP_DigestInit_ex(*ctx, EVP_sha512_256(), NULL)) { + /* Check whether the header and this file use the same numbers */ + DEBUGASSERT(EVP_MD_CTX_size(*ctx) == CURL_SHA512_256_DIGEST_SIZE); + /* Check whether the block size is correct */ + DEBUGASSERT(EVP_MD_CTX_block_size(*ctx) == CURL_SHA512_256_BLOCK_SIZE); + + return CURLE_OK; /* Success */ + } + + /* Cleanup */ + EVP_MD_CTX_destroy(*ctx); + return CURLE_FAILED_INIT; +} + + +/** + * Process portion of bytes. + * + * @param context the calculation context + * @param data bytes to add to hash + * @return CURLE_OK if succeed, + * error code otherwise + */ +static CURLcode +Curl_sha512_256_update(void *context, + const unsigned char *data, + size_t length) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + if(!EVP_DigestUpdate(*ctx, data, length)) + return CURLE_SSL_CIPHER; + + return CURLE_OK; +} + + +/** + * Finalise SHA-512/256 calculation, return digest. + * + * @param context the calculation context + * @param[out] digest set to the hash, must be #CURL_SHA512_256_DIGEST_SIZE + # bytes + * @return CURLE_OK if succeed, + * error code otherwise + */ +static CURLcode +Curl_sha512_256_finish(unsigned char *digest, + void *context) +{ + CURLcode ret; + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + +#ifdef NEED_NETBSD_SHA512_256_WORKAROUND + /* Use a larger buffer to work around a bug in NetBSD: + https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=58039 */ + unsigned char tmp_digest[CURL_SHA512_256_DIGEST_SIZE * 2]; + ret = EVP_DigestFinal_ex(*ctx, + tmp_digest, NULL) ? CURLE_OK : CURLE_SSL_CIPHER; + if(ret == CURLE_OK) + memcpy(digest, tmp_digest, CURL_SHA512_256_DIGEST_SIZE); + explicit_memset(tmp_digest, 0, sizeof(tmp_digest)); +#else /* ! NEED_NETBSD_SHA512_256_WORKAROUND */ + ret = EVP_DigestFinal_ex(*ctx, digest, NULL) ? CURLE_OK : CURLE_SSL_CIPHER; +#endif /* ! NEED_NETBSD_SHA512_256_WORKAROUND */ + + EVP_MD_CTX_destroy(*ctx); + *ctx = NULL; + + return ret; +} + +#elif defined(USE_GNUTLS_SHA512_256) + +#define CURL_SHA512_256_BLOCK_SIZE SHA512_256_BLOCK_SIZE +#define CURL_SHA512_256_DIGEST_SIZE SHA512_256_DIGEST_SIZE + +/** + * Context type used for SHA-512/256 calculations + */ +typedef struct sha512_256_ctx Curl_sha512_256_ctx; + +/** + * Initialise structure for SHA-512/256 calculation. + * + * @param context the calculation context + * @return always CURLE_OK + */ +static CURLcode +Curl_sha512_256_init(void *context) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + /* Check whether the header and this file use the same numbers */ + DEBUGASSERT(CURL_SHA512_256_DIGEST_LENGTH == CURL_SHA512_256_DIGEST_SIZE); + + sha512_256_init(ctx); + + return CURLE_OK; +} + + +/** + * Process portion of bytes. + * + * @param context the calculation context + * @param data bytes to add to hash + * @param length number of bytes in @a data + * @return always CURLE_OK + */ +static CURLcode +Curl_sha512_256_update(void *context, + const unsigned char *data, + size_t length) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + DEBUGASSERT((data != NULL) || (length == 0)); + + sha512_256_update(ctx, length, (const uint8_t *)data); + + return CURLE_OK; +} + + +/** + * Finalise SHA-512/256 calculation, return digest. + * + * @param context the calculation context + * @param[out] digest set to the hash, must be #CURL_SHA512_256_DIGEST_SIZE + # bytes + * @return always CURLE_OK + */ +static CURLcode +Curl_sha512_256_finish(unsigned char *digest, + void *context) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + sha512_256_digest(ctx, + (size_t)CURL_SHA512_256_DIGEST_SIZE, (uint8_t *)digest); + + return CURLE_OK; +} + +#else /* No system or TLS backend SHA-512/256 implementation available */ + +/* Use local implementation */ +#define HAS_SHA512_256_IMPLEMENTATION 1 + +/* ** This implementation of SHA-512/256 hash calculation was originally ** * + * ** written by Evgeny Grin (Karlson2k) for GNU libmicrohttpd. ** * + * ** The author ported the code to libcurl. The ported code is provided ** * + * ** under curl license. ** * + * ** This is a minimal version with minimal optimizations. Performance ** * + * ** can be significantly improved. Big-endian store and load macros ** * + * ** are obvious targets for optimization. ** */ + +#ifdef __GNUC__ +# if defined(__has_attribute) && defined(__STDC_VERSION__) +# if __has_attribute(always_inline) && __STDC_VERSION__ >= 199901 +# define MHDX_INLINE inline __attribute__((always_inline)) +# endif +# endif +#endif + +#if !defined(MHDX_INLINE) && \ + defined(_MSC_VER) && !defined(__GNUC__) && !defined(__clang__) +# if _MSC_VER >= 1400 +# define MHDX_INLINE __forceinline +# endif +#endif + +#if !defined(MHDX_INLINE) + /* Assume that 'inline' keyword works or the + * macro was already defined correctly. */ +# define MHDX_INLINE inline +#endif + +/* Bits manipulation macros and functions. + Can be moved to other headers to reuse. */ + +#define MHDX_GET_64BIT_BE(ptr) \ + ( ((curl_uint64_t)(((const unsigned char*)(ptr))[0]) << 56) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[1]) << 48) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[2]) << 40) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[3]) << 32) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[4]) << 24) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[5]) << 16) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[6]) << 8) | \ + (curl_uint64_t)(((const unsigned char*)(ptr))[7]) ) + +#define MHDX_PUT_64BIT_BE(ptr,val) do { \ + ((unsigned char*)(ptr))[7]=(unsigned char)((curl_uint64_t)(val)); \ + ((unsigned char*)(ptr))[6]=(unsigned char)(((curl_uint64_t)(val)) >> 8); \ + ((unsigned char*)(ptr))[5]=(unsigned char)(((curl_uint64_t)(val)) >> 16); \ + ((unsigned char*)(ptr))[4]=(unsigned char)(((curl_uint64_t)(val)) >> 24); \ + ((unsigned char*)(ptr))[3]=(unsigned char)(((curl_uint64_t)(val)) >> 32); \ + ((unsigned char*)(ptr))[2]=(unsigned char)(((curl_uint64_t)(val)) >> 40); \ + ((unsigned char*)(ptr))[1]=(unsigned char)(((curl_uint64_t)(val)) >> 48); \ + ((unsigned char*)(ptr))[0]=(unsigned char)(((curl_uint64_t)(val)) >> 56); \ + } while(0) + +/* Defined as a function. The macro version may duplicate the binary code + * size as each argument is used twice, so if any calculation is used + * as an argument, the calculation could be done twice. */ +static MHDX_INLINE curl_uint64_t +MHDx_rotr64(curl_uint64_t value, unsigned int bits) +{ + bits %= 64; + if(0 == bits) + return value; + /* Defined in a form which modern compiler could optimize. */ + return (value >> bits) | (value << (64 - bits)); +} + +/* SHA-512/256 specific data */ + +/** + * Number of bits in a single SHA-512/256 word. + */ +#define SHA512_256_WORD_SIZE_BITS 64 + +/** + * Number of bytes in a single SHA-512/256 word. + */ +#define SHA512_256_BYTES_IN_WORD (SHA512_256_WORD_SIZE_BITS / 8) + +/** + * Hash is kept internally as 8 64-bit words. + * This is the intermediate hash size, used during computing the final digest. + */ +#define SHA512_256_HASH_SIZE_WORDS 8 + +/** + * Size of the SHA-512/256 resulting digest in words. + * This is the final digest size, not intermediate hash. + */ +#define SHA512_256_DIGEST_SIZE_WORDS (SHA512_256_HASH_SIZE_WORDS / 2) + +/** + * Size of the SHA-512/256 resulting digest in bytes + * This is the final digest size, not intermediate hash. + */ +#define CURL_SHA512_256_DIGEST_SIZE \ + (SHA512_256_DIGEST_SIZE_WORDS * SHA512_256_BYTES_IN_WORD) + +/** + * Size of the SHA-512/256 single processing block in bits. + */ +#define SHA512_256_BLOCK_SIZE_BITS 1024 + +/** + * Size of the SHA-512/256 single processing block in bytes. + */ +#define CURL_SHA512_256_BLOCK_SIZE (SHA512_256_BLOCK_SIZE_BITS / 8) + +/** + * Size of the SHA-512/256 single processing block in words. + */ +#define SHA512_256_BLOCK_SIZE_WORDS \ + (SHA512_256_BLOCK_SIZE_BITS / SHA512_256_WORD_SIZE_BITS) + +/** + * SHA-512/256 calculation context + */ +struct mhdx_sha512_256ctx +{ + /** + * Intermediate hash value. The variable is properly aligned. Smart + * compilers may automatically use fast load/store instruction for big + * endian data on little endian machine. + */ + curl_uint64_t H[SHA512_256_HASH_SIZE_WORDS]; + /** + * SHA-512/256 input data buffer. The buffer is properly aligned. Smart + * compilers may automatically use fast load/store instruction for big + * endian data on little endian machine. + */ + curl_uint64_t buffer[SHA512_256_BLOCK_SIZE_WORDS]; + /** + * The number of bytes, lower part + */ + curl_uint64_t count; + /** + * The number of bits, high part. Unlike lower part, this counts the number + * of bits, not bytes. + */ + curl_uint64_t count_bits_hi; +}; + +/** + * Context type used for SHA-512/256 calculations + */ +typedef struct mhdx_sha512_256ctx Curl_sha512_256_ctx; + + +/** + * Initialise structure for SHA-512/256 calculation. + * + * @param context the calculation context + * @return always CURLE_OK + */ +static CURLcode +MHDx_sha512_256_init(void *context) +{ + struct mhdx_sha512_256ctx *const ctx = (struct mhdx_sha512_256ctx *) context; + + /* Check whether the header and this file use the same numbers */ + DEBUGASSERT(CURL_SHA512_256_DIGEST_LENGTH == CURL_SHA512_256_DIGEST_SIZE); + + DEBUGASSERT(sizeof(curl_uint64_t) == 8); + + /* Initial hash values, see FIPS PUB 180-4 section 5.3.6.2 */ + /* Values generated by "IV Generation Function" as described in + * section 5.3.6 */ + ctx->H[0] = CURL_UINT64_C(0x22312194FC2BF72C); + ctx->H[1] = CURL_UINT64_C(0x9F555FA3C84C64C2); + ctx->H[2] = CURL_UINT64_C(0x2393B86B6F53B151); + ctx->H[3] = CURL_UINT64_C(0x963877195940EABD); + ctx->H[4] = CURL_UINT64_C(0x96283EE2A88EFFE3); + ctx->H[5] = CURL_UINT64_C(0xBE5E1E2553863992); + ctx->H[6] = CURL_UINT64_C(0x2B0199FC2C85B8AA); + ctx->H[7] = CURL_UINT64_C(0x0EB72DDC81C52CA2); + + /* Initialise number of bytes and high part of number of bits. */ + ctx->count = CURL_UINT64_C(0); + ctx->count_bits_hi = CURL_UINT64_C(0); + + return CURLE_OK; +} + + +/** + * Base of the SHA-512/256 transformation. + * Gets a full 128 bytes block of data and updates hash values; + * @param H hash values + * @param data the data buffer with #CURL_SHA512_256_BLOCK_SIZE bytes block + */ +static void +MHDx_sha512_256_transform(curl_uint64_t H[SHA512_256_HASH_SIZE_WORDS], + const void *data) +{ + /* Working variables, + see FIPS PUB 180-4 section 6.7, 6.4. */ + curl_uint64_t a = H[0]; + curl_uint64_t b = H[1]; + curl_uint64_t c = H[2]; + curl_uint64_t d = H[3]; + curl_uint64_t e = H[4]; + curl_uint64_t f = H[5]; + curl_uint64_t g = H[6]; + curl_uint64_t h = H[7]; + + /* Data buffer, used as a cyclic buffer. + See FIPS PUB 180-4 section 5.2.2, 6.7, 6.4. */ + curl_uint64_t W[16]; + + /* 'Ch' and 'Maj' macro functions are defined with widely-used optimization. + See FIPS PUB 180-4 formulae 4.8, 4.9. */ +#define Sha512_Ch(x,y,z) ( (z) ^ ((x) & ((y) ^ (z))) ) +#define Sha512_Maj(x,y,z) ( ((x) & (y)) ^ ((z) & ((x) ^ (y))) ) + + /* Four 'Sigma' macro functions. + See FIPS PUB 180-4 formulae 4.10, 4.11, 4.12, 4.13. */ +#define SIG0(x) \ + ( MHDx_rotr64((x), 28) ^ MHDx_rotr64((x), 34) ^ MHDx_rotr64((x), 39) ) +#define SIG1(x) \ + ( MHDx_rotr64((x), 14) ^ MHDx_rotr64((x), 18) ^ MHDx_rotr64((x), 41) ) +#define sig0(x) \ + ( MHDx_rotr64((x), 1) ^ MHDx_rotr64((x), 8) ^ ((x) >> 7) ) +#define sig1(x) \ + ( MHDx_rotr64((x), 19) ^ MHDx_rotr64((x), 61) ^ ((x) >> 6) ) + + if(1) { + unsigned int t; + /* K constants array. + See FIPS PUB 180-4 section 4.2.3 for K values. */ + static const curl_uint64_t K[80] = { + CURL_UINT64_C(0x428a2f98d728ae22), CURL_UINT64_C(0x7137449123ef65cd), + CURL_UINT64_C(0xb5c0fbcfec4d3b2f), CURL_UINT64_C(0xe9b5dba58189dbbc), + CURL_UINT64_C(0x3956c25bf348b538), CURL_UINT64_C(0x59f111f1b605d019), + CURL_UINT64_C(0x923f82a4af194f9b), CURL_UINT64_C(0xab1c5ed5da6d8118), + CURL_UINT64_C(0xd807aa98a3030242), CURL_UINT64_C(0x12835b0145706fbe), + CURL_UINT64_C(0x243185be4ee4b28c), CURL_UINT64_C(0x550c7dc3d5ffb4e2), + CURL_UINT64_C(0x72be5d74f27b896f), CURL_UINT64_C(0x80deb1fe3b1696b1), + CURL_UINT64_C(0x9bdc06a725c71235), CURL_UINT64_C(0xc19bf174cf692694), + CURL_UINT64_C(0xe49b69c19ef14ad2), CURL_UINT64_C(0xefbe4786384f25e3), + CURL_UINT64_C(0x0fc19dc68b8cd5b5), CURL_UINT64_C(0x240ca1cc77ac9c65), + CURL_UINT64_C(0x2de92c6f592b0275), CURL_UINT64_C(0x4a7484aa6ea6e483), + CURL_UINT64_C(0x5cb0a9dcbd41fbd4), CURL_UINT64_C(0x76f988da831153b5), + CURL_UINT64_C(0x983e5152ee66dfab), CURL_UINT64_C(0xa831c66d2db43210), + CURL_UINT64_C(0xb00327c898fb213f), CURL_UINT64_C(0xbf597fc7beef0ee4), + CURL_UINT64_C(0xc6e00bf33da88fc2), CURL_UINT64_C(0xd5a79147930aa725), + CURL_UINT64_C(0x06ca6351e003826f), CURL_UINT64_C(0x142929670a0e6e70), + CURL_UINT64_C(0x27b70a8546d22ffc), CURL_UINT64_C(0x2e1b21385c26c926), + CURL_UINT64_C(0x4d2c6dfc5ac42aed), CURL_UINT64_C(0x53380d139d95b3df), + CURL_UINT64_C(0x650a73548baf63de), CURL_UINT64_C(0x766a0abb3c77b2a8), + CURL_UINT64_C(0x81c2c92e47edaee6), CURL_UINT64_C(0x92722c851482353b), + CURL_UINT64_C(0xa2bfe8a14cf10364), CURL_UINT64_C(0xa81a664bbc423001), + CURL_UINT64_C(0xc24b8b70d0f89791), CURL_UINT64_C(0xc76c51a30654be30), + CURL_UINT64_C(0xd192e819d6ef5218), CURL_UINT64_C(0xd69906245565a910), + CURL_UINT64_C(0xf40e35855771202a), CURL_UINT64_C(0x106aa07032bbd1b8), + CURL_UINT64_C(0x19a4c116b8d2d0c8), CURL_UINT64_C(0x1e376c085141ab53), + CURL_UINT64_C(0x2748774cdf8eeb99), CURL_UINT64_C(0x34b0bcb5e19b48a8), + CURL_UINT64_C(0x391c0cb3c5c95a63), CURL_UINT64_C(0x4ed8aa4ae3418acb), + CURL_UINT64_C(0x5b9cca4f7763e373), CURL_UINT64_C(0x682e6ff3d6b2b8a3), + CURL_UINT64_C(0x748f82ee5defb2fc), CURL_UINT64_C(0x78a5636f43172f60), + CURL_UINT64_C(0x84c87814a1f0ab72), CURL_UINT64_C(0x8cc702081a6439ec), + CURL_UINT64_C(0x90befffa23631e28), CURL_UINT64_C(0xa4506cebde82bde9), + CURL_UINT64_C(0xbef9a3f7b2c67915), CURL_UINT64_C(0xc67178f2e372532b), + CURL_UINT64_C(0xca273eceea26619c), CURL_UINT64_C(0xd186b8c721c0c207), + CURL_UINT64_C(0xeada7dd6cde0eb1e), CURL_UINT64_C(0xf57d4f7fee6ed178), + CURL_UINT64_C(0x06f067aa72176fba), CURL_UINT64_C(0x0a637dc5a2c898a6), + CURL_UINT64_C(0x113f9804bef90dae), CURL_UINT64_C(0x1b710b35131c471b), + CURL_UINT64_C(0x28db77f523047d84), CURL_UINT64_C(0x32caab7b40c72493), + CURL_UINT64_C(0x3c9ebe0a15c9bebc), CURL_UINT64_C(0x431d67c49c100d4c), + CURL_UINT64_C(0x4cc5d4becb3e42b6), CURL_UINT64_C(0x597f299cfc657e2a), + CURL_UINT64_C(0x5fcb6fab3ad6faec), CURL_UINT64_C(0x6c44198c4a475817) + }; + + /* One step of SHA-512/256 computation, + see FIPS PUB 180-4 section 6.4.2 step 3. + * Note: this macro updates working variables in-place, without rotation. + * Note: the first (vH += SIG1(vE) + Ch(vE,vF,vG) + kt + wt) equals T1 in + FIPS PUB 180-4 section 6.4.2 step 3. + the second (vH += SIG0(vA) + Maj(vE,vF,vC) equals T1 + T2 in + FIPS PUB 180-4 section 6.4.2 step 3. + * Note: 'wt' must be used exactly one time in this macro as macro for + 'wt' calculation may change other data as well every time when + used. */ +#define SHA2STEP64(vA,vB,vC,vD,vE,vF,vG,vH,kt,wt) do { \ + (vD) += ((vH) += SIG1((vE)) + Sha512_Ch((vE),(vF),(vG)) + (kt) + (wt)); \ + (vH) += SIG0((vA)) + Sha512_Maj((vA),(vB),(vC)); } while (0) + + /* One step of SHA-512/256 computation with working variables rotation, + see FIPS PUB 180-4 section 6.4.2 step 3. This macro version reassigns + all working variables on each step. */ +#define SHA2STEP64RV(vA,vB,vC,vD,vE,vF,vG,vH,kt,wt) do { \ + curl_uint64_t tmp_h_ = (vH); \ + SHA2STEP64((vA),(vB),(vC),(vD),(vE),(vF),(vG),tmp_h_,(kt),(wt)); \ + (vH) = (vG); \ + (vG) = (vF); \ + (vF) = (vE); \ + (vE) = (vD); \ + (vD) = (vC); \ + (vC) = (vB); \ + (vB) = (vA); \ + (vA) = tmp_h_; } while(0) + + /* Get value of W(t) from input data buffer for 0 <= t <= 15, + See FIPS PUB 180-4 section 6.2. + Input data must be read in big-endian bytes order, + see FIPS PUB 180-4 section 3.1.2. */ +#define SHA512_GET_W_FROM_DATA(buf,t) \ + MHDX_GET_64BIT_BE( \ + ((const unsigned char*) (buf)) + (t) * SHA512_256_BYTES_IN_WORD) + + /* During first 16 steps, before making any calculation on each step, the + W element is read from the input data buffer as a big-endian value and + stored in the array of W elements. */ + for(t = 0; t < 16; ++t) { + SHA2STEP64RV(a, b, c, d, e, f, g, h, K[t], \ + W[t] = SHA512_GET_W_FROM_DATA(data, t)); + } + + /* 'W' generation and assignment for 16 <= t <= 79. + See FIPS PUB 180-4 section 6.4.2. + As only the last 16 'W' are used in calculations, it is possible to + use 16 elements array of W as a cyclic buffer. + Note: ((t-16) & 15) have same value as (t & 15) */ +#define Wgen(w,t) \ + (curl_uint64_t)( (w)[(t - 16) & 15] + sig1((w)[((t) - 2) & 15]) \ + + (w)[((t) - 7) & 15] + sig0((w)[((t) - 15) & 15]) ) + + /* During the last 64 steps, before making any calculation on each step, + current W element is generated from other W elements of the cyclic + buffer and the generated value is stored back in the cyclic buffer. */ + for(t = 16; t < 80; ++t) { + SHA2STEP64RV(a, b, c, d, e, f, g, h, K[t], \ + W[t & 15] = Wgen(W, t)); + } + } + + /* Compute and store the intermediate hash. + See FIPS PUB 180-4 section 6.4.2 step 4. */ + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + H[5] += f; + H[6] += g; + H[7] += h; +} + + +/** + * Process portion of bytes. + * + * @param context the calculation context + * @param data bytes to add to hash + * @param length number of bytes in @a data + * @return always CURLE_OK + */ +static CURLcode +MHDx_sha512_256_update(void *context, + const unsigned char *data, + size_t length) +{ + unsigned int bytes_have; /**< Number of bytes in the context buffer */ + struct mhdx_sha512_256ctx *const ctx = (struct mhdx_sha512_256ctx *)context; + /* the void pointer here is required to mute Intel compiler warning */ + void *const ctx_buf = ctx->buffer; + + DEBUGASSERT((data != NULL) || (length == 0)); + + if(0 == length) + return CURLE_OK; /* Shortcut, do nothing */ + + /* Note: (count & (CURL_SHA512_256_BLOCK_SIZE-1)) + equals (count % CURL_SHA512_256_BLOCK_SIZE) for this block size. */ + bytes_have = (unsigned int) (ctx->count & (CURL_SHA512_256_BLOCK_SIZE - 1)); + ctx->count += length; + if(length > ctx->count) + ctx->count_bits_hi += 1U << 3; /* Value wrap */ + ctx->count_bits_hi += ctx->count >> 61; + ctx->count &= CURL_UINT64_C(0x1FFFFFFFFFFFFFFF); + + if(0 != bytes_have) { + unsigned int bytes_left = CURL_SHA512_256_BLOCK_SIZE - bytes_have; + if(length >= bytes_left) { + /* Combine new data with data in the buffer and process the full + block. */ + memcpy(((unsigned char *) ctx_buf) + bytes_have, + data, + bytes_left); + data += bytes_left; + length -= bytes_left; + MHDx_sha512_256_transform(ctx->H, ctx->buffer); + bytes_have = 0; + } + } + + while(CURL_SHA512_256_BLOCK_SIZE <= length) { + /* Process any full blocks of new data directly, + without copying to the buffer. */ + MHDx_sha512_256_transform(ctx->H, data); + data += CURL_SHA512_256_BLOCK_SIZE; + length -= CURL_SHA512_256_BLOCK_SIZE; + } + + if(0 != length) { + /* Copy incomplete block of new data (if any) + to the buffer. */ + memcpy(((unsigned char *) ctx_buf) + bytes_have, data, length); + } + + return CURLE_OK; +} + + + +/** + * Size of "length" insertion in bits. + * See FIPS PUB 180-4 section 5.1.2. + */ +#define SHA512_256_SIZE_OF_LEN_ADD_BITS 128 + +/** + * Size of "length" insertion in bytes. + */ +#define SHA512_256_SIZE_OF_LEN_ADD (SHA512_256_SIZE_OF_LEN_ADD_BITS / 8) + +/** + * Finalise SHA-512/256 calculation, return digest. + * + * @param context the calculation context + * @param[out] digest set to the hash, must be #CURL_SHA512_256_DIGEST_SIZE + # bytes + * @return always CURLE_OK + */ +static CURLcode +MHDx_sha512_256_finish(unsigned char *digest, + void *context) +{ + struct mhdx_sha512_256ctx *const ctx = (struct mhdx_sha512_256ctx *)context; + curl_uint64_t num_bits; /**< Number of processed bits */ + unsigned int bytes_have; /**< Number of bytes in the context buffer */ + /* the void pointer here is required to mute Intel compiler warning */ + void *const ctx_buf = ctx->buffer; + + /* Memorise the number of processed bits. + The padding and other data added here during the postprocessing must + not change the amount of hashed data. */ + num_bits = ctx->count << 3; + + /* Note: (count & (CURL_SHA512_256_BLOCK_SIZE-1)) + equals (count % CURL_SHA512_256_BLOCK_SIZE) for this block size. */ + bytes_have = (unsigned int) (ctx->count & (CURL_SHA512_256_BLOCK_SIZE - 1)); + + /* Input data must be padded with a single bit "1", then with zeros and + the finally the length of data in bits must be added as the final bytes + of the last block. + See FIPS PUB 180-4 section 5.1.2. */ + + /* Data is always processed in form of bytes (not by individual bits), + therefore position of the first padding bit in byte is always + predefined (0x80). */ + /* Buffer always have space at least for one byte (as full buffers are + processed when formed). */ + ((unsigned char *) ctx_buf)[bytes_have++] = 0x80U; + + if(CURL_SHA512_256_BLOCK_SIZE - bytes_have < SHA512_256_SIZE_OF_LEN_ADD) { + /* No space in the current block to put the total length of message. + Pad the current block with zeros and process it. */ + if(bytes_have < CURL_SHA512_256_BLOCK_SIZE) + memset(((unsigned char *) ctx_buf) + bytes_have, 0, + CURL_SHA512_256_BLOCK_SIZE - bytes_have); + /* Process the full block. */ + MHDx_sha512_256_transform(ctx->H, ctx->buffer); + /* Start the new block. */ + bytes_have = 0; + } + + /* Pad the rest of the buffer with zeros. */ + memset(((unsigned char *) ctx_buf) + bytes_have, 0, + CURL_SHA512_256_BLOCK_SIZE - SHA512_256_SIZE_OF_LEN_ADD - bytes_have); + /* Put high part of number of bits in processed message and then lower + part of number of bits as big-endian values. + See FIPS PUB 180-4 section 5.1.2. */ + /* Note: the target location is predefined and buffer is always aligned */ + MHDX_PUT_64BIT_BE(((unsigned char *) ctx_buf) \ + + CURL_SHA512_256_BLOCK_SIZE \ + - SHA512_256_SIZE_OF_LEN_ADD, \ + ctx->count_bits_hi); + MHDX_PUT_64BIT_BE(((unsigned char *) ctx_buf) \ + + CURL_SHA512_256_BLOCK_SIZE \ + - SHA512_256_SIZE_OF_LEN_ADD \ + + SHA512_256_BYTES_IN_WORD, \ + num_bits); + /* Process the full final block. */ + MHDx_sha512_256_transform(ctx->H, ctx->buffer); + + /* Put in BE mode the leftmost part of the hash as the final digest. + See FIPS PUB 180-4 section 6.7. */ + + MHDX_PUT_64BIT_BE((digest + 0 * SHA512_256_BYTES_IN_WORD), ctx->H[0]); + MHDX_PUT_64BIT_BE((digest + 1 * SHA512_256_BYTES_IN_WORD), ctx->H[1]); + MHDX_PUT_64BIT_BE((digest + 2 * SHA512_256_BYTES_IN_WORD), ctx->H[2]); + MHDX_PUT_64BIT_BE((digest + 3 * SHA512_256_BYTES_IN_WORD), ctx->H[3]); + + /* Erase potentially sensitive data. */ + memset(ctx, 0, sizeof(struct mhdx_sha512_256ctx)); + + return CURLE_OK; +} + +/* Map to the local implementation */ +#define Curl_sha512_256_init MHDx_sha512_256_init +#define Curl_sha512_256_update MHDx_sha512_256_update +#define Curl_sha512_256_finish MHDx_sha512_256_finish + +#endif /* Local SHA-512/256 code */ + + +/** + * Compute SHA-512/256 hash for the given data in one function call + * @param[out] output the pointer to put the hash + * @param[in] input the pointer to the data to process + * @param input_size the size of the data pointed by @a input + * @return always #CURLE_OK + */ +CURLcode +Curl_sha512_256it(unsigned char *output, const unsigned char *input, + size_t input_size) +{ + Curl_sha512_256_ctx ctx; + CURLcode res; + + res = Curl_sha512_256_init(&ctx); + if(res != CURLE_OK) + return res; + + res = Curl_sha512_256_update(&ctx, (const void *) input, input_size); + + if(res != CURLE_OK) { + (void) Curl_sha512_256_finish(output, &ctx); + return res; + } + + return Curl_sha512_256_finish(output, &ctx); +} + +/* Wrapper function, takes 'unsigned int' as length type, returns void */ +static void +Curl_sha512_256_update_i(void *context, + const unsigned char *data, + unsigned int length) +{ + /* Hypothetically the function may fail, but assume it does not */ + (void) Curl_sha512_256_update(context, data, length); +} + +/* Wrapper function, returns void */ +static void +Curl_sha512_256_finish_v(unsigned char *result, + void *context) +{ + /* Hypothetically the function may fail, but assume it does not */ + (void) Curl_sha512_256_finish(result, context); +} + +/* Wrapper function, takes 'unsigned int' as length type, returns void */ + +const struct HMAC_params Curl_HMAC_SHA512_256[] = { + { + /* Initialize context procedure. */ + Curl_sha512_256_init, + /* Update context with data. */ + Curl_sha512_256_update_i, + /* Get final result procedure. */ + Curl_sha512_256_finish_v, + /* Context structure size. */ + sizeof(Curl_sha512_256_ctx), + /* Maximum key length (bytes). */ + CURL_SHA512_256_BLOCK_SIZE, + /* Result length (bytes). */ + CURL_SHA512_256_DIGEST_SIZE + } +}; + +#endif /* !CURL_DISABLE_DIGEST_AUTH && !CURL_DISABLE_SHA512_256 */ diff --git a/deps/curl/lib/curl_sha512_256.h b/deps/curl/lib/curl_sha512_256.h new file mode 100644 index 00000000000..a84e77bc303 --- /dev/null +++ b/deps/curl/lib/curl_sha512_256.h @@ -0,0 +1,44 @@ +#ifndef HEADER_CURL_SHA512_256_H +#define HEADER_CURL_SHA512_256_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Evgeny Grin (Karlson2k), . + * + * 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.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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#if !defined(CURL_DISABLE_DIGEST_AUTH) && !defined(CURL_DISABLE_SHA512_256) + +#include +#include "curl_hmac.h" + +#define CURL_HAVE_SHA512_256 + +extern const struct HMAC_params Curl_HMAC_SHA512_256[1]; + +#define CURL_SHA512_256_DIGEST_LENGTH 32 + +CURLcode +Curl_sha512_256it(unsigned char *output, const unsigned char *input, + size_t input_size); + +#endif /* !CURL_DISABLE_DIGEST_AUTH && !CURL_DISABLE_SHA512_256 */ + +#endif /* HEADER_CURL_SHA256_H */ diff --git a/deps/curl/lib/curl_sspi.c b/deps/curl/lib/curl_sspi.c index eb21e7e2b09..680bb661b28 100644 --- a/deps/curl/lib/curl_sspi.c +++ b/deps/curl/lib/curl_sspi.c @@ -52,10 +52,10 @@ typedef PSecurityFunctionTable (APIENTRY *INITSECURITYINTERFACE_FN)(VOID); #endif /* Handle of security.dll or secur32.dll, depending on Windows version */ -HMODULE s_hSecDll = NULL; +HMODULE Curl_hSecDll = NULL; /* Pointer to SSPI dispatch table */ -PSecurityFunctionTable s_pSecFn = NULL; +PSecurityFunctionTable Curl_pSecFn = NULL; /* * Curl_sspi_global_init() @@ -79,29 +79,29 @@ CURLcode Curl_sspi_global_init(void) INITSECURITYINTERFACE_FN pInitSecurityInterface; /* If security interface is not yet initialized try to do this */ - if(!s_hSecDll) { + if(!Curl_hSecDll) { /* Security Service Provider Interface (SSPI) functions are located in * security.dll on WinNT 4.0 and in secur32.dll on Win9x. Win2K and XP * have both these DLLs (security.dll forwards calls to secur32.dll) */ /* Load SSPI dll into the address space of the calling process */ if(curlx_verify_windows_version(4, 0, 0, PLATFORM_WINNT, VERSION_EQUAL)) - s_hSecDll = Curl_load_library(TEXT("security.dll")); + Curl_hSecDll = Curl_load_library(TEXT("security.dll")); else - s_hSecDll = Curl_load_library(TEXT("secur32.dll")); - if(!s_hSecDll) + Curl_hSecDll = Curl_load_library(TEXT("secur32.dll")); + if(!Curl_hSecDll) return CURLE_FAILED_INIT; /* Get address of the InitSecurityInterfaceA function from the SSPI dll */ pInitSecurityInterface = CURLX_FUNCTION_CAST(INITSECURITYINTERFACE_FN, - (GetProcAddress(s_hSecDll, SECURITYENTRYPOINT))); + (GetProcAddress(Curl_hSecDll, SECURITYENTRYPOINT))); if(!pInitSecurityInterface) return CURLE_FAILED_INIT; /* Get pointer to Security Service Provider Interface dispatch table */ - s_pSecFn = pInitSecurityInterface(); - if(!s_pSecFn) + Curl_pSecFn = pInitSecurityInterface(); + if(!Curl_pSecFn) return CURLE_FAILED_INIT; } @@ -119,10 +119,10 @@ CURLcode Curl_sspi_global_init(void) */ void Curl_sspi_global_cleanup(void) { - if(s_hSecDll) { - FreeLibrary(s_hSecDll); - s_hSecDll = NULL; - s_pSecFn = NULL; + if(Curl_hSecDll) { + FreeLibrary(Curl_hSecDll); + Curl_hSecDll = NULL; + Curl_pSecFn = NULL; } } @@ -134,7 +134,7 @@ void Curl_sspi_global_cleanup(void) * * Parameters: * - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * identity [in/out] - The identity structure. * diff --git a/deps/curl/lib/curl_sspi.h b/deps/curl/lib/curl_sspi.h index 9816d59c842..535a1ff650a 100644 --- a/deps/curl/lib/curl_sspi.h +++ b/deps/curl/lib/curl_sspi.h @@ -57,8 +57,8 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity); /* Forward-declaration of global variables defined in curl_sspi.c */ -extern HMODULE s_hSecDll; -extern PSecurityFunctionTable s_pSecFn; +extern HMODULE Curl_hSecDll; +extern PSecurityFunctionTable Curl_pSecFn; /* Provide some definitions missing in old headers */ #define SP_NAME_DIGEST "WDigest" @@ -70,227 +70,6 @@ extern PSecurityFunctionTable s_pSecFn; #define ISC_REQ_USE_HTTP_STYLE 0x01000000 #endif -#ifndef ISC_RET_REPLAY_DETECT -#define ISC_RET_REPLAY_DETECT 0x00000004 -#endif - -#ifndef ISC_RET_SEQUENCE_DETECT -#define ISC_RET_SEQUENCE_DETECT 0x00000008 -#endif - -#ifndef ISC_RET_CONFIDENTIALITY -#define ISC_RET_CONFIDENTIALITY 0x00000010 -#endif - -#ifndef ISC_RET_ALLOCATED_MEMORY -#define ISC_RET_ALLOCATED_MEMORY 0x00000100 -#endif - -#ifndef ISC_RET_STREAM -#define ISC_RET_STREAM 0x00008000 -#endif - -#ifndef SEC_E_INSUFFICIENT_MEMORY -# define SEC_E_INSUFFICIENT_MEMORY ((HRESULT)0x80090300L) -#endif -#ifndef SEC_E_INVALID_HANDLE -# define SEC_E_INVALID_HANDLE ((HRESULT)0x80090301L) -#endif -#ifndef SEC_E_UNSUPPORTED_FUNCTION -# define SEC_E_UNSUPPORTED_FUNCTION ((HRESULT)0x80090302L) -#endif -#ifndef SEC_E_TARGET_UNKNOWN -# define SEC_E_TARGET_UNKNOWN ((HRESULT)0x80090303L) -#endif -#ifndef SEC_E_INTERNAL_ERROR -# define SEC_E_INTERNAL_ERROR ((HRESULT)0x80090304L) -#endif -#ifndef SEC_E_SECPKG_NOT_FOUND -# define SEC_E_SECPKG_NOT_FOUND ((HRESULT)0x80090305L) -#endif -#ifndef SEC_E_NOT_OWNER -# define SEC_E_NOT_OWNER ((HRESULT)0x80090306L) -#endif -#ifndef SEC_E_CANNOT_INSTALL -# define SEC_E_CANNOT_INSTALL ((HRESULT)0x80090307L) -#endif -#ifndef SEC_E_INVALID_TOKEN -# define SEC_E_INVALID_TOKEN ((HRESULT)0x80090308L) -#endif -#ifndef SEC_E_CANNOT_PACK -# define SEC_E_CANNOT_PACK ((HRESULT)0x80090309L) -#endif -#ifndef SEC_E_QOP_NOT_SUPPORTED -# define SEC_E_QOP_NOT_SUPPORTED ((HRESULT)0x8009030AL) -#endif -#ifndef SEC_E_NO_IMPERSONATION -# define SEC_E_NO_IMPERSONATION ((HRESULT)0x8009030BL) -#endif -#ifndef SEC_E_LOGON_DENIED -# define SEC_E_LOGON_DENIED ((HRESULT)0x8009030CL) -#endif -#ifndef SEC_E_UNKNOWN_CREDENTIALS -# define SEC_E_UNKNOWN_CREDENTIALS ((HRESULT)0x8009030DL) -#endif -#ifndef SEC_E_NO_CREDENTIALS -# define SEC_E_NO_CREDENTIALS ((HRESULT)0x8009030EL) -#endif -#ifndef SEC_E_MESSAGE_ALTERED -# define SEC_E_MESSAGE_ALTERED ((HRESULT)0x8009030FL) -#endif -#ifndef SEC_E_OUT_OF_SEQUENCE -# define SEC_E_OUT_OF_SEQUENCE ((HRESULT)0x80090310L) -#endif -#ifndef SEC_E_NO_AUTHENTICATING_AUTHORITY -# define SEC_E_NO_AUTHENTICATING_AUTHORITY ((HRESULT)0x80090311L) -#endif -#ifndef SEC_E_BAD_PKGID -# define SEC_E_BAD_PKGID ((HRESULT)0x80090316L) -#endif -#ifndef SEC_E_CONTEXT_EXPIRED -# define SEC_E_CONTEXT_EXPIRED ((HRESULT)0x80090317L) -#endif -#ifndef SEC_E_INCOMPLETE_MESSAGE -# define SEC_E_INCOMPLETE_MESSAGE ((HRESULT)0x80090318L) -#endif -#ifndef SEC_E_INCOMPLETE_CREDENTIALS -# define SEC_E_INCOMPLETE_CREDENTIALS ((HRESULT)0x80090320L) -#endif -#ifndef SEC_E_BUFFER_TOO_SMALL -# define SEC_E_BUFFER_TOO_SMALL ((HRESULT)0x80090321L) -#endif -#ifndef SEC_E_WRONG_PRINCIPAL -# define SEC_E_WRONG_PRINCIPAL ((HRESULT)0x80090322L) -#endif -#ifndef SEC_E_TIME_SKEW -# define SEC_E_TIME_SKEW ((HRESULT)0x80090324L) -#endif -#ifndef SEC_E_UNTRUSTED_ROOT -# define SEC_E_UNTRUSTED_ROOT ((HRESULT)0x80090325L) -#endif -#ifndef SEC_E_ILLEGAL_MESSAGE -# define SEC_E_ILLEGAL_MESSAGE ((HRESULT)0x80090326L) -#endif -#ifndef SEC_E_CERT_UNKNOWN -# define SEC_E_CERT_UNKNOWN ((HRESULT)0x80090327L) -#endif -#ifndef SEC_E_CERT_EXPIRED -# define SEC_E_CERT_EXPIRED ((HRESULT)0x80090328L) -#endif -#ifndef SEC_E_ENCRYPT_FAILURE -# define SEC_E_ENCRYPT_FAILURE ((HRESULT)0x80090329L) -#endif -#ifndef SEC_E_DECRYPT_FAILURE -# define SEC_E_DECRYPT_FAILURE ((HRESULT)0x80090330L) -#endif -#ifndef SEC_E_ALGORITHM_MISMATCH -# define SEC_E_ALGORITHM_MISMATCH ((HRESULT)0x80090331L) -#endif -#ifndef SEC_E_SECURITY_QOS_FAILED -# define SEC_E_SECURITY_QOS_FAILED ((HRESULT)0x80090332L) -#endif -#ifndef SEC_E_UNFINISHED_CONTEXT_DELETED -# define SEC_E_UNFINISHED_CONTEXT_DELETED ((HRESULT)0x80090333L) -#endif -#ifndef SEC_E_NO_TGT_REPLY -# define SEC_E_NO_TGT_REPLY ((HRESULT)0x80090334L) -#endif -#ifndef SEC_E_NO_IP_ADDRESSES -# define SEC_E_NO_IP_ADDRESSES ((HRESULT)0x80090335L) -#endif -#ifndef SEC_E_WRONG_CREDENTIAL_HANDLE -# define SEC_E_WRONG_CREDENTIAL_HANDLE ((HRESULT)0x80090336L) -#endif -#ifndef SEC_E_CRYPTO_SYSTEM_INVALID -# define SEC_E_CRYPTO_SYSTEM_INVALID ((HRESULT)0x80090337L) -#endif -#ifndef SEC_E_MAX_REFERRALS_EXCEEDED -# define SEC_E_MAX_REFERRALS_EXCEEDED ((HRESULT)0x80090338L) -#endif -#ifndef SEC_E_MUST_BE_KDC -# define SEC_E_MUST_BE_KDC ((HRESULT)0x80090339L) -#endif -#ifndef SEC_E_STRONG_CRYPTO_NOT_SUPPORTED -# define SEC_E_STRONG_CRYPTO_NOT_SUPPORTED ((HRESULT)0x8009033AL) -#endif -#ifndef SEC_E_TOO_MANY_PRINCIPALS -# define SEC_E_TOO_MANY_PRINCIPALS ((HRESULT)0x8009033BL) -#endif -#ifndef SEC_E_NO_PA_DATA -# define SEC_E_NO_PA_DATA ((HRESULT)0x8009033CL) -#endif -#ifndef SEC_E_PKINIT_NAME_MISMATCH -# define SEC_E_PKINIT_NAME_MISMATCH ((HRESULT)0x8009033DL) -#endif -#ifndef SEC_E_SMARTCARD_LOGON_REQUIRED -# define SEC_E_SMARTCARD_LOGON_REQUIRED ((HRESULT)0x8009033EL) -#endif -#ifndef SEC_E_SHUTDOWN_IN_PROGRESS -# define SEC_E_SHUTDOWN_IN_PROGRESS ((HRESULT)0x8009033FL) -#endif -#ifndef SEC_E_KDC_INVALID_REQUEST -# define SEC_E_KDC_INVALID_REQUEST ((HRESULT)0x80090340L) -#endif -#ifndef SEC_E_KDC_UNABLE_TO_REFER -# define SEC_E_KDC_UNABLE_TO_REFER ((HRESULT)0x80090341L) -#endif -#ifndef SEC_E_KDC_UNKNOWN_ETYPE -# define SEC_E_KDC_UNKNOWN_ETYPE ((HRESULT)0x80090342L) -#endif -#ifndef SEC_E_UNSUPPORTED_PREAUTH -# define SEC_E_UNSUPPORTED_PREAUTH ((HRESULT)0x80090343L) -#endif -#ifndef SEC_E_DELEGATION_REQUIRED -# define SEC_E_DELEGATION_REQUIRED ((HRESULT)0x80090345L) -#endif -#ifndef SEC_E_BAD_BINDINGS -# define SEC_E_BAD_BINDINGS ((HRESULT)0x80090346L) -#endif -#ifndef SEC_E_MULTIPLE_ACCOUNTS -# define SEC_E_MULTIPLE_ACCOUNTS ((HRESULT)0x80090347L) -#endif -#ifndef SEC_E_NO_KERB_KEY -# define SEC_E_NO_KERB_KEY ((HRESULT)0x80090348L) -#endif -#ifndef SEC_E_CERT_WRONG_USAGE -# define SEC_E_CERT_WRONG_USAGE ((HRESULT)0x80090349L) -#endif -#ifndef SEC_E_DOWNGRADE_DETECTED -# define SEC_E_DOWNGRADE_DETECTED ((HRESULT)0x80090350L) -#endif -#ifndef SEC_E_SMARTCARD_CERT_REVOKED -# define SEC_E_SMARTCARD_CERT_REVOKED ((HRESULT)0x80090351L) -#endif -#ifndef SEC_E_ISSUING_CA_UNTRUSTED -# define SEC_E_ISSUING_CA_UNTRUSTED ((HRESULT)0x80090352L) -#endif -#ifndef SEC_E_REVOCATION_OFFLINE_C -# define SEC_E_REVOCATION_OFFLINE_C ((HRESULT)0x80090353L) -#endif -#ifndef SEC_E_PKINIT_CLIENT_FAILURE -# define SEC_E_PKINIT_CLIENT_FAILURE ((HRESULT)0x80090354L) -#endif -#ifndef SEC_E_SMARTCARD_CERT_EXPIRED -# define SEC_E_SMARTCARD_CERT_EXPIRED ((HRESULT)0x80090355L) -#endif -#ifndef SEC_E_NO_S4U_PROT_SUPPORT -# define SEC_E_NO_S4U_PROT_SUPPORT ((HRESULT)0x80090356L) -#endif -#ifndef SEC_E_CROSSREALM_DELEGATION_FAILURE -# define SEC_E_CROSSREALM_DELEGATION_FAILURE ((HRESULT)0x80090357L) -#endif -#ifndef SEC_E_REVOCATION_OFFLINE_KDC -# define SEC_E_REVOCATION_OFFLINE_KDC ((HRESULT)0x80090358L) -#endif -#ifndef SEC_E_ISSUING_CA_UNTRUSTED_KDC -# define SEC_E_ISSUING_CA_UNTRUSTED_KDC ((HRESULT)0x80090359L) -#endif -#ifndef SEC_E_KDC_CERT_EXPIRED -# define SEC_E_KDC_CERT_EXPIRED ((HRESULT)0x8009035AL) -#endif -#ifndef SEC_E_KDC_CERT_REVOKED -# define SEC_E_KDC_CERT_REVOKED ((HRESULT)0x8009035BL) -#endif #ifndef SEC_E_INVALID_PARAMETER # define SEC_E_INVALID_PARAMETER ((HRESULT)0x8009035DL) #endif @@ -301,30 +80,6 @@ extern PSecurityFunctionTable s_pSecFn; # define SEC_E_POLICY_NLTM_ONLY ((HRESULT)0x8009035FL) #endif -#ifndef SEC_I_CONTINUE_NEEDED -# define SEC_I_CONTINUE_NEEDED ((HRESULT)0x00090312L) -#endif -#ifndef SEC_I_COMPLETE_NEEDED -# define SEC_I_COMPLETE_NEEDED ((HRESULT)0x00090313L) -#endif -#ifndef SEC_I_COMPLETE_AND_CONTINUE -# define SEC_I_COMPLETE_AND_CONTINUE ((HRESULT)0x00090314L) -#endif -#ifndef SEC_I_LOCAL_LOGON -# define SEC_I_LOCAL_LOGON ((HRESULT)0x00090315L) -#endif -#ifndef SEC_I_CONTEXT_EXPIRED -# define SEC_I_CONTEXT_EXPIRED ((HRESULT)0x00090317L) -#endif -#ifndef SEC_I_INCOMPLETE_CREDENTIALS -# define SEC_I_INCOMPLETE_CREDENTIALS ((HRESULT)0x00090320L) -#endif -#ifndef SEC_I_RENEGOTIATE -# define SEC_I_RENEGOTIATE ((HRESULT)0x00090321L) -#endif -#ifndef SEC_I_NO_LSA_CONTEXT -# define SEC_I_NO_LSA_CONTEXT ((HRESULT)0x00090323L) -#endif #ifndef SEC_I_SIGNATURE_NEEDED # define SEC_I_SIGNATURE_NEEDED ((HRESULT)0x0009035CL) #endif @@ -333,6 +88,22 @@ extern PSecurityFunctionTable s_pSecFn; # define CRYPT_E_REVOKED ((HRESULT)0x80092010L) #endif +#ifndef CRYPT_E_NO_REVOCATION_DLL +# define CRYPT_E_NO_REVOCATION_DLL ((HRESULT)0x80092011L) +#endif + +#ifndef CRYPT_E_NO_REVOCATION_CHECK +# define CRYPT_E_NO_REVOCATION_CHECK ((HRESULT)0x80092012L) +#endif + +#ifndef CRYPT_E_REVOCATION_OFFLINE +# define CRYPT_E_REVOCATION_OFFLINE ((HRESULT)0x80092013L) +#endif + +#ifndef CRYPT_E_NOT_IN_REVOCATION_DATABASE +# define CRYPT_E_NOT_IN_REVOCATION_DATABASE ((HRESULT)0x80092014L) +#endif + #ifdef UNICODE # define SECFLAG_WINNT_AUTH_IDENTITY \ (unsigned long)SEC_WINNT_AUTH_IDENTITY_UNICODE diff --git a/deps/curl/lib/curl_threads.c b/deps/curl/lib/curl_threads.c index e13e2947c2c..fbbbf9b2d32 100644 --- a/deps/curl/lib/curl_threads.c +++ b/deps/curl/lib/curl_threads.c @@ -35,7 +35,9 @@ #endif #include "curl_threads.h" +#ifdef BUILDING_LIBCURL #include "curl_memory.h" +#endif /* The last #include file should be: */ #include "memdebug.h" @@ -100,20 +102,23 @@ int Curl_thread_join(curl_thread_t *hnd) #elif defined(USE_THREADS_WIN32) -/* !checksrc! disable SPACEBEFOREPAREN 1 */ -curl_thread_t Curl_thread_create(unsigned int (CURL_STDCALL *func) (void *), +curl_thread_t Curl_thread_create( +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) + DWORD +#else + unsigned int +#endif + (CURL_STDCALL *func) (void *), void *arg) { -#ifdef _WIN32_WCE +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) typedef HANDLE curl_win_thread_handle_t; -#elif defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) - typedef unsigned long curl_win_thread_handle_t; #else typedef uintptr_t curl_win_thread_handle_t; #endif curl_thread_t t; curl_win_thread_handle_t thread_handle; -#ifdef _WIN32_WCE +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) thread_handle = CreateThread(NULL, 0, func, arg, 0, NULL); #else thread_handle = _beginthreadex(NULL, 0, func, arg, 0, NULL); @@ -133,7 +138,8 @@ curl_thread_t Curl_thread_create(unsigned int (CURL_STDCALL *func) (void *), void Curl_thread_destroy(curl_thread_t hnd) { - CloseHandle(hnd); + if(hnd != curl_thread_t_null) + CloseHandle(hnd); } int Curl_thread_join(curl_thread_t *hnd) diff --git a/deps/curl/lib/curl_threads.h b/deps/curl/lib/curl_threads.h index facbc73705f..c9f18a4e09e 100644 --- a/deps/curl/lib/curl_threads.h +++ b/deps/curl/lib/curl_threads.h @@ -40,8 +40,7 @@ # define curl_thread_t HANDLE # define curl_thread_t_null (HANDLE)0 # if !defined(_WIN32_WINNT) || !defined(_WIN32_WINNT_VISTA) || \ - (_WIN32_WINNT < _WIN32_WINNT_VISTA) || \ - (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) + (_WIN32_WINNT < _WIN32_WINNT_VISTA) # define Curl_mutex_init(m) InitializeCriticalSection(m) # else # define Curl_mutex_init(m) InitializeCriticalSectionEx(m, 0, 1) @@ -53,8 +52,13 @@ #if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) -/* !checksrc! disable SPACEBEFOREPAREN 1 */ -curl_thread_t Curl_thread_create(unsigned int (CURL_STDCALL *func) (void *), +curl_thread_t Curl_thread_create( +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) + DWORD +#else + unsigned int +#endif + (CURL_STDCALL *func) (void *), void *arg); void Curl_thread_destroy(curl_thread_t hnd); diff --git a/deps/curl/lib/curl_trc.c b/deps/curl/lib/curl_trc.c index 76c35ba386a..8f1be07e7d4 100644 --- a/deps/curl/lib/curl_trc.c +++ b/deps/curl/lib/curl_trc.c @@ -36,6 +36,7 @@ #include "cf-socket.h" #include "connect.h" +#include "doh.h" #include "http2.h" #include "http_proxy.h" #include "cf-h1-proxy.h" @@ -52,6 +53,9 @@ #include "curl_memory.h" #include "memdebug.h" +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif void Curl_debug(struct Curl_easy *data, curl_infotype type, char *ptr, size_t size) @@ -61,7 +65,7 @@ void Curl_debug(struct Curl_easy *data, curl_infotype type, "* ", "< ", "> ", "{ ", "} ", "{ ", "} " }; if(data->set.fdebug) { bool inCallback = Curl_is_in_callback(data); - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); (void)(*data->set.fdebug)(data, type, ptr, size, data->set.debugdata); Curl_set_in_callback(data, inCallback); } @@ -105,36 +109,60 @@ void Curl_failf(struct Curl_easy *data, const char *fmt, ...) } } +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* Curl_infof() is for info message along the way */ #define MAXINFO 2048 +static void trc_infof(struct Curl_easy *data, struct curl_trc_feat *feat, + const char * const fmt, va_list ap) CURL_PRINTF(3, 0); + +static void trc_infof(struct Curl_easy *data, struct curl_trc_feat *feat, + const char * const fmt, va_list ap) +{ + int len = 0; + char buffer[MAXINFO + 5]; + if(feat) + len = msnprintf(buffer, (MAXINFO + 1), "[%s] ", feat->name); + len += mvsnprintf(buffer + len, (MAXINFO + 1) - len, fmt, ap); + if(len >= MAXINFO) { /* too long, shorten with '...' */ + --len; + buffer[len++] = '.'; + buffer[len++] = '.'; + buffer[len++] = '.'; + } + buffer[len++] = '\n'; + buffer[len] = '\0'; + Curl_debug(data, CURLINFO_TEXT, buffer, len); +} + void Curl_infof(struct Curl_easy *data, const char *fmt, ...) { DEBUGASSERT(!strchr(fmt, '\n')); - if(data && data->set.verbose) { + if(Curl_trc_is_verbose(data)) { va_list ap; - int len; - char buffer[MAXINFO + 2]; va_start(ap, fmt); - len = mvsnprintf(buffer, MAXINFO, fmt, ap); + trc_infof(data, data->state.feat, fmt, ap); va_end(ap); - buffer[len++] = '\n'; - buffer[len] = '\0'; - Curl_debug(data, CURLINFO_TEXT, buffer, len); } } -#if !defined(CURL_DISABLE_VERBOSE_STRINGS) - void Curl_trc_cf_infof(struct Curl_easy *data, struct Curl_cfilter *cf, const char *fmt, ...) { DEBUGASSERT(cf); - if(data && Curl_trc_cf_is_verbose(cf, data)) { + if(Curl_trc_cf_is_verbose(cf, data)) { va_list ap; - int len; + int len = 0; char buffer[MAXINFO + 2]; - len = msnprintf(buffer, MAXINFO, "[%s] ", cf->cft->name); + if(data->state.feat) + len += msnprintf(buffer + len, MAXINFO - len, "[%s] ", + data->state.feat->name); + if(cf->sockindex) + len += msnprintf(buffer + len, MAXINFO - len, "[%s-%d] ", + cf->cft->name, cf->sockindex); + else + len += msnprintf(buffer + len, MAXINFO - len, "[%s] ", cf->cft->name); va_start(ap, fmt); len += mvsnprintf(buffer + len, MAXINFO - len, fmt, ap); va_end(ap); @@ -144,45 +172,193 @@ void Curl_trc_cf_infof(struct Curl_easy *data, struct Curl_cfilter *cf, } } +struct curl_trc_feat Curl_trc_feat_read = { + "READ", + CURL_LOG_LVL_NONE, +}; +struct curl_trc_feat Curl_trc_feat_write = { + "WRITE", + CURL_LOG_LVL_NONE, +}; + +void Curl_trc_read(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_read)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_read, fmt, ap); + va_end(ap); + } +} + +void Curl_trc_write(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_write)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_write, fmt, ap); + va_end(ap); + } +} + +#ifndef CURL_DISABLE_FTP +struct curl_trc_feat Curl_trc_feat_ftp = { + "FTP", + CURL_LOG_LVL_NONE, +}; + +void Curl_trc_ftp(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ftp)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_ftp, fmt, ap); + va_end(ap); + } +} +#endif /* !CURL_DISABLE_FTP */ + +#ifndef CURL_DISABLE_SMTP +struct curl_trc_feat Curl_trc_feat_smtp = { + "SMTP", + CURL_LOG_LVL_NONE, +}; + +void Curl_trc_smtp(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_smtp)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_smtp, fmt, ap); + va_end(ap); + } +} +#endif /* !CURL_DISABLE_SMTP */ + +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) +struct curl_trc_feat Curl_trc_feat_ws = { + "WS", + CURL_LOG_LVL_NONE, +}; -static struct Curl_cftype *cf_types[] = { - &Curl_cft_tcp, - &Curl_cft_udp, - &Curl_cft_unix, - &Curl_cft_tcp_accept, - &Curl_cft_happy_eyeballs, - &Curl_cft_setup, +void Curl_trc_ws(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ws)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_ws, fmt, ap); + va_end(ap); + } +} +#endif /* !CURL_DISABLE_WEBSOCKETS && !CURL_DISABLE_HTTP */ + +#define TRC_CT_NONE (0) +#define TRC_CT_PROTOCOL (1<<(0)) +#define TRC_CT_NETWORK (1<<(1)) +#define TRC_CT_PROXY (1<<(2)) + +struct trc_feat_def { + struct curl_trc_feat *feat; + unsigned int category; +}; + +static struct trc_feat_def trc_feats[] = { + { &Curl_trc_feat_read, TRC_CT_NONE }, + { &Curl_trc_feat_write, TRC_CT_NONE }, +#ifndef CURL_DISABLE_FTP + { &Curl_trc_feat_ftp, TRC_CT_PROTOCOL }, +#endif +#ifndef CURL_DISABLE_DOH + { &Curl_doh_trc, TRC_CT_NETWORK }, +#endif +#ifndef CURL_DISABLE_SMTP + { &Curl_trc_feat_smtp, TRC_CT_PROTOCOL }, +#endif +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) + { &Curl_trc_feat_ws, TRC_CT_PROTOCOL }, +#endif +}; + +struct trc_cft_def { + struct Curl_cftype *cft; + unsigned int category; +}; + +static struct trc_cft_def trc_cfts[] = { + { &Curl_cft_tcp, TRC_CT_NETWORK }, + { &Curl_cft_udp, TRC_CT_NETWORK }, + { &Curl_cft_unix, TRC_CT_NETWORK }, + { &Curl_cft_tcp_accept, TRC_CT_NETWORK }, + { &Curl_cft_happy_eyeballs, TRC_CT_NETWORK }, + { &Curl_cft_setup, TRC_CT_PROTOCOL }, #ifdef USE_NGHTTP2 - &Curl_cft_nghttp2, + { &Curl_cft_nghttp2, TRC_CT_PROTOCOL }, #endif #ifdef USE_SSL - &Curl_cft_ssl, - &Curl_cft_ssl_proxy, + { &Curl_cft_ssl, TRC_CT_NETWORK }, +#ifndef CURL_DISABLE_PROXY + { &Curl_cft_ssl_proxy, TRC_CT_PROXY }, +#endif #endif #if !defined(CURL_DISABLE_PROXY) #if !defined(CURL_DISABLE_HTTP) - &Curl_cft_h1_proxy, + { &Curl_cft_h1_proxy, TRC_CT_PROXY }, #ifdef USE_NGHTTP2 - &Curl_cft_h2_proxy, + { &Curl_cft_h2_proxy, TRC_CT_PROXY }, #endif - &Curl_cft_http_proxy, + { &Curl_cft_http_proxy, TRC_CT_PROXY }, #endif /* !CURL_DISABLE_HTTP */ - &Curl_cft_haproxy, - &Curl_cft_socks_proxy, + { &Curl_cft_haproxy, TRC_CT_PROXY }, + { &Curl_cft_socks_proxy, TRC_CT_PROXY }, #endif /* !CURL_DISABLE_PROXY */ -#ifdef ENABLE_QUIC - &Curl_cft_http3, +#ifdef USE_HTTP3 + { &Curl_cft_http3, TRC_CT_PROTOCOL }, #endif #if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) - &Curl_cft_http_connect, + { &Curl_cft_http_connect, TRC_CT_PROTOCOL }, #endif - NULL, }; -CURLcode Curl_trc_opt(const char *config) +static void trc_apply_level_by_name(const char * const token, int lvl) { - char *token, *tok_buf, *tmp; size_t i; + + for(i = 0; i < ARRAYSIZE(trc_cfts); ++i) { + if(strcasecompare(token, trc_cfts[i].cft->name)) { + trc_cfts[i].cft->log_level = lvl; + break; + } + } + for(i = 0; i < ARRAYSIZE(trc_feats); ++i) { + if(strcasecompare(token, trc_feats[i].feat->name)) { + trc_feats[i].feat->log_level = lvl; + break; + } + } +} + +static void trc_apply_level_by_category(int category, int lvl) +{ + size_t i; + + for(i = 0; i < ARRAYSIZE(trc_cfts); ++i) { + if(!category || (trc_cfts[i].category & category)) + trc_cfts[i].cft->log_level = lvl; + } + for(i = 0; i < ARRAYSIZE(trc_feats); ++i) { + if(!category || (trc_feats[i].category & category)) + trc_feats[i].feat->log_level = lvl; + } +} + +static CURLcode trc_opt(const char *config) +{ + char *token, *tok_buf, *tmp; int lvl; tmp = strdup(config); @@ -204,48 +380,51 @@ CURLcode Curl_trc_opt(const char *config) lvl = CURL_LOG_LVL_INFO; break; } - for(i = 0; cf_types[i]; ++i) { - if(strcasecompare(token, "all")) { - cf_types[i]->log_level = lvl; - } - else if(strcasecompare(token, cf_types[i]->name)) { - cf_types[i]->log_level = lvl; - break; - } - } + if(strcasecompare(token, "all")) + trc_apply_level_by_category(TRC_CT_NONE, lvl); + else if(strcasecompare(token, "protocol")) + trc_apply_level_by_category(TRC_CT_PROTOCOL, lvl); + else if(strcasecompare(token, "network")) + trc_apply_level_by_category(TRC_CT_NETWORK, lvl); + else if(strcasecompare(token, "proxy")) + trc_apply_level_by_category(TRC_CT_PROXY, lvl); + else + trc_apply_level_by_name(token, lvl); + token = strtok_r(NULL, ", ", &tok_buf); } free(tmp); return CURLE_OK; } -CURLcode Curl_trc_init(void) +CURLcode Curl_trc_opt(const char *config) { + CURLcode result = config ? trc_opt(config) : CURLE_OK; #ifdef DEBUGBUILD - /* WIP: we use the auto-init from an env var only in DEBUG builds for - * convenience. */ - const char *config = getenv("CURL_DEBUG"); - if(config) { - return Curl_trc_opt(config); + /* CURL_DEBUG can override anything */ + if(!result) { + const char *dbg_config = getenv("CURL_DEBUG"); + if(dbg_config) + result = trc_opt(dbg_config); } -#endif - return CURLE_OK; +#endif /* DEBUGBUILD */ + return result; } -#else /* !CURL_DISABLE_VERBOSE_STRINGS) */ CURLcode Curl_trc_init(void) { +#ifdef DEBUGBUILD + return Curl_trc_opt(NULL); +#else return CURLE_OK; +#endif } -#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) -void Curl_trc_cf_infof(struct Curl_easy *data, struct Curl_cfilter *cf, - const char *fmt, ...) +#else /* defined(CURL_DISABLE_VERBOSE_STRINGS) */ + +CURLcode Curl_trc_init(void) { - (void)data; - (void)cf; - (void)fmt; + return CURLE_OK; } -#endif -#endif /* !DEBUGBUILD */ +#endif /* !defined(CURL_DISABLE_VERBOSE_STRINGS) */ diff --git a/deps/curl/lib/curl_trc.h b/deps/curl/lib/curl_trc.h index 84b5471d859..67a7f4aa10c 100644 --- a/deps/curl/lib/curl_trc.h +++ b/deps/curl/lib/curl_trc.h @@ -54,97 +54,176 @@ CURLcode Curl_trc_opt(const char *config); void Curl_debug(struct Curl_easy *data, curl_infotype type, char *ptr, size_t size); -/** - * Output an informational message when transfer's verbose logging is enabled. - */ -void Curl_infof(struct Curl_easy *data, -#if defined(__GNUC__) && !defined(printf) && \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ - !defined(__MINGW32__) - const char *fmt, ...) - __attribute__((format(printf, 2, 3))); -#else - const char *fmt, ...); -#endif - /** * Output a failure message on registered callbacks for transfer. */ void Curl_failf(struct Curl_easy *data, -#if defined(__GNUC__) && !defined(printf) && \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ - !defined(__MINGW32__) - const char *fmt, ...) - __attribute__((format(printf, 2, 3))); -#else - const char *fmt, ...); -#endif + const char *fmt, ...) CURL_PRINTF(2, 3); #define failf Curl_failf -/** - * Output an informational message when both transfer's verbose logging - * and connection filters verbose logging are enabled. - */ -void Curl_trc_cf_infof(struct Curl_easy *data, struct Curl_cfilter *cf, -#if defined(__GNUC__) && !defined(printf) && \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ - !defined(__MINGW32__) - const char *fmt, ...) - __attribute__((format(printf, 3, 4))); -#else - const char *fmt, ...); -#endif - #define CURL_LOG_LVL_NONE 0 #define CURL_LOG_LVL_INFO 1 -#if !defined(CURL_DISABLE_VERBOSE_STRINGS) -/* informational messages enabled */ - -#define Curl_trc_is_verbose(data) ((data) && (data)->set.verbose) -#define Curl_trc_cf_is_verbose(cf, data) \ - ((data) && (data)->set.verbose && \ - (cf) && (cf)->cft->log_level >= CURL_LOG_LVL_INFO) - -/* explainer: we have some mix configuration and werror settings - * that define HAVE_VARIADIC_MACROS_C99 even though C89 is enforced - * on gnuc and some other compiler. Need to treat carefully. - */ -#if defined(HAVE_VARIADIC_MACROS_C99) && \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define CURL_HAVE_C99 +#endif +#ifdef CURL_HAVE_C99 #define infof(data, ...) \ do { if(Curl_trc_is_verbose(data)) \ Curl_infof(data, __VA_ARGS__); } while(0) #define CURL_TRC_CF(data, cf, ...) \ do { if(Curl_trc_cf_is_verbose(cf, data)) \ Curl_trc_cf_infof(data, cf, __VA_ARGS__); } while(0) +#define CURL_TRC_WRITE(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_write)) \ + Curl_trc_write(data, __VA_ARGS__); } while(0) +#define CURL_TRC_READ(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_read)) \ + Curl_trc_read(data, __VA_ARGS__); } while(0) + +#ifndef CURL_DISABLE_FTP +#define CURL_TRC_FTP(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ftp)) \ + Curl_trc_ftp(data, __VA_ARGS__); } while(0) +#endif /* !CURL_DISABLE_FTP */ +#ifndef CURL_DISABLE_SMTP +#define CURL_TRC_SMTP(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_smtp)) \ + Curl_trc_smtp(data, __VA_ARGS__); } while(0) +#endif /* !CURL_DISABLE_SMTP */ +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) +#define CURL_TRC_WS(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ws)) \ + Curl_trc_ws(data, __VA_ARGS__); } while(0) +#endif /* !CURL_DISABLE_WEBSOCKETS && !CURL_DISABLE_HTTP */ + +#else /* CURL_HAVE_C99 */ -#else /* no variadic macro args */ #define infof Curl_infof #define CURL_TRC_CF Curl_trc_cf_infof -#endif /* variadic macro args */ +#define CURL_TRC_WRITE Curl_trc_write +#define CURL_TRC_READ Curl_trc_read + +#ifndef CURL_DISABLE_FTP +#define CURL_TRC_FTP Curl_trc_ftp +#endif +#ifndef CURL_DISABLE_SMTP +#define CURL_TRC_SMTP Curl_trc_smtp +#endif +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) +#define CURL_TRC_WS Curl_trc_ws +#endif + +#endif /* !CURL_HAVE_C99 */ + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +/* informational messages enabled */ + +struct curl_trc_feat { + const char *name; + int log_level; +}; +extern struct curl_trc_feat Curl_trc_feat_read; +extern struct curl_trc_feat Curl_trc_feat_write; + +#define Curl_trc_is_verbose(data) \ + ((data) && (data)->set.verbose && \ + (!(data)->state.feat || \ + ((data)->state.feat->log_level >= CURL_LOG_LVL_INFO))) +#define Curl_trc_cf_is_verbose(cf, data) \ + (Curl_trc_is_verbose(data) && \ + (cf) && (cf)->cft->log_level >= CURL_LOG_LVL_INFO) +#define Curl_trc_ft_is_verbose(data, ft) \ + (Curl_trc_is_verbose(data) && \ + (ft)->log_level >= CURL_LOG_LVL_INFO) -#else /* !CURL_DISABLE_VERBOSE_STRINGS */ +/** + * Output an informational message when transfer's verbose logging is enabled. + */ +void Curl_infof(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); + +/** + * Output an informational message when both transfer's verbose logging + * and connection filters verbose logging are enabled. + */ +void Curl_trc_cf_infof(struct Curl_easy *data, struct Curl_cfilter *cf, + const char *fmt, ...) CURL_PRINTF(3, 4); +void Curl_trc_write(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +void Curl_trc_read(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); + +#ifndef CURL_DISABLE_FTP +extern struct curl_trc_feat Curl_trc_feat_ftp; +void Curl_trc_ftp(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +#endif +#ifndef CURL_DISABLE_SMTP +extern struct curl_trc_feat Curl_trc_feat_smtp; +void Curl_trc_smtp(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +#endif +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) +extern struct curl_trc_feat Curl_trc_feat_ws; +void Curl_trc_ws(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +#endif + + +#else /* defined(CURL_DISABLE_VERBOSE_STRINGS) */ /* All informational messages are not compiled in for size savings */ -#define Curl_trc_is_verbose(d) ((void)(d), FALSE) -#define Curl_trc_cf_is_verbose(x,y) ((void)(x), (void)(y), FALSE) - -#if defined(HAVE_VARIADIC_MACROS_C99) -#define infof(...) Curl_nop_stmt -#define CURL_TRC_CF(...) Curl_nop_stmt -#define Curl_trc_cf_infof(...) Curl_nop_stmt -#elif defined(HAVE_VARIADIC_MACROS_GCC) -#define infof(x...) Curl_nop_stmt -#define CURL_TRC_CF(x...) Curl_nop_stmt -#define Curl_trc_cf_infof(x...) Curl_nop_stmt -#else -#error "missing VARIADIC macro define, fix and rebuild!" +#define Curl_trc_is_verbose(d) (FALSE) +#define Curl_trc_cf_is_verbose(x,y) (FALSE) +#define Curl_trc_ft_is_verbose(x,y) (FALSE) + +static void Curl_infof(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + +static void Curl_trc_cf_infof(struct Curl_easy *data, + struct Curl_cfilter *cf, + const char *fmt, ...) +{ + (void)data; (void)cf; (void)fmt; +} + +struct curl_trc_feat; + +static void Curl_trc_write(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + +static void Curl_trc_read(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + +#ifndef CURL_DISABLE_FTP +static void Curl_trc_ftp(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} +#endif +#ifndef CURL_DISABLE_SMTP +static void Curl_trc_smtp(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} +#endif +#if !defined(CURL_DISABLE_WEBSOCKETS) || !defined(CURL_DISABLE_HTTP) +static void Curl_trc_ws(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} #endif -#endif /* CURL_DISABLE_VERBOSE_STRINGS */ +#endif /* !defined(CURL_DISABLE_VERBOSE_STRINGS) */ #endif /* HEADER_CURL_TRC_H */ diff --git a/deps/curl/lib/curlx.h b/deps/curl/lib/curlx.h index 7a753d68247..f0e4e6470b1 100644 --- a/deps/curl/lib/curlx.h +++ b/deps/curl/lib/curlx.h @@ -31,10 +31,8 @@ * be. */ -#include -/* this is still a public header file that provides the curl_mprintf() - functions while they still are offered publicly. They will be made library- - private one day */ +/* map standard printf functions to curl implementations */ +#include "curl_printf.h" #include "strcase.h" /* "strcase.h" provides the strcasecompare protos */ @@ -68,51 +66,4 @@ #include "version_win32.h" /* "version_win32.h" provides curlx_verify_windows_version() */ -/* Now setup curlx_ * names for the functions that are to become curlx_ and - be removed from a future libcurl official API: - curlx_getenv - curlx_mprintf (and its variations) - curlx_strcasecompare - curlx_strncasecompare - -*/ - -#define curlx_getenv curl_getenv -#define curlx_mvsnprintf curl_mvsnprintf -#define curlx_msnprintf curl_msnprintf -#define curlx_maprintf curl_maprintf -#define curlx_mvaprintf curl_mvaprintf -#define curlx_msprintf curl_msprintf -#define curlx_mprintf curl_mprintf -#define curlx_mfprintf curl_mfprintf -#define curlx_mvsprintf curl_mvsprintf -#define curlx_mvprintf curl_mvprintf -#define curlx_mvfprintf curl_mvfprintf - -#ifdef ENABLE_CURLX_PRINTF -/* If this define is set, we define all "standard" printf() functions to use - the curlx_* version instead. It makes the source code transparent and - easier to understand/patch. Undefine them first. */ -# undef printf -# undef fprintf -# undef sprintf -# undef msnprintf -# undef vprintf -# undef vfprintf -# undef vsprintf -# undef mvsnprintf -# undef aprintf -# undef vaprintf - -# define printf curlx_mprintf -# define fprintf curlx_mfprintf -# define sprintf curlx_msprintf -# define msnprintf curlx_msnprintf -# define vprintf curlx_mvprintf -# define vfprintf curlx_mvfprintf -# define mvsnprintf curlx_mvsnprintf -# define aprintf curlx_maprintf -# define vaprintf curlx_mvaprintf -#endif /* ENABLE_CURLX_PRINTF */ - #endif /* HEADER_CURL_CURLX_H */ diff --git a/deps/curl/lib/cw-out.c b/deps/curl/lib/cw-out.c new file mode 100644 index 00000000000..4d3df0a650a --- /dev/null +++ b/deps/curl/lib/cw-out.c @@ -0,0 +1,473 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 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.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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include + +#include "urldata.h" +#include "cfilters.h" +#include "headers.h" +#include "multiif.h" +#include "sendf.h" +#include "cw-out.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +/** + * OVERALL DESIGN of this client writer + * + * The 'cw-out' writer is supposed to be the last writer in a transfer's + * stack. It is always added when that stack is initialized. Its purpose + * is to pass BODY and HEADER bytes to the client-installed callback + * functions. + * + * These callback may return `CURL_WRITEFUNC_PAUSE` to indicate that the + * data had not been written and the whole transfer should stop receiving + * new data. Or at least, stop calling the functions. When the transfer + * is "unpaused" by the client, the previous data shall be passed as + * if nothing happened. + * + * The `cw-out` writer therefore manages buffers for bytes that could + * not be written. Data that was already in flight from the server also + * needs buffering on paused transfer when it arrives. + * + * In addition, the writer allows buffering of "small" body writes, + * so client functions are called less often. That is only enabled on a + * number of conditions. + * + * HEADER and BODY data may arrive in any order. For paused transfers, + * a list of `struct cw_out_buf` is kept for `cw_out_type` types. The + * list may be: [BODY]->[HEADER]->[BODY]->[HEADER].... + * When unpausing, this list is "played back" to the client callbacks. + * + * The amount of bytes being buffered is limited by `DYN_PAUSE_BUFFER` + * and when that is exceeded `CURLE_TOO_LARGE` is returned as error. + */ +typedef enum { + CW_OUT_NONE, + CW_OUT_BODY, + CW_OUT_HDS +} cw_out_type; + +struct cw_out_buf { + struct cw_out_buf *next; + struct dynbuf b; + cw_out_type type; +}; + +static struct cw_out_buf *cw_out_buf_create(cw_out_type otype) +{ + struct cw_out_buf *cwbuf = calloc(1, sizeof(*cwbuf)); + if(cwbuf) { + cwbuf->type = otype; + Curl_dyn_init(&cwbuf->b, DYN_PAUSE_BUFFER); + } + return cwbuf; +} + +static void cw_out_buf_free(struct cw_out_buf *cwbuf) +{ + if(cwbuf) { + Curl_dyn_free(&cwbuf->b); + free(cwbuf); + } +} + +struct cw_out_ctx { + struct Curl_cwriter super; + struct cw_out_buf *buf; + BIT(paused); + BIT(errored); +}; + +static CURLcode cw_out_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes); +static void cw_out_close(struct Curl_easy *data, struct Curl_cwriter *writer); +static CURLcode cw_out_init(struct Curl_easy *data, + struct Curl_cwriter *writer); + +struct Curl_cwtype Curl_cwt_out = { + "cw-out", + NULL, + cw_out_init, + cw_out_write, + cw_out_close, + sizeof(struct cw_out_ctx) +}; + +static CURLcode cw_out_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct cw_out_ctx *ctx = writer->ctx; + (void)data; + ctx->buf = NULL; + return CURLE_OK; +} + +static void cw_out_bufs_free(struct cw_out_ctx *ctx) +{ + while(ctx->buf) { + struct cw_out_buf *next = ctx->buf->next; + cw_out_buf_free(ctx->buf); + ctx->buf = next; + } +} + +static size_t cw_out_bufs_len(struct cw_out_ctx *ctx) +{ + struct cw_out_buf *cwbuf = ctx->buf; + size_t len = 0; + while(cwbuf) { + len += Curl_dyn_len(&cwbuf->b); + cwbuf = cwbuf->next; + } + return len; +} + +static void cw_out_close(struct Curl_easy *data, struct Curl_cwriter *writer) +{ + struct cw_out_ctx *ctx = writer->ctx; + + (void)data; + cw_out_bufs_free(ctx); +} + +/** + * Return the current curl_write_callback and user_data for the buf type + */ +static void cw_get_writefunc(struct Curl_easy *data, cw_out_type otype, + curl_write_callback *pwcb, void **pwcb_data, + size_t *pmax_write, size_t *pmin_write) +{ + switch(otype) { + case CW_OUT_BODY: + *pwcb = data->set.fwrite_func; + *pwcb_data = data->set.out; + *pmax_write = CURL_MAX_WRITE_SIZE; + /* if we ever want buffering of BODY output, we can set `min_write` + * the preferred size. The default should always be to pass data + * to the client as it comes without delay */ + *pmin_write = 0; + break; + case CW_OUT_HDS: + *pwcb = data->set.fwrite_header ? data->set.fwrite_header : + (data->set.writeheader ? data->set.fwrite_func : NULL); + *pwcb_data = data->set.writeheader; + *pmax_write = 0; /* do not chunk-write headers, write them as they are */ + *pmin_write = 0; + break; + default: + *pwcb = NULL; + *pwcb_data = NULL; + *pmax_write = CURL_MAX_WRITE_SIZE; + *pmin_write = 0; + } +} + +static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, + struct Curl_easy *data, + cw_out_type otype, + bool flush_all, + const char *buf, size_t blen, + size_t *pconsumed) +{ + curl_write_callback wcb; + void *wcb_data; + size_t max_write, min_write; + size_t wlen, nwritten; + + /* If we errored once, we do not invoke the client callback again */ + if(ctx->errored) + return CURLE_WRITE_ERROR; + + /* write callbacks may get NULLed by the client between calls. */ + cw_get_writefunc(data, otype, &wcb, &wcb_data, &max_write, &min_write); + if(!wcb) { + *pconsumed = blen; + return CURLE_OK; + } + + *pconsumed = 0; + while(blen && !ctx->paused) { + if(!flush_all && blen < min_write) + break; + wlen = max_write ? CURLMIN(blen, max_write) : blen; + Curl_set_in_callback(data, TRUE); + nwritten = wcb((char *)buf, 1, wlen, wcb_data); + Curl_set_in_callback(data, FALSE); + CURL_TRC_WRITE(data, "cw_out, wrote %zu %s bytes -> %zu", + wlen, (otype == CW_OUT_BODY) ? "body" : "header", + nwritten); + if(CURL_WRITEFUNC_PAUSE == nwritten) { + if(data->conn && data->conn->handler->flags & PROTOPT_NONETWORK) { + /* Protocols that work without network cannot be paused. This is + actually only FILE:// just now, and it cannot pause since the + transfer is not done using the "normal" procedure. */ + failf(data, "Write callback asked for PAUSE when not supported"); + return CURLE_WRITE_ERROR; + } + /* mark the connection as RECV paused */ + data->req.keepon |= KEEP_RECV_PAUSE; + ctx->paused = TRUE; + CURL_TRC_WRITE(data, "cw_out, PAUSE requested by client"); + break; + } + else if(CURL_WRITEFUNC_ERROR == nwritten) { + failf(data, "client returned ERROR on write of %zu bytes", wlen); + return CURLE_WRITE_ERROR; + } + else if(nwritten != wlen) { + failf(data, "Failure writing output to destination, " + "passed %zu returned %zd", wlen, nwritten); + return CURLE_WRITE_ERROR; + } + *pconsumed += nwritten; + blen -= nwritten; + buf += nwritten; + } + return CURLE_OK; +} + +static CURLcode cw_out_buf_flush(struct cw_out_ctx *ctx, + struct Curl_easy *data, + struct cw_out_buf *cwbuf, + bool flush_all) +{ + CURLcode result = CURLE_OK; + + if(Curl_dyn_len(&cwbuf->b)) { + size_t consumed; + + result = cw_out_ptr_flush(ctx, data, cwbuf->type, flush_all, + Curl_dyn_ptr(&cwbuf->b), + Curl_dyn_len(&cwbuf->b), + &consumed); + if(result) + return result; + + if(consumed) { + if(consumed == Curl_dyn_len(&cwbuf->b)) { + Curl_dyn_free(&cwbuf->b); + } + else { + DEBUGASSERT(consumed < Curl_dyn_len(&cwbuf->b)); + result = Curl_dyn_tail(&cwbuf->b, Curl_dyn_len(&cwbuf->b) - consumed); + if(result) + return result; + } + } + } + return result; +} + +static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx, + struct Curl_easy *data, + struct cw_out_buf **pcwbuf, + bool flush_all) +{ + struct cw_out_buf *cwbuf = *pcwbuf; + CURLcode result; + + if(!cwbuf) + return CURLE_OK; + if(ctx->paused) + return CURLE_OK; + + /* write the end of the chain until it blocks or gets empty */ + while(cwbuf->next) { + struct cw_out_buf **plast = &cwbuf->next; + while((*plast)->next) + plast = &(*plast)->next; + result = cw_out_flush_chain(ctx, data, plast, flush_all); + if(result) + return result; + if(*plast) { + /* could not write last, paused again? */ + DEBUGASSERT(ctx->paused); + return CURLE_OK; + } + } + + result = cw_out_buf_flush(ctx, data, cwbuf, flush_all); + if(result) + return result; + if(!Curl_dyn_len(&cwbuf->b)) { + cw_out_buf_free(cwbuf); + *pcwbuf = NULL; + } + return CURLE_OK; +} + +static CURLcode cw_out_append(struct cw_out_ctx *ctx, + cw_out_type otype, + const char *buf, size_t blen) +{ + if(cw_out_bufs_len(ctx) + blen > DYN_PAUSE_BUFFER) + return CURLE_TOO_LARGE; + + /* if we do not have a buffer, or it is of another type, make a new one. + * And for CW_OUT_HDS always make a new one, so we "replay" headers + * exactly as they came in */ + if(!ctx->buf || (ctx->buf->type != otype) || (otype == CW_OUT_HDS)) { + struct cw_out_buf *cwbuf = cw_out_buf_create(otype); + if(!cwbuf) + return CURLE_OUT_OF_MEMORY; + cwbuf->next = ctx->buf; + ctx->buf = cwbuf; + } + DEBUGASSERT(ctx->buf && (ctx->buf->type == otype)); + return Curl_dyn_addn(&ctx->buf->b, buf, blen); +} + +static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, + struct Curl_easy *data, + cw_out_type otype, + bool flush_all, + const char *buf, size_t blen) +{ + CURLcode result = CURLE_OK; + + /* if we have buffered data and it is a different type than what + * we are writing now, try to flush all */ + if(ctx->buf && ctx->buf->type != otype) { + result = cw_out_flush_chain(ctx, data, &ctx->buf, TRUE); + if(result) + goto out; + } + + if(ctx->buf) { + /* still have buffered data, append and flush */ + result = cw_out_append(ctx, otype, buf, blen); + if(result) + return result; + result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all); + if(result) + goto out; + } + else { + /* nothing buffered, try direct write */ + size_t consumed; + result = cw_out_ptr_flush(ctx, data, otype, flush_all, + buf, blen, &consumed); + if(result) + return result; + if(consumed < blen) { + /* did not write all, append the rest */ + result = cw_out_append(ctx, otype, buf + consumed, blen - consumed); + if(result) + goto out; + } + } + +out: + if(result) { + /* We do not want to invoked client callbacks a second time after + * encountering an error. See issue #13337 */ + ctx->errored = TRUE; + cw_out_bufs_free(ctx); + } + return result; +} + +static CURLcode cw_out_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) +{ + struct cw_out_ctx *ctx = writer->ctx; + CURLcode result; + bool flush_all = !!(type & CLIENTWRITE_EOS); + + if((type & CLIENTWRITE_BODY) || + ((type & CLIENTWRITE_HEADER) && data->set.include_header)) { + result = cw_out_do_write(ctx, data, CW_OUT_BODY, flush_all, buf, blen); + if(result) + return result; + } + + if(type & (CLIENTWRITE_HEADER|CLIENTWRITE_INFO)) { + result = cw_out_do_write(ctx, data, CW_OUT_HDS, flush_all, buf, blen); + if(result) + return result; + } + + return CURLE_OK; +} + +bool Curl_cw_out_is_paused(struct Curl_easy *data) +{ + struct Curl_cwriter *cw_out; + struct cw_out_ctx *ctx; + + cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out); + if(!cw_out) + return FALSE; + + ctx = (struct cw_out_ctx *)cw_out; + CURL_TRC_WRITE(data, "cw-out is%spaused", ctx->paused ? "" : " not"); + return ctx->paused; +} + +static CURLcode cw_out_flush(struct Curl_easy *data, + bool unpause, bool flush_all) +{ + struct Curl_cwriter *cw_out; + CURLcode result = CURLE_OK; + + cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out); + if(cw_out) { + struct cw_out_ctx *ctx = (struct cw_out_ctx *)cw_out; + if(ctx->errored) + return CURLE_WRITE_ERROR; + if(unpause && ctx->paused) + ctx->paused = FALSE; + if(ctx->paused) + return CURLE_OK; /* not doing it */ + + result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all); + if(result) { + ctx->errored = TRUE; + cw_out_bufs_free(ctx); + return result; + } + } + return result; +} + +CURLcode Curl_cw_out_unpause(struct Curl_easy *data) +{ + CURL_TRC_WRITE(data, "cw-out unpause"); + return cw_out_flush(data, TRUE, FALSE); +} + +CURLcode Curl_cw_out_done(struct Curl_easy *data) +{ + CURL_TRC_WRITE(data, "cw-out done"); + return cw_out_flush(data, FALSE, TRUE); +} diff --git a/deps/curl/lib/curl_ntlm_wb.h b/deps/curl/lib/cw-out.h similarity index 60% rename from deps/curl/lib/curl_ntlm_wb.h rename to deps/curl/lib/cw-out.h index 37704c0fe0b..ca4c2e435d2 100644 --- a/deps/curl/lib/curl_ntlm_wb.h +++ b/deps/curl/lib/cw-out.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_NTLM_WB_H -#define HEADER_CURL_NTLM_WB_H +#ifndef HEADER_CURL_CW_OUT_H +#define HEADER_CURL_CW_OUT_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -26,20 +26,28 @@ #include "curl_setup.h" -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) +#include "sendf.h" -/* this is for ntlm header input */ -CURLcode Curl_input_ntlm_wb(struct Curl_easy *data, - struct connectdata *conn, bool proxy, - const char *header); +/** + * The client writer type "cw-out" that does the actual writing to + * the client callbacks. Intended to be the last installed in the + * client writer stack of a transfer. + */ +extern struct Curl_cwtype Curl_cwt_out; -/* this is for creating ntlm header output */ -CURLcode Curl_output_ntlm_wb(struct Curl_easy *data, struct connectdata *conn, - bool proxy); +/** + * Return TRUE iff 'cw-out' client write has paused data. + */ +bool Curl_cw_out_is_paused(struct Curl_easy *data); -void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn); +/** + * Flush any buffered date to the client, chunk collation still applies. + */ +CURLcode Curl_cw_out_unpause(struct Curl_easy *data); -#endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */ +/** + * Mark EndOfStream reached and flush ALL data to the client. + */ +CURLcode Curl_cw_out_done(struct Curl_easy *data); -#endif /* HEADER_CURL_NTLM_WB_H */ +#endif /* HEADER_CURL_CW_OUT_H */ diff --git a/deps/curl/lib/dict.c b/deps/curl/lib/dict.c index 3172b382909..7d9c18dc9db 100644 --- a/deps/curl/lib/dict.c +++ b/deps/curl/lib/dict.c @@ -76,7 +76,7 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done); */ const struct Curl_handler Curl_handler_dict = { - "DICT", /* scheme */ + "dict", /* scheme */ ZERO_NULL, /* setup_connection */ dict_do, /* do_it */ ZERO_NULL, /* done */ @@ -89,7 +89,8 @@ const struct Curl_handler Curl_handler_dict = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_DICT, /* defport */ @@ -122,10 +123,12 @@ static char *unescape_word(const char *input) } /* sendf() sends formatted data to the server */ -static CURLcode sendf(curl_socket_t sockfd, struct Curl_easy *data, - const char *fmt, ...) +static CURLcode sendf(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); + +static CURLcode sendf(struct Curl_easy *data, const char *fmt, ...) { - ssize_t bytes_written; + size_t bytes_written; size_t write_len; CURLcode result = CURLE_OK; char *s; @@ -143,7 +146,7 @@ static CURLcode sendf(curl_socket_t sockfd, struct Curl_easy *data, for(;;) { /* Write the buffer to the socket */ - result = Curl_write(data, sockfd, sptr, write_len, &bytes_written); + result = Curl_xfer_send(data, sptr, write_len, FALSE, &bytes_written); if(result) break; @@ -175,8 +178,6 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) char *nthdef = NULL; /* This is not part of the protocol, but required by RFC 2229 */ CURLcode result; - struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; char *path; @@ -225,7 +226,7 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) goto error; } - result = sendf(sockfd, data, + result = sendf(data, "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" "MATCH " "%s " /* database */ @@ -240,7 +241,7 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) failf(data, "Failed sending DICT request"); goto error; } - Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); /* no upload */ + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); /* no upload */ } else if(strncasecompare(path, DICT_DEFINE, sizeof(DICT_DEFINE)-1) || strncasecompare(path, DICT_DEFINE2, sizeof(DICT_DEFINE2)-1) || @@ -273,7 +274,7 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) goto error; } - result = sendf(sockfd, data, + result = sendf(data, "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" "DEFINE " "%s " /* database */ @@ -286,7 +287,7 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) failf(data, "Failed sending DICT request"); goto error; } - Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); } else { @@ -299,7 +300,7 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) if(ppath[i] == ':') ppath[i] = ' '; } - result = sendf(sockfd, data, + result = sendf(data, "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" "%s\r\n" "QUIT\r\n", ppath); @@ -308,7 +309,7 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) goto error; } - Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); } } diff --git a/deps/curl/lib/dllmain.c b/deps/curl/lib/dllmain.c new file mode 100644 index 00000000000..41e97b37eb5 --- /dev/null +++ b/deps/curl/lib/dllmain.c @@ -0,0 +1,81 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 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.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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_OPENSSL +#include +#endif + +/* The fourth-to-last include */ +#ifdef __CYGWIN__ +#define WIN32_LEAN_AND_MEAN +#include +#ifdef _WIN32 +#undef _WIN32 +#endif +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* DllMain() must only be defined for Windows and Cygwin DLL builds. */ +#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(CURL_STATICLIB) + +#if defined(USE_OPENSSL) && \ + !defined(OPENSSL_IS_AWSLC) && \ + !defined(OPENSSL_IS_BORINGSSL) && \ + !defined(LIBRESSL_VERSION_NUMBER) && \ + (OPENSSL_VERSION_NUMBER >= 0x10100000L) +#define PREVENT_OPENSSL_MEMLEAK +#endif + +#ifdef PREVENT_OPENSSL_MEMLEAK +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved); +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + (void)hinstDLL; + (void)lpvReserved; + + switch(fdwReason) { + case DLL_PROCESS_ATTACH: + break; + case DLL_PROCESS_DETACH: + break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + /* Call OPENSSL_thread_stop to prevent a memory leak in case OpenSSL is + linked statically. + https://github.com/curl/curl/issues/12327#issuecomment-1826405944 */ + OPENSSL_thread_stop(); + break; + } + return TRUE; +} +#endif /* OpenSSL */ + +#endif /* DLL build */ diff --git a/deps/curl/lib/doh.c b/deps/curl/lib/doh.c index 7a38eab01f4..8769372e0be 100644 --- a/deps/curl/lib/doh.c +++ b/deps/curl/lib/doh.c @@ -42,9 +42,13 @@ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" +#include "escape.h" #define DNS_CLASS_IN 0x01 +/* doh_print_buf truncates if the hex string will be more than this */ +#define LOCAL_PB_HEXMAX 400 + #ifndef CURL_DISABLE_VERBOSE_STRINGS static const char * const errors[]={ "", @@ -69,36 +73,41 @@ static const char *doh_strerror(DOHcode code) return errors[code]; return "bad error code"; } -#endif + +struct curl_trc_feat Curl_doh_trc = { + "DoH", + CURL_LOG_LVL_NONE, +}; +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ /* @unittest 1655 */ -UNITTEST DOHcode doh_encode(const char *host, - DNStype dnstype, - unsigned char *dnsp, /* buffer */ - size_t len, /* buffer size */ - size_t *olen) /* output length */ +UNITTEST DOHcode doh_req_encode(const char *host, + DNStype dnstype, + unsigned char *dnsp, /* buffer */ + size_t len, /* buffer size */ + size_t *olen) /* output length */ { const size_t hostlen = strlen(host); unsigned char *orig = dnsp; const char *hostp = host; /* The expected output length is 16 bytes more than the length of - * the QNAME-encoding of the host name. + * the QNAME-encoding of the hostname. * * A valid DNS name may not contain a zero-length label, except at - * the end. For this reason, a name beginning with a dot, or + * the end. For this reason, a name beginning with a dot, or * containing a sequence of two or more consecutive dots, is invalid * and cannot be encoded as a QNAME. * - * If the host name ends with a trailing dot, the corresponding - * QNAME-encoding is one byte longer than the host name. If (as is + * If the hostname ends with a trailing dot, the corresponding + * QNAME-encoding is one byte longer than the hostname. If (as is * also valid) the hostname is shortened by the omission of the * trailing dot, then its QNAME-encoding will be two bytes longer - * than the host name. + * than the hostname. * * Each [ label, dot ] pair is encoded as [ length, label ], - * preserving overall length. A final [ label ] without a dot is + * preserving overall length. A final [ label ] without a dot is * also encoded as [ length, label ], increasing overall length * by one. The encoding is completed by appending a zero byte, * representing the zero-length root label, again increasing @@ -156,7 +165,7 @@ UNITTEST DOHcode doh_encode(const char *host, *dnsp++ = 0; /* append zero-length label for root */ /* There are assigned TYPE codes beyond 255: use range [1..65535] */ - *dnsp++ = (unsigned char)(255 & (dnstype>>8)); /* upper 8 bit TYPE */ + *dnsp++ = (unsigned char)(255 & (dnstype >> 8)); /* upper 8 bit TYPE */ *dnsp++ = (unsigned char)(255 & dnstype); /* lower 8 bit TYPE */ *dnsp++ = '\0'; /* upper 8 bit CLASS */ @@ -171,7 +180,7 @@ UNITTEST DOHcode doh_encode(const char *host, } static size_t -doh_write_cb(const void *contents, size_t size, size_t nmemb, void *userp) +doh_write_cb(char *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; struct dynbuf *mem = (struct dynbuf *)userp; @@ -182,54 +191,80 @@ doh_write_cb(const void *contents, size_t size, size_t nmemb, void *userp) return realsize; } +#if defined(USE_HTTPSRR) && defined(DEBUGBUILD) +static void doh_print_buf(struct Curl_easy *data, + const char *prefix, + unsigned char *buf, size_t len) +{ + unsigned char hexstr[LOCAL_PB_HEXMAX]; + size_t hlen = LOCAL_PB_HEXMAX; + bool truncated = FALSE; + + if(len > (LOCAL_PB_HEXMAX / 2)) + truncated = TRUE; + Curl_hexencode(buf, len, hexstr, hlen); + if(!truncated) + infof(data, "%s: len=%d, val=%s", prefix, (int)len, hexstr); + else + infof(data, "%s: len=%d (truncated)val=%s", prefix, (int)len, hexstr); + return; +} +#endif + /* called from multi.c when this DoH transfer is complete */ static int doh_done(struct Curl_easy *doh, CURLcode result) { - struct Curl_easy *data = doh->set.dohfor; - struct dohdata *dohp = data->req.doh; - /* so one of the DoH request done for the 'data' transfer is now complete! */ - dohp->pending--; - infof(data, "a DoH request is completed, %u to go", dohp->pending); - if(result) - infof(data, "DoH request %s", curl_easy_strerror(result)); + struct Curl_easy *data; /* the transfer that asked for the DoH probe */ - if(!dohp->pending) { - /* DoH completed */ - curl_slist_free_all(dohp->headers); - dohp->headers = NULL; - Curl_expire(data, 0, EXPIRE_RUN_NOW); + data = Curl_multi_get_handle(doh->multi, doh->set.dohfor_mid); + if(!data) { + DEBUGF(infof(doh, "doh_done: xfer for mid=%" FMT_OFF_T + " not found", doh->set.dohfor_mid)); + DEBUGASSERT(0); + } + else { + struct doh_probes *dohp = data->req.doh; + /* one of the DoH request done for the 'data' transfer is now complete! */ + dohp->pending--; + infof(doh, "a DoH request is completed, %u to go", dohp->pending); + if(result) + infof(doh, "DoH request %s", curl_easy_strerror(result)); + + if(!dohp->pending) { + /* DoH completed, run the transfer picking up the results */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } } return 0; } -#define ERROR_CHECK_SETOPT(x,y) \ -do { \ - result = curl_easy_setopt(doh, x, y); \ - if(result && \ - result != CURLE_NOT_BUILT_IN && \ - result != CURLE_UNKNOWN_OPTION) \ - goto error; \ -} while(0) - -static CURLcode dohprobe(struct Curl_easy *data, - struct dnsprobe *p, DNStype dnstype, - const char *host, - const char *url, CURLM *multi, - struct curl_slist *headers) +#define ERROR_CHECK_SETOPT(x,y) \ + do { \ + result = curl_easy_setopt((CURL *)doh, x, y); \ + if(result && \ + result != CURLE_NOT_BUILT_IN && \ + result != CURLE_UNKNOWN_OPTION) \ + goto error; \ + } while(0) + +static CURLcode doh_run_probe(struct Curl_easy *data, + struct doh_probe *p, DNStype dnstype, + const char *host, + const char *url, CURLM *multi, + struct curl_slist *headers) { struct Curl_easy *doh = NULL; - char *nurl = NULL; CURLcode result = CURLE_OK; timediff_t timeout_ms; - DOHcode d = doh_encode(host, dnstype, p->dohbuffer, sizeof(p->dohbuffer), - &p->dohlen); + DOHcode d = doh_req_encode(host, dnstype, p->req_body, sizeof(p->req_body), + &p->req_body_len); if(d) { failf(data, "Failed to encode DoH packet [%d]", d); return CURLE_OUT_OF_MEMORY; } p->dnstype = dnstype; - Curl_dyn_init(&p->serverdoh, DYN_DOH_RESPONSE); + Curl_dyn_init(&p->resp_body, DYN_DOH_RESPONSE); timeout_ms = Curl_timeleft(data, NULL, TRUE); if(timeout_ms <= 0) { @@ -238,118 +273,126 @@ static CURLcode dohprobe(struct Curl_easy *data, } /* Curl_open() is the internal version of curl_easy_init() */ result = Curl_open(&doh); - if(!result) { - /* pass in the struct pointer via a local variable to please coverity and - the gcc typecheck helpers */ - struct dynbuf *resp = &p->serverdoh; - ERROR_CHECK_SETOPT(CURLOPT_URL, url); - ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https"); - ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_write_cb); - ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, resp); - ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, p->dohbuffer); - ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)p->dohlen); - ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, headers); + if(result) + goto error; + + /* pass in the struct pointer via a local variable to please coverity and + the gcc typecheck helpers */ + doh->state.internal = TRUE; +#ifndef CURL_DISABLE_VERBOSE_STRINGS + doh->state.feat = &Curl_doh_trc; +#endif + ERROR_CHECK_SETOPT(CURLOPT_URL, url); + ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https"); + ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_write_cb); + ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, &p->resp_body); + ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, p->req_body); + ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)p->req_body_len); + ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, headers); #ifdef USE_HTTP2 - ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); + ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); + ERROR_CHECK_SETOPT(CURLOPT_PIPEWAIT, 1L); #endif -#ifndef CURLDEBUG - /* enforce HTTPS if not debug */ - ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS); +#ifndef DEBUGBUILD + /* enforce HTTPS if not debug */ + ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS); #else - /* in debug mode, also allow http */ - ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS); + /* in debug mode, also allow http */ + ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS); #endif - ERROR_CHECK_SETOPT(CURLOPT_TIMEOUT_MS, (long)timeout_ms); - ERROR_CHECK_SETOPT(CURLOPT_SHARE, data->share); - if(data->set.err && data->set.err != stderr) - ERROR_CHECK_SETOPT(CURLOPT_STDERR, data->set.err); - if(data->set.verbose) - ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L); - if(data->set.no_signal) - ERROR_CHECK_SETOPT(CURLOPT_NOSIGNAL, 1L); - - ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYHOST, - data->set.doh_verifyhost ? 2L : 0L); - ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYPEER, - data->set.doh_verifypeer ? 1L : 0L); - ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYSTATUS, - data->set.doh_verifystatus ? 1L : 0L); - - /* Inherit *some* SSL options from the user's transfer. This is a - best-guess as to which options are needed for compatibility. #3661 - - Note DoH does not inherit the user's proxy server so proxy SSL settings - have no effect and are not inherited. If that changes then two new - options should be added to check doh proxy insecure separately, - CURLOPT_DOH_PROXY_SSL_VERIFYHOST and CURLOPT_DOH_PROXY_SSL_VERIFYPEER. - */ - if(data->set.ssl.falsestart) - ERROR_CHECK_SETOPT(CURLOPT_SSL_FALSESTART, 1L); - if(data->set.str[STRING_SSL_CAFILE]) { - ERROR_CHECK_SETOPT(CURLOPT_CAINFO, - data->set.str[STRING_SSL_CAFILE]); - } - if(data->set.blobs[BLOB_CAINFO]) { - ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB, - data->set.blobs[BLOB_CAINFO]); - } - if(data->set.str[STRING_SSL_CAPATH]) { - ERROR_CHECK_SETOPT(CURLOPT_CAPATH, - data->set.str[STRING_SSL_CAPATH]); - } - if(data->set.str[STRING_SSL_CRLFILE]) { - ERROR_CHECK_SETOPT(CURLOPT_CRLFILE, - data->set.str[STRING_SSL_CRLFILE]); - } - if(data->set.ssl.certinfo) - ERROR_CHECK_SETOPT(CURLOPT_CERTINFO, 1L); - if(data->set.ssl.fsslctx) - ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl.fsslctx); - if(data->set.ssl.fsslctxp) - ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl.fsslctxp); - if(data->set.str[STRING_SSL_EC_CURVES]) { - ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES, - data->set.str[STRING_SSL_EC_CURVES]); - } + ERROR_CHECK_SETOPT(CURLOPT_TIMEOUT_MS, (long)timeout_ms); + ERROR_CHECK_SETOPT(CURLOPT_SHARE, (CURLSH *)data->share); + if(data->set.err && data->set.err != stderr) + ERROR_CHECK_SETOPT(CURLOPT_STDERR, data->set.err); + if(Curl_trc_ft_is_verbose(data, &Curl_doh_trc)) + ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L); + if(data->set.no_signal) + ERROR_CHECK_SETOPT(CURLOPT_NOSIGNAL, 1L); + + ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYHOST, + data->set.doh_verifyhost ? 2L : 0L); + ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYPEER, + data->set.doh_verifypeer ? 1L : 0L); + ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYSTATUS, + data->set.doh_verifystatus ? 1L : 0L); + + /* Inherit *some* SSL options from the user's transfer. This is a + best-guess as to which options are needed for compatibility. #3661 + + Note DoH does not inherit the user's proxy server so proxy SSL settings + have no effect and are not inherited. If that changes then two new + options should be added to check doh proxy insecure separately, + CURLOPT_DOH_PROXY_SSL_VERIFYHOST and CURLOPT_DOH_PROXY_SSL_VERIFYPEER. + */ + if(data->set.ssl.falsestart) + ERROR_CHECK_SETOPT(CURLOPT_SSL_FALSESTART, 1L); + if(data->set.str[STRING_SSL_CAFILE]) { + ERROR_CHECK_SETOPT(CURLOPT_CAINFO, + data->set.str[STRING_SSL_CAFILE]); + } + if(data->set.blobs[BLOB_CAINFO]) { + ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB, + data->set.blobs[BLOB_CAINFO]); + } + if(data->set.str[STRING_SSL_CAPATH]) { + ERROR_CHECK_SETOPT(CURLOPT_CAPATH, + data->set.str[STRING_SSL_CAPATH]); + } + if(data->set.str[STRING_SSL_CRLFILE]) { + ERROR_CHECK_SETOPT(CURLOPT_CRLFILE, + data->set.str[STRING_SSL_CRLFILE]); + } + if(data->set.ssl.certinfo) + ERROR_CHECK_SETOPT(CURLOPT_CERTINFO, 1L); + if(data->set.ssl.fsslctx) + ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl.fsslctx); + if(data->set.ssl.fsslctxp) + ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl.fsslctxp); + if(data->set.fdebug) + ERROR_CHECK_SETOPT(CURLOPT_DEBUGFUNCTION, data->set.fdebug); + if(data->set.debugdata) + ERROR_CHECK_SETOPT(CURLOPT_DEBUGDATA, data->set.debugdata); + if(data->set.str[STRING_SSL_EC_CURVES]) { + ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES, + data->set.str[STRING_SSL_EC_CURVES]); + } - { - long mask = - (data->set.ssl.enable_beast ? - CURLSSLOPT_ALLOW_BEAST : 0) | - (data->set.ssl.no_revoke ? - CURLSSLOPT_NO_REVOKE : 0) | - (data->set.ssl.no_partialchain ? - CURLSSLOPT_NO_PARTIALCHAIN : 0) | - (data->set.ssl.revoke_best_effort ? - CURLSSLOPT_REVOKE_BEST_EFFORT : 0) | - (data->set.ssl.native_ca_store ? - CURLSSLOPT_NATIVE_CA : 0) | - (data->set.ssl.auto_client_cert ? - CURLSSLOPT_AUTO_CLIENT_CERT : 0); - - (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, mask); - } + { + long mask = + (data->set.ssl.enable_beast ? + CURLSSLOPT_ALLOW_BEAST : 0) | + (data->set.ssl.no_revoke ? + CURLSSLOPT_NO_REVOKE : 0) | + (data->set.ssl.no_partialchain ? + CURLSSLOPT_NO_PARTIALCHAIN : 0) | + (data->set.ssl.revoke_best_effort ? + CURLSSLOPT_REVOKE_BEST_EFFORT : 0) | + (data->set.ssl.native_ca_store ? + CURLSSLOPT_NATIVE_CA : 0) | + (data->set.ssl.auto_client_cert ? + CURLSSLOPT_AUTO_CLIENT_CERT : 0); + + (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, mask); + } - doh->set.fmultidone = doh_done; - doh->set.dohfor = data; /* identify for which transfer this is done */ - p->easy = doh; + doh->set.fmultidone = doh_done; + doh->set.dohfor_mid = data->mid; /* for which transfer this is done */ - /* DoH private_data must be null because the user must have a way to - distinguish their transfer's handle from DoH handles in user - callbacks (ie SSL CTX callback). */ - DEBUGASSERT(!doh->set.private_data); + /* DoH handles must not inherit private_data. The handles may be passed to + the user via callbacks and the user will be able to identify them as + internal handles because private data is not set. The user can then set + private_data via CURLOPT_PRIVATE if they so choose. */ + DEBUGASSERT(!doh->set.private_data); - if(curl_multi_add_handle(multi, doh)) - goto error; - } - else + if(curl_multi_add_handle(multi, doh)) goto error; - free(nurl); + + p->easy_mid = doh->mid; return CURLE_OK; error: - free(nurl); Curl_close(&doh); + p->easy_mid = -1; return result; } @@ -364,10 +407,16 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, int *waitp) { CURLcode result = CURLE_OK; - int slot; - struct dohdata *dohp; + struct doh_probes *dohp; struct connectdata *conn = data->conn; - *waitp = TRUE; /* this never returns synchronously */ + size_t i; +#ifdef USE_HTTPSRR + /* for now, this is only used when ECH is enabled */ +# ifdef USE_ECH + char *qname = NULL; +# endif +#endif + *waitp = FALSE; (void)hostname; (void)port; @@ -375,52 +424,83 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, DEBUGASSERT(conn); /* start clean, consider allocating this struct on demand */ - dohp = data->req.doh = calloc(sizeof(struct dohdata), 1); + dohp = data->req.doh = calloc(1, sizeof(struct doh_probes)); if(!dohp) return NULL; + for(i = 0; i < DOH_SLOT_COUNT; ++i) { + dohp->probe[i].easy_mid = -1; + } + conn->bits.doh = TRUE; dohp->host = hostname; dohp->port = port; - dohp->headers = + dohp->req_hds = curl_slist_append(NULL, "Content-Type: application/dns-message"); - if(!dohp->headers) + if(!dohp->req_hds) goto error; /* create IPv4 DoH request */ - result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V4], - DNS_TYPE_A, hostname, data->set.str[STRING_DOH], - data->multi, dohp->headers); + result = doh_run_probe(data, &dohp->probe[DOH_SLOT_IPV4], + DNS_TYPE_A, hostname, data->set.str[STRING_DOH], + data->multi, dohp->req_hds); if(result) goto error; dohp->pending++; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if((conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { /* create IPv6 DoH request */ - result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V6], - DNS_TYPE_AAAA, hostname, data->set.str[STRING_DOH], - data->multi, dohp->headers); + result = doh_run_probe(data, &dohp->probe[DOH_SLOT_IPV6], + DNS_TYPE_AAAA, hostname, data->set.str[STRING_DOH], + data->multi, dohp->req_hds); if(result) goto error; dohp->pending++; } #endif + +#ifdef USE_HTTPSRR + /* + * TODO: Figure out the conditions under which we want to make + * a request for an HTTPS RR when we are not doing ECH. For now, + * making this request breaks a bunch of DoH tests, e.g. test2100, + * where the additional request does not match the pre-cooked data + * files, so there is a bit of work attached to making the request + * in a non-ECH use-case. For the present, we will only make the + * request when ECH is enabled in the build and is being used for + * the curl operation. + */ +# ifdef USE_ECH + if(data->set.tls_ech & CURLECH_ENABLE + || data->set.tls_ech & CURLECH_HARD) { + if(port == 443) + qname = strdup(hostname); + else + qname = aprintf("_%d._https.%s", port, hostname); + if(!qname) + goto error; + result = doh_run_probe(data, &dohp->probe[DOH_SLOT_HTTPS_RR], + DNS_TYPE_HTTPS, qname, data->set.str[STRING_DOH], + data->multi, dohp->req_hds); + Curl_safefree(qname); + if(result) + goto error; + dohp->pending++; + } +# endif +#endif + *waitp = TRUE; /* this never returns synchronously */ return NULL; error: - curl_slist_free_all(dohp->headers); - data->req.doh->headers = NULL; - for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) { - Curl_close(&dohp->probe[slot].easy); - } - Curl_safefree(data->req.doh); + Curl_doh_cleanup(data); return NULL; } -static DOHcode skipqname(const unsigned char *doh, size_t dohlen, - unsigned int *indexp) +static DOHcode doh_skipqname(const unsigned char *doh, size_t dohlen, + unsigned int *indexp) { unsigned char length; do { @@ -438,29 +518,32 @@ static DOHcode skipqname(const unsigned char *doh, size_t dohlen, return DOH_DNS_BAD_LABEL; if(dohlen < (*indexp + 1 + length)) return DOH_DNS_OUT_OF_RANGE; - *indexp += 1 + length; + *indexp += (unsigned int)(1 + length); } while(length); return DOH_OK; } -static unsigned short get16bit(const unsigned char *doh, int index) +static unsigned short doh_get16bit(const unsigned char *doh, + unsigned int index) { return (unsigned short)((doh[index] << 8) | doh[index + 1]); } -static unsigned int get32bit(const unsigned char *doh, int index) +static unsigned int doh_get32bit(const unsigned char *doh, unsigned int index) { - /* make clang and gcc optimize this to bswap by incrementing - the pointer first. */ - doh += index; - - /* avoid undefined behavior by casting to unsigned before shifting - 24 bits, possibly into the sign bit. codegen is same, but - ub sanitizer won't be upset */ - return ( (unsigned)doh[0] << 24) | (doh[1] << 16) |(doh[2] << 8) | doh[3]; + /* make clang and gcc optimize this to bswap by incrementing + the pointer first. */ + doh += index; + + /* avoid undefined behavior by casting to unsigned before shifting + 24 bits, possibly into the sign bit. codegen is same, but + ub sanitizer will not be upset */ + return ((unsigned)doh[0] << 24) | ((unsigned)doh[1] << 16) | + ((unsigned)doh[2] << 8) | doh[3]; } -static DOHcode store_a(const unsigned char *doh, int index, struct dohentry *d) +static void doh_store_a(const unsigned char *doh, int index, + struct dohentry *d) { /* silently ignore addresses over the limit */ if(d->numaddr < DOH_MAX_ADDR) { @@ -469,12 +552,10 @@ static DOHcode store_a(const unsigned char *doh, int index, struct dohentry *d) memcpy(&a->ip.v4, &doh[index], 4); d->numaddr++; } - return DOH_OK; } -static DOHcode store_aaaa(const unsigned char *doh, - int index, - struct dohentry *d) +static void doh_store_aaaa(const unsigned char *doh, int index, + struct dohentry *d) { /* silently ignore addresses over the limit */ if(d->numaddr < DOH_MAX_ADDR) { @@ -483,13 +564,27 @@ static DOHcode store_aaaa(const unsigned char *doh, memcpy(&a->ip.v6, &doh[index], 16); d->numaddr++; } +} + +#ifdef USE_HTTPSRR +static DOHcode doh_store_https(const unsigned char *doh, int index, + struct dohentry *d, uint16_t len) +{ + /* silently ignore RRs over the limit */ + if(d->numhttps_rrs < DOH_MAX_HTTPS) { + struct dohhttps_rr *h = &d->https_rrs[d->numhttps_rrs]; + h->val = Curl_memdup(&doh[index], len); + if(!h->val) + return DOH_OUT_OF_MEM; + h->len = len; + d->numhttps_rrs++; + } return DOH_OK; } +#endif -static DOHcode store_cname(const unsigned char *doh, - size_t dohlen, - unsigned int index, - struct dohentry *d) +static DOHcode doh_store_cname(const unsigned char *doh, size_t dohlen, + unsigned int index, struct dohentry *d) { struct dynbuf *c; unsigned int loop = 128; /* a valid DNS name can never loop this much */ @@ -511,7 +606,7 @@ static DOHcode store_cname(const unsigned char *doh, /* move to the new index */ newpos = (length & 0x3f) << 8 | doh[index + 1]; - index = newpos; + index = (unsigned int)newpos; continue; } else if(length & 0xc0) @@ -538,36 +633,40 @@ static DOHcode store_cname(const unsigned char *doh, return DOH_OK; } -static DOHcode rdata(const unsigned char *doh, - size_t dohlen, - unsigned short rdlength, - unsigned short type, - int index, - struct dohentry *d) +static DOHcode doh_rdata(const unsigned char *doh, + size_t dohlen, + unsigned short rdlength, + unsigned short type, + int index, + struct dohentry *d) { /* RDATA - A (TYPE 1): 4 bytes - AAAA (TYPE 28): 16 bytes - - NS (TYPE 2): N bytes */ + - NS (TYPE 2): N bytes + - HTTPS (TYPE 65): N bytes */ DOHcode rc; switch(type) { case DNS_TYPE_A: if(rdlength != 4) return DOH_DNS_RDATA_LEN; - rc = store_a(doh, index, d); - if(rc) - return rc; + doh_store_a(doh, index, d); break; case DNS_TYPE_AAAA: if(rdlength != 16) return DOH_DNS_RDATA_LEN; - rc = store_aaaa(doh, index, d); + doh_store_aaaa(doh, index, d); + break; +#ifdef USE_HTTPSRR + case DNS_TYPE_HTTPS: + rc = doh_store_https(doh, index, d, rdlength); if(rc) return rc; break; +#endif case DNS_TYPE_CNAME: - rc = store_cname(doh, dohlen, index, d); + rc = doh_store_cname(doh, dohlen, (unsigned int)index, d); if(rc) return rc; break; @@ -591,10 +690,10 @@ UNITTEST void de_init(struct dohentry *de) } -UNITTEST DOHcode doh_decode(const unsigned char *doh, - size_t dohlen, - DNStype dnstype, - struct dohentry *d) +UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, + size_t dohlen, + DNStype dnstype, + struct dohentry *d) { unsigned char rcode; unsigned short qdcount; @@ -614,9 +713,9 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(rcode) return DOH_DNS_BAD_RCODE; /* bad rcode */ - qdcount = get16bit(doh, 4); + qdcount = doh_get16bit(doh, 4); while(qdcount) { - rc = skipqname(doh, dohlen, &index); + rc = doh_skipqname(doh, dohlen, &index); if(rc) return rc; /* bad qname */ if(dohlen < (index + 4)) @@ -625,19 +724,19 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, qdcount--; } - ancount = get16bit(doh, 6); + ancount = doh_get16bit(doh, 6); while(ancount) { unsigned short class; unsigned int ttl; - rc = skipqname(doh, dohlen, &index); + rc = doh_skipqname(doh, dohlen, &index); if(rc) return rc; /* bad qname */ if(dohlen < (index + 2)) return DOH_DNS_OUT_OF_RANGE; - type = get16bit(doh, index); + type = doh_get16bit(doh, index); if((type != DNS_TYPE_CNAME) /* may be synthesized from DNAME */ && (type != DNS_TYPE_DNAME) /* if present, accept and ignore */ && (type != dnstype)) @@ -647,7 +746,7 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(dohlen < (index + 2)) return DOH_DNS_OUT_OF_RANGE; - class = get16bit(doh, index); + class = doh_get16bit(doh, index); if(DNS_CLASS_IN != class) return DOH_DNS_UNEXPECTED_CLASS; /* unsupported */ index += 2; @@ -655,7 +754,7 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(dohlen < (index + 4)) return DOH_DNS_OUT_OF_RANGE; - ttl = get32bit(doh, index); + ttl = doh_get32bit(doh, index); if(ttl < d->ttl) d->ttl = ttl; index += 4; @@ -663,21 +762,21 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(dohlen < (index + 2)) return DOH_DNS_OUT_OF_RANGE; - rdlength = get16bit(doh, index); + rdlength = doh_get16bit(doh, index); index += 2; if(dohlen < (index + rdlength)) return DOH_DNS_OUT_OF_RANGE; - rc = rdata(doh, dohlen, rdlength, type, index, d); + rc = doh_rdata(doh, dohlen, rdlength, type, (int)index, d); if(rc) - return rc; /* bad rdata */ + return rc; /* bad doh_rdata */ index += rdlength; ancount--; } - nscount = get16bit(doh, 8); + nscount = doh_get16bit(doh, 8); while(nscount) { - rc = skipqname(doh, dohlen, &index); + rc = doh_skipqname(doh, dohlen, &index); if(rc) return rc; /* bad qname */ @@ -689,7 +788,7 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(dohlen < (index + 2)) return DOH_DNS_OUT_OF_RANGE; - rdlength = get16bit(doh, index); + rdlength = doh_get16bit(doh, index); index += 2; if(dohlen < (index + rdlength)) return DOH_DNS_OUT_OF_RANGE; @@ -697,9 +796,9 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, nscount--; } - arcount = get16bit(doh, 10); + arcount = doh_get16bit(doh, 10); while(arcount) { - rc = skipqname(doh, dohlen, &index); + rc = doh_skipqname(doh, dohlen, &index); if(rc) return rc; /* bad qname */ @@ -711,7 +810,7 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(dohlen < (index + 2)) return DOH_DNS_OUT_OF_RANGE; - rdlength = get16bit(doh, index); + rdlength = doh_get16bit(doh, index); index += 2; if(dohlen < (index + rdlength)) return DOH_DNS_OUT_OF_RANGE; @@ -722,7 +821,11 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, if(index != dohlen) return DOH_DNS_MALFORMAT; /* something is wrong */ +#ifdef USE_HTTTPS + if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr && !d->numhttps_rrs) +#else if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr) +#endif /* nothing stored! */ return DOH_NO_CONTENT; @@ -730,29 +833,27 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh, } #ifndef CURL_DISABLE_VERBOSE_STRINGS -static void showdoh(struct Curl_easy *data, - const struct dohentry *d) +static void doh_show(struct Curl_easy *data, + const struct dohentry *d) { int i; - infof(data, "TTL: %u seconds", d->ttl); + infof(data, "[DoH] TTL: %u seconds", d->ttl); for(i = 0; i < d->numaddr; i++) { const struct dohaddr *a = &d->addr[i]; if(a->type == DNS_TYPE_A) { - infof(data, "DoH A: %u.%u.%u.%u", + infof(data, "[DoH] A: %u.%u.%u.%u", a->ip.v4[0], a->ip.v4[1], a->ip.v4[2], a->ip.v4[3]); } else if(a->type == DNS_TYPE_AAAA) { int j; - char buffer[128]; - char *ptr; - size_t len; - msnprintf(buffer, 128, "DoH AAAA: "); - ptr = &buffer[10]; - len = 118; + char buffer[128] = "[DoH] AAAA: "; + size_t len = strlen(buffer); + char *ptr = &buffer[len]; + len = sizeof(buffer) - len; for(j = 0; j < 16; j += 2) { size_t l; - msnprintf(ptr, len, "%s%02x%02x", j?":":"", d->addr[i].ip.v6[j], + msnprintf(ptr, len, "%s%02x%02x", j ? ":" : "", d->addr[i].ip.v6[j], d->addr[i].ip.v6[j + 1]); l = strlen(ptr); len -= l; @@ -761,12 +862,22 @@ static void showdoh(struct Curl_easy *data, infof(data, "%s", buffer); } } +#ifdef USE_HTTPSRR + for(i = 0; i < d->numhttps_rrs; i++) { +# ifdef DEBUGBUILD + doh_print_buf(data, "DoH HTTPS", + d->https_rrs[i].val, d->https_rrs[i].len); +# else + infof(data, "DoH HTTPS RR: length %d", d->https_rrs[i].len); +# endif + } +#endif for(i = 0; i < d->numcname; i++) { infof(data, "CNAME: %s", Curl_dyn_ptr(&d->cname[i])); } } #else -#define showdoh(x,y) +#define doh_show(x,y) #endif /* @@ -774,38 +885,39 @@ static void showdoh(struct Curl_easy *data, * * This function returns a pointer to the first element of a newly allocated * Curl_addrinfo struct linked list filled with the data from a set of DoH - * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for + * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for * a IPv6 stack, but usable also for IPv4, all hosts and environments. * * The memory allocated by this function *MUST* be free'd later on calling - * Curl_freeaddrinfo(). For each successful call to this function there + * Curl_freeaddrinfo(). For each successful call to this function there * must be an associated call later to Curl_freeaddrinfo(). */ -static struct Curl_addrinfo * -doh2ai(const struct dohentry *de, const char *hostname, int port) +static CURLcode doh2ai(const struct dohentry *de, const char *hostname, + int port, struct Curl_addrinfo **aip) { struct Curl_addrinfo *ai; struct Curl_addrinfo *prevai = NULL; struct Curl_addrinfo *firstai = NULL; struct sockaddr_in *addr; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif CURLcode result = CURLE_OK; int i; size_t hostlen = strlen(hostname) + 1; /* include null-terminator */ - if(!de) - /* no input == no output! */ - return NULL; + DEBUGASSERT(de); + + if(!de->numaddr) + return CURLE_COULDNT_RESOLVE_HOST; for(i = 0; i < de->numaddr; i++) { size_t ss_size; CURL_SA_FAMILY_T addrtype; if(de->addr[i].type == DNS_TYPE_AAAA) { -#ifndef ENABLE_IPV6 - /* we can't handle IPv6 addresses */ +#ifndef USE_IPV6 + /* we cannot handle IPv6 addresses */ continue; #else ss_size = sizeof(struct sockaddr_in6); @@ -849,16 +961,24 @@ doh2ai(const struct dohentry *de, const char *hostname, int port) addr = (void *)ai->ai_addr; /* storage area for this info */ DEBUGASSERT(sizeof(struct in_addr) == sizeof(de->addr[i].ip.v4)); memcpy(&addr->sin_addr, &de->addr[i].ip.v4, sizeof(struct in_addr)); +#ifdef __MINGW32__ + addr->sin_family = (short)addrtype; +#else addr->sin_family = addrtype; +#endif addr->sin_port = htons((unsigned short)port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: addr6 = (void *)ai->ai_addr; /* storage area for this info */ DEBUGASSERT(sizeof(struct in6_addr) == sizeof(de->addr[i].ip.v6)); memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr)); +#ifdef __MINGW32__ + addr6->sin6_family = (short)addrtype; +#else addr6->sin6_family = addrtype; +#endif addr6->sin6_port = htons((unsigned short)port); break; #endif @@ -871,14 +991,26 @@ doh2ai(const struct dohentry *de, const char *hostname, int port) Curl_freeaddrinfo(firstai); firstai = NULL; } + *aip = firstai; - return firstai; + return result; } #ifndef CURL_DISABLE_VERBOSE_STRINGS -static const char *type2name(DNStype dnstype) +static const char *doh_type2name(DNStype dnstype) { - return (dnstype == DNS_TYPE_A)?"A":"AAAA"; + switch(dnstype) { + case DNS_TYPE_A: + return "A"; + case DNS_TYPE_AAAA: + return "AAAA"; +#ifdef USE_HTTPSRR + case DNS_TYPE_HTTPS: + return "HTTPS"; +#endif + default: + return "unknown"; + } } #endif @@ -888,71 +1020,341 @@ UNITTEST void de_cleanup(struct dohentry *d) for(i = 0; i < d->numcname; i++) { Curl_dyn_free(&d->cname[i]); } +#ifdef USE_HTTPSRR + for(i = 0; i < d->numhttps_rrs; i++) + Curl_safefree(d->https_rrs[i].val); +#endif } +#ifdef USE_HTTPSRR + +/* + * @brief decode the DNS name in a binary RRData + * @param buf points to the buffer (in/out) + * @param remaining points to the remaining buffer length (in/out) + * @param dnsname returns the string form name on success + * @return is 1 for success, error otherwise + * + * The encoding here is defined in + * https://tools.ietf.org/html/rfc1035#section-3.1 + * + * The input buffer pointer will be modified so it points to + * just after the end of the DNS name encoding on output. (And + * that is why it is an "unsigned char **" :-) + */ +static CURLcode doh_decode_rdata_name(unsigned char **buf, size_t *remaining, + char **dnsname) +{ + unsigned char *cp = NULL; + int rem = 0; + unsigned char clen = 0; /* chunk len */ + struct dynbuf thename; + + DEBUGASSERT(buf && remaining && dnsname); + if(!buf || !remaining || !dnsname) + return CURLE_OUT_OF_MEMORY; + rem = (int)*remaining; + if(rem <= 0) { + Curl_dyn_free(&thename); + return CURLE_OUT_OF_MEMORY; + } + Curl_dyn_init(&thename, CURL_MAXLEN_host_name); + cp = *buf; + clen = *cp++; + if(clen == 0) { + /* special case - return "." as name */ + if(Curl_dyn_addn(&thename, ".", 1)) + return CURLE_OUT_OF_MEMORY; + } + while(clen) { + if(clen >= rem) { + Curl_dyn_free(&thename); + return CURLE_OUT_OF_MEMORY; + } + if(Curl_dyn_addn(&thename, cp, clen) || + Curl_dyn_addn(&thename, ".", 1)) + return CURLE_TOO_LARGE; + + cp += clen; + rem -= (clen + 1); + if(rem <= 0) { + Curl_dyn_free(&thename); + return CURLE_OUT_OF_MEMORY; + } + clen = *cp++; + } + *buf = cp; + *remaining = rem - 1; + *dnsname = Curl_dyn_ptr(&thename); + return CURLE_OK; +} + +static CURLcode doh_decode_rdata_alpn(unsigned char *rrval, size_t len, + char **alpns) +{ + /* + * spec here is as per draft-ietf-dnsop-svcb-https, section-7.1.1 + * encoding is catenated list of strings each preceded by a one + * octet length + * output is comma-sep list of the strings + * implementations may or may not handle quoting of comma within + * string values, so we might see a comma within the wire format + * version of a string, in which case we will precede that by a + * backslash - same goes for a backslash character, and of course + * we need to use two backslashes in strings when we mean one;-) + */ + int remaining = (int) len; + char *oval; + size_t i; + unsigned char *cp = rrval; + struct dynbuf dval; + + if(!alpns) + return CURLE_OUT_OF_MEMORY; + Curl_dyn_init(&dval, DYN_DOH_RESPONSE); + remaining = (int)len; + cp = rrval; + while(remaining > 0) { + size_t tlen = (size_t) *cp++; + + /* if not 1st time, add comma */ + if(remaining != (int)len && Curl_dyn_addn(&dval, ",", 1)) + goto err; + remaining--; + if(tlen > (size_t)remaining) + goto err; + /* add escape char if needed, clunky but easier to read */ + for(i = 0; i != tlen; i++) { + if('\\' == *cp || ',' == *cp) { + if(Curl_dyn_addn(&dval, "\\", 1)) + goto err; + } + if(Curl_dyn_addn(&dval, cp++, 1)) + goto err; + } + remaining -= (int)tlen; + } + /* this string is always null terminated */ + oval = Curl_dyn_ptr(&dval); + if(!oval) + goto err; + *alpns = oval; + return CURLE_OK; +err: + Curl_dyn_free(&dval); + return CURLE_BAD_CONTENT_ENCODING; +} + +#ifdef DEBUGBUILD +static CURLcode doh_test_alpn_escapes(void) +{ + /* we will use an example from draft-ietf-dnsop-svcb, figure 10 */ + static unsigned char example[] = { + 0x08, /* length 8 */ + 0x66, 0x5c, 0x6f, 0x6f, 0x2c, 0x62, 0x61, 0x72, /* value "f\\oo,bar" */ + 0x02, /* length 2 */ + 0x68, 0x32 /* value "h2" */ + }; + size_t example_len = sizeof(example); + char *aval = NULL; + static const char *expected = "f\\\\oo\\,bar,h2"; + + if(doh_decode_rdata_alpn(example, example_len, &aval) != CURLE_OK) + return CURLE_BAD_CONTENT_ENCODING; + if(strlen(aval) != strlen(expected)) + return CURLE_BAD_CONTENT_ENCODING; + if(memcmp(aval, expected, strlen(aval))) + return CURLE_BAD_CONTENT_ENCODING; + return CURLE_OK; +} +#endif + +static CURLcode doh_resp_decode_httpsrr(unsigned char *rrval, size_t len, + struct Curl_https_rrinfo **hrr) +{ + size_t remaining = len; + unsigned char *cp = rrval; + uint16_t pcode = 0, plen = 0; + struct Curl_https_rrinfo *lhrr = NULL; + char *dnsname = NULL; + +#ifdef DEBUGBUILD + /* a few tests of escaping, should not be here but ok for now */ + if(doh_test_alpn_escapes() != CURLE_OK) + return CURLE_OUT_OF_MEMORY; +#endif + lhrr = calloc(1, sizeof(struct Curl_https_rrinfo)); + if(!lhrr) + return CURLE_OUT_OF_MEMORY; + lhrr->val = Curl_memdup(rrval, len); + if(!lhrr->val) + goto err; + lhrr->len = len; + if(remaining <= 2) + goto err; + lhrr->priority = (uint16_t)((cp[0] << 8) + cp[1]); + cp += 2; + remaining -= (uint16_t)2; + if(doh_decode_rdata_name(&cp, &remaining, &dnsname) != CURLE_OK) + goto err; + lhrr->target = dnsname; + while(remaining >= 4) { + pcode = (uint16_t)((*cp << 8) + (*(cp + 1))); + cp += 2; + plen = (uint16_t)((*cp << 8) + (*(cp + 1))); + cp += 2; + remaining -= 4; + if(pcode == HTTPS_RR_CODE_ALPN) { + if(doh_decode_rdata_alpn(cp, plen, &lhrr->alpns) != CURLE_OK) + goto err; + } + if(pcode == HTTPS_RR_CODE_NO_DEF_ALPN) + lhrr->no_def_alpn = TRUE; + else if(pcode == HTTPS_RR_CODE_IPV4) { + if(!plen) + goto err; + lhrr->ipv4hints = Curl_memdup(cp, plen); + if(!lhrr->ipv4hints) + goto err; + lhrr->ipv4hints_len = (size_t)plen; + } + else if(pcode == HTTPS_RR_CODE_ECH) { + if(!plen) + goto err; + lhrr->echconfiglist = Curl_memdup(cp, plen); + if(!lhrr->echconfiglist) + goto err; + lhrr->echconfiglist_len = (size_t)plen; + } + else if(pcode == HTTPS_RR_CODE_IPV6) { + if(!plen) + goto err; + lhrr->ipv6hints = Curl_memdup(cp, plen); + if(!lhrr->ipv6hints) + goto err; + lhrr->ipv6hints_len = (size_t)plen; + } + if(plen > 0 && plen <= remaining) { + cp += plen; + remaining -= plen; + } + } + DEBUGASSERT(!remaining); + *hrr = lhrr; + return CURLE_OK; +err: + if(lhrr) { + Curl_safefree(lhrr->target); + Curl_safefree(lhrr->echconfiglist); + Curl_safefree(lhrr->val); + Curl_safefree(lhrr->alpns); + Curl_safefree(lhrr); + } + return CURLE_OUT_OF_MEMORY; +} + +# ifdef DEBUGBUILD +static void doh_print_httpsrr(struct Curl_easy *data, + struct Curl_https_rrinfo *hrr) +{ + DEBUGASSERT(hrr); + infof(data, "HTTPS RR: priority %d, target: %s", + hrr->priority, hrr->target); + if(hrr->alpns) + infof(data, "HTTPS RR: alpns %s", hrr->alpns); + else + infof(data, "HTTPS RR: no alpns"); + if(hrr->no_def_alpn) + infof(data, "HTTPS RR: no_def_alpn set"); + else + infof(data, "HTTPS RR: no_def_alpn not set"); + if(hrr->ipv4hints) { + doh_print_buf(data, "HTTPS RR: ipv4hints", + hrr->ipv4hints, hrr->ipv4hints_len); + } + else + infof(data, "HTTPS RR: no ipv4hints"); + if(hrr->echconfiglist) { + doh_print_buf(data, "HTTPS RR: ECHConfigList", + hrr->echconfiglist, hrr->echconfiglist_len); + } + else + infof(data, "HTTPS RR: no ECHConfigList"); + if(hrr->ipv6hints) { + doh_print_buf(data, "HTTPS RR: ipv6hint", + hrr->ipv6hints, hrr->ipv6hints_len); + } + else + infof(data, "HTTPS RR: no ipv6hints"); + return; +} +# endif +#endif + CURLcode Curl_doh_is_resolved(struct Curl_easy *data, struct Curl_dns_entry **dnsp) { CURLcode result; - struct dohdata *dohp = data->req.doh; + struct doh_probes *dohp = data->req.doh; *dnsp = NULL; /* defaults to no response */ if(!dohp) return CURLE_OUT_OF_MEMORY; - if(!dohp->probe[DOH_PROBE_SLOT_IPADDR_V4].easy && - !dohp->probe[DOH_PROBE_SLOT_IPADDR_V6].easy) { + if(dohp->probe[DOH_SLOT_IPV4].easy_mid < 0 && + dohp->probe[DOH_SLOT_IPV6].easy_mid < 0) { failf(data, "Could not DoH-resolve: %s", data->state.async.hostname); - return CONN_IS_PROXIED(data->conn)?CURLE_COULDNT_RESOLVE_PROXY: + return CONN_IS_PROXIED(data->conn) ? CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST; } else if(!dohp->pending) { - DOHcode rc[DOH_PROBE_SLOTS] = { - DOH_OK, DOH_OK - }; + DOHcode rc[DOH_SLOT_COUNT]; struct dohentry de; int slot; + + memset(rc, 0, sizeof(rc)); /* remove DoH handles from multi handle and close them */ - for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) { - curl_multi_remove_handle(data->multi, dohp->probe[slot].easy); - Curl_close(&dohp->probe[slot].easy); - } + Curl_doh_close(data); /* parse the responses, create the struct and return it! */ de_init(&de); - for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) { - struct dnsprobe *p = &dohp->probe[slot]; + for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { + struct doh_probe *p = &dohp->probe[slot]; if(!p->dnstype) continue; - rc[slot] = doh_decode(Curl_dyn_uptr(&p->serverdoh), - Curl_dyn_len(&p->serverdoh), - p->dnstype, - &de); - Curl_dyn_free(&p->serverdoh); + rc[slot] = doh_resp_decode(Curl_dyn_uptr(&p->resp_body), + Curl_dyn_len(&p->resp_body), + p->dnstype, &de); + Curl_dyn_free(&p->resp_body); +#ifndef CURL_DISABLE_VERBOSE_STRINGS if(rc[slot]) { infof(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]), - type2name(p->dnstype), dohp->host); + doh_type2name(p->dnstype), dohp->host); } +#endif } /* next slot */ result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */ - if(!rc[DOH_PROBE_SLOT_IPADDR_V4] || !rc[DOH_PROBE_SLOT_IPADDR_V6]) { + if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) { /* we have an address, of one kind or other */ struct Curl_dns_entry *dns; struct Curl_addrinfo *ai; - infof(data, "DoH Host name: %s", dohp->host); - showdoh(data, &de); - ai = doh2ai(&de, dohp->host, dohp->port); - if(!ai) { + if(Curl_trc_ft_is_verbose(data, &Curl_doh_trc)) { + infof(data, "[DoH] hostname: %s", dohp->host); + doh_show(data, &de); + } + + result = doh2ai(&de, dohp->host, dohp->port, &ai); + if(result) { de_cleanup(&de); - return CURLE_OUT_OF_MEMORY; + return result; } if(data->share) Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); /* we got a response, store it in the cache */ - dns = Curl_cache_addr(data, ai, dohp->host, 0, dohp->port); + dns = Curl_cache_addr(data, ai, dohp->host, 0, dohp->port, FALSE); if(data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); @@ -969,10 +1371,26 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, } /* address processing done */ /* Now process any build-specific attributes retrieved from DNS */ +#ifdef USE_HTTPSRR + if(de.numhttps_rrs > 0 && result == CURLE_OK && *dnsp) { + struct Curl_https_rrinfo *hrr = NULL; + result = doh_resp_decode_httpsrr(de.https_rrs->val, de.https_rrs->len, + &hrr); + if(result) { + infof(data, "Failed to decode HTTPS RR"); + return result; + } + infof(data, "Some HTTPS RR to process"); +# ifdef DEBUGBUILD + doh_print_httpsrr(data, hrr); +# endif + (*dnsp)->hinfo = hrr; + } +#endif /* All done */ de_cleanup(&de); - Curl_safefree(data->req.doh); + Curl_doh_cleanup(data); return result; } /* !dohp->pending */ @@ -981,4 +1399,44 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, return CURLE_OK; } +void Curl_doh_close(struct Curl_easy *data) +{ + struct doh_probes *doh = data->req.doh; + if(doh && data->multi) { + struct Curl_easy *probe_data; + curl_off_t mid; + size_t slot; + for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { + mid = doh->probe[slot].easy_mid; + if(mid < 0) + continue; + doh->probe[slot].easy_mid = -1; + /* should have been called before data is removed from multi handle */ + DEBUGASSERT(data->multi); + probe_data = data->multi ? Curl_multi_get_handle(data->multi, mid) : + NULL; + if(!probe_data) { + DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%" + FMT_OFF_T " not found!", + doh->probe[slot].easy_mid)); + continue; + } + /* data->multi might already be reset at this time */ + curl_multi_remove_handle(data->multi, probe_data); + Curl_close(&probe_data); + } + } +} + +void Curl_doh_cleanup(struct Curl_easy *data) +{ + struct doh_probes *doh = data->req.doh; + if(doh) { + Curl_doh_close(data); + curl_slist_free_all(doh->req_hds); + data->req.doh->req_hds = NULL; + Curl_safefree(data->req.doh); + } +} + #endif /* CURL_DISABLE_DOH */ diff --git a/deps/curl/lib/doh.h b/deps/curl/lib/doh.h index 7d7b694f33a..aae32a65409 100644 --- a/deps/curl/lib/doh.h +++ b/deps/curl/lib/doh.h @@ -26,6 +26,9 @@ #include "urldata.h" #include "curl_addrinfo.h" +#ifdef USE_HTTPSRR +# include +#endif #ifndef CURL_DISABLE_DOH @@ -51,22 +54,44 @@ typedef enum { DNS_TYPE_NS = 2, DNS_TYPE_CNAME = 5, DNS_TYPE_AAAA = 28, - DNS_TYPE_DNAME = 39 /* RFC6672 */ + DNS_TYPE_DNAME = 39, /* RFC6672 */ + DNS_TYPE_HTTPS = 65 } DNStype; /* one of these for each DoH request */ -struct dnsprobe { - CURL *easy; +struct doh_probe { + curl_off_t easy_mid; /* multi id of easy handle doing the lookup */ DNStype dnstype; - unsigned char dohbuffer[512]; - size_t dohlen; - struct dynbuf serverdoh; + unsigned char req_body[512]; + size_t req_body_len; + struct dynbuf resp_body; }; -struct dohdata { - struct curl_slist *headers; - struct dnsprobe probe[DOH_PROBE_SLOTS]; - unsigned int pending; /* still outstanding requests */ +enum doh_slot_num { + /* Explicit values for first two symbols so as to match hard-coded + * constants in existing code + */ + DOH_SLOT_IPV4 = 0, /* make 'V4' stand out for readability */ + DOH_SLOT_IPV6 = 1, /* 'V6' likewise */ + + /* Space here for (possibly build-specific) additional slot definitions */ +#ifdef USE_HTTPSRR + DOH_SLOT_HTTPS_RR = 2, /* for HTTPS RR */ +#endif + + /* for example */ + /* #ifdef WANT_DOH_FOOBAR_TXT */ + /* DOH_PROBE_SLOT_FOOBAR_TXT, */ + /* #endif */ + + /* AFTER all slot definitions, establish how many we have */ + DOH_SLOT_COUNT +}; + +struct doh_probes { + struct curl_slist *req_hds; + struct doh_probe probe[DOH_SLOT_COUNT]; + unsigned int pending; /* still outstanding probes */ int port; const char *host; }; @@ -84,10 +109,9 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, CURLcode Curl_doh_is_resolved(struct Curl_easy *data, struct Curl_dns_entry **dns); -int Curl_doh_getsock(struct connectdata *conn, curl_socket_t *socks); - #define DOH_MAX_ADDR 24 #define DOH_MAX_CNAME 4 +#define DOH_MAX_HTTPS 4 struct dohaddr { int type; @@ -97,29 +121,66 @@ struct dohaddr { } ip; }; +#ifdef USE_HTTPSRR + +/* + * These are the code points for DNS wire format SvcParams as + * per draft-ietf-dnsop-svcb-https + * Not all are supported now, and even those that are may need + * more work in future to fully support the spec. + */ +#define HTTPS_RR_CODE_ALPN 0x01 +#define HTTPS_RR_CODE_NO_DEF_ALPN 0x02 +#define HTTPS_RR_CODE_PORT 0x03 +#define HTTPS_RR_CODE_IPV4 0x04 +#define HTTPS_RR_CODE_ECH 0x05 +#define HTTPS_RR_CODE_IPV6 0x06 + +/* + * These may need escaping when found within an ALPN string + * value. + */ +#define COMMA_CHAR ',' +#define BACKSLASH_CHAR '\\' + +struct dohhttps_rr { + uint16_t len; /* raw encoded length */ + unsigned char *val; /* raw encoded octets */ +}; +#endif + struct dohentry { struct dynbuf cname[DOH_MAX_CNAME]; struct dohaddr addr[DOH_MAX_ADDR]; int numaddr; unsigned int ttl; int numcname; +#ifdef USE_HTTPSRR + struct dohhttps_rr https_rrs[DOH_MAX_HTTPS]; + int numhttps_rrs; +#endif }; - -#ifdef DEBUGBUILD -DOHcode doh_encode(const char *host, - DNStype dnstype, - unsigned char *dnsp, /* buffer */ - size_t len, /* buffer size */ - size_t *olen); /* output length */ -DOHcode doh_decode(const unsigned char *doh, - size_t dohlen, - DNStype dnstype, - struct dohentry *d); -void de_init(struct dohentry *d); -void de_cleanup(struct dohentry *d); +void Curl_doh_close(struct Curl_easy *data); +void Curl_doh_cleanup(struct Curl_easy *data); + +#ifdef UNITTESTS +UNITTEST DOHcode doh_req_encode(const char *host, + DNStype dnstype, + unsigned char *dnsp, /* buffer */ + size_t len, /* buffer size */ + size_t *olen); /* output length */ +UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, + size_t dohlen, + DNStype dnstype, + struct dohentry *d); + +UNITTEST void de_init(struct dohentry *d); +UNITTEST void de_cleanup(struct dohentry *d); #endif +extern struct curl_trc_feat Curl_doh_trc; + #else /* if DoH is disabled */ #define Curl_doh(a,b,c,d) NULL #define Curl_doh_is_resolved(x,y) CURLE_COULDNT_RESOLVE_HOST diff --git a/deps/curl/lib/dynbuf.c b/deps/curl/lib/dynbuf.c index 0c9c491aebc..eab07efbf05 100644 --- a/deps/curl/lib/dynbuf.c +++ b/deps/curl/lib/dynbuf.c @@ -51,7 +51,7 @@ void Curl_dyn_init(struct dynbuf *s, size_t toobig) } /* - * free the buffer and re-init the necessary fields. It doesn't touch the + * free the buffer and re-init the necessary fields. It does not touch the * 'init' field and thus this buffer can be reused to add data to again. */ void Curl_dyn_free(struct dynbuf *s) @@ -71,16 +71,17 @@ static CURLcode dyn_nappend(struct dynbuf *s, size_t a = s->allc; size_t fit = len + indx + 1; /* new string + old string + zero byte */ - /* try to detect if there's rubbish in the struct */ + /* try to detect if there is rubbish in the struct */ DEBUGASSERT(s->init == DYNINIT); DEBUGASSERT(s->toobig); DEBUGASSERT(indx < s->toobig); DEBUGASSERT(!s->leng || s->bufr); DEBUGASSERT(a <= s->toobig); + DEBUGASSERT(!len || mem); if(fit > s->toobig) { Curl_dyn_free(s); - return CURLE_OUT_OF_MEMORY; + return CURLE_TOO_LARGE; } else if(!a) { DEBUGASSERT(!indx); @@ -174,10 +175,12 @@ CURLcode Curl_dyn_addn(struct dynbuf *s, const void *mem, size_t len) */ CURLcode Curl_dyn_add(struct dynbuf *s, const char *str) { - size_t n = strlen(str); + size_t n; + DEBUGASSERT(str); DEBUGASSERT(s); DEBUGASSERT(s->init == DYNINIT); DEBUGASSERT(!s->leng || s->bufr); + n = strlen(str); return dyn_nappend(s, (unsigned char *)str, n); } @@ -191,10 +194,14 @@ CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) DEBUGASSERT(s); DEBUGASSERT(s->init == DYNINIT); DEBUGASSERT(!s->leng || s->bufr); + DEBUGASSERT(fmt); rc = Curl_dyn_vprintf(s, fmt, ap); if(!rc) return CURLE_OK; + else if(rc == MERR_TOO_LARGE) + return CURLE_TOO_LARGE; + return CURLE_OUT_OF_MEMORY; #else char *str; str = vaprintf(fmt, ap); /* this allocs a new string to append */ @@ -206,8 +213,8 @@ CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) } /* If we failed, we cleanup the whole buffer and return error */ Curl_dyn_free(s); -#endif return CURLE_OUT_OF_MEMORY; +#endif } /* diff --git a/deps/curl/lib/dynbuf.h b/deps/curl/lib/dynbuf.h index 6291eabd37d..7dbaab886e2 100644 --- a/deps/curl/lib/dynbuf.h +++ b/deps/curl/lib/dynbuf.h @@ -61,9 +61,9 @@ CURLcode Curl_dyn_addn(struct dynbuf *s, const void *mem, size_t len) CURLcode Curl_dyn_add(struct dynbuf *s, const char *str) WARN_UNUSED_RESULT; CURLcode Curl_dyn_addf(struct dynbuf *s, const char *fmt, ...) - WARN_UNUSED_RESULT; + WARN_UNUSED_RESULT CURL_PRINTF(2, 3); CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) - WARN_UNUSED_RESULT; + WARN_UNUSED_RESULT CURL_PRINTF(2, 0); void Curl_dyn_reset(struct dynbuf *s); CURLcode Curl_dyn_tail(struct dynbuf *s, size_t trail); CURLcode Curl_dyn_setlen(struct dynbuf *s, size_t set); @@ -89,4 +89,5 @@ int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save); #define DYN_H1_TRAILER 4096 #define DYN_PINGPPONG_CMD (64*1024) #define DYN_IMAP_CMD (64*1024) +#define DYN_MQTT_RECV (64*1024) #endif diff --git a/deps/curl/lib/dynhds.c b/deps/curl/lib/dynhds.c index 007dfc588c8..2c92ca63fe6 100644 --- a/deps/curl/lib/dynhds.c +++ b/deps/curl/lib/dynhds.c @@ -27,6 +27,10 @@ #include "strcase.h" /* The last 3 #include files should be in this order */ +#ifdef USE_NGHTTP2 +#include +#include +#endif /* USE_NGHTTP2 */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" @@ -137,7 +141,7 @@ void Curl_dynhds_set_opts(struct dynhds *dynhds, int opts) struct dynhds_entry *Curl_dynhds_getn(struct dynhds *dynhds, size_t n) { DEBUGASSERT(dynhds); - return (n < dynhds->hds_len)? dynhds->hds[n] : NULL; + return (n < dynhds->hds_len) ? dynhds->hds[n] : NULL; } struct dynhds_entry *Curl_dynhds_get(struct dynhds *dynhds, const char *name, @@ -268,10 +272,10 @@ CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds, CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line) { - return Curl_dynhds_h1_add_line(dynhds, line, line? strlen(line) : 0); + return Curl_dynhds_h1_add_line(dynhds, line, line ? strlen(line) : 0); } -#ifdef DEBUGBUILD +#ifdef UNITTESTS /* used by unit2602.c */ bool Curl_dynhds_contains(struct dynhds *dynhds, @@ -344,6 +348,8 @@ size_t Curl_dynhds_cremove(struct dynhds *dynhds, const char *name) return Curl_dynhds_remove(dynhds, name, strlen(name)); } +#endif + CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf) { CURLcode result = CURLE_OK; @@ -363,4 +369,28 @@ CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf) return result; } -#endif +#ifdef USE_NGHTTP2 + +nghttp2_nv *Curl_dynhds_to_nva(struct dynhds *dynhds, size_t *pcount) +{ + nghttp2_nv *nva = calloc(1, sizeof(nghttp2_nv) * dynhds->hds_len); + size_t i; + + *pcount = 0; + if(!nva) + return NULL; + + for(i = 0; i < dynhds->hds_len; ++i) { + struct dynhds_entry *e = dynhds->hds[i]; + DEBUGASSERT(e); + nva[i].name = (unsigned char *)e->name; + nva[i].namelen = e->namelen; + nva[i].value = (unsigned char *)e->value; + nva[i].valuelen = e->valuelen; + nva[i].flags = NGHTTP2_NV_FLAG_NONE; + } + *pcount = dynhds->hds_len; + return nva; +} + +#endif /* USE_NGHTTP2 */ diff --git a/deps/curl/lib/dynhds.h b/deps/curl/lib/dynhds.h index 8a053480e99..fb162a30deb 100644 --- a/deps/curl/lib/dynhds.h +++ b/deps/curl/lib/dynhds.h @@ -95,6 +95,9 @@ struct dynhds_entry *Curl_dynhds_get(struct dynhds *dynhds, const char *name, size_t namelen); struct dynhds_entry *Curl_dynhds_cget(struct dynhds *dynhds, const char *name); +#ifdef UNITTESTS +/* used by unit2602.c */ + /** * Return TRUE iff one or more headers with the given name exist. */ @@ -115,20 +118,6 @@ size_t Curl_dynhds_count_name(struct dynhds *dynhds, */ size_t Curl_dynhds_ccount_name(struct dynhds *dynhds, const char *name); -/** - * Add a header, name + value, to `dynhds` at the end. Does *not* - * check for duplicate names. - */ -CURLcode Curl_dynhds_add(struct dynhds *dynhds, - const char *name, size_t namelen, - const char *value, size_t valuelen); - -/** - * Add a header, c-string name + value, to `dynhds` at the end. - */ -CURLcode Curl_dynhds_cadd(struct dynhds *dynhds, - const char *name, const char *value); - /** * Remove all entries with the given name. * Returns number of entries removed. @@ -146,19 +135,34 @@ size_t Curl_dynhds_cremove(struct dynhds *dynhds, const char *name); CURLcode Curl_dynhds_set(struct dynhds *dynhds, const char *name, size_t namelen, const char *value, size_t valuelen); +#endif CURLcode Curl_dynhds_cset(struct dynhds *dynhds, const char *name, const char *value); /** - * Add a single header from a HTTP/1.1 formatted line at the end. Line + * Add a header, name + value, to `dynhds` at the end. Does *not* + * check for duplicate names. + */ +CURLcode Curl_dynhds_add(struct dynhds *dynhds, + const char *name, size_t namelen, + const char *value, size_t valuelen); + +/** + * Add a header, c-string name + value, to `dynhds` at the end. + */ +CURLcode Curl_dynhds_cadd(struct dynhds *dynhds, + const char *name, const char *value); + +/** + * Add a single header from an HTTP/1.1 formatted line at the end. Line * may contain a delimiting \r\n or just \n. Any characters after * that will be ignored. */ CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line); /** - * Add a single header from a HTTP/1.1 formatted line at the end. Line + * Add a single header from an HTTP/1.1 formatted line at the end. Line * may contain a delimiting \r\n or just \n. Any characters after * that will be ignored. */ @@ -171,4 +175,13 @@ CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds, */ CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf); +#ifdef USE_NGHTTP2 + +#include +#include + +nghttp2_nv *Curl_dynhds_to_nva(struct dynhds *dynhds, size_t *pcount); + +#endif /* USE_NGHTTP2 */ + #endif /* HEADER_CURL_DYNHDS_H */ diff --git a/deps/curl/lib/easy.c b/deps/curl/lib/easy.c index 16bbd35251d..d16fa8c07af 100644 --- a/deps/curl/lib/easy.c +++ b/deps/curl/lib/easy.c @@ -112,7 +112,8 @@ static curl_simple_lock s_lock = CURL_SIMPLE_LOCK_INIT; #define system_strdup strdup #endif -#if defined(_MSC_VER) && defined(_DLL) && !defined(__POCC__) +#if defined(_MSC_VER) && defined(_DLL) +# pragma warning(push) # pragma warning(disable:4232) /* MSVC extension, dllimport identity */ #endif @@ -125,12 +126,12 @@ curl_free_callback Curl_cfree = (curl_free_callback)free; curl_realloc_callback Curl_crealloc = (curl_realloc_callback)realloc; curl_strdup_callback Curl_cstrdup = (curl_strdup_callback)system_strdup; curl_calloc_callback Curl_ccalloc = (curl_calloc_callback)calloc; -#if defined(WIN32) && defined(UNICODE) +#if defined(_WIN32) && defined(UNICODE) curl_wcsdup_callback Curl_cwcsdup = Curl_wcsdup; #endif -#if defined(_MSC_VER) && defined(_DLL) && !defined(__POCC__) -# pragma warning(default:4232) /* MSVC extension, dllimport identity */ +#if defined(_MSC_VER) && defined(_DLL) +# pragma warning(pop) #endif #ifdef DEBUGBUILD @@ -153,7 +154,7 @@ static CURLcode global_init(long flags, bool memoryfuncs) Curl_crealloc = (curl_realloc_callback)realloc; Curl_cstrdup = (curl_strdup_callback)system_strdup; Curl_ccalloc = (curl_calloc_callback)calloc; -#if defined(WIN32) && defined(UNICODE) +#if defined(_WIN32) && defined(UNICODE) Curl_cwcsdup = (curl_wcsdup_callback)_wcsdup; #endif } @@ -188,18 +189,10 @@ static CURLcode global_init(long flags, bool memoryfuncs) goto fail; } -#if defined(USE_SSH) if(Curl_ssh_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_ssh_init failed\n")); goto fail; } -#endif - -#ifdef USE_WOLFSSH - if(WS_SUCCESS != wolfSSH_Init()) { - DEBUGF(fprintf(stderr, "Error: wolfSSH_Init failed\n")); - return CURLE_FAILED_INIT; - } -#endif easy_init_flags = flags; @@ -250,7 +243,7 @@ CURLcode curl_global_init_mem(long flags, curl_malloc_callback m, global_init_lock(); if(initialized) { - /* Already initialized, don't do it again, but bump the variable anyway to + /* Already initialized, do not do it again, but bump the variable anyway to work like curl_global_init() and require the same amount of cleanup calls. */ initialized++; @@ -276,7 +269,8 @@ CURLcode curl_global_init_mem(long flags, curl_malloc_callback m, /** * curl_global_cleanup() globally cleanups curl, uses the value of - * "easy_init_flags" to determine what needs to be cleaned up and what doesn't. + * "easy_init_flags" to determine what needs to be cleaned up and what does + * not. */ void curl_global_cleanup(void) { @@ -295,7 +289,7 @@ void curl_global_cleanup(void) Curl_ssl_cleanup(); Curl_resolver_global_cleanup(); -#ifdef WIN32 +#ifdef _WIN32 Curl_win32_cleanup(easy_init_flags); #endif @@ -303,9 +297,6 @@ void curl_global_cleanup(void) Curl_ssh_cleanup(); -#ifdef USE_WOLFSSH - (void)wolfSSH_Cleanup(); -#endif #ifdef DEBUGBUILD free(leakpointer); #endif @@ -356,7 +347,7 @@ CURLsslset curl_global_sslset(curl_sslbackend id, const char *name, * curl_easy_init() is the external interface to alloc, setup and init an * easy handle that is returned. If anything goes wrong, NULL is returned. */ -struct Curl_easy *curl_easy_init(void) +CURL *curl_easy_init(void) { CURLcode result; struct Curl_easy *data; @@ -385,7 +376,7 @@ struct Curl_easy *curl_easy_init(void) return data; } -#ifdef CURLDEBUG +#ifdef DEBUGBUILD struct socketmonitor { struct socketmonitor *next; /* the next node in the list or NULL */ @@ -400,25 +391,22 @@ struct events { int running_handles; /* store the returned number */ }; +#define DEBUG_EV_POLL 0 + /* events_timer * * Callback that gets called with a new value when the timeout should be * updated. */ - -static int events_timer(struct Curl_multi *multi, /* multi handle */ +static int events_timer(CURLM *multi, /* multi handle */ long timeout_ms, /* see above */ - void *userp) /* private callback pointer */ + void *userp) /* private callback pointer */ { struct events *ev = userp; (void)multi; - if(timeout_ms == -1) - /* timeout removed */ - timeout_ms = 0; - else if(timeout_ms == 0) - /* timeout is already reached! */ - timeout_ms = 1; /* trigger asap */ - +#if DEBUG_EV_POLL + fprintf(stderr, "events_timer: set timeout %ldms\n", timeout_ms); +#endif ev->ms = timeout_ms; ev->msbump = TRUE; return 0; @@ -461,7 +449,7 @@ static short socketcb2poll(int pollmask) * Callback that gets called with information about socket activity to * monitor. */ -static int events_socket(struct Curl_easy *easy, /* easy handle */ +static int events_socket(CURL *easy, /* easy handle */ curl_socket_t s, /* socket */ int what, /* see above */ void *userp, /* private callback @@ -472,6 +460,8 @@ static int events_socket(struct Curl_easy *easy, /* easy handle */ struct events *ev = userp; struct socketmonitor *m; struct socketmonitor *prev = NULL; + bool found = FALSE; + struct Curl_easy *data = easy; #if defined(CURL_DISABLE_VERBOSE_STRINGS) (void) easy; @@ -481,7 +471,7 @@ static int events_socket(struct Curl_easy *easy, /* easy handle */ m = ev->list; while(m) { if(m->socket.fd == s) { - + found = TRUE; if(what == CURL_POLL_REMOVE) { struct socketmonitor *nxt = m->next; /* remove this node from the list of monitored sockets */ @@ -490,28 +480,29 @@ static int events_socket(struct Curl_easy *easy, /* easy handle */ else ev->list = nxt; free(m); - m = nxt; - infof(easy, "socket cb: socket %d REMOVED", s); + infof(data, "socket cb: socket %" FMT_SOCKET_T " REMOVED", s); } else { /* The socket 's' is already being monitored, update the activity mask. Convert from libcurl bitmask to the poll one. */ m->socket.events = socketcb2poll(what); - infof(easy, "socket cb: socket %d UPDATED as %s%s", s, - (what&CURL_POLL_IN)?"IN":"", - (what&CURL_POLL_OUT)?"OUT":""); + infof(data, "socket cb: socket %" FMT_SOCKET_T + " UPDATED as %s%s", s, + (what&CURL_POLL_IN) ? "IN" : "", + (what&CURL_POLL_OUT) ? "OUT" : ""); } break; } prev = m; m = m->next; /* move to next node */ } - if(!m) { + + if(!found) { if(what == CURL_POLL_REMOVE) { - /* this happens a bit too often, libcurl fix perhaps? */ - /* fprintf(stderr, - "%s: socket %d asked to be REMOVED but not present!\n", - __func__, s); */ + /* should not happen if our logic is correct, but is no drama. */ + DEBUGF(infof(data, "socket cb: asked to REMOVE socket %" + FMT_SOCKET_T "but not present!", s)); + DEBUGASSERT(0); } else { m = malloc(sizeof(struct socketmonitor)); @@ -521,9 +512,9 @@ static int events_socket(struct Curl_easy *easy, /* easy handle */ m->socket.events = socketcb2poll(what); m->socket.revents = 0; ev->list = m; - infof(easy, "socket cb: socket %d ADDED as %s%s", s, - (what&CURL_POLL_IN)?"IN":"", - (what&CURL_POLL_OUT)?"OUT":""); + infof(data, "socket cb: socket %" FMT_SOCKET_T " ADDED as %s%s", s, + (what&CURL_POLL_IN) ? "IN" : "", + (what&CURL_POLL_OUT) ? "OUT" : ""); } else return CURLE_OUT_OF_MEMORY; @@ -571,14 +562,15 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) int pollrc; int i; struct curltime before; - struct curltime after; /* populate the fds[] array */ for(m = ev->list, f = &fds[0]; m; m = m->next) { f->fd = m->socket.fd; f->events = m->socket.events; f->revents = 0; - /* fprintf(stderr, "poll() %d check socket %d\n", numfds, f->fd); */ +#if DEBUG_EV_POLL + fprintf(stderr, "poll() %d check socket %d\n", numfds, f->fd); +#endif f++; numfds++; } @@ -586,12 +578,27 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) /* get the time stamp to use to figure out how long poll takes */ before = Curl_now(); - /* wait for activity or timeout */ - pollrc = Curl_poll(fds, numfds, ev->ms); - if(pollrc < 0) - return CURLE_UNRECOVERABLE_POLL; - - after = Curl_now(); + if(numfds) { + /* wait for activity or timeout */ +#if DEBUG_EV_POLL + fprintf(stderr, "poll(numfds=%d, timeout=%ldms)\n", numfds, ev->ms); +#endif + pollrc = Curl_poll(fds, (unsigned int)numfds, ev->ms); +#if DEBUG_EV_POLL + fprintf(stderr, "poll(numfds=%d, timeout=%ldms) -> %d\n", + numfds, ev->ms, pollrc); +#endif + if(pollrc < 0) + return CURLE_UNRECOVERABLE_POLL; + } + else { +#if DEBUG_EV_POLL + fprintf(stderr, "poll, but no fds, wait timeout=%ldms\n", ev->ms); +#endif + pollrc = 0; + if(ev->ms > 0) + Curl_wait_ms(ev->ms); + } ev->msbump = FALSE; /* reset here */ @@ -604,25 +611,37 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) } else { /* here pollrc is > 0 */ + struct Curl_llist_node *e = Curl_llist_head(&multi->process); + struct Curl_easy *data; + DEBUGASSERT(e); + data = Curl_node_elem(e); + DEBUGASSERT(data); /* loop over the monitored sockets to see which ones had activity */ - for(i = 0; i< numfds; i++) { + for(i = 0; i < numfds; i++) { if(fds[i].revents) { /* socket activity, tell libcurl */ int act = poll2cselect(fds[i].revents); /* convert */ - infof(multi->easyp, "call curl_multi_socket_action(socket %d)", - fds[i].fd); + + /* sending infof "randomly" to the first easy handle */ + infof(data, "call curl_multi_socket_action(socket " + "%" FMT_SOCKET_T ")", (curl_socket_t)fds[i].fd); mcode = curl_multi_socket_action(multi, fds[i].fd, act, &ev->running_handles); } } - if(!ev->msbump) { + + if(!ev->msbump && ev->ms >= 0) { /* If nothing updated the timeout, we decrease it by the spent time. * If it was updated, it has the new timeout time stored already. */ - timediff_t timediff = Curl_timediff(after, before); + timediff_t timediff = Curl_timediff(Curl_now(), before); if(timediff > 0) { +#if DEBUG_EV_POLL + fprintf(stderr, "poll timeout %ldms not updated, decrease by " + "time spent %ldms\n", ev->ms, (long)timediff); +#endif if(timediff > ev->ms) ev->ms = 0; else @@ -634,7 +653,7 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) if(mcode) return CURLE_URL_MALFORMAT; - /* we don't really care about the "msgs_in_queue" value returned in the + /* we do not really care about the "msgs_in_queue" value returned in the second argument */ msg = curl_multi_info_read(multi, &pollrc); if(msg) { @@ -655,15 +674,15 @@ static CURLcode easy_events(struct Curl_multi *multi) { /* this struct is made static to allow it to be used after this function returns and curl_multi_remove_handle() is called */ - static struct events evs = {2, FALSE, 0, NULL, 0}; + static struct events evs = {-1, FALSE, 0, NULL, 0}; /* if running event-based, do some further multi inits */ events_setup(multi, &evs); return wait_or_timeout(multi, &evs); } -#else /* CURLDEBUG */ -/* when not built with debug, this function doesn't exist */ +#else /* DEBUGBUILD */ +/* when not built with debug, this function does not exist */ #define easy_events(x) CURLE_NOT_BUILT_IN #endif @@ -695,9 +714,9 @@ static CURLcode easy_transfer(struct Curl_multi *multi) /* Make sure to return some kind of error if there was a multi problem */ if(mcode) { result = (mcode == CURLM_OUT_OF_MEMORY) ? CURLE_OUT_OF_MEMORY : - /* The other multi errors should never happen, so return - something suitably generic */ - CURLE_BAD_FUNCTION_ARGUMENT; + /* The other multi errors should never happen, so return + something suitably generic */ + CURLE_BAD_FUNCTION_ARGUMENT; } return result; @@ -713,7 +732,7 @@ static CURLcode easy_transfer(struct Curl_multi *multi) * easy handle, destroys the multi handle and returns the easy handle's return * code. * - * REALITY: it can't just create and destroy the multi handle that easily. It + * REALITY: it cannot just create and destroy the multi handle that easily. It * needs to keep it around since if this easy handle is used again by this * function, the same multi handle must be reused so that the same pools and * caches can be used. @@ -735,6 +754,8 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) /* clear this as early as possible */ data->set.errorbuffer[0] = 0; + data->state.os_errno = 0; + if(data->multi) { failf(data, "easy handle already used in multi handle"); return CURLE_FAILED_INIT; @@ -748,30 +769,33 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) multi = Curl_multi_handle(1, 3, 7); if(!multi) return CURLE_OUT_OF_MEMORY; - data->multi_easy = multi; } if(multi->in_callback) return CURLE_RECURSIVE_API_CALL; /* Copy the MAXCONNECTS option to the multi handle */ - curl_multi_setopt(multi, CURLMOPT_MAXCONNECTS, data->set.maxconnects); + curl_multi_setopt(multi, CURLMOPT_MAXCONNECTS, (long)data->set.maxconnects); + data->multi_easy = NULL; /* pretend it does not exist */ mcode = curl_multi_add_handle(multi, data); if(mcode) { curl_multi_cleanup(multi); - data->multi_easy = NULL; if(mcode == CURLM_OUT_OF_MEMORY) return CURLE_OUT_OF_MEMORY; return CURLE_FAILED_INIT; } - sigpipe_ignore(data, &pipe_st); + /* assign this after curl_multi_add_handle() */ + data->multi_easy = multi; + + sigpipe_init(&pipe_st); + sigpipe_apply(data, &pipe_st); /* run the transfer */ result = events ? easy_events(multi) : easy_transfer(multi); - /* ignoring the return code isn't nice, but atm we can't really handle + /* ignoring the return code is not nice, but atm we cannot really handle a failure here, room for future improvement! */ (void)curl_multi_remove_handle(multi, data); @@ -786,12 +810,12 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) * curl_easy_perform() is the external interface that performs a blocking * transfer as previously setup. */ -CURLcode curl_easy_perform(struct Curl_easy *data) +CURLcode curl_easy_perform(CURL *data) { return easy_perform(data, FALSE); } -#ifdef CURLDEBUG +#ifdef DEBUGBUILD /* * curl_easy_perform_ev() is the external interface that performs a blocking * transfer using the event-based API internally. @@ -807,8 +831,9 @@ CURLcode curl_easy_perform_ev(struct Curl_easy *data) * curl_easy_cleanup() is the external interface to cleaning/freeing the given * easy handle. */ -void curl_easy_cleanup(struct Curl_easy *data) +void curl_easy_cleanup(CURL *ptr) { + struct Curl_easy *data = ptr; if(GOOD_EASY_HANDLE(data)) { SIGPIPE_VARIABLE(pipe_st); sigpipe_ignore(data, &pipe_st); @@ -822,7 +847,7 @@ void curl_easy_cleanup(struct Curl_easy *data) * information from a performed transfer and similar. */ #undef curl_easy_getinfo -CURLcode curl_easy_getinfo(struct Curl_easy *data, CURLINFO info, ...) +CURLcode curl_easy_getinfo(CURL *data, CURLINFO info, ...) { va_list arg; void *paramp; @@ -848,18 +873,18 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) dst->set = src->set; Curl_mime_initpart(&dst->set.mimepost); - /* clear all string pointers first */ + /* clear all dest string and blob pointers first, in case we error out + mid-function */ memset(dst->set.str, 0, STRING_LAST * sizeof(char *)); + memset(dst->set.blobs, 0, BLOB_LAST * sizeof(struct curl_blob *)); /* duplicate all strings */ - for(i = (enum dupstring)0; i< STRING_LASTZEROTERMINATED; i++) { + for(i = (enum dupstring)0; i < STRING_LASTZEROTERMINATED; i++) { result = Curl_setstropt(&dst->set.str[i], src->set.str[i]); if(result) return result; } - /* clear all blob pointers first */ - memset(dst->set.blobs, 0, BLOB_LAST * sizeof(struct curl_blob *)); /* duplicate all blobs */ for(j = (enum dupblob)0; j < BLOB_LAST; j++) { result = Curl_setblobopt(&dst->set.blobs[j], src->set.blobs[j]); @@ -869,10 +894,13 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) /* duplicate memory areas pointed to */ i = STRING_COPYPOSTFIELDS; - if(src->set.postfieldsize && src->set.str[i]) { - /* postfieldsize is curl_off_t, Curl_memdup() takes a size_t ... */ - dst->set.str[i] = Curl_memdup(src->set.str[i], - curlx_sotouz(src->set.postfieldsize)); + if(src->set.str[i]) { + if(src->set.postfieldsize == -1) + dst->set.str[i] = strdup(src->set.str[i]); + else + /* postfieldsize is curl_off_t, Curl_memdup() takes a size_t ... */ + dst->set.str[i] = Curl_memdup(src->set.str[i], + curlx_sotouz(src->set.postfieldsize)); if(!dst->set.str[i]) return CURLE_OUT_OF_MEMORY; /* point to the new copy */ @@ -893,8 +921,9 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) * given input easy handle. The returned handle will be a new working handle * with all options set exactly as the input source handle. */ -struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) +CURL *curl_easy_duphandle(CURL *d) { + struct Curl_easy *data = d; struct Curl_easy *outcurl = calloc(1, sizeof(struct Curl_easy)); if(!outcurl) goto fail; @@ -912,8 +941,7 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) Curl_dyn_init(&outcurl->state.headerb, CURL_MAX_HTTP_HEADER); - /* the connection cache is setup on demand */ - outcurl->state.conn_cache = NULL; + /* the connection pool is setup on demand */ outcurl->state.lastconnect_id = -1; outcurl->state.recent_conn_id = -1; outcurl->id = -1; @@ -922,20 +950,19 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) outcurl->progress.callback = data->progress.callback; #ifndef CURL_DISABLE_COOKIES - if(data->cookies) { + outcurl->state.cookielist = NULL; + if(data->cookies && data->state.cookie_engine) { /* If cookies are enabled in the parent handle, we enable them in the clone as well! */ - outcurl->cookies = Curl_cookie_init(data, - data->cookies->filename, - outcurl->cookies, + outcurl->cookies = Curl_cookie_init(outcurl, NULL, outcurl->cookies, data->set.cookiesession); if(!outcurl->cookies) goto fail; } - if(data->set.cookielist) { - outcurl->set.cookielist = Curl_slist_duplicate(data->set.cookielist); - if(!outcurl->set.cookielist) + if(data->state.cookielist) { + outcurl->state.cookielist = Curl_slist_duplicate(data->state.cookielist); + if(!outcurl->state.cookielist) goto fail; } #endif @@ -981,11 +1008,14 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) (void)Curl_hsts_loadcb(outcurl, outcurl->hsts); } #endif + +#ifdef CURLRES_ASYNCH /* Clone the resolver handle, if present, for the new handle */ if(Curl_resolver_duphandle(outcurl, &outcurl->state.async.resolver, data->state.async.resolver)) goto fail; +#endif #ifdef USE_ARES { @@ -1008,7 +1038,9 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) goto fail; } #endif /* USE_ARES */ - +#ifndef CURL_DISABLE_HTTP + Curl_llist_init(&outcurl->state.httphdrs, NULL); +#endif Curl_initinfo(outcurl); outcurl->magic = CURLEASY_MAGIC_NUMBER; @@ -1021,13 +1053,9 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) if(outcurl) { #ifndef CURL_DISABLE_COOKIES - curl_slist_free_all(outcurl->set.cookielist); - outcurl->set.cookielist = NULL; + free(outcurl->cookies); #endif - Curl_safefree(outcurl->state.buffer); Curl_dyn_free(&outcurl->state.headerb); - Curl_safefree(outcurl->state.url); - Curl_safefree(outcurl->state.referer); Curl_altsvc_cleanup(&outcurl->asi); Curl_hsts_cleanup(&outcurl->hsts); Curl_freeset(outcurl); @@ -1041,9 +1069,10 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) * curl_easy_reset() is an external interface that allows an app to re- * initialize a session handle to the default values. */ -void curl_easy_reset(struct Curl_easy *data) +void curl_easy_reset(CURL *d) { - Curl_free_request_state(data); + struct Curl_easy *data = d; + Curl_req_hard_reset(&data->req, data); /* zero out UserDefined data: */ Curl_freeset(data); @@ -1082,115 +1111,94 @@ void curl_easy_reset(struct Curl_easy *data) * NOTE: This is one of few API functions that are allowed to be called from * within a callback. */ -CURLcode curl_easy_pause(struct Curl_easy *data, int action) +CURLcode curl_easy_pause(CURL *d, int action) { struct SingleRequest *k; CURLcode result = CURLE_OK; int oldstate; int newstate; + bool recursive = FALSE; + bool keep_changed, unpause_read, not_all_paused; + struct Curl_easy *data = d; if(!GOOD_EASY_HANDLE(data) || !data->conn) - /* crazy input, don't continue */ + /* crazy input, do not continue */ return CURLE_BAD_FUNCTION_ARGUMENT; + if(Curl_is_in_callback(data)) + recursive = TRUE; k = &data->req; oldstate = k->keepon & (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE); /* first switch off both pause bits then set the new pause bits */ newstate = (k->keepon &~ (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE)) | - ((action & CURLPAUSE_RECV)?KEEP_RECV_PAUSE:0) | - ((action & CURLPAUSE_SEND)?KEEP_SEND_PAUSE:0); - - if((newstate & (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE)) == oldstate) { - /* Not changing any pause state, return */ - DEBUGF(infof(data, "pause: no change, early return")); - return CURLE_OK; - } - - /* Unpause parts in active mime tree. */ - if((k->keepon & ~newstate & KEEP_SEND_PAUSE) && - (data->mstate == MSTATE_PERFORMING || - data->mstate == MSTATE_RATELIMITING) && - data->state.fread_func == (curl_read_callback) Curl_mime_read) { - Curl_mime_unpause(data->state.in); - } - - /* put it back in the keepon */ + ((action & CURLPAUSE_RECV) ? KEEP_RECV_PAUSE : 0) | + ((action & CURLPAUSE_SEND) ? KEEP_SEND_PAUSE : 0); + + keep_changed = ((newstate & (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE)) != oldstate); + not_all_paused = (newstate & (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) != + (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE); + unpause_read = ((k->keepon & ~newstate & KEEP_SEND_PAUSE) && + (data->mstate == MSTATE_PERFORMING || + data->mstate == MSTATE_RATELIMITING)); + /* Unpausing writes is detected on the next run in + * transfer.c:Curl_sendrecv(). This is because this may result + * in a transfer error if the application's callbacks fail */ + + /* Set the new keepon state, so it takes effect no matter what error + * may happen afterwards. */ k->keepon = newstate; - if(!(newstate & KEEP_RECV_PAUSE)) { - Curl_conn_ev_data_pause(data, FALSE); - - if(data->state.tempcount) { - /* there are buffers for sending that can be delivered as the receive - pausing is lifted! */ - unsigned int i; - unsigned int count = data->state.tempcount; - struct tempbuf writebuf[3]; /* there can only be three */ - - /* copy the structs to allow for immediate re-pausing */ - for(i = 0; i < data->state.tempcount; i++) { - writebuf[i] = data->state.tempwrite[i]; - Curl_dyn_init(&data->state.tempwrite[i].b, DYN_PAUSE_BUFFER); - } - data->state.tempcount = 0; - - for(i = 0; i < count; i++) { - /* even if one function returns error, this loops through and frees - all buffers */ - if(!result) - result = Curl_client_write(data, writebuf[i].type, - Curl_dyn_ptr(&writebuf[i].b), - Curl_dyn_len(&writebuf[i].b)); - Curl_dyn_free(&writebuf[i].b); + /* If not completely pausing both directions now, run again in any case. */ + if(not_all_paused) { + Curl_expire(data, 0, EXPIRE_RUN_NOW); + /* reset the too-slow time keeper */ + data->state.keeps_speed.tv_sec = 0; + /* Simulate socket events on next run for unpaused directions */ + if(!(newstate & KEEP_SEND_PAUSE)) + data->state.select_bits |= CURL_CSELECT_OUT; + if(!(newstate & KEEP_RECV_PAUSE)) + data->state.select_bits |= CURL_CSELECT_IN; + /* On changes, tell application to update its timers. */ + if(keep_changed && data->multi) { + if(Curl_update_timer(data->multi)) { + result = CURLE_ABORTED_BY_CALLBACK; + goto out; } - - if(result) - return result; } } -#ifdef USE_HYPER - if(!(newstate & KEEP_SEND_PAUSE)) { - /* need to wake the send body waker */ - if(data->hyp.send_body_waker) { - hyper_waker_wake(data->hyp.send_body_waker); - data->hyp.send_body_waker = NULL; - } + if(unpause_read) { + result = Curl_creader_unpause(data); + if(result) + goto out; } -#endif - - /* if there's no error and we're not pausing both directions, we want - to have this handle checked soon */ - if((newstate & (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) != - (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) { - Curl_expire(data, 0, EXPIRE_RUN_NOW); /* get this handle going again */ - - /* reset the too-slow time keeper */ - data->state.keeps_speed.tv_sec = 0; - if(!data->state.tempcount) - /* if not pausing again, force a recv/send check of this connection as - the data might've been read off the socket already */ - data->conn->cselect_bits = CURL_CSELECT_IN | CURL_CSELECT_OUT; - if(data->multi) { - if(Curl_update_timer(data->multi)) - return CURLE_ABORTED_BY_CALLBACK; - } + if(!(k->keepon & KEEP_RECV_PAUSE) && Curl_cwriter_is_paused(data)) { + Curl_conn_ev_data_pause(data, FALSE); + result = Curl_cwriter_unpause(data); } - if(!data->state.done) +out: + if(!result && !data->state.done && keep_changed) /* This transfer may have been moved in or out of the bundle, update the corresponding socket callback, if used */ result = Curl_updatesocket(data); + if(recursive) + /* this might have called a callback recursively which might have set this + to false again on exit */ + Curl_set_in_callback(data, TRUE); + return result; } -static CURLcode easy_connection(struct Curl_easy *data, curl_socket_t *sfd, +static CURLcode easy_connection(struct Curl_easy *data, struct connectdata **connp) { + curl_socket_t sfd; + if(!data) return CURLE_BAD_FUNCTION_ARGUMENT; @@ -1200,9 +1208,9 @@ static CURLcode easy_connection(struct Curl_easy *data, curl_socket_t *sfd, return CURLE_UNSUPPORTED_PROTOCOL; } - *sfd = Curl_getconnectinfo(data, connp); + sfd = Curl_getconnectinfo(data, connp); - if(*sfd == CURL_SOCKET_BAD) { + if(sfd == CURL_SOCKET_BAD) { failf(data, "Failed to get recent socket"); return CURLE_UNSUPPORTED_PROTOCOL; } @@ -1215,18 +1223,17 @@ static CURLcode easy_connection(struct Curl_easy *data, curl_socket_t *sfd, * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. * Returns CURLE_OK on success, error code on error. */ -CURLcode curl_easy_recv(struct Curl_easy *data, void *buffer, size_t buflen, - size_t *n) +CURLcode curl_easy_recv(CURL *d, void *buffer, size_t buflen, size_t *n) { - curl_socket_t sfd; CURLcode result; ssize_t n1; struct connectdata *c; + struct Curl_easy *data = d; if(Curl_is_in_callback(data)) return CURLE_RECURSIVE_API_CALL; - result = easy_connection(data, &sfd, &c); + result = easy_connection(data, &c); if(result) return result; @@ -1236,7 +1243,7 @@ CURLcode curl_easy_recv(struct Curl_easy *data, void *buffer, size_t buflen, Curl_attach_connection(data, c); *n = 0; - result = Curl_read(data, sfd, buffer, buflen, &n1); + result = Curl_conn_recv(data, FIRSTSOCKET, buffer, buflen, &n1); if(result) return result; @@ -1245,14 +1252,13 @@ CURLcode curl_easy_recv(struct Curl_easy *data, void *buffer, size_t buflen, return CURLE_OK; } -#ifdef USE_WEBSOCKETS +#ifndef CURL_DISABLE_WEBSOCKETS CURLcode Curl_connect_only_attach(struct Curl_easy *data) { - curl_socket_t sfd; CURLcode result; struct connectdata *c = NULL; - result = easy_connection(data, &sfd, &c); + result = easy_connection(data, &c); if(result) return result; @@ -1263,7 +1269,7 @@ CURLcode Curl_connect_only_attach(struct Curl_easy *data) return CURLE_OK; } -#endif /* USE_WEBSOCKETS */ +#endif /* !CURL_DISABLE_WEBSOCKETS */ /* * Sends data over the connected socket. @@ -1271,15 +1277,14 @@ CURLcode Curl_connect_only_attach(struct Curl_easy *data) * This is the private internal version of curl_easy_send() */ CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, - size_t buflen, ssize_t *n) + size_t buflen, size_t *n) { - curl_socket_t sfd; CURLcode result; - ssize_t n1; struct connectdata *c = NULL; SIGPIPE_VARIABLE(pipe_st); - result = easy_connection(data, &sfd, &c); + *n = 0; + result = easy_connection(data, &c); if(result) return result; @@ -1288,20 +1293,12 @@ CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, needs to be reattached */ Curl_attach_connection(data, c); - *n = 0; sigpipe_ignore(data, &pipe_st); - result = Curl_write(data, sfd, buffer, buflen, &n1); + result = Curl_conn_send(data, FIRSTSOCKET, buffer, buflen, FALSE, n); sigpipe_restore(&pipe_st); - if(n1 == -1) + if(result && result != CURLE_AGAIN) return CURLE_SEND_ERROR; - - /* detect EAGAIN */ - if(!result && !n1) - return CURLE_AGAIN; - - *n = n1; - return result; } @@ -1309,75 +1306,32 @@ CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, * Sends data over the connected socket. Use after successful * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. */ -CURLcode curl_easy_send(struct Curl_easy *data, const void *buffer, - size_t buflen, size_t *n) +CURLcode curl_easy_send(CURL *d, const void *buffer, size_t buflen, size_t *n) { - ssize_t written = 0; + size_t written = 0; CURLcode result; + struct Curl_easy *data = d; if(Curl_is_in_callback(data)) return CURLE_RECURSIVE_API_CALL; result = Curl_senddata(data, buffer, buflen, &written); - *n = (size_t)written; + *n = written; return result; } -/* - * Wrapper to call functions in Curl_conncache_foreach() - * - * Returns always 0. - */ -static int conn_upkeep(struct Curl_easy *data, - struct connectdata *conn, - void *param) -{ - struct curltime *now = param; - - if(Curl_timediff(*now, conn->keepalive) <= data->set.upkeep_interval_ms) - return 0; - - /* briefly attach for action */ - Curl_attach_connection(data, conn); - if(conn->handler->connection_check) { - /* Do a protocol-specific keepalive check on the connection. */ - conn->handler->connection_check(data, conn, CONNCHECK_KEEPALIVE); - } - else { - /* Do the generic action on the FIRSTSOCKE filter chain */ - Curl_conn_keep_alive(data, conn, FIRSTSOCKET); - } - Curl_detach_connection(data); - - conn->keepalive = *now; - return 0; /* continue iteration */ -} - -static CURLcode upkeep(struct conncache *conn_cache, void *data) -{ - struct curltime now = Curl_now(); - /* Loop over every connection and make connection alive. */ - Curl_conncache_foreach(data, - conn_cache, - &now, - conn_upkeep); - return CURLE_OK; -} - /* * Performs connection upkeep for the given session handle. */ -CURLcode curl_easy_upkeep(struct Curl_easy *data) +CURLcode curl_easy_upkeep(CURL *d) { + struct Curl_easy *data = d; /* Verify that we got an easy handle we can work with. */ if(!GOOD_EASY_HANDLE(data)) return CURLE_BAD_FUNCTION_ARGUMENT; - if(data->multi_easy) { - /* Use the common function to keep connections alive. */ - return upkeep(&data->multi_easy->conn_cache, data); - } - else { - /* No connections, so just return success */ - return CURLE_OK; - } + if(Curl_is_in_callback(data)) + return CURLE_RECURSIVE_API_CALL; + + /* Use the common function to keep connections alive. */ + return Curl_cpool_upkeep(data); } diff --git a/deps/curl/lib/easy_lock.h b/deps/curl/lib/easy_lock.h index 6399a39bde8..4f6764d427d 100644 --- a/deps/curl/lib/easy_lock.h +++ b/deps/curl/lib/easy_lock.h @@ -31,13 +31,6 @@ #if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x600 #ifdef __MINGW32__ -#ifndef __MINGW64_VERSION_MAJOR -#if (__MINGW32_MAJOR_VERSION < 5) || \ - (__MINGW32_MAJOR_VERSION == 5 && __MINGW32_MINOR_VERSION == 0) -/* mingw >= 5.0.1 defines SRWLOCK, and slightly different from MS define */ -typedef PVOID SRWLOCK, *PSRWLOCK; -#endif -#endif #ifndef SRWLOCK_INIT #define SRWLOCK_INIT NULL #endif @@ -100,6 +93,15 @@ static inline void curl_simple_lock_unlock(curl_simple_lock *lock) atomic_store_explicit(lock, false, memory_order_release); } +#elif defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) + +#include + +#define curl_simple_lock pthread_mutex_t +#define CURL_SIMPLE_LOCK_INIT PTHREAD_MUTEX_INITIALIZER +#define curl_simple_lock_lock(m) pthread_mutex_lock(m) +#define curl_simple_lock_unlock(m) pthread_mutex_unlock(m) + #else #undef GLOBAL_INIT_IS_THREADSAFE diff --git a/deps/curl/lib/easygetopt.c b/deps/curl/lib/easygetopt.c index 2b8a521cd2e..86833bf6b9e 100644 --- a/deps/curl/lib/easygetopt.c +++ b/deps/curl/lib/easygetopt.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * ___|___/|_| ______| * - * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) 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 @@ -42,7 +42,7 @@ static struct curl_easyoption *lookup(const char *name, CURLoption id) } else { if((o->id == id) && !(o->flags & CURLOT_FLAG_ALIAS)) - /* don't match alias options */ + /* do not match alias options */ return o; } o++; diff --git a/deps/curl/lib/easyif.h b/deps/curl/lib/easyif.h index 64489529660..181ce38f7bc 100644 --- a/deps/curl/lib/easyif.h +++ b/deps/curl/lib/easyif.h @@ -28,13 +28,13 @@ * Prototypes for library-wide functions provided by easy.c */ CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, - size_t buflen, ssize_t *n); + size_t buflen, size_t *n); -#ifdef USE_WEBSOCKETS +#ifndef CURL_DISABLE_WEBSOCKETS CURLcode Curl_connect_only_attach(struct Curl_easy *data); #endif -#ifdef CURLDEBUG +#ifdef DEBUGBUILD CURL_EXTERN CURLcode curl_easy_perform_ev(struct Curl_easy *easy); #endif diff --git a/deps/curl/lib/easyoptions.c b/deps/curl/lib/easyoptions.c index e69c658b0c7..81091c405aa 100644 --- a/deps/curl/lib/easyoptions.c +++ b/deps/curl/lib/easyoptions.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) 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 @@ -86,6 +86,7 @@ struct curl_easyoption Curl_easyopts[] = { {"DOH_SSL_VERIFYPEER", CURLOPT_DOH_SSL_VERIFYPEER, CURLOT_LONG, 0}, {"DOH_SSL_VERIFYSTATUS", CURLOPT_DOH_SSL_VERIFYSTATUS, CURLOT_LONG, 0}, {"DOH_URL", CURLOPT_DOH_URL, CURLOT_STRING, 0}, + {"ECH", CURLOPT_ECH, CURLOT_STRING, 0}, {"EGDSOCKET", CURLOPT_EGDSOCKET, CURLOT_STRING, 0}, {"ENCODING", CURLOPT_ACCEPT_ENCODING, CURLOT_STRING, CURLOT_FLAG_ALIAS}, {"ERRORBUFFER", CURLOPT_ERRORBUFFER, CURLOT_OBJECT, 0}, @@ -274,6 +275,8 @@ struct curl_easyoption Curl_easyopts[] = { {"SEEKFUNCTION", CURLOPT_SEEKFUNCTION, CURLOT_FUNCTION, 0}, {"SERVER_RESPONSE_TIMEOUT", CURLOPT_SERVER_RESPONSE_TIMEOUT, CURLOT_LONG, 0}, + {"SERVER_RESPONSE_TIMEOUT_MS", CURLOPT_SERVER_RESPONSE_TIMEOUT_MS, + CURLOT_LONG, 0}, {"SERVICE_NAME", CURLOPT_SERVICE_NAME, CURLOT_STRING, 0}, {"SHARE", CURLOPT_SHARE, CURLOT_OBJECT, 0}, {"SOCKOPTDATA", CURLOPT_SOCKOPTDATA, CURLOT_CBPTR, 0}, @@ -325,6 +328,7 @@ struct curl_easyoption Curl_easyopts[] = { CURLOT_LONG, 0}, {"TCP_FASTOPEN", CURLOPT_TCP_FASTOPEN, CURLOT_LONG, 0}, {"TCP_KEEPALIVE", CURLOPT_TCP_KEEPALIVE, CURLOT_LONG, 0}, + {"TCP_KEEPCNT", CURLOPT_TCP_KEEPCNT, CURLOT_LONG, 0}, {"TCP_KEEPIDLE", CURLOPT_TCP_KEEPIDLE, CURLOT_LONG, 0}, {"TCP_KEEPINTVL", CURLOPT_TCP_KEEPINTVL, CURLOT_LONG, 0}, {"TCP_NODELAY", CURLOPT_TCP_NODELAY, CURLOT_LONG, 0}, @@ -373,6 +377,6 @@ struct curl_easyoption Curl_easyopts[] = { */ int Curl_easyopts_check(void) { - return ((CURLOPT_LASTENTRY%10000) != (323 + 1)); + return ((CURLOPT_LASTENTRY%10000) != (326 + 1)); } #endif diff --git a/deps/curl/lib/escape.c b/deps/curl/lib/escape.c index 56aa2b39888..eaad6d33ad4 100644 --- a/deps/curl/lib/escape.c +++ b/deps/curl/lib/escape.c @@ -29,6 +29,8 @@ #include +struct Curl_easy; + #include "urldata.h" #include "warnless.h" #include "escape.h" @@ -38,33 +40,6 @@ #include "curl_memory.h" #include "memdebug.h" -/* Portable character check (remember EBCDIC). Do not use isalnum() because - its behavior is altered by the current locale. - See https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 -*/ -bool Curl_isunreserved(unsigned char in) -{ - switch(in) { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case 'a': case 'b': case 'c': case 'd': case 'e': - case 'f': case 'g': case 'h': case 'i': case 'j': - case 'k': case 'l': case 'm': case 'n': case 'o': - case 'p': case 'q': case 'r': case 's': case 't': - case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': - case 'A': case 'B': case 'C': case 'D': case 'E': - case 'F': case 'G': case 'H': case 'I': case 'J': - case 'K': case 'L': case 'M': case 'N': case 'O': - case 'P': case 'Q': case 'R': case 'S': case 'T': - case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': - case '-': case '.': case '_': case '~': - return TRUE; - default: - break; - } - return FALSE; -} - /* for ABI-compatibility with previous versions */ char *curl_escape(const char *string, int inlength) { @@ -80,26 +55,27 @@ char *curl_unescape(const char *string, int length) /* Escapes for URL the given unescaped string of given length. * 'data' is ignored since 7.82.0. */ -char *curl_easy_escape(struct Curl_easy *data, const char *string, +char *curl_easy_escape(CURL *data, const char *string, int inlength) { size_t length; struct dynbuf d; (void)data; - if(inlength < 0) + if(!string || (inlength < 0)) return NULL; - Curl_dyn_init(&d, CURL_MAX_INPUT_LENGTH * 3); - - length = (inlength?(size_t)inlength:strlen(string)); + length = (inlength ? (size_t)inlength : strlen(string)); if(!length) return strdup(""); + Curl_dyn_init(&d, length * 3 + 1); + while(length--) { - unsigned char in = *string++; /* treat the characters unsigned */ + /* treat the characters unsigned */ + unsigned char in = (unsigned char)*string++; - if(Curl_isunreserved(in)) { + if(ISUNRESERVED(in)) { /* append this */ if(Curl_dyn_addn(&d, &in, 1)) return NULL; @@ -108,7 +84,7 @@ char *curl_easy_escape(struct Curl_easy *data, const char *string, /* encode it */ const char hex[] = "0123456789ABCDEF"; char out[3]={'%'}; - out[1] = hex[in>>4]; + out[1] = hex[in >> 4]; out[2] = hex[in & 0xf]; if(Curl_dyn_addn(&d, out, 3)) return NULL; @@ -154,7 +130,7 @@ CURLcode Curl_urldecode(const char *string, size_t length, DEBUGASSERT(string); DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */ - alloc = (length?length:strlen(string)); + alloc = (length ? length : strlen(string)); ns = malloc(alloc + 1); if(!ns) @@ -164,7 +140,7 @@ CURLcode Curl_urldecode(const char *string, size_t length, *ostring = ns; while(alloc) { - unsigned char in = *string; + unsigned char in = (unsigned char)*string; if(('%' == in) && (alloc > 2) && ISXDIGIT(string[1]) && ISXDIGIT(string[2])) { /* this is two hexadecimal digits following a '%' */ @@ -184,7 +160,7 @@ CURLcode Curl_urldecode(const char *string, size_t length, return CURLE_URL_MALFORMAT; } - *ns++ = in; + *ns++ = (char)in; } *ns = 0; /* terminate it */ @@ -202,12 +178,12 @@ CURLcode Curl_urldecode(const char *string, size_t length, * If olen == NULL, no output length is stored. * 'data' is ignored since 7.82.0. */ -char *curl_easy_unescape(struct Curl_easy *data, const char *string, +char *curl_easy_unescape(CURL *data, const char *string, int length, int *olen) { char *str = NULL; (void)data; - if(length >= 0) { + if(string && (length >= 0)) { size_t inputlen = (size_t)length; size_t outputlen; CURLcode res = Curl_urldecode(string, inputlen, &str, &outputlen, @@ -233,3 +209,29 @@ void curl_free(void *p) { free(p); } + +/* + * Curl_hexencode() + * + * Converts binary input to lowercase hex-encoded ASCII output. + * Null-terminated. + */ +void Curl_hexencode(const unsigned char *src, size_t len, /* input length */ + unsigned char *out, size_t olen) /* output buffer size */ +{ + const char *hex = "0123456789abcdef"; + DEBUGASSERT(src && len && (olen >= 3)); + if(src && len && (olen >= 3)) { + while(len-- && (olen >= 3)) { + /* clang-tidy warns on this line without this comment: */ + /* NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult) */ + *out++ = (unsigned char)hex[(*src & 0xF0) >> 4]; + *out++ = (unsigned char)hex[*src & 0x0F]; + ++src; + olen -= 2; + } + *out = 0; + } + else if(olen) + *out = 0; +} diff --git a/deps/curl/lib/escape.h b/deps/curl/lib/escape.h index cdbb712acc1..690e4178795 100644 --- a/deps/curl/lib/escape.h +++ b/deps/curl/lib/escape.h @@ -26,7 +26,7 @@ /* Escape and unescape URL encoding in strings. The functions return a new * allocated string or NULL if an error occurred. */ -bool Curl_isunreserved(unsigned char in); +#include "curl_ctype.h" enum urlreject { REJECT_NADA = 2, @@ -38,4 +38,7 @@ CURLcode Curl_urldecode(const char *string, size_t length, char **ostring, size_t *olen, enum urlreject ctrl); +void Curl_hexencode(const unsigned char *src, size_t len, /* input length */ + unsigned char *out, size_t olen); /* output buffer size */ + #endif /* HEADER_CURL_ESCAPE_H */ diff --git a/deps/curl/lib/file.c b/deps/curl/lib/file.c index c751e8861a9..4cd8d0ff83e 100644 --- a/deps/curl/lib/file.c +++ b/deps/curl/lib/file.c @@ -50,6 +50,14 @@ #include #endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_DIRENT_H +#include +#endif + #include "strtoofft.h" #include "urldata.h" #include @@ -59,6 +67,7 @@ #include "file.h" #include "speedcheck.h" #include "getinfo.h" +#include "multiif.h" #include "transfer.h" #include "url.h" #include "parsedate.h" /* for the week day and month names */ @@ -69,7 +78,7 @@ #include "curl_memory.h" #include "memdebug.h" -#if defined(WIN32) || defined(MSDOS) || defined(__EMX__) +#if defined(_WIN32) || defined(MSDOS) || defined(__EMX__) #define DOS_FILESYSTEM 1 #elif defined(__amigaos4__) #define AMIGA_FILESYSTEM 1 @@ -100,7 +109,7 @@ static CURLcode file_setup_connection(struct Curl_easy *data, */ const struct Curl_handler Curl_handler_file = { - "FILE", /* scheme */ + "file", /* scheme */ file_setup_connection, /* setup_connection */ file_do, /* do_it */ file_done, /* done */ @@ -113,7 +122,8 @@ const struct Curl_handler Curl_handler_file = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ file_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ 0, /* defport */ @@ -137,7 +147,7 @@ static CURLcode file_setup_connection(struct Curl_easy *data, /* * file_connect() gets called from Curl_protocol_connect() to allow us to - * do protocol-specific actions at connect-time. We emulate a + * do protocol-specific actions at connect-time. We emulate a * connect-then-transfer protocol and "connect" to the file here */ static CURLcode file_connect(struct Curl_easy *data, bool *done) @@ -167,18 +177,18 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) return result; #ifdef DOS_FILESYSTEM - /* If the first character is a slash, and there's + /* If the first character is a slash, and there is something that looks like a drive at the beginning of - the path, skip the slash. If we remove the initial + the path, skip the slash. If we remove the initial slash in all cases, paths without drive letters end up - relative to the current directory which isn't how + relative to the current directory which is not how browsers work. Some browsers accept | instead of : as the drive letter separator, so we do too. On other platforms, we need the slash to indicate an - absolute pathname. On Windows, absolute paths start + absolute pathname. On Windows, absolute paths start with a drive letter. */ actual_path = real_path; @@ -213,7 +223,7 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) * A leading slash in an AmigaDOS path denotes the parent * directory, and hence we block this as it is relative. * Absolute paths start with 'volumename:', so we check for - * this first. Failing that, we treat the path as a real unix + * this first. Failing that, we treat the path as a real Unix * path, but only if the application was compiled with -lunix. */ fd = -1; @@ -290,16 +300,17 @@ static CURLcode file_upload(struct Curl_easy *data) int fd; int mode; CURLcode result = CURLE_OK; - char *buf = data->state.buffer; + char *xfer_ulbuf; + size_t xfer_ulblen; curl_off_t bytecount = 0; struct_stat file_stat; - const char *buf2; + const char *sendbuf; + bool eos = FALSE; /* - * Since FILE: doesn't do the full init, we need to provide some extra + * Since FILE: does not do the full init, we need to provide some extra * assignments here. */ - data->req.upload_fromhere = buf; if(!dir) return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ @@ -320,7 +331,7 @@ static CURLcode file_upload(struct Curl_easy *data) fd = open(file->path, mode, data->set.new_file_perms); if(fd < 0) { - failf(data, "Can't open %s for writing", file->path); + failf(data, "cannot open %s for writing", file->path); return CURLE_WRITE_ERROR; } @@ -332,17 +343,22 @@ static CURLcode file_upload(struct Curl_easy *data) if(data->state.resume_from < 0) { if(fstat(fd, &file_stat)) { close(fd); - failf(data, "Can't get the size of %s", file->path); + failf(data, "cannot get the size of %s", file->path); return CURLE_WRITE_ERROR; } data->state.resume_from = (curl_off_t)file_stat.st_size; } - while(!result) { + result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen); + if(result) + goto out; + + while(!result && !eos) { size_t nread; ssize_t nwrite; size_t readcount; - result = Curl_fillreadbuffer(data, data->set.buffer_size, &readcount); + + result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &readcount, &eos); if(result) break; @@ -356,19 +372,19 @@ static CURLcode file_upload(struct Curl_easy *data) if((curl_off_t)nread <= data->state.resume_from) { data->state.resume_from -= nread; nread = 0; - buf2 = buf; + sendbuf = xfer_ulbuf; } else { - buf2 = buf + data->state.resume_from; + sendbuf = xfer_ulbuf + data->state.resume_from; nread -= (size_t)data->state.resume_from; data->state.resume_from = 0; } } else - buf2 = buf; + sendbuf = xfer_ulbuf; /* write the data to the target */ - nwrite = write(fd, buf2, nread); + nwrite = write(fd, sendbuf, nread); if((size_t)nwrite != nread) { result = CURLE_SEND_ERROR; break; @@ -386,7 +402,9 @@ static CURLcode file_upload(struct Curl_easy *data) if(!result && Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; +out: close(fd); + Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf); return result; } @@ -395,13 +413,13 @@ static CURLcode file_upload(struct Curl_easy *data) * file_do() is the protocol-specific function for the do-phase, separated * from the connect-phase above. Other protocols merely setup the transfer in * the do-phase, to have it done in the main transfer loop but since some - * platforms we support don't allow select()ing etc on file handles (as + * platforms we support do not allow select()ing etc on file handles (as * opposed to sockets) we instead perform the whole do-operation in this * function. */ static CURLcode file_do(struct Curl_easy *data, bool *done) { - /* This implementation ignores the host name in conformance with + /* This implementation ignores the hostname in conformance with RFC 1738. Only local files (reachable via the standard file system) are supported. This means that files on remotely mounted directories (via NFS, Samba, NT sharing) can be accessed through a file:// URL @@ -413,15 +431,13 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) curl_off_t expected_size = -1; bool size_known; bool fstated = FALSE; - char *buf = data->state.buffer; - curl_off_t bytecount = 0; int fd; struct FILEPROTO *file; + char *xfer_buf; + size_t xfer_blen; *done = TRUE; /* unconditionally */ - Curl_pgrsStartNow(data); - if(data->state.upload) return file_upload(data); @@ -439,12 +455,9 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) fstated = TRUE; } - if(fstated && !data->state.range && data->set.timecondition) { - if(!Curl_meets_timecondition(data, data->info.filetime)) { - *done = TRUE; - return CURLE_OK; - } - } + if(fstated && !data->state.range && data->set.timecondition && + !Curl_meets_timecondition(data, data->info.filetime)) + return CURLE_OK; if(fstated) { time_t filetime; @@ -452,17 +465,17 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) const struct tm *tm = &buffer; char header[80]; int headerlen; - char accept_ranges[24]= { "Accept-ranges: bytes\r\n" }; + static const char accept_ranges[]= { "Accept-ranges: bytes\r\n" }; if(expected_size >= 0) { - headerlen = msnprintf(header, sizeof(header), - "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", - expected_size); + headerlen = + msnprintf(header, sizeof(header), "Content-Length: %" FMT_OFF_T "\r\n", + expected_size); result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen); if(result) return result; result = Curl_client_write(data, CLIENTWRITE_HEADER, - accept_ranges, strlen(accept_ranges)); + accept_ranges, sizeof(accept_ranges) - 1); if(result != CURLE_OK) return result; } @@ -473,23 +486,26 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) return result; /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ - headerlen = msnprintf(header, sizeof(header), - "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n%s", - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], - tm->tm_mday, - Curl_month[tm->tm_mon], - tm->tm_year + 1900, - tm->tm_hour, - tm->tm_min, - tm->tm_sec, - data->req.no_body ? "": "\r\n"); + headerlen = + msnprintf(header, sizeof(header), + "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", + Curl_wkday[tm->tm_wday ? tm->tm_wday-1 : 6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen); + if(!result) + /* end of headers */ + result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2); if(result) return result; /* set the file size to make it available post transfer */ Curl_pgrsSetDownloadSize(data, expected_size); if(data->req.no_body) - return result; + return CURLE_OK; } /* Check whether file range has been specified */ @@ -501,7 +517,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) * of the stream if the filesize could be determined */ if(data->state.resume_from < 0) { if(!fstated) { - failf(data, "Can't get the size of file."); + failf(data, "cannot get the size of file."); return CURLE_READ_ERROR; } data->state.resume_from += (curl_off_t)statbuf.st_size; @@ -509,7 +525,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) if(data->state.resume_from > 0) { /* We check explicitly if we have a start offset, because - * expected_size may be -1 if we don't know how large the file is, + * expected_size may be -1 if we do not know how large the file is, * in which case we should not adjust it. */ if(data->state.resume_from <= expected_size) expected_size -= data->state.resume_from; @@ -536,51 +552,90 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) Curl_pgrsSetDownloadSize(data, expected_size); if(data->state.resume_from) { - if(data->state.resume_from != - lseek(fd, data->state.resume_from, SEEK_SET)) + if(!S_ISDIR(statbuf.st_mode)) { + if(data->state.resume_from != + lseek(fd, data->state.resume_from, SEEK_SET)) + return CURLE_BAD_DOWNLOAD_RESUME; + } + else { return CURLE_BAD_DOWNLOAD_RESUME; + } } - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - - while(!result) { - ssize_t nread; - /* Don't fill a whole buffer if we want less than all data */ - size_t bytestoread; + result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen); + if(result) + goto out; - if(size_known) { - bytestoread = (expected_size < data->set.buffer_size) ? - curlx_sotouz(expected_size) : (size_t)data->set.buffer_size; - } - else - bytestoread = data->set.buffer_size-1; + if(!S_ISDIR(statbuf.st_mode)) { + while(!result) { + ssize_t nread; + /* Do not fill a whole buffer if we want less than all data */ + size_t bytestoread; - nread = read(fd, buf, bytestoread); + if(size_known) { + bytestoread = (expected_size < (curl_off_t)(xfer_blen-1)) ? + curlx_sotouz(expected_size) : (xfer_blen-1); + } + else + bytestoread = xfer_blen-1; - if(nread > 0) - buf[nread] = 0; + nread = read(fd, xfer_buf, bytestoread); - if(nread <= 0 || (size_known && (expected_size == 0))) - break; + if(nread > 0) + xfer_buf[nread] = 0; - bytecount += nread; - if(size_known) - expected_size -= nread; + if(nread <= 0 || (size_known && (expected_size == 0))) + break; - result = Curl_client_write(data, CLIENTWRITE_BODY, buf, nread); - if(result) - return result; + if(size_known) + expected_size -= nread; - Curl_pgrsSetDownloadCounter(data, bytecount); + result = Curl_client_write(data, CLIENTWRITE_BODY, xfer_buf, nread); + if(result) + goto out; - if(Curl_pgrsUpdate(data)) - result = CURLE_ABORTED_BY_CALLBACK; - else - result = Curl_speedcheck(data, Curl_now()); + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + else + result = Curl_speedcheck(data, Curl_now()); + if(result) + goto out; + } + } + else { +#ifdef HAVE_OPENDIR + DIR *dir = opendir(file->path); + struct dirent *entry; + + if(!dir) { + result = CURLE_READ_ERROR; + goto out; + } + else { + while((entry = readdir(dir))) { + if(entry->d_name[0] != '.') { + result = Curl_client_write(data, CLIENTWRITE_BODY, + entry->d_name, strlen(entry->d_name)); + if(result) + break; + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); + if(result) + break; + } + } + closedir(dir); + } +#else + failf(data, "Directory listing not yet implemented on this platform."); + result = CURLE_READ_ERROR; +#endif } + if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; +out: + Curl_multi_xfer_buf_release(data, xfer_buf); return result; } diff --git a/deps/curl/lib/fileinfo.h b/deps/curl/lib/fileinfo.h index ce009da06df..0b3f56d9d49 100644 --- a/deps/curl/lib/fileinfo.h +++ b/deps/curl/lib/fileinfo.h @@ -30,7 +30,7 @@ struct fileinfo { struct curl_fileinfo info; - struct Curl_llist_element list; + struct Curl_llist_node list; struct dynbuf buf; }; diff --git a/deps/curl/lib/fopen.c b/deps/curl/lib/fopen.c index b6e3cadddef..7373e08831d 100644 --- a/deps/curl/lib/fopen.c +++ b/deps/curl/lib/fopen.c @@ -39,58 +39,105 @@ #include "curl_memory.h" #include "memdebug.h" +/* + The dirslash() function breaks a null-terminated pathname string into + directory and filename components then returns the directory component up + to, *AND INCLUDING*, a final '/'. If there is no directory in the path, + this instead returns a "" string. + + This function returns a pointer to malloc'ed memory. + + The input path to this function is expected to have a filename part. +*/ + +#ifdef _WIN32 +#define PATHSEP "\\" +#define IS_SEP(x) (((x) == '/') || ((x) == '\\')) +#elif defined(MSDOS) || defined(__EMX__) || defined(OS2) +#define PATHSEP "\\" +#define IS_SEP(x) ((x) == '\\') +#else +#define PATHSEP "/" +#define IS_SEP(x) ((x) == '/') +#endif + +static char *dirslash(const char *path) +{ + size_t n; + struct dynbuf out; + DEBUGASSERT(path); + Curl_dyn_init(&out, CURL_MAX_INPUT_LENGTH); + n = strlen(path); + if(n) { + /* find the rightmost path separator, if any */ + while(n && !IS_SEP(path[n-1])) + --n; + /* skip over all the path separators, if any */ + while(n && IS_SEP(path[n-1])) + --n; + } + if(Curl_dyn_addn(&out, path, n)) + return NULL; + /* if there was a directory, append a single trailing slash */ + if(n && Curl_dyn_addn(&out, PATHSEP, 1)) + return NULL; + return Curl_dyn_ptr(&out); +} + /* * Curl_fopen() opens a file for writing with a temp name, to be renamed * to the final name when completed. If there is an existing file using this * name at the time of the open, this function will clone the mode from that - * file. if 'tempname' is non-NULL, it needs a rename after the file is + * file. if 'tempname' is non-NULL, it needs a rename after the file is * written. */ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, FILE **fh, char **tempname) { CURLcode result = CURLE_WRITE_ERROR; - unsigned char randsuffix[9]; + unsigned char randbuf[41]; char *tempstore = NULL; struct_stat sb; int fd = -1; + char *dir = NULL; *tempname = NULL; *fh = fopen(filename, FOPEN_WRITETEXT); if(!*fh) goto fail; - if(fstat(fileno(*fh), &sb) == -1 || !S_ISREG(sb.st_mode)) + if(fstat(fileno(*fh), &sb) == -1 || !S_ISREG(sb.st_mode)) { return CURLE_OK; + } fclose(*fh); *fh = NULL; - result = Curl_rand_hex(data, randsuffix, sizeof(randsuffix)); + result = Curl_rand_alnum(data, randbuf, sizeof(randbuf)); if(result) goto fail; - tempstore = aprintf("%s.%s.tmp", filename, randsuffix); + dir = dirslash(filename); + if(dir) { + /* The temp filename should not end up too long for the target file + system */ + tempstore = aprintf("%s%s.tmp", dir, randbuf); + free(dir); + } + if(!tempstore) { result = CURLE_OUT_OF_MEMORY; goto fail; } result = CURLE_WRITE_ERROR; - fd = open(tempstore, O_WRONLY | O_CREAT | O_EXCL, 0600); +#if (defined(ANDROID) || defined(__ANDROID__)) && \ + (defined(__i386__) || defined(__arm__)) + fd = open(tempstore, O_WRONLY | O_CREAT | O_EXCL, (mode_t)(0600|sb.st_mode)); +#else + fd = open(tempstore, O_WRONLY | O_CREAT | O_EXCL, 0600|sb.st_mode); +#endif if(fd == -1) goto fail; -#ifdef HAVE_FCHMOD - { - struct_stat nsb; - if((fstat(fd, &nsb) != -1) && - (nsb.st_uid == sb.st_uid) && (nsb.st_gid == sb.st_gid)) { - /* if the user and group are the same, clone the original mode */ - if(fchmod(fd, (mode_t)sb.st_mode) == -1) - goto fail; - } - } -#endif - *fh = fdopen(fd, FOPEN_WRITETEXT); if(!*fh) goto fail; @@ -105,7 +152,6 @@ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, } free(tempstore); - return result; } diff --git a/deps/curl/lib/formdata.c b/deps/curl/lib/formdata.c index 8984b63223c..cea61b22e17 100644 --- a/deps/curl/lib/formdata.c +++ b/deps/curl/lib/formdata.c @@ -26,6 +26,8 @@ #include +struct Curl_easy; + #include "formdata.h" #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_FORM_API) @@ -216,8 +218,8 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, struct curl_forms *forms = NULL; char *array_value = NULL; /* value read from an array */ - /* This is a state variable, that if TRUE means that we're parsing an - array that we got passed to us. If FALSE we're parsing the input + /* This is a state variable, that if TRUE means that we are parsing an + array that we got passed to us. If FALSE we are parsing the input va_list arguments. */ bool array_state = FALSE; @@ -260,7 +262,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, switch(option) { case CURLFORM_ARRAY: if(array_state) - /* we don't support an array from within an array */ + /* we do not support an array from within an array */ return_value = CURL_FORMADD_ILLEGAL_ARRAY; else { forms = va_arg(params, struct curl_forms *); @@ -277,13 +279,13 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, case CURLFORM_PTRNAME: current_form->flags |= HTTPPOST_PTRNAME; /* fall through */ - /* FALLTHROUGH */ + FALLTHROUGH(); case CURLFORM_COPYNAME: if(current_form->name) return_value = CURL_FORMADD_OPTION_TWICE; else { - char *name = array_state? - array_value:va_arg(params, char *); + char *name = array_state ? + array_value : va_arg(params, char *); if(name) current_form->name = name; /* store for the moment */ else @@ -295,7 +297,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, return_value = CURL_FORMADD_OPTION_TWICE; else current_form->namelength = - array_state?(size_t)array_value:(size_t)va_arg(params, long); + array_state ? (size_t)array_value : (size_t)va_arg(params, long); break; /* @@ -303,13 +305,13 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, */ case CURLFORM_PTRCONTENTS: current_form->flags |= HTTPPOST_PTRCONTENTS; - /* FALLTHROUGH */ + FALLTHROUGH(); case CURLFORM_COPYCONTENTS: if(current_form->value) return_value = CURL_FORMADD_OPTION_TWICE; else { char *value = - array_state?array_value:va_arg(params, char *); + array_state ? array_value : va_arg(params, char *); if(value) current_form->value = value; /* store for the moment */ else @@ -318,22 +320,23 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, break; case CURLFORM_CONTENTSLENGTH: current_form->contentslength = - array_state?(size_t)array_value:(size_t)va_arg(params, long); + array_state ? (size_t)array_value : (size_t)va_arg(params, long); break; case CURLFORM_CONTENTLEN: current_form->flags |= CURL_HTTPPOST_LARGE; current_form->contentslength = - array_state?(curl_off_t)(size_t)array_value:va_arg(params, curl_off_t); + array_state ? (curl_off_t)(size_t)array_value : + va_arg(params, curl_off_t); break; - /* Get contents from a given file name */ + /* Get contents from a given filename */ case CURLFORM_FILECONTENT: if(current_form->flags & (HTTPPOST_PTRCONTENTS|HTTPPOST_READFILE)) return_value = CURL_FORMADD_OPTION_TWICE; else { - const char *filename = array_state? - array_value:va_arg(params, char *); + const char *filename = array_state ? + array_value : va_arg(params, char *); if(filename) { current_form->value = strdup(filename); if(!current_form->value) @@ -351,7 +354,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, /* We upload a file */ case CURLFORM_FILE: { - const char *filename = array_state?array_value: + const char *filename = array_state ? array_value : va_arg(params, char *); if(current_form->value) { @@ -401,7 +404,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, return_value = CURL_FORMADD_OPTION_TWICE; else { char *buffer = - array_state?array_value:va_arg(params, char *); + array_state ? array_value : va_arg(params, char *); if(buffer) { current_form->buffer = buffer; /* store for the moment */ current_form->value = buffer; /* make it non-NULL to be accepted @@ -417,7 +420,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, return_value = CURL_FORMADD_OPTION_TWICE; else current_form->bufferlength = - array_state?(size_t)array_value:(size_t)va_arg(params, long); + array_state ? (size_t)array_value : (size_t)va_arg(params, long); break; case CURLFORM_STREAM: @@ -426,10 +429,10 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, return_value = CURL_FORMADD_OPTION_TWICE; else { char *userp = - array_state?array_value:va_arg(params, char *); + array_state ? array_value : va_arg(params, char *); if(userp) { current_form->userp = userp; - current_form->value = userp; /* this isn't strictly true but we + current_form->value = userp; /* this is not strictly true but we derive a value from this later on and we need this non-NULL to be accepted as a fine form part */ @@ -442,7 +445,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, case CURLFORM_CONTENTTYPE: { const char *contenttype = - array_state?array_value:va_arg(params, char *); + array_state ? array_value : va_arg(params, char *); if(current_form->contenttype) { if(current_form->flags & HTTPPOST_FILENAME) { if(contenttype) { @@ -485,8 +488,8 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, { /* this "cast increases required alignment of target type" but we consider it OK anyway */ - struct curl_slist *list = array_state? - (struct curl_slist *)(void *)array_value: + struct curl_slist *list = array_state ? + (struct curl_slist *)(void *)array_value : va_arg(params, struct curl_slist *); if(current_form->contentheader) @@ -499,7 +502,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, case CURLFORM_FILENAME: case CURLFORM_BUFFER: { - const char *filename = array_state?array_value: + const char *filename = array_state ? array_value : va_arg(params, char *); if(current_form->showfilename) return_value = CURL_FORMADD_OPTION_TWICE; @@ -569,7 +572,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, if(((form->flags & HTTPPOST_FILENAME) || (form->flags & HTTPPOST_BUFFER)) && !form->contenttype) { - char *f = (form->flags & HTTPPOST_BUFFER)? + char *f = (form->flags & HTTPPOST_BUFFER) ? form->showfilename : form->value; char const *type; type = Curl_mime_contenttype(f); @@ -599,13 +602,13 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, } if(!(form->flags & HTTPPOST_PTRNAME) && (form == first_form) ) { - /* Note that there's small risk that form->name is NULL here if the + /* Note that there is small risk that form->name is NULL here if the app passed in a bad combo, so we better check for that first. */ if(form->name) { /* copy name (without strdup; possibly not null-terminated) */ - form->name = Curl_memdup(form->name, form->namelength? - form->namelength: - strlen(form->name) + 1); + form->name = Curl_memdup0(form->name, form->namelength ? + form->namelength : + strlen(form->name)); } if(!form->name) { return_value = CURL_FORMADD_MEMORY; @@ -764,7 +767,7 @@ void curl_formfree(struct curl_httppost *form) ) free(form->contents); /* free the contents */ free(form->contenttype); /* free the content type */ - free(form->showfilename); /* free the faked file name */ + free(form->showfilename); /* free the faked filename */ free(form); /* free the struct */ form = next; } while(form); /* continue */ @@ -779,16 +782,28 @@ static CURLcode setname(curl_mimepart *part, const char *name, size_t len) if(!name || !len) return curl_mime_name(part, name); - zname = malloc(len + 1); + zname = Curl_memdup0(name, len); if(!zname) return CURLE_OUT_OF_MEMORY; - memcpy(zname, name, len); - zname[len] = '\0'; res = curl_mime_name(part, zname); free(zname); return res; } +/* wrap call to fseeko so it matches the calling convention of callback */ +static int fseeko_wrapper(void *stream, curl_off_t offset, int whence) +{ +#if defined(HAVE__FSEEKI64) + return _fseeki64(stream, (__int64)offset, whence); +#elif defined(HAVE_FSEEKO) && defined(HAVE_DECL_FSEEKO) + return fseeko(stream, (off_t)offset, whence); +#else + if(offset > LONG_MAX) + return -1; + return fseek(stream, (long)offset, whence); +#endif +} + /* * Curl_getformdata() converts a linked list of "meta data" into a mime * structure. The input list is in 'post', while the output is stored in @@ -799,7 +814,7 @@ static CURLcode setname(curl_mimepart *part, const char *name, size_t len) * a NULL pointer in the 'data' argument. */ -CURLcode Curl_getformdata(struct Curl_easy *data, +CURLcode Curl_getformdata(CURL *data, curl_mimepart *finalform, struct curl_httppost *post, curl_read_callback fread_func) @@ -868,14 +883,13 @@ CURLcode Curl_getformdata(struct Curl_easy *data, if(post->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE)) { if(!strcmp(file->contents, "-")) { - /* There are a few cases where the code below won't work; in + /* There are a few cases where the code below will not work; in particular, freopen(stdin) by the caller is not guaranteed to result as expected. This feature has been kept for backward - compatibility: use of "-" pseudo file name should be avoided. */ + compatibility: use of "-" pseudo filename should be avoided. */ result = curl_mime_data_cb(part, (curl_off_t) -1, (curl_read_callback) fread, - CURLX_FUNCTION_CAST(curl_seek_callback, - fseek), + fseeko_wrapper, NULL, (void *) stdin); } else @@ -885,7 +899,8 @@ CURLcode Curl_getformdata(struct Curl_easy *data, } else if(post->flags & HTTPPOST_BUFFER) result = curl_mime_data(part, post->buffer, - post->bufferlength? post->bufferlength: -1); + post->bufferlength ? + post->bufferlength : -1); else if(post->flags & HTTPPOST_CALLBACK) { /* the contents should be read with the callback and the size is set with the contentslength */ @@ -904,7 +919,7 @@ CURLcode Curl_getformdata(struct Curl_easy *data, } } - /* Set fake file name. */ + /* Set fake filename. */ if(!result && post->showfilename) if(post->more || (post->flags & (HTTPPOST_FILENAME | HTTPPOST_BUFFER | HTTPPOST_CALLBACK))) diff --git a/deps/curl/lib/formdata.h b/deps/curl/lib/formdata.h index af466249fdb..0e35e1892bf 100644 --- a/deps/curl/lib/formdata.h +++ b/deps/curl/lib/formdata.h @@ -38,8 +38,8 @@ struct FormInfo { long flags; char *buffer; /* pointer to existing buffer used for file upload */ size_t bufferlength; - char *showfilename; /* The file name to show. If not set, the actual - file name will be used */ + char *showfilename; /* The filename to show. If not set, the actual + filename will be used */ char *userp; /* pointer for the read callback */ struct curl_slist *contentheader; struct FormInfo *more; @@ -49,7 +49,7 @@ struct FormInfo { bool showfilename_alloc; }; -CURLcode Curl_getformdata(struct Curl_easy *data, +CURLcode Curl_getformdata(CURL *data, curl_mimepart *, struct curl_httppost *post, curl_read_callback fread_func); diff --git a/deps/curl/lib/ftp.c b/deps/curl/lib/ftp.c index e24ae9c15d1..16ab0af0de1 100644 --- a/deps/curl/lib/ftp.c +++ b/deps/curl/lib/ftp.c @@ -72,6 +72,7 @@ #include "warnless.h" #include "http_proxy.h" #include "socks.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -84,21 +85,99 @@ #define INET_ADDRSTRLEN 16 #endif +/* macro to check for a three-digit ftp status code at the start of the + given string */ +#define STATUSCODE(line) (ISDIGIT(line[0]) && ISDIGIT(line[1]) && \ + ISDIGIT(line[2])) + +/* macro to check for the last line in an FTP server response */ +#define LASTLINE(line) (STATUSCODE(line) && (' ' == line[3])) + #ifdef CURL_DISABLE_VERBOSE_STRINGS #define ftp_pasv_verbose(a,b,c,d) Curl_nop_stmt +#define FTP_CSTATE(c) "" +#define FTP_DSTATE(d) "" +#else /* CURL_DISABLE_VERBOSE_STRINGS */ + /* for tracing purposes */ +static const char * const ftp_state_names[]={ + "STOP", + "WAIT220", + "AUTH", + "USER", + "PASS", + "ACCT", + "PBSZ", + "PROT", + "CCC", + "PWD", + "SYST", + "NAMEFMT", + "QUOTE", + "RETR_PREQUOTE", + "STOR_PREQUOTE", + "POSTQUOTE", + "CWD", + "MKD", + "MDTM", + "TYPE", + "LIST_TYPE", + "RETR_TYPE", + "STOR_TYPE", + "SIZE", + "RETR_SIZE", + "STOR_SIZE", + "REST", + "RETR_REST", + "PORT", + "PRET", + "PASV", + "LIST", + "RETR", + "STOR", + "QUIT" +}; +#define FTP_CSTATE(c) ((c)? ftp_state_names[(c)->proto.ftpc.state] : "???") +#define FTP_DSTATE(d) (((d) && (d)->conn)? \ + ftp_state_names[(d)->conn->proto.ftpc.state] : "???") + +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + +/* This is the ONLY way to change FTP state! */ +static void _ftp_state(struct Curl_easy *data, + ftpstate newstate +#ifdef DEBUGBUILD + , int lineno +#endif + ) +{ + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; + +#if defined(CURL_DISABLE_VERBOSE_STRINGS) +#ifdef DEBUGBUILD + (void)lineno; +#endif +#else /* CURL_DISABLE_VERBOSE_STRINGS */ + if(ftpc->state != newstate) +#ifdef DEBUGBUILD + CURL_TRC_FTP(data, "[%s] -> [%s] (line %d)", FTP_DSTATE(data), + ftp_state_names[newstate], lineno); +#else + CURL_TRC_FTP(data, "[%s] -> [%s]", FTP_DSTATE(data), + ftp_state_names[newstate]); #endif +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + + ftpc->state = newstate; +} + /* Local API functions */ #ifndef DEBUGBUILD -static void _ftp_state(struct Curl_easy *data, - ftpstate newstate); #define ftp_state(x,y) _ftp_state(x,y) -#else -static void _ftp_state(struct Curl_easy *data, - ftpstate newstate, - int lineno); +#else /* !DEBUGBUILD */ #define ftp_state(x,y) _ftp_state(x,y,__LINE__) -#endif +#endif /* DEBUGBUILD */ static CURLcode ftp_sendquote(struct Curl_easy *data, struct connectdata *conn, @@ -109,7 +188,7 @@ static CURLcode ftp_regular_transfer(struct Curl_easy *data, bool *done); #ifndef CURL_DISABLE_VERBOSE_STRINGS static void ftp_pasv_verbose(struct Curl_easy *data, struct Curl_addrinfo *ai, - char *newhost, /* ascii version */ + char *newhost, /* ASCII version */ int port); #endif static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data); @@ -142,7 +221,7 @@ static CURLcode wc_statemach(struct Curl_easy *data); static void wc_data_dtor(void *ptr); static CURLcode ftp_state_retr(struct Curl_easy *data, curl_off_t filesize); static CURLcode ftp_readresp(struct Curl_easy *data, - curl_socket_t sockfd, + int sockindex, struct pingpong *pp, int *ftpcode, size_t *size); @@ -154,7 +233,7 @@ static CURLcode ftp_dophase_done(struct Curl_easy *data, */ const struct Curl_handler Curl_handler_ftp = { - "FTP", /* scheme */ + "ftp", /* scheme */ ftp_setup_connection, /* setup_connection */ ftp_do, /* do_it */ ftp_done, /* done */ @@ -167,7 +246,8 @@ const struct Curl_handler Curl_handler_ftp = { ftp_domore_getsock, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_FTP, /* defport */ @@ -185,7 +265,7 @@ const struct Curl_handler Curl_handler_ftp = { */ const struct Curl_handler Curl_handler_ftps = { - "FTPS", /* scheme */ + "ftps", /* scheme */ ftp_setup_connection, /* setup_connection */ ftp_do, /* do_it */ ftp_done, /* done */ @@ -198,7 +278,8 @@ const struct Curl_handler Curl_handler_ftps = { ftp_domore_getsock, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_FTPS, /* defport */ @@ -209,11 +290,11 @@ const struct Curl_handler Curl_handler_ftps = { }; #endif -static void close_secondarysocket(struct Curl_easy *data, - struct connectdata *conn) +static void close_secondarysocket(struct Curl_easy *data) { + CURL_TRC_FTP(data, "[%s] closing DATA connection", FTP_DSTATE(data)); Curl_conn_close(data, SECONDARYSOCKET); - Curl_conn_cf_discard_all(data, conn, SECONDARYSOCKET); + Curl_conn_cf_discard_all(data, data->conn, SECONDARYSOCKET); } /* @@ -221,10 +302,10 @@ static void close_secondarysocket(struct Curl_easy *data, * requests on files respond with headers passed to the client/stdout that * looked like HTTP ones. * - * This approach is not very elegant, it causes confusion and is error-prone. - * It is subject for removal at the next (or at least a future) soname bump. - * Until then you can test the effects of the removal by undefining the - * following define named CURL_FTP_HTTPSTYLE_HEAD. + * This approach is not elegant, it causes confusion and is error-prone. It is + * subject for removal at the next (or at least a future) soname bump. Until + * then you can test the effects of the removal by undefining the following + * define named CURL_FTP_HTTPSTYLE_HEAD. */ #define CURL_FTP_HTTPSTYLE_HEAD 1 @@ -246,178 +327,174 @@ static void freedirs(struct ftp_conn *ftpc) Curl_safefree(ftpc->newhost); } +#ifdef CURL_PREFER_LF_LINEENDS /*********************************************************************** * - * AcceptServerConnect() - * - * After connection request is received from the server this function is - * called to accept the connection and close the listening socket - * + * Lineend Conversions + * On ASCII transfers, e.g. directory listings, we might get lines + * ending in '\r\n' and we prefer just '\n'. + * We might also get a lonely '\r' which we convert into a '\n'. */ -static CURLcode AcceptServerConnect(struct Curl_easy *data) +struct ftp_cw_lc_ctx { + struct Curl_cwriter super; + bool newline_pending; +}; + +static CURLcode ftp_cw_lc_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) { - struct connectdata *conn = data->conn; - curl_socket_t sock = conn->sock[SECONDARYSOCKET]; - curl_socket_t s = CURL_SOCKET_BAD; -#ifdef ENABLE_IPV6 - struct Curl_sockaddr_storage add; -#else - struct sockaddr_in add; -#endif - curl_socklen_t size = (curl_socklen_t) sizeof(add); - CURLcode result; + static const char nl = '\n'; + struct ftp_cw_lc_ctx *ctx = writer->ctx; + + if(!(type & CLIENTWRITE_BODY) || + data->conn->proto.ftpc.transfertype != 'A') + return Curl_cwriter_write(data, writer->next, type, buf, blen); + + /* ASCII mode BODY data, convert lineends */ + while(blen) { + /* do not pass EOS when writing parts */ + int chunk_type = (type & ~CLIENTWRITE_EOS); + const char *cp; + size_t chunk_len; + CURLcode result; + + if(ctx->newline_pending) { + if(buf[0] != '\n') { + /* previous chunk ended in '\r' and we do not see a '\n' in this one, + * need to write a newline. */ + result = Curl_cwriter_write(data, writer->next, chunk_type, &nl, 1); + if(result) + return result; + } + /* either we just wrote the newline or it is part of the next + * chunk of bytes we write. */ + ctx->newline_pending = FALSE; + } - if(0 == getsockname(sock, (struct sockaddr *) &add, &size)) { - size = sizeof(add); + cp = memchr(buf, '\r', blen); + if(!cp) + break; - s = accept(sock, (struct sockaddr *) &add, &size); + /* write the bytes before the '\r', excluding the '\r' */ + chunk_len = cp - buf; + if(chunk_len) { + result = Curl_cwriter_write(data, writer->next, chunk_type, + buf, chunk_len); + if(result) + return result; + } + /* skip the '\r', we now have a newline pending */ + buf = cp + 1; + blen = blen - chunk_len - 1; + ctx->newline_pending = TRUE; } - if(CURL_SOCKET_BAD == s) { - failf(data, "Error accept()ing server connect"); - return CURLE_FTP_PORT_FAILED; + /* Any remaining data does not contain a '\r' */ + if(blen) { + DEBUGASSERT(!ctx->newline_pending); + return Curl_cwriter_write(data, writer->next, type, buf, blen); } - infof(data, "Connection accepted from server"); - /* when this happens within the DO state it is important that we mark us as - not needing DO_MORE anymore */ - conn->bits.do_more = FALSE; - - (void)curlx_nonblock(s, TRUE); /* enable non-blocking */ - /* Replace any filter on SECONDARY with one listening on this socket */ - result = Curl_conn_tcp_accepted_set(data, conn, SECONDARYSOCKET, &s); - if(result) - return result; - - if(data->set.fsockopt) { - int error = 0; - - /* activate callback for setting socket options */ - Curl_set_in_callback(data, true); - error = data->set.fsockopt(data->set.sockopt_client, - s, - CURLSOCKTYPE_ACCEPT); - Curl_set_in_callback(data, false); - - if(error) { - close_secondarysocket(data, conn); - return CURLE_ABORTED_BY_CALLBACK; + else if(type & CLIENTWRITE_EOS) { + /* EndOfStream, if we have a trailing cr, now is the time to write it */ + if(ctx->newline_pending) { + ctx->newline_pending = FALSE; + return Curl_cwriter_write(data, writer->next, type, &nl, 1); } + /* Always pass on the EOS type indicator */ + return Curl_cwriter_write(data, writer->next, type, buf, 0); } - return CURLE_OK; - -} - -/* - * ftp_timeleft_accept() returns the amount of milliseconds left allowed for - * waiting server to connect. If the value is negative, the timeout time has - * already elapsed. - * - * The start time is stored in progress.t_acceptdata - as set with - * Curl_pgrsTime(..., TIMER_STARTACCEPT); - * - */ -static timediff_t ftp_timeleft_accept(struct Curl_easy *data) -{ - timediff_t timeout_ms = DEFAULT_ACCEPT_TIMEOUT; - timediff_t other; - struct curltime now; - - if(data->set.accepttimeout > 0) - timeout_ms = data->set.accepttimeout; - - now = Curl_now(); - - /* check if the generic timeout possibly is set shorter */ - other = Curl_timeleft(data, &now, FALSE); - if(other && (other < timeout_ms)) - /* note that this also works fine for when other happens to be negative - due to it already having elapsed */ - timeout_ms = other; - else { - /* subtract elapsed time */ - timeout_ms -= Curl_timediff(now, data->progress.t_acceptdata); - if(!timeout_ms) - /* avoid returning 0 as that means no timeout! */ - return -1; - } - - return timeout_ms; } +static const struct Curl_cwtype ftp_cw_lc = { + "ftp-lineconv", + NULL, + Curl_cwriter_def_init, + ftp_cw_lc_write, + Curl_cwriter_def_close, + sizeof(struct ftp_cw_lc_ctx) +}; +#endif /* CURL_PREFER_LF_LINEENDS */ /*********************************************************************** * - * ReceivedServerConnect() - * - * After allowing server to connect to us from data port, this function - * checks both data connection for connection establishment and ctrl - * connection for a negative response regarding a failure in connecting + * ftp_check_ctrl_on_data_wait() * */ -static CURLcode ReceivedServerConnect(struct Curl_easy *data, bool *received) +static CURLcode ftp_check_ctrl_on_data_wait(struct Curl_easy *data) { struct connectdata *conn = data->conn; curl_socket_t ctrl_sock = conn->sock[FIRSTSOCKET]; - curl_socket_t data_sock = conn->sock[SECONDARYSOCKET]; struct ftp_conn *ftpc = &conn->proto.ftpc; struct pingpong *pp = &ftpc->pp; - int result; - timediff_t timeout_ms; ssize_t nread; int ftpcode; - - *received = FALSE; - - timeout_ms = ftp_timeleft_accept(data); - infof(data, "Checking for server connect"); - if(timeout_ms < 0) { - /* if a timeout was already reached, bail out */ - failf(data, "Accept timeout occurred while waiting server connect"); - return CURLE_FTP_ACCEPT_TIMEOUT; - } + bool response = FALSE; /* First check whether there is a cached response from server */ - if(pp->cache_size && pp->cache && pp->cache[0] > '3') { + if(Curl_dyn_len(&pp->recvbuf) && (*Curl_dyn_ptr(&pp->recvbuf) > '3')) { /* Data connection could not be established, let's return */ infof(data, "There is negative response in cache while serv connect"); (void)Curl_GetFTPResponse(data, &nread, &ftpcode); return CURLE_FTP_ACCEPT_FAILED; } - result = Curl_socket_check(ctrl_sock, data_sock, CURL_SOCKET_BAD, 0); - - /* see if the connection request is already here */ - switch(result) { - case -1: /* error */ - /* let's die here */ - failf(data, "Error while waiting for server connect"); - return CURLE_FTP_ACCEPT_FAILED; - case 0: /* Server connect is not received yet */ - break; /* loop */ - default: + if(pp->overflow) + /* there is pending control data still in the buffer to read */ + response = TRUE; + else { + int socketstate = Curl_socket_check(ctrl_sock, CURL_SOCKET_BAD, + CURL_SOCKET_BAD, 0); + /* see if the connection request is already here */ + switch(socketstate) { + case -1: /* error */ + /* let's die here */ + failf(data, "Error while waiting for server connect"); + return CURLE_FTP_ACCEPT_FAILED; + default: + if(socketstate & CURL_CSELECT_IN) + response = TRUE; + break; + } + } - if(result & CURL_CSELECT_IN2) { - infof(data, "Ready to accept data connection from server"); - *received = TRUE; + if(response) { + infof(data, "Ctrl conn has data while waiting for data conn"); + if(pp->overflow > 3) { + char *r = Curl_dyn_ptr(&pp->recvbuf); + + DEBUGASSERT((pp->overflow + pp->nfinal) <= + Curl_dyn_len(&pp->recvbuf)); + /* move over the most recently handled response line */ + r += pp->nfinal; + + if(LASTLINE(r)) { + int status = curlx_sltosi(strtol(r, NULL, 10)); + if(status == 226) { + /* funny timing situation where we get the final message on the + control connection before traffic on the data connection has been + noticed. Leave the 226 in there and use this as a trigger to read + the data socket. */ + infof(data, "Got 226 before data activity"); + return CURLE_OK; + } + } } - else if(result & CURL_CSELECT_IN) { - infof(data, "Ctrl conn has data while waiting for data conn"); - (void)Curl_GetFTPResponse(data, &nread, &ftpcode); - if(ftpcode/100 > 3) - return CURLE_FTP_ACCEPT_FAILED; + (void)Curl_GetFTPResponse(data, &nread, &ftpcode); - return CURLE_WEIRD_SERVER_REPLY; - } + infof(data, "FTP code: %03d", ftpcode); - break; - } /* switch() */ + if(ftpcode/100 > 3) + return CURLE_FTP_ACCEPT_FAILED; + + return CURLE_WEIRD_SERVER_REPLY; + } return CURLE_OK; } - /*********************************************************************** * * InitiateTransfer() @@ -432,31 +509,27 @@ static CURLcode InitiateTransfer(struct Curl_easy *data) struct connectdata *conn = data->conn; bool connected; - DEBUGF(infof(data, "ftp InitiateTransfer()")); - if(conn->bits.ftp_use_data_ssl && data->set.ftp_use_port && - !Curl_conn_is_ssl(conn, SECONDARYSOCKET)) { - result = Curl_ssl_cfilter_add(data, conn, SECONDARYSOCKET); - if(result) - return result; - } + CURL_TRC_FTP(data, "InitiateTransfer()"); result = Curl_conn_connect(data, SECONDARYSOCKET, TRUE, &connected); if(result || !connected) return result; if(conn->proto.ftpc.state_saved == FTP_STOR) { - /* When we know we're uploading a specified file, we can get the file + /* When we know we are uploading a specified file, we can get the file size prior to the actual upload. */ Curl_pgrsSetUploadSize(data, data->state.infilesize); /* set the SO_SNDBUF for the secondary socket for those who need it */ - Curl_sndbufset(conn->sock[SECONDARYSOCKET]); + Curl_sndbuf_init(conn->sock[SECONDARYSOCKET]); - Curl_setup_transfer(data, -1, -1, FALSE, SECONDARYSOCKET); + /* FTP upload, shutdown DATA, ignore shutdown errors, as we rely + * on the server response on the CONTROL connection. */ + Curl_xfer_setup2(data, CURL_XFER_SEND, -1, TRUE, TRUE); } else { - /* FTP download: */ - Curl_setup_transfer(data, SECONDARYSOCKET, - conn->proto.ftpc.retr_size_saved, FALSE, -1); + /* FTP download, shutdown, do not ignore errors */ + Curl_xfer_setup2(data, CURL_XFER_RECV, + conn->proto.ftpc.retr_size_saved, TRUE, FALSE); } conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */ @@ -465,68 +538,6 @@ static CURLcode InitiateTransfer(struct Curl_easy *data) return CURLE_OK; } -/*********************************************************************** - * - * AllowServerConnect() - * - * When we've issue the PORT command, we have told the server to connect to - * us. This function checks whether data connection is established if so it is - * accepted. - * - */ -static CURLcode AllowServerConnect(struct Curl_easy *data, bool *connected) -{ - timediff_t timeout_ms; - CURLcode result = CURLE_OK; - - *connected = FALSE; - infof(data, "Preparing for accepting server on data port"); - - /* Save the time we start accepting server connect */ - Curl_pgrsTime(data, TIMER_STARTACCEPT); - - timeout_ms = ftp_timeleft_accept(data); - if(timeout_ms < 0) { - /* if a timeout was already reached, bail out */ - failf(data, "Accept timeout occurred while waiting server connect"); - result = CURLE_FTP_ACCEPT_TIMEOUT; - goto out; - } - - /* see if the connection request is already here */ - result = ReceivedServerConnect(data, connected); - if(result) - goto out; - - if(*connected) { - result = AcceptServerConnect(data); - if(result) - goto out; - - result = InitiateTransfer(data); - if(result) - goto out; - } - else { - /* Add timeout to multi handle and break out of the loop */ - Curl_expire(data, data->set.accepttimeout ? - data->set.accepttimeout: DEFAULT_ACCEPT_TIMEOUT, - EXPIRE_FTP_ACCEPT); - } - -out: - DEBUGF(infof(data, "ftp AllowServerConnect() -> %d", result)); - return result; -} - -/* macro to check for a three-digit ftp status code at the start of the - given string */ -#define STATUSCODE(line) (ISDIGIT(line[0]) && ISDIGIT(line[1]) && \ - ISDIGIT(line[2])) - -/* macro to check for the last line in an FTP server response */ -#define LASTLINE(line) (STATUSCODE(line) && (' ' == line[3])) - static bool ftp_endofresp(struct Curl_easy *data, struct connectdata *conn, char *line, size_t len, int *code) { @@ -542,18 +553,18 @@ static bool ftp_endofresp(struct Curl_easy *data, struct connectdata *conn, } static CURLcode ftp_readresp(struct Curl_easy *data, - curl_socket_t sockfd, + int sockindex, struct pingpong *pp, int *ftpcode, /* return the ftp-code if done */ size_t *size) /* size of the response */ { int code; - CURLcode result = Curl_pp_readresp(data, sockfd, pp, &code, size); + CURLcode result = Curl_pp_readresp(data, sockindex, pp, &code, size); #ifdef HAVE_GSSAPI { struct connectdata *conn = data->conn; - char * const buf = data->state.buffer; + char * const buf = Curl_dyn_ptr(&data->conn->proto.ftpc.pp.recvbuf); /* handle the security-oriented responses 6xx ***/ switch(code) { @@ -609,7 +620,7 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, { /* * We cannot read just one byte per read() and then go back to select() as - * the OpenSSL read() doesn't grok that properly. + * the OpenSSL read() does not grok that properly. * * Alas, read as much as possible, split up into lines, use the ending * line in a response or continue reading. */ @@ -623,6 +634,8 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, int cache_skip = 0; int value_to_be_ignored = 0; + CURL_TRC_FTP(data, "getFTPResponse start"); + if(ftpcode) *ftpcode = 0; /* 0 for errors */ else @@ -652,42 +665,48 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, * * A caution here is that the ftp_readresp() function has a cache that may * contain pieces of a response from the previous invoke and we need to - * make sure we don't just wait for input while there is unhandled data in + * make sure we do not just wait for input while there is unhandled data in * that cache. But also, if the cache is there, we call ftp_readresp() and - * the cache wasn't good enough to continue we must not just busy-loop + * the cache was not good enough to continue we must not just busy-loop * around this function. * */ - if(pp->cache && (cache_skip < 2)) { + if(Curl_dyn_len(&pp->recvbuf) && (cache_skip < 2)) { /* - * There's a cache left since before. We then skipping the wait for + * There is a cache left since before. We then skipping the wait for * socket action, unless this is the same cache like the previous round * as then the cache was deemed not enough to act on and we then need to * wait for more data anyway. */ } else if(!Curl_conn_data_pending(data, FIRSTSOCKET)) { - switch(SOCKET_READABLE(sockfd, interval_ms)) { - case -1: /* select() error, stop reading */ + curl_socket_t wsock = Curl_pp_needs_flush(data, pp) ? + sockfd : CURL_SOCKET_BAD; + int ev = Curl_socket_check(sockfd, CURL_SOCKET_BAD, wsock, interval_ms); + if(ev < 0) { failf(data, "FTP response aborted due to select/poll error: %d", SOCKERRNO); return CURLE_RECV_ERROR; - - case 0: /* timeout */ + } + else if(ev == 0) { if(Curl_pgrsUpdate(data)) return CURLE_ABORTED_BY_CALLBACK; continue; /* just continue in our loop for the timeout duration */ + } + } - default: /* for clarity */ + if(Curl_pp_needs_flush(data, pp)) { + result = Curl_pp_flushsend(data, pp); + if(result) break; - } } - result = ftp_readresp(data, sockfd, pp, ftpcode, &nread); + + result = ftp_readresp(data, FIRSTSOCKET, pp, ftpcode, &nread); if(result) break; - if(!nread && pp->cache) + if(!nread && Curl_dyn_len(&pp->recvbuf)) /* bump cache skip counter as on repeated skips we must wait for more data */ cache_skip++; @@ -698,86 +717,21 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, *nreadp += nread; - } /* while there's buffer left and loop is requested */ + } /* while there is buffer left and loop is requested */ pp->pending_resp = FALSE; + CURL_TRC_FTP(data, "getFTPResponse -> result=%d, nread=%zd, ftpcode=%d", + result, *nreadp, *ftpcode); return result; } -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - /* for debug purposes */ -static const char * const ftp_state_names[]={ - "STOP", - "WAIT220", - "AUTH", - "USER", - "PASS", - "ACCT", - "PBSZ", - "PROT", - "CCC", - "PWD", - "SYST", - "NAMEFMT", - "QUOTE", - "RETR_PREQUOTE", - "STOR_PREQUOTE", - "POSTQUOTE", - "CWD", - "MKD", - "MDTM", - "TYPE", - "LIST_TYPE", - "RETR_TYPE", - "STOR_TYPE", - "SIZE", - "RETR_SIZE", - "STOR_SIZE", - "REST", - "RETR_REST", - "PORT", - "PRET", - "PASV", - "LIST", - "RETR", - "STOR", - "QUIT" -}; -#endif - -/* This is the ONLY way to change FTP state! */ -static void _ftp_state(struct Curl_easy *data, - ftpstate newstate -#ifdef DEBUGBUILD - , int lineno -#endif - ) -{ - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - -#if defined(DEBUGBUILD) - -#if defined(CURL_DISABLE_VERBOSE_STRINGS) - (void) lineno; -#else - if(ftpc->state != newstate) - infof(data, "FTP %p (line %d) state change from %s to %s", - (void *)ftpc, lineno, ftp_state_names[ftpc->state], - ftp_state_names[newstate]); -#endif -#endif - - ftpc->state = newstate; -} - static CURLcode ftp_state_user(struct Curl_easy *data, struct connectdata *conn) { CURLcode result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "USER %s", - conn->user?conn->user:""); + conn->user ? conn->user : ""); if(!result) { struct ftp_conn *ftpc = &conn->proto.ftpc; ftpc->ftp_trying_alternative = FALSE; @@ -815,24 +769,18 @@ static int ftp_domore_getsock(struct Curl_easy *data, * remote site, or we could wait for that site to connect to us. Or just * handle ordinary commands. */ - - DEBUGF(infof(data, "ftp_domore_getsock()")); - if(conn->cfilter[SECONDARYSOCKET] - && !Curl_conn_is_connected(conn, SECONDARYSOCKET)) - return Curl_conn_get_select_socks(data, SECONDARYSOCKET, socks); + CURL_TRC_FTP(data, "[%s] ftp_domore_getsock()", FTP_DSTATE(data)); if(FTP_STOP == ftpc->state) { - int bits = GETSOCK_READSOCK(0); - - /* if stopped and still in this state, then we're also waiting for a + /* if stopped and still in this state, then we are also waiting for a connect on the secondary connection */ + DEBUGASSERT(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD || + (conn->cfilter[SECONDARYSOCKET] && + !Curl_conn_is_connected(conn, SECONDARYSOCKET))); socks[0] = conn->sock[FIRSTSOCKET]; - if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) { - socks[1] = conn->sock[SECONDARYSOCKET]; - bits |= GETSOCK_WRITESOCK(1) | GETSOCK_READSOCK(1); - } - - return bits; + /* An unconnected SECONDARY will add its socket by itself + * via its adjust_pollset() */ + return GETSOCK_READSOCK(0); } return Curl_pp_getsock(data, &conn->proto.ftpc.pp, socks); } @@ -911,7 +859,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, char hbuf[NI_MAXHOST]; struct sockaddr *sa = (struct sockaddr *)&ss; struct sockaddr_in * const sa4 = (void *)sa; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 * const sa6 = (void *)sa; #endif static const char mode[][5] = { "EPRT", "PORT" }; @@ -919,13 +867,15 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, int error; char *host = NULL; char *string_ftpport = data->set.str[STRING_FTPPORT]; - struct Curl_dns_entry *h = NULL; + struct Curl_dns_entry *dns_entry = NULL; unsigned short port_min = 0; unsigned short port_max = 0; unsigned short port; bool possibly_non_local = TRUE; char buffer[STRERROR_LEN]; char *addr = NULL; + size_t addrlen = 0; + char ipstr[50]; /* Step 1, figure out what is requested, * accepted format : @@ -934,32 +884,17 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, if(data->set.str[STRING_FTPPORT] && (strlen(data->set.str[STRING_FTPPORT]) > 1)) { - -#ifdef ENABLE_IPV6 - size_t addrlen = INET6_ADDRSTRLEN > strlen(string_ftpport) ? - INET6_ADDRSTRLEN : strlen(string_ftpport); -#else - size_t addrlen = INET_ADDRSTRLEN > strlen(string_ftpport) ? - INET_ADDRSTRLEN : strlen(string_ftpport); -#endif - char *ip_start = string_ftpport; char *ip_end = NULL; - char *port_start = NULL; - char *port_sep = NULL; - - addr = calloc(addrlen + 1, 1); - if(!addr) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(*string_ftpport == '[') { /* [ipv6]:port(-range) */ - ip_start = string_ftpport + 1; - ip_end = strchr(string_ftpport, ']'); - if(ip_end) - strncpy(addr, ip_start, ip_end - ip_start); + char *ip_start = string_ftpport + 1; + ip_end = strchr(ip_start, ']'); + if(ip_end) { + addrlen = ip_end - ip_start; + addr = ip_start; + } } else #endif @@ -969,28 +904,27 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, } else { ip_end = strchr(string_ftpport, ':'); + addr = string_ftpport; if(ip_end) { /* either ipv6 or (ipv4|domain|interface):port(-range) */ -#ifdef ENABLE_IPV6 + addrlen = ip_end - string_ftpport; +#ifdef USE_IPV6 if(Curl_inet_pton(AF_INET6, string_ftpport, &sa6->sin6_addr) == 1) { /* ipv6 */ port_min = port_max = 0; - strcpy(addr, string_ftpport); ip_end = NULL; /* this got no port ! */ } - else #endif - /* (ipv4|domain|interface):port(-range) */ - strncpy(addr, string_ftpport, ip_end - ip_start); } else /* ipv4|interface */ - strcpy(addr, string_ftpport); + addrlen = strlen(string_ftpport); } /* parse the port */ if(ip_end) { - port_start = strchr(ip_end, ':'); + char *port_sep = NULL; + char *port_start = strchr(ip_end, ':'); if(port_start) { port_min = curlx_ultous(strtoul(port_start + 1, NULL, 10)); port_sep = strchr(port_start, '-'); @@ -1011,22 +945,29 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, if(port_min > port_max) port_min = port_max = 0; - if(*addr != '\0') { + if(addrlen) { + DEBUGASSERT(addr); + if(addrlen >= sizeof(ipstr)) + goto out; + memcpy(ipstr, addr, addrlen); + ipstr[addrlen] = 0; + /* attempt to get the address of the given interface name */ switch(Curl_if2ip(conn->remote_addr->family, -#ifdef ENABLE_IPV6 - Curl_ipv6_scope(&conn->remote_addr->sa_addr), +#ifdef USE_IPV6 + Curl_ipv6_scope(&conn->remote_addr->curl_sa_addr), conn->scope_id, #endif - addr, hbuf, sizeof(hbuf))) { + ipstr, hbuf, sizeof(hbuf))) { case IF2IP_NOT_FOUND: - /* not an interface, use the given string as host name instead */ - host = addr; + /* not an interface, use the given string as hostname instead */ + host = ipstr; break; case IF2IP_AF_NOT_SUPPORTED: goto out; case IF2IP_FOUND: - host = hbuf; /* use the hbuf for host name */ + host = hbuf; /* use the hbuf for hostname */ + break; } } else @@ -1036,7 +977,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, if(!host) { const char *r; - /* not an interface and not a host name, get default by extracting + /* not an interface and not a hostname, get default by extracting the IP from the control connection */ sslen = sizeof(ss); if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) { @@ -1045,7 +986,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, goto out; } switch(sa->sa_family) { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: r = Curl_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, sizeof(hbuf)); break; @@ -1057,20 +998,17 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, if(!r) { goto out; } - host = hbuf; /* use this host name */ + host = hbuf; /* use this hostname */ possibly_non_local = FALSE; /* we know it is local now */ } /* resolv ip/host to ip */ - rc = Curl_resolv(data, host, 0, FALSE, &h); + rc = Curl_resolv(data, host, 0, FALSE, &dns_entry); if(rc == CURLRESOLV_PENDING) - (void)Curl_resolver_wait_resolv(data, &h); - if(h) { - res = h->addr; - /* when we return from this function, we can forget about this entry - to we can unlock it now already */ - Curl_resolv_unlock(data, h); - } /* (h) */ + (void)Curl_resolver_wait_resolv(data, &dns_entry); + if(dns_entry) { + res = dns_entry->addr; + } else res = NULL; /* failure! */ @@ -1095,7 +1033,8 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, Curl_strerror(error, buffer, sizeof(buffer))); goto out; } - DEBUGF(infof(data, "ftp_state_use_port(), opened socket")); + CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), opened socket", + FTP_DSTATE(data)); /* step 3, bind to a suitable local address */ @@ -1105,7 +1044,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, for(port = port_min; port <= port_max;) { if(sa->sa_family == AF_INET) sa4->sin_port = htons(port); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else sa6->sin6_port = htons(port); #endif @@ -1114,7 +1053,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, /* It failed. */ error = SOCKERRNO; if(possibly_non_local && (error == EADDRNOTAVAIL)) { - /* The requested bind address is not local. Use the address used for + /* The requested bind address is not local. Use the address used for * the control connection instead and restart the port loop */ infof(data, "bind(port=%hu) on non-local address failed: %s", port, @@ -1127,7 +1066,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, goto out; } port = port_min; - possibly_non_local = FALSE; /* don't try this again */ + possibly_non_local = FALSE; /* do not try this again */ continue; } if(error != EADDRINUSE && error != EACCES) { @@ -1156,7 +1095,8 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); goto out; } - DEBUGF(infof(data, "ftp_state_use_port(), socket bound to port %d", port)); + CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), socket bound to port %d", + FTP_DSTATE(data), port); /* step 4, listen on the socket */ @@ -1165,7 +1105,8 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); goto out; } - DEBUGF(infof(data, "ftp_state_use_port(), listening on %d", port)); + CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), listening on %d", + FTP_DSTATE(data), port); /* step 5, send the proper FTP command */ @@ -1173,7 +1114,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, below */ Curl_printable_address(ai, myhost, sizeof(myhost)); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(!conn->bits.ftp_use_eprt && conn->bits.ipv6) /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the request and enable EPRT again! */ @@ -1194,7 +1135,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, case AF_INET: port = ntohs(sa4->sin_port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: port = ntohs(sa6->sin6_port); break; @@ -1213,7 +1154,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, */ result = Curl_pp_sendf(data, &ftpc->pp, "%s |%d|%s|%hu|", mode[fcmd], - sa->sa_family == AF_INET?1:2, + sa->sa_family == AF_INET ? 1 : 2, myhost, port); if(result) { failf(data, "Failure sending EPRT command: %s", @@ -1229,7 +1170,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, char *dest = target; /* translate x.x.x.x to x,x,x,x */ - while(source && *source) { + while(*source) { if(*source == '.') *dest = ','; else @@ -1238,7 +1179,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, source++; } *dest = 0; - msnprintf(dest, 20, ",%d,%d", (int)(port>>8), (int)(port&0xff)); + msnprintf(dest, 20, ",%d,%d", (int)(port >> 8), (int)(port & 0xff)); result = Curl_pp_sendf(data, &ftpc->pp, "%s %s", mode[fcmd], target); if(result) { @@ -1252,21 +1193,34 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, /* store which command was sent */ ftpc->count1 = fcmd; + ftp_state(data, FTP_PORT); /* Replace any filter on SECONDARY with one listening on this socket */ result = Curl_conn_tcp_listen_set(data, conn, SECONDARYSOCKET, &portsock); - if(result) - goto out; - portsock = CURL_SOCKET_BAD; /* now held in filter */ - ftp_state(data, FTP_PORT); + if(!result) + portsock = CURL_SOCKET_BAD; /* now held in filter */ out: + /* If we looked up a dns_entry, now is the time to safely release it */ + if(dns_entry) + Curl_resolv_unlink(data, &dns_entry); if(result) { ftp_state(data, FTP_STOP); } + else { + /* successfully setup the list socket filter. Do we need more? */ + if(conn->bits.ftp_use_data_ssl && data->set.ftp_use_port && + !Curl_conn_is_ssl(conn, SECONDARYSOCKET)) { + result = Curl_ssl_cfilter_add(data, conn, SECONDARYSOCKET); + } + data->conn->bits.do_more = FALSE; + Curl_pgrsTime(data, TIMER_STARTACCEPT); + Curl_expire(data, data->set.accepttimeout ? + data->set.accepttimeout: DEFAULT_ACCEPT_TIMEOUT, + EXPIRE_FTP_ACCEPT); + } if(portsock != CURL_SOCKET_BAD) Curl_socket_close(data, conn, portsock); - free(addr); return result; } @@ -1299,7 +1253,7 @@ static CURLcode ftp_state_use_pasv(struct Curl_easy *data, conn->bits.ftp_use_epsv = TRUE; #endif - modeoff = conn->bits.ftp_use_epsv?0:1; + modeoff = conn->bits.ftp_use_epsv ? 0 : 1; result = Curl_pp_sendf(data, &ftpc->pp, "%s", mode[modeoff]); if(!result) { @@ -1324,7 +1278,7 @@ static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data) struct connectdata *conn = data->conn; if(ftp->transfer != PPTRANSFER_BODY) { - /* doesn't transfer any data */ + /* does not transfer any data */ /* still possibly do PRE QUOTE jobs */ ftp_state(data, FTP_RETR_PREQUOTE); @@ -1342,9 +1296,9 @@ static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data) struct ftp_conn *ftpc = &conn->proto.ftpc; if(!conn->proto.ftpc.file) result = Curl_pp_sendf(data, &ftpc->pp, "PRET %s", - data->set.str[STRING_CUSTOMREQUEST]? - data->set.str[STRING_CUSTOMREQUEST]: - (data->state.list_only?"NLST":"LIST")); + data->set.str[STRING_CUSTOMREQUEST] ? + data->set.str[STRING_CUSTOMREQUEST] : + (data->state.list_only ? "NLST" : "LIST")); else if(data->state.upload) result = Curl_pp_sendf(data, &ftpc->pp, "PRET STOR %s", conn->proto.ftpc.file); @@ -1392,7 +1346,7 @@ static CURLcode ftp_state_size(struct Curl_easy *data, if((ftp->transfer == PPTRANSFER_INFO) && ftpc->file) { /* if a "head"-like request is being made (on a file) */ - /* we know ftpc->file is a valid pointer to a file name */ + /* we know ftpc->file is a valid pointer to a filename */ result = Curl_pp_sendf(data, &ftpc->pp, "SIZE %s", ftpc->file); if(!result) ftp_state(data, FTP_SIZE); @@ -1449,11 +1403,11 @@ static CURLcode ftp_state_list(struct Curl_easy *data) } cmd = aprintf("%s%s%s", - data->set.str[STRING_CUSTOMREQUEST]? - data->set.str[STRING_CUSTOMREQUEST]: - (data->state.list_only?"NLST":"LIST"), - lstArg? " ": "", - lstArg? lstArg: ""); + data->set.str[STRING_CUSTOMREQUEST] ? + data->set.str[STRING_CUSTOMREQUEST] : + (data->state.list_only ? "NLST" : "LIST"), + lstArg ? " " : "", + lstArg ? lstArg : ""); free(lstArg); if(!cmd) @@ -1470,13 +1424,13 @@ static CURLcode ftp_state_list(struct Curl_easy *data) static CURLcode ftp_state_retr_prequote(struct Curl_easy *data) { - /* We've sent the TYPE, now we must send the list of prequote strings */ + /* We have sent the TYPE, now we must send the list of prequote strings */ return ftp_state_quote(data, TRUE, FTP_RETR_PREQUOTE); } static CURLcode ftp_state_stor_prequote(struct Curl_easy *data) { - /* We've sent the TYPE, now we must send the list of prequote strings */ + /* We have sent the TYPE, now we must send the list of prequote strings */ return ftp_state_quote(data, TRUE, FTP_STOR_PREQUOTE); } @@ -1488,7 +1442,7 @@ static CURLcode ftp_state_type(struct Curl_easy *data) struct ftp_conn *ftpc = &conn->proto.ftpc; /* If we have selected NOBODY and HEADER, it means that we only want file - information. Which in FTP can't be much more than the file size and + information. Which in FTP cannot be much more than the file size and date. */ if(data->req.no_body && ftpc->file && ftp_need_type(conn, data->state.prefer_ascii)) { @@ -1548,13 +1502,13 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, if((data->state.resume_from && !sizechecked) || ((data->state.resume_from > 0) && sizechecked)) { - /* we're about to continue the uploading of a file */ + /* we are about to continue the uploading of a file */ /* 1. get already existing file's size. We use the SIZE command for this which may not exist in the server! The SIZE command is not in RFC959. */ /* 2. This used to set REST. But since we can do append, we - don't another ftp command. We just skip the source file + do not another ftp command. We just skip the source file offset and then we APPEND the rest on the file instead */ /* 3. pass file-size number of bytes in the source file */ @@ -1574,11 +1528,11 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, append = TRUE; /* Let's read off the proper amount of bytes from the input. */ - if(conn->seek_func) { - Curl_set_in_callback(data, true); - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - Curl_set_in_callback(data, false); + if(data->set.seek_func) { + Curl_set_in_callback(data, TRUE); + seekerr = data->set.seek_func(data->set.seek_client, + data->state.resume_from, SEEK_SET); + Curl_set_in_callback(data, FALSE); } if(seekerr != CURL_SEEKFUNC_OK) { @@ -1587,15 +1541,16 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, failf(data, "Could not seek stream"); return CURLE_FTP_COULDNT_USE_REST; } - /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ + /* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ do { + char scratch[4*1024]; size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : + (data->state.resume_from - passed > (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); size_t actuallyread = - data->state.fread_func(data->state.buffer, 1, readthisamountnow, + data->state.fread_func(scratch, 1, readthisamountnow, data->state.in); passed += actuallyread; @@ -1608,27 +1563,27 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, } while(passed < data->state.resume_from); } /* now, decrease the size of the read */ - if(data->state.infilesize>0) { + if(data->state.infilesize > 0) { data->state.infilesize -= data->state.resume_from; if(data->state.infilesize <= 0) { infof(data, "File already completely uploaded"); /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); - /* Set ->transfer so that we won't get any error in - * ftp_done() because we didn't transfer anything! */ + /* Set ->transfer so that we will not get any error in + * ftp_done() because we did not transfer anything! */ ftp->transfer = PPTRANSFER_NONE; ftp_state(data, FTP_STOP); return CURLE_OK; } } - /* we've passed, proceed as normal */ + /* we have passed, proceed as normal */ } /* resume_from */ - result = Curl_pp_sendf(data, &ftpc->pp, append?"APPE %s":"STOR %s", + result = Curl_pp_sendf(data, &ftpc->pp, append ? "APPE %s" : "STOR %s", ftpc->file); if(!result) ftp_state(data, FTP_STOR); @@ -1676,7 +1631,7 @@ static CURLcode ftp_state_quote(struct Curl_easy *data, int i = 0; /* Skip count1 items in the linked list */ - while((i< ftpc->count1) && item) { + while((i < ftpc->count1) && item) { item = item->next; i++; } @@ -1714,16 +1669,16 @@ static CURLcode ftp_state_quote(struct Curl_easy *data, } else { if(data->set.ignorecl || data->state.prefer_ascii) { - /* 'ignorecl' is used to support download of growing files. It + /* 'ignorecl' is used to support download of growing files. It prevents the state machine from requesting the file size from - the server. With an unknown file size the download continues + the server. With an unknown file size the download continues until the server terminates it, otherwise the client stops if - the received byte count exceeds the reported file size. Set + the received byte count exceeds the reported file size. Set option CURLOPT_IGNORE_CONTENT_LENGTH to 1 to enable this behavior. In addition: asking for the size for 'TYPE A' transfers is not - constructive since servers don't report the converted size. So + constructive since servers do not report the converted size. So skip it. */ result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); @@ -1761,7 +1716,7 @@ static CURLcode ftp_epsv_disable(struct Curl_easy *data, && !(conn->bits.tunnel_proxy || conn->bits.socksproxy) #endif ) { - /* We can't disable EPSV when doing IPv6, so this is instead a fail */ + /* We cannot disable EPSV when doing IPv6, so this is instead a fail */ failf(data, "Failed EPSV attempt, exiting"); return CURLE_WEIRD_SERVER_REPLY; } @@ -1786,14 +1741,14 @@ static CURLcode ftp_epsv_disable(struct Curl_easy *data, static char *control_address(struct connectdata *conn) { /* Returns the control connection IP address. - If a proxy tunnel is used, returns the original host name instead, because + If a proxy tunnel is used, returns the original hostname instead, because the effective control connection address is the proxy address, not the ftp host. */ #ifndef CURL_DISABLE_PROXY if(conn->bits.tunnel_proxy || conn->bits.socksproxy) return conn->host.name; #endif - return conn->primary_ip; + return conn->primary.remote_ip; } static bool match_pasv_6nums(const char *p, @@ -1828,7 +1783,9 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, struct Curl_dns_entry *addr = NULL; enum resolve_t rc; unsigned short connectport; /* the local port connect() should use! */ - char *str = &data->state.buffer[4]; /* start on the first letter */ + struct pingpong *pp = &ftpc->pp; + char *str = + Curl_dyn_ptr(&pp->recvbuf) + 4; /* start on the first letter */ /* if we come here again, make sure the former name is cleared */ Curl_safefree(ftpc->newhost); @@ -1908,7 +1865,7 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, if(!ftpc->newhost) return CURLE_OUT_OF_MEMORY; - ftpc->newport = (unsigned short)(((ip[4]<<8) + ip[5]) & 0xffff); + ftpc->newport = (unsigned short)(((ip[4] << 8) + ip[5]) & 0xffff); } else if(ftpc->count1 == 0) { /* EPSV failed, move on to PASV */ @@ -1923,22 +1880,22 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, if(conn->bits.proxy) { /* * This connection uses a proxy and we need to connect to the proxy again - * here. We don't want to rely on a former host lookup that might've + * here. We do not want to rely on a former host lookup that might've * expired now, instead we remake the lookup here and now! */ const char * const host_name = conn->bits.socksproxy ? conn->socks_proxy.host.name : conn->http_proxy.host.name; - rc = Curl_resolv(data, host_name, conn->port, FALSE, &addr); + rc = Curl_resolv(data, host_name, conn->primary.remote_port, FALSE, &addr); if(rc == CURLRESOLV_PENDING) /* BLOCKING, ignores the return code but 'addr' will be NULL in case of failure */ (void)Curl_resolver_wait_resolv(data, &addr); - connectport = - (unsigned short)conn->port; /* we connect to the proxy's port */ + /* we connect to the proxy's port */ + connectport = (unsigned short)conn->primary.remote_port; if(!addr) { - failf(data, "Can't resolve proxy host %s:%hu", host_name, connectport); + failf(data, "cannot resolve proxy host %s:%hu", host_name, connectport); return CURLE_COULDNT_RESOLVE_PROXY; } } @@ -1950,7 +1907,6 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, /* postponed address resolution in case of tcp fastopen */ if(conn->bits.tcp_fastopen && !conn->bits.reuse && !ftpc->newhost[0]) { - Curl_conn_ev_update_info(data, conn); Curl_safefree(ftpc->newhost); ftpc->newhost = strdup(control_address(conn)); if(!ftpc->newhost) @@ -1965,17 +1921,18 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, connectport = ftpc->newport; /* we connect to the remote port */ if(!addr) { - failf(data, "Can't resolve new host %s:%hu", ftpc->newhost, connectport); + failf(data, "cannot resolve new host %s:%hu", + ftpc->newhost, connectport); return CURLE_FTP_CANT_GET_HOST; } } result = Curl_conn_setup(data, conn, SECONDARYSOCKET, addr, - conn->bits.ftp_use_data_ssl? + conn->bits.ftp_use_data_ssl ? CURL_CF_SSL_ENABLE : CURL_CF_SSL_DISABLE); if(result) { - Curl_resolv_unlock(data, addr); /* we're done using this address */ + Curl_resolv_unlink(data, &addr); /* we are done using this address */ if(ftpc->count1 == 0 && ftpcode == 229) return ftp_epsv_disable(data, conn); @@ -1993,7 +1950,7 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, /* this just dumps information about this second connection */ ftp_pasv_verbose(data, addr->addr, ftpc->newhost, connectport); - Curl_resolv_unlock(data, addr); /* we're done using this address */ + Curl_resolv_unlink(data, &addr); /* we are done using this address */ Curl_safefree(conn->secondaryhostname); conn->secondary_port = ftpc->newport; @@ -2067,6 +2024,31 @@ static bool ftp_213_date(const char *p, int *year, int *month, int *day, return TRUE; } +static CURLcode client_write_header(struct Curl_easy *data, + char *buf, size_t blen) +{ + /* Some replies from an FTP server are written to the client + * as CLIENTWRITE_HEADER, formatted as if they came from a + * HTTP conversation. + * In all protocols, CLIENTWRITE_HEADER data is only passed to + * the body write callback when data->set.include_header is set + * via CURLOPT_HEADER. + * For historic reasons, FTP never played this game and expects + * all its HEADERs to do that always. Set that flag during the + * call to Curl_client_write() so it does the right thing. + * + * Notice that we cannot enable this flag for FTP in general, + * as an FTP transfer might involve an HTTP proxy connection and + * headers from CONNECT should not automatically be part of the + * output. */ + CURLcode result; + bool save = data->set.include_header; + data->set.include_header = TRUE; + result = Curl_client_write(data, CLIENTWRITE_HEADER, buf, blen); + data->set.include_header = save; + return result; +} + static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, int ftpcode) { @@ -2081,8 +2063,9 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the last .sss part is optional and means fractions of a second */ int year, month, day, hour, minute, second; - if(ftp_213_date(&data->state.buffer[4], - &year, &month, &day, &hour, &minute, &second)) { + struct pingpong *pp = &ftpc->pp; + char *resp = Curl_dyn_ptr(&pp->recvbuf) + 4; + if(ftp_213_date(resp, &year, &month, &day, &hour, &minute, &second)) { /* we have a time, reformat it */ char timebuf[24]; msnprintf(timebuf, sizeof(timebuf), @@ -2111,17 +2094,17 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, return result; /* format: "Tue, 15 Nov 1994 12:45:26" */ - headerbuflen = msnprintf(headerbuf, sizeof(headerbuf), - "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], - tm->tm_mday, - Curl_month[tm->tm_mon], - tm->tm_year + 1900, - tm->tm_hour, - tm->tm_min, - tm->tm_sec); - result = Curl_client_write(data, CLIENTWRITE_BOTH, headerbuf, - headerbuflen); + headerbuflen = + msnprintf(headerbuf, sizeof(headerbuf), + "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", + Curl_wkday[tm->tm_wday ? tm->tm_wday-1 : 6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + result = client_write_header(data, headerbuf, headerbuflen); if(result) return result; } /* end of a ridiculous amount of conditionals */ @@ -2212,7 +2195,7 @@ static CURLcode ftp_state_retr(struct Curl_easy *data, struct connectdata *conn = data->conn; struct ftp_conn *ftpc = &conn->proto.ftpc; - DEBUGF(infof(data, "ftp_state_retr()")); + CURL_TRC_FTP(data, "[%s] ftp_state_retr()", FTP_DSTATE(data)); if(data->set.max_filesize && (filesize > data->set.max_filesize)) { failf(data, "Maximum file size exceeded"); return CURLE_FILESIZE_EXCEEDED; @@ -2223,20 +2206,20 @@ static CURLcode ftp_state_retr(struct Curl_easy *data, /* We always (attempt to) get the size of downloads, so it is done before this even when not doing resumes. */ if(filesize == -1) { - infof(data, "ftp server doesn't support SIZE"); - /* We couldn't get the size and therefore we can't know if there really + infof(data, "ftp server does not support SIZE"); + /* We could not get the size and therefore we cannot know if there really is a part of the file left to get, although the server will just - close the connection when we start the connection so it won't cause + close the connection when we start the connection so it will not cause us any harm, just not make us exit as nicely. */ } else { /* We got a file size report, so we check that there actually is a part of the file left to get, or else we go home. */ - if(data->state.resume_from< 0) { - /* We're supposed to download the last abs(from) bytes */ + if(data->state.resume_from < 0) { + /* We are supposed to download the last abs(from) bytes */ if(filesize < -data->state.resume_from) { - failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T - ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", + failf(data, "Offset (%" FMT_OFF_T + ") was beyond file size (%" FMT_OFF_T ")", data->state.resume_from, filesize); return CURLE_BAD_DOWNLOAD_RESUME; } @@ -2247,8 +2230,8 @@ static CURLcode ftp_state_retr(struct Curl_easy *data, } else { if(filesize < data->state.resume_from) { - failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T - ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", + failf(data, "Offset (%" FMT_OFF_T + ") was beyond file size (%" FMT_OFF_T ")", data->state.resume_from, filesize); return CURLE_BAD_DOWNLOAD_RESUME; } @@ -2259,21 +2242,21 @@ static CURLcode ftp_state_retr(struct Curl_easy *data, if(ftp->downloadsize == 0) { /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); infof(data, "File already completely downloaded"); - /* Set ->transfer so that we won't get any error in ftp_done() - * because we didn't transfer the any file */ + /* Set ->transfer so that we will not get any error in ftp_done() + * because we did not transfer the any file */ ftp->transfer = PPTRANSFER_NONE; ftp_state(data, FTP_STOP); return CURLE_OK; } /* Set resume file transfer offset */ - infof(data, "Instructs server to resume from offset %" - CURL_FORMAT_CURL_OFF_T, data->state.resume_from); + infof(data, "Instructs server to resume from offset %" FMT_OFF_T, + data->state.resume_from); - result = Curl_pp_sendf(data, &ftpc->pp, "REST %" CURL_FORMAT_CURL_OFF_T, + result = Curl_pp_sendf(data, &ftpc->pp, "REST %" FMT_OFF_T, data->state.resume_from); if(!result) ftp_state(data, FTP_RETR_REST); @@ -2294,7 +2277,8 @@ static CURLcode ftp_state_size_resp(struct Curl_easy *data, { CURLcode result = CURLE_OK; curl_off_t filesize = -1; - char *buf = data->state.buffer; + char *buf = Curl_dyn_ptr(&data->conn->proto.ftpc.pp.recvbuf); + size_t len = data->conn->proto.ftpc.pp.nfinal; /* get the size from the ascii string: */ if(ftpcode == 213) { @@ -2302,13 +2286,13 @@ static CURLcode ftp_state_size_resp(struct Curl_easy *data, for all the digits at the end of the response and parse only those as a number. */ char *start = &buf[4]; - char *fdigit = strchr(start, '\r'); + char *fdigit = memchr(start, '\r', len); if(fdigit) { - do + fdigit--; + if(*fdigit == '\n') + fdigit--; + while(ISDIGIT(fdigit[-1]) && (fdigit > start)) fdigit--; - while(ISDIGIT(*fdigit) && (fdigit > start)); - if(!ISDIGIT(*fdigit)) - fdigit++; } else fdigit = start; @@ -2330,8 +2314,8 @@ static CURLcode ftp_state_size_resp(struct Curl_easy *data, if(-1 != filesize) { char clbuf[128]; int clbuflen = msnprintf(clbuf, sizeof(clbuf), - "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", filesize); - result = Curl_client_write(data, CLIENTWRITE_BOTH, clbuf, clbuflen); + "Content-Length: %" FMT_OFF_T "\r\n", filesize); + result = client_write_header(data, clbuf, clbuflen); if(result) return result; } @@ -2365,8 +2349,7 @@ static CURLcode ftp_state_rest_resp(struct Curl_easy *data, #ifdef CURL_FTP_HTTPSTYLE_HEAD if(ftpcode == 350) { char buffer[24]= { "Accept-ranges: bytes\r\n" }; - result = Curl_client_write(data, CLIENTWRITE_BOTH, buffer, - strlen(buffer)); + result = client_write_header(data, buffer, strlen(buffer)); if(result) return result; } @@ -2407,21 +2390,21 @@ static CURLcode ftp_state_stor_resp(struct Curl_easy *data, /* PORT means we are now awaiting the server to connect to us. */ if(data->set.ftp_use_port) { + struct ftp_conn *ftpc = &conn->proto.ftpc; bool connected; ftp_state(data, FTP_STOP); /* no longer in STOR state */ - result = AllowServerConnect(data, &connected); + result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &connected); if(result) return result; if(!connected) { - struct ftp_conn *ftpc = &conn->proto.ftpc; infof(data, "Data conn was not available immediately"); ftpc->wait_data_conn = TRUE; + return ftp_check_ctrl_on_data_wait(data); } - - return CURLE_OK; + ftpc->wait_data_conn = FALSE; } return InitiateTransfer(data); } @@ -2471,14 +2454,14 @@ static CURLcode ftp_state_get_resp(struct Curl_easy *data, !data->set.ignorecl && (ftp->downloadsize < 1)) { /* - * It seems directory listings either don't show the size or very - * often uses size 0 anyway. ASCII transfers may very well turn out - * that the transferred amount of data is not the same as this line - * tells, why using this number in those cases only confuses us. + * It seems directory listings either do not show the size or often uses + * size 0 anyway. ASCII transfers may cause that the transferred amount + * of data is not the same as this line tells, why using this number in + * those cases only confuses us. * * Example D above makes this parsing a little tricky */ char *bytes; - char *buf = data->state.buffer; + char *buf = Curl_dyn_ptr(&conn->proto.ftpc.pp.recvbuf); bytes = strstr(buf, " bytes"); if(bytes) { long in = (long)(--bytes-buf); @@ -2511,44 +2494,43 @@ static CURLcode ftp_state_get_resp(struct Curl_easy *data, else if((instate != FTP_LIST) && (data->state.prefer_ascii)) size = -1; /* kludge for servers that understate ASCII mode file size */ - infof(data, "Maxdownload = %" CURL_FORMAT_CURL_OFF_T, - data->req.maxdownload); + infof(data, "Maxdownload = %" FMT_OFF_T, data->req.maxdownload); if(instate != FTP_LIST) - infof(data, "Getting file with size: %" CURL_FORMAT_CURL_OFF_T, - size); + infof(data, "Getting file with size: %" FMT_OFF_T, size); /* FTP download: */ conn->proto.ftpc.state_saved = instate; conn->proto.ftpc.retr_size_saved = size; if(data->set.ftp_use_port) { + struct ftp_conn *ftpc = &conn->proto.ftpc; bool connected; - result = AllowServerConnect(data, &connected); + result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &connected); if(result) return result; if(!connected) { - struct ftp_conn *ftpc = &conn->proto.ftpc; infof(data, "Data conn was not available immediately"); ftp_state(data, FTP_STOP); ftpc->wait_data_conn = TRUE; + return ftp_check_ctrl_on_data_wait(data); } + ftpc->wait_data_conn = FALSE; } - else - return InitiateTransfer(data); + return InitiateTransfer(data); } else { if((instate == FTP_LIST) && (ftpcode == 450)) { /* simply no matching files in the dir listing */ - ftp->transfer = PPTRANSFER_NONE; /* don't download anything */ + ftp->transfer = PPTRANSFER_NONE; /* do not download anything */ ftp_state(data, FTP_STOP); /* this phase is over */ } else { failf(data, "RETR response: %03d", ftpcode); - return instate == FTP_RETR && ftpcode == 550? - CURLE_REMOTE_FILE_NOT_FOUND: + return instate == FTP_RETR && ftpcode == 550 ? + CURLE_REMOTE_FILE_NOT_FOUND : CURLE_FTP_COULDNT_RETR_FILE; } } @@ -2600,7 +2582,7 @@ static CURLcode ftp_state_user_resp(struct Curl_easy *data, /* 331 Password required for ... (the server requires to send the user's password too) */ result = Curl_pp_sendf(data, &ftpc->pp, "PASS %s", - conn->passwd?conn->passwd:""); + conn->passwd ? conn->passwd : ""); if(!result) ftp_state(data, FTP_PASS); } @@ -2629,7 +2611,7 @@ static CURLcode ftp_state_user_resp(struct Curl_easy *data, if(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER] && !ftpc->ftp_trying_alternative) { - /* Ok, USER failed. Let's try the supplied command. */ + /* Ok, USER failed. Let's try the supplied command. */ result = Curl_pp_sendf(data, &ftpc->pp, "%s", data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]); @@ -2666,7 +2648,6 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, struct connectdata *conn) { CURLcode result; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; int ftpcode; struct ftp_conn *ftpc = &conn->proto.ftpc; struct pingpong *pp = &ftpc->pp; @@ -2676,7 +2657,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, if(pp->sendleft) return Curl_pp_flushsend(data, pp); - result = ftp_readresp(data, sock, pp, &ftpcode, &nread); + result = ftp_readresp(data, FIRSTSOCKET, pp, &ftpcode, &nread); if(result) return result; @@ -2716,7 +2697,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, #endif if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) { - /* We don't have a SSL/TLS control connection yet, but FTPS is + /* We do not have a SSL/TLS control connection yet, but FTPS is requested. Try a FTPS connection now */ ftpc->count3 = 0; @@ -2733,7 +2714,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, default: failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d", (int)data->set.ftpsslauth); - return CURLE_UNKNOWN_OPTION; /* we don't know what to do */ + return CURLE_UNKNOWN_OPTION; /* we do not know what to do */ } result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s", ftpauth[ftpc->count1]); @@ -2747,7 +2728,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, case FTP_AUTH: /* we have gotten the response to a previous AUTH command */ - if(pp->cache_size) + if(pp->overflow) return CURLE_WEIRD_SERVER_REPLY; /* Forbid pipelining in response. */ /* RFC2228 (page 5) says: @@ -2812,7 +2793,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, if(ftpcode/100 == 2) /* We have enabled SSL for the data connection! */ conn->bits.ftp_use_data_ssl = - (data->set.use_ssl != CURLUSESSL_CONTROL) ? TRUE : FALSE; + (data->set.use_ssl != CURLUSESSL_CONTROL); /* FTP servers typically responds with 500 if they decide to reject our 'P' request */ else if(data->set.use_ssl > CURLUSESSL_CONTROL) @@ -2833,7 +2814,13 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, case FTP_CCC: if(ftpcode < 500) { /* First shut down the SSL layer (note: this call will block) */ - result = Curl_ssl_cfilter_remove(data, FIRSTSOCKET); + /* This has only been tested on the proftpd server, and the mod_tls + * code sends a close notify alert without waiting for a close notify + * alert in response. Thus we wait for a close notify alert from the + * server, but we do not send one. Let's hope other servers do + * the same... */ + result = Curl_ssl_cfilter_remove(data, FIRSTSOCKET, + (data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE)); if(result) failf(data, "Failed to clear the command channel (CCC)"); @@ -2845,14 +2832,11 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, case FTP_PWD: if(ftpcode == 257) { - char *ptr = &data->state.buffer[4]; /* start on the first letter */ - const size_t buf_size = data->set.buffer_size; - char *dir; + char *ptr = Curl_dyn_ptr(&pp->recvbuf) + 4; /* start on the first + letter */ bool entry_extracted = FALSE; - - dir = malloc(nread + 1); - if(!dir) - return CURLE_OUT_OF_MEMORY; + struct dynbuf out; + Curl_dyn_init(&out, 1000); /* Reply format is like 257[rubbish]"" and the @@ -2864,33 +2848,30 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, */ /* scan for the first double-quote for non-standard responses */ - while(ptr < &data->state.buffer[buf_size] - && *ptr != '\n' && *ptr != '\0' && *ptr != '"') + while(*ptr != '\n' && *ptr != '\0' && *ptr != '"') ptr++; if('\"' == *ptr) { /* it started good */ - char *store; - ptr++; - for(store = dir; *ptr;) { + for(ptr++; *ptr; ptr++) { if('\"' == *ptr) { if('\"' == ptr[1]) { /* "quote-doubling" */ - *store = ptr[1]; + result = Curl_dyn_addn(&out, &ptr[1], 1); ptr++; } else { /* end of path */ - entry_extracted = TRUE; + if(Curl_dyn_len(&out)) + entry_extracted = TRUE; break; /* get out of this loop */ } } else - *store = *ptr; - store++; - ptr++; + result = Curl_dyn_addn(&out, ptr, 1); + if(result) + return result; } - *store = '\0'; /* null-terminate */ } if(entry_extracted) { /* If the path name does not look like an absolute path (i.e.: it @@ -2904,6 +2885,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, The method used here is to check the server OS: we do it only if the path name looks strange to minimize overhead on other systems. */ + char *dir = Curl_dyn_ptr(&out); if(!ftpc->server_os && dir[0] != '/') { result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST"); @@ -2927,36 +2909,34 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, data->state.most_recent_ftp_entrypath = ftpc->entrypath; } else { - /* couldn't get the path */ - free(dir); + /* could not get the path */ + Curl_dyn_free(&out); infof(data, "Failed to figure out path"); } } ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE")); + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_DSTATE(data)); break; case FTP_SYST: if(ftpcode == 215) { - char *ptr = &data->state.buffer[4]; /* start on the first letter */ + char *ptr = Curl_dyn_ptr(&pp->recvbuf) + 4; /* start on the first + letter */ char *os; - char *store; - - os = malloc(nread + 1); - if(!os) - return CURLE_OUT_OF_MEMORY; + char *start; /* Reply format is like 215 */ while(*ptr == ' ') ptr++; - for(store = os; *ptr && *ptr != ' ';) - *store++ = *ptr++; - *store = '\0'; /* null-terminate */ + for(start = ptr; *ptr && *ptr != ' '; ptr++) + ; + os = Curl_memdup0(start, ptr - start); + if(!os) + return CURLE_OUT_OF_MEMORY; /* Check for special servers here. */ - if(strcasecompare(os, "OS/400")) { /* Force OS400 name format 1. */ result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1"); @@ -2980,7 +2960,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, } ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE")); + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_DSTATE(data)); break; case FTP_NAMEFMT: @@ -2991,7 +2971,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, } ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE")); + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_DSTATE(data)); break; case FTP_QUOTE: @@ -3028,7 +3008,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, else { /* return failure */ failf(data, "Server denied you to change to the given directory"); - ftpc->cwdfail = TRUE; /* don't remember this path as we failed + ftpc->cwdfail = TRUE; /* do not remember this path as we failed to enter it */ result = CURLE_REMOTE_ACCESS_DENIED; } @@ -3108,7 +3088,6 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, break; case FTP_QUIT: - /* fallthrough, just stop! */ default: /* internal error */ ftp_state(data, FTP_STOP); @@ -3131,7 +3110,7 @@ static CURLcode ftp_multi_statemach(struct Curl_easy *data, /* Check for the state outside of the Curl_socket_check() return code checks since at times we are in fact already in this state when this function gets called. */ - *done = (ftpc->state == FTP_STOP) ? TRUE : FALSE; + *done = (ftpc->state == FTP_STOP); return result; } @@ -3183,8 +3162,7 @@ static CURLcode ftp_connect(struct Curl_easy *data, conn->bits.ftp_use_control_ssl = TRUE; } - Curl_pp_setup(pp); /* once per transfer */ - Curl_pp_init(data, pp); /* init the generic pingpong data */ + Curl_pp_init(pp); /* once per transfer */ /* When we connect, we start in the state where we await the 220 response */ @@ -3235,14 +3213,13 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, case CURLE_REMOTE_FILE_NOT_FOUND: case CURLE_WRITE_ERROR: /* the connection stays alive fine even though this happened */ - /* fall-through */ - case CURLE_OK: /* doesn't affect the control connection's status */ + case CURLE_OK: /* does not affect the control connection's status */ if(!premature) break; /* until we cope better with prematurely ended requests, let them * fallback as if in complete failure */ - /* FALLTHROUGH */ + FALLTHROUGH(); default: /* by default, an error means the control connection is wedged and should not be used anymore */ ftpc->ctl_valid = FALSE; @@ -3255,9 +3232,9 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, if(data->state.wildcardmatch) { if(data->set.chunk_end && ftpc->file) { - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); data->set.chunk_end(data->set.wildcardptr); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); } ftpc->known_filesize = -1; } @@ -3284,7 +3261,8 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, if(data->set.ftp_filemethod == FTPFILE_NOCWD) pathLen = 0; /* relative path => working directory is FTP home */ else - pathLen -= ftpc->file?strlen(ftpc->file):0; /* file is url-decoded */ + /* file is url-decoded */ + pathLen -= ftpc->file ? strlen(ftpc->file) : 0; rawPath[pathLen] = '\0'; ftpc->prevpath = rawPath; @@ -3302,7 +3280,7 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, /* free the dir tree and file parts */ freedirs(ftpc); - /* shut down the socket to inform the server we're done */ + /* shut down the socket to inform the server we are done */ #ifdef _WIN32_WCE shutdown(conn->sock[SECONDARYSOCKET], 2); /* SD_BOTH */ @@ -3320,7 +3298,7 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, } } - close_secondarysocket(data, conn); + close_secondarysocket(data); } if(!result && (ftp->transfer == PPTRANSFER_BODY) && ftpc->ctl_valid && @@ -3386,8 +3364,8 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, (data->state.infilesize != data->req.writebytecount) && !data->set.crlf && (ftp->transfer == PPTRANSFER_BODY)) { - failf(data, "Uploaded unaligned file size (%" CURL_FORMAT_CURL_OFF_T - " out of %" CURL_FORMAT_CURL_OFF_T " bytes)", + failf(data, "Uploaded unaligned file size (%" FMT_OFF_T + " out of %" FMT_OFF_T " bytes)", data->req.writebytecount, data->state.infilesize); result = CURLE_PARTIAL_FILE; } @@ -3395,22 +3373,14 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, else { if((-1 != data->req.size) && (data->req.size != data->req.bytecount) && -#ifdef CURL_DO_LINEEND_CONV - /* Most FTP servers don't adjust their file SIZE response for CRLFs, so - * we'll check to see if the discrepancy can be explained by the number - * of CRLFs we've changed to LFs. - */ - ((data->req.size + data->state.crlf_conversions) != - data->req.bytecount) && -#endif /* CURL_DO_LINEEND_CONV */ (data->req.maxdownload != data->req.bytecount)) { - failf(data, "Received only partial file: %" CURL_FORMAT_CURL_OFF_T - " bytes", data->req.bytecount); + failf(data, "Received only partial file: %" FMT_OFF_T " bytes", + data->req.bytecount); result = CURLE_PARTIAL_FILE; } else if(!ftpc->dont_check && !data->req.bytecount && - (data->req.size>0)) { + (data->req.size > 0)) { failf(data, "No data was received"); result = CURLE_FTP_COULDNT_RETR_FILE; } @@ -3423,6 +3393,7 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, /* Send any post-transfer QUOTE strings? */ if(!status && !result && !premature && data->set.postquote) result = ftp_sendquote(data, conn, data->set.postquote); + CURL_TRC_FTP(data, "[%s] done, result=%d", FTP_DSTATE(data), result); Curl_safefree(ftp->pathalloc); return result; } @@ -3457,7 +3428,7 @@ CURLcode ftp_sendquote(struct Curl_easy *data, /* if a command starts with an asterisk, which a legal FTP command never can, the command will be allowed to fail without it causing any aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server reponds. */ + is successful, whatever the server responds. */ if(cmd[0] == '*') { cmd++; @@ -3493,7 +3464,7 @@ CURLcode ftp_sendquote(struct Curl_easy *data, static int ftp_need_type(struct connectdata *conn, bool ascii_wanted) { - return conn->proto.ftpc.transfertype != (ascii_wanted?'A':'I'); + return conn->proto.ftpc.transfertype != (ascii_wanted ? 'A' : 'I'); } /*********************************************************************** @@ -3510,7 +3481,7 @@ static CURLcode ftp_nb_type(struct Curl_easy *data, { struct ftp_conn *ftpc = &conn->proto.ftpc; CURLcode result; - char want = (char)(ascii?'A':'I'); + char want = (char)(ascii ? 'A' : 'I'); if(ftpc->transfertype == want) { ftp_state(data, newstate); @@ -3532,7 +3503,7 @@ static CURLcode ftp_nb_type(struct Curl_easy *data, * ftp_pasv_verbose() * * This function only outputs some informationals about this second connection - * when we've issued a PASV command before and thus we have connected to a + * when we have issued a PASV command before and thus we have connected to a * possibly new IP address. * */ @@ -3540,7 +3511,7 @@ static CURLcode ftp_nb_type(struct Curl_easy *data, static void ftp_pasv_verbose(struct Curl_easy *data, struct Curl_addrinfo *ai, - char *newhost, /* ascii version */ + char *newhost, /* ASCII version */ int port) { char buf[256]; @@ -3573,20 +3544,25 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) * complete */ struct FTP *ftp = NULL; - /* if the second connection isn't done yet, wait for it to have - * connected to the remote host. When using proxy tunneling, this - * means the tunnel needs to have been establish. However, we - * can not expect the remote host to talk to us in any way yet. - * So, when using ftps: the SSL handshake will not start until we - * tell the remote server that we are there. */ + /* if the second connection has been set up, try to connect it fully + * to the remote host. This may not complete at this time, for several + * reasons: + * - we do EPTR and the server will not connect to our listen socket + * until we send more FTP commands + * - an SSL filter is in place and the server will not start the TLS + * handshake until we send more FTP commands + */ if(conn->cfilter[SECONDARYSOCKET]) { + bool is_eptr = Curl_conn_is_tcp_listen(data, SECONDARYSOCKET); result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &connected); - if(result || !Curl_conn_is_ip_connected(data, SECONDARYSOCKET)) { - if(result && (ftpc->count1 == 0)) { + if(result || (!connected && !is_eptr && + !Curl_conn_is_ip_connected(data, SECONDARYSOCKET))) { + if(result && !is_eptr && (ftpc->count1 == 0)) { *completep = -1; /* go back to DOING please */ /* this is a EPSV connect failing, try PASV instead */ return ftp_epsv_disable(data, conn); } + *completep = (int)complete; return result; } } @@ -3601,34 +3577,32 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) *completep = (int)complete; - /* if we got an error or if we don't wait for a data connection return + /* if we got an error or if we do not wait for a data connection return immediately */ if(result || !ftpc->wait_data_conn) return result; /* if we reach the end of the FTP state machine here, *complete will be TRUE but so is ftpc->wait_data_conn, which says we need to wait for the - data connection and therefore we're not actually complete */ + data connection and therefore we are not actually complete */ *completep = 0; } if(ftp->transfer <= PPTRANSFER_INFO) { - /* a transfer is about to take place, or if not a file name was given - so we'll do a SIZE on it later and then we need the right TYPE first */ + /* a transfer is about to take place, or if not a filename was given so we + will do a SIZE on it later and then we need the right TYPE first */ if(ftpc->wait_data_conn) { bool serv_conned; - result = ReceivedServerConnect(data, &serv_conned); + result = Curl_conn_connect(data, SECONDARYSOCKET, TRUE, &serv_conned); if(result) return result; /* Failed to accept data connection */ if(serv_conned) { /* It looks data connection is established */ - result = AcceptServerConnect(data); ftpc->wait_data_conn = FALSE; - if(!result) - result = InitiateTransfer(data); + result = InitiateTransfer(data); if(result) return result; @@ -3636,6 +3610,11 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) *completep = 1; /* this state is now complete when the server has connected back to us */ } + else { + result = ftp_check_ctrl_on_data_wait(data); + if(result) + return result; + } } else if(data->state.upload) { result = ftp_nb_type(data, conn, data->state.prefer_ascii, @@ -3653,7 +3632,7 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) result = Curl_range(data); if(result == CURLE_OK && data->req.maxdownload >= 0) { - /* Don't check for successful transfer */ + /* Do not check for successful transfer */ ftpc->dont_check = TRUE; } @@ -3686,12 +3665,13 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) } /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); if(!ftpc->wait_data_conn) { /* no waiting for the data connection so this is now complete */ *completep = 1; - DEBUGF(infof(data, "DO-MORE phase ends with %d", (int)result)); + CURL_TRC_FTP(data, "[%s] DO-MORE phase ends with %d", FTP_DSTATE(data), + (int)result); } return result; @@ -3715,7 +3695,7 @@ CURLcode ftp_perform(struct Curl_easy *data, /* this is FTP and no proxy */ CURLcode result = CURLE_OK; - DEBUGF(infof(data, "DO phase starts")); + CURL_TRC_FTP(data, "[%s] DO phase starts", FTP_DSTATE(data)); if(data->req.no_body) { /* requested no body means no transfer... */ @@ -3735,10 +3715,15 @@ CURLcode ftp_perform(struct Curl_easy *data, *connected = Curl_conn_is_connected(data->conn, SECONDARYSOCKET); - infof(data, "ftp_perform ends with SECONDARY: %d", *connected); + if(*connected) + infof(data, "[FTP] [%s] perform, DATA connection established", + FTP_DSTATE(data)); + else + CURL_TRC_FTP(data, "[%s] perform, awaiting DATA connect", + FTP_DSTATE(data)); if(*dophase_done) - DEBUGF(infof(data, "DO phase is complete1")); + CURL_TRC_FTP(data, "[%s] DO phase is complete1", FTP_DSTATE(data)); return result; } @@ -3765,8 +3750,7 @@ static CURLcode init_wc_data(struct Curl_easy *data) last_slash++; if(last_slash[0] == '\0') { wildcard->state = CURLWC_CLEAN; - result = ftp_parse_url_path(data); - return result; + return ftp_parse_url_path(data); } wildcard->pattern = strdup(last_slash); if(!wildcard->pattern) @@ -3782,8 +3766,7 @@ static CURLcode init_wc_data(struct Curl_easy *data) } else { /* only list */ wildcard->state = CURLWC_CLEAN; - result = ftp_parse_url_path(data); - return result; + return ftp_parse_url_path(data); } } @@ -3811,7 +3794,7 @@ static CURLcode init_wc_data(struct Curl_easy *data) if(data->set.ftp_filemethod == FTPFILE_NOCWD) data->set.ftp_filemethod = FTPFILE_MULTICWD; - /* try to parse ftp url */ + /* try to parse ftp URL */ result = ftp_parse_url_path(data); if(result) { goto fail; @@ -3877,7 +3860,7 @@ static CURLcode wc_statemach(struct Curl_easy *data) wildcard->state = CURLWC_CLEAN; continue; } - if(wildcard->filelist.size == 0) { + if(Curl_llist_count(&wildcard->filelist) == 0) { /* no corresponding file */ wildcard->state = CURLWC_CLEAN; return CURLE_REMOTE_FILE_NOT_FOUND; @@ -3888,7 +3871,8 @@ static CURLcode wc_statemach(struct Curl_easy *data) case CURLWC_DOWNLOADING: { /* filelist has at least one file, lets get first one */ struct ftp_conn *ftpc = &conn->proto.ftpc; - struct curl_fileinfo *finfo = wildcard->filelist.head->ptr; + struct Curl_llist_node *head = Curl_llist_head(&wildcard->filelist); + struct curl_fileinfo *finfo = Curl_node_elem(head); struct FTP *ftp = data->req.p.ftp; char *tmp_path = aprintf("%s%s", wildcard->path, finfo->filename); @@ -3902,10 +3886,11 @@ static CURLcode wc_statemach(struct Curl_easy *data) infof(data, "Wildcard - START of \"%s\"", finfo->filename); if(data->set.chunk_bgn) { long userresponse; - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); userresponse = data->set.chunk_bgn( - finfo, data->set.wildcardptr, (int)wildcard->filelist.size); - Curl_set_in_callback(data, false); + finfo, data->set.wildcardptr, + (int)Curl_llist_count(&wildcard->filelist)); + Curl_set_in_callback(data, FALSE); switch(userresponse) { case CURL_CHUNK_BGN_FUNC_SKIP: infof(data, "Wildcard - \"%s\" skipped by user", @@ -3929,10 +3914,11 @@ static CURLcode wc_statemach(struct Curl_easy *data) if(result) return result; - /* we don't need the Curl_fileinfo of first file anymore */ - Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL); + /* we do not need the Curl_fileinfo of first file anymore */ + Curl_node_remove(Curl_llist_head(&wildcard->filelist)); - if(wildcard->filelist.size == 0) { /* remains only one file to down. */ + if(Curl_llist_count(&wildcard->filelist) == 0) { + /* remains only one file to down. */ wildcard->state = CURLWC_CLEAN; /* after that will be ftp_do called once again and no transfer will be done because of CURLWC_CLEAN state */ @@ -3943,12 +3929,12 @@ static CURLcode wc_statemach(struct Curl_easy *data) case CURLWC_SKIP: { if(data->set.chunk_end) { - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); data->set.chunk_end(data->set.wildcardptr); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); } - Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL); - wildcard->state = (wildcard->filelist.size == 0) ? + Curl_node_remove(Curl_llist_head(&wildcard->filelist)); + wildcard->state = (Curl_llist_count(&wildcard->filelist) == 0) ? CURLWC_CLEAN : CURLWC_DOWNLOADING; continue; } @@ -3994,6 +3980,24 @@ static CURLcode ftp_do(struct Curl_easy *data, bool *done) *done = FALSE; /* default to false */ ftpc->wait_data_conn = FALSE; /* default to no such wait */ +#ifdef CURL_PREFER_LF_LINEENDS + { + /* FTP data may need conversion. */ + struct Curl_cwriter *ftp_lc_writer; + + result = Curl_cwriter_create(&ftp_lc_writer, data, &ftp_cw_lc, + CURL_CW_CONTENT_DECODE); + if(result) + return result; + + result = Curl_cwriter_add(data, ftp_lc_writer); + if(result) { + Curl_cwriter_free(data, ftp_lc_writer); + return result; + } + } +#endif /* CURL_PREFER_LF_LINEENDS */ + if(data->state.wildcardmatch) { result = wc_statemach(data); if(data->wildcard->state == CURLWC_SKIP || @@ -4066,7 +4070,7 @@ static CURLcode ftp_disconnect(struct Curl_easy *data, bad in any way, sending quit and waiting around here will make the disconnect wait in vain and cause more problems than we need to. - ftp_quit() will check the state of ftp->ctl_valid. If it's ok it + ftp_quit() will check the state of ftp->ctl_valid. If it is ok it will try to send the QUIT command, otherwise it will just return. */ if(dead_connection) @@ -4154,18 +4158,17 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data) return CURLE_OUT_OF_MEMORY; } - ftpc->dirs[0] = calloc(1, dirlen + 1); + ftpc->dirs[0] = Curl_memdup0(rawPath, dirlen); if(!ftpc->dirs[0]) { free(rawPath); return CURLE_OUT_OF_MEMORY; } - strncpy(ftpc->dirs[0], rawPath, dirlen); ftpc->dirdepth = 1; /* we consider it to be a single dir */ - fileName = slashPos + 1; /* rest is file name */ + fileName = slashPos + 1; /* rest is filename */ } else - fileName = rawPath; /* file name only (or empty) */ + fileName = rawPath; /* filename only (or empty) */ break; default: /* allow pretty much anything */ @@ -4196,22 +4199,21 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data) ++compLen; /* we skip empty path components, like "x//y" since the FTP command - CWD requires a parameter and a non-existent parameter a) doesn't + CWD requires a parameter and a non-existent parameter a) does not work on many servers and b) has no effect on the others. */ if(compLen > 0) { - char *comp = calloc(1, compLen + 1); + char *comp = Curl_memdup0(curPos, compLen); if(!comp) { free(rawPath); return CURLE_OUT_OF_MEMORY; } - strncpy(comp, curPos, compLen); ftpc->dirs[ftpc->dirdepth++] = comp; } curPos = slashPos + 1; } } DEBUGASSERT((size_t)ftpc->dirdepth <= dirAlloc); - fileName = curPos; /* the rest is the file name (or empty) */ + fileName = curPos; /* the rest is the filename (or empty) */ } break; } /* switch */ @@ -4223,8 +4225,8 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data) we make it a NULL pointer */ if(data->state.upload && !ftpc->file && (ftp->transfer == PPTRANSFER_BODY)) { - /* We need a file name when uploading. Return error! */ - failf(data, "Uploading to a URL without a file name"); + /* We need a filename when uploading. Return error! */ + failf(data, "Uploading to a URL without a filename"); free(rawPath); return CURLE_URL_MALFORMAT; } @@ -4240,7 +4242,7 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data) if(data->set.ftp_filemethod == FTPFILE_NOCWD) n = 0; /* CWD to entry for relative paths */ else - n -= ftpc->file?strlen(ftpc->file):0; + n -= ftpc->file ? strlen(ftpc->file) : 0; if((strlen(oldPath) == n) && !strncmp(rawPath, oldPath, n)) { infof(data, "Request has same path as previous transfer"); @@ -4265,16 +4267,16 @@ static CURLcode ftp_dophase_done(struct Curl_easy *data, bool connected) CURLcode result = ftp_do_more(data, &completed); if(result) { - close_secondarysocket(data, conn); + close_secondarysocket(data); return result; } } if(ftp->transfer != PPTRANSFER_BODY) /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); else if(!connected) - /* since we didn't connect now, we want do_more to get called */ + /* since we did not connect now, we want do_more to get called */ conn->bits.do_more = TRUE; ftpc->ctl_valid = TRUE; /* seems good */ @@ -4289,11 +4291,11 @@ static CURLcode ftp_doing(struct Curl_easy *data, CURLcode result = ftp_multi_statemach(data, dophase_done); if(result) - DEBUGF(infof(data, "DO phase failed")); + CURL_TRC_FTP(data, "[%s] DO phase failed", FTP_DSTATE(data)); else if(*dophase_done) { result = ftp_dophase_done(data, FALSE /* not connected */); - DEBUGF(infof(data, "DO phase is complete2")); + CURL_TRC_FTP(data, "[%s] DO phase is complete2", FTP_DSTATE(data)); } return result; } @@ -4356,7 +4358,7 @@ static CURLcode ftp_setup_connection(struct Curl_easy *data, CURLcode result = CURLE_OK; struct ftp_conn *ftpc = &conn->proto.ftpc; - ftp = calloc(sizeof(struct FTP), 1); + ftp = calloc(1, sizeof(struct FTP)); if(!ftp) return CURLE_OUT_OF_MEMORY; @@ -4379,10 +4381,10 @@ static CURLcode ftp_setup_connection(struct Curl_easy *data, } data->req.p.ftp = ftp; - ftp->path = &data->state.up.path[1]; /* don't include the initial slash */ + ftp->path = &data->state.up.path[1]; /* do not include the initial slash */ /* FTP URLs support an extension like ";type=" that - * we'll try to get now! */ + * we will try to get now! */ type = strstr(ftp->path, ";type="); if(!type) @@ -4417,6 +4419,7 @@ static CURLcode ftp_setup_connection(struct Curl_easy *data, ftpc->use_ssl = data->set.use_ssl; ftpc->ccc = data->set.ftp_ccc; + CURL_TRC_FTP(data, "[%s] setup connection -> %d", FTP_CSTATE(conn), result); return result; } diff --git a/deps/curl/lib/ftp.h b/deps/curl/lib/ftp.h index 977fc883b17..3d0af015878 100644 --- a/deps/curl/lib/ftp.h +++ b/deps/curl/lib/ftp.h @@ -61,7 +61,7 @@ enum { FTP_STOR_PREQUOTE, FTP_POSTQUOTE, FTP_CWD, /* change dir */ - FTP_MKD, /* if the dir didn't exist */ + FTP_MKD, /* if the dir did not exist */ FTP_MDTM, /* to figure out the datestamp */ FTP_TYPE, /* to set type when doing a head-like request */ FTP_LIST_TYPE, /* set type when about to do a dir list */ @@ -123,7 +123,7 @@ struct ftp_conn { char *account; char *alternative_to_user; char *entrypath; /* the PWD reply when we logged on */ - char *file; /* url-decoded file name (or path) */ + char *file; /* url-decoded filename (or path) */ char **dirs; /* realloc()ed array for path components */ char *newhost; char *prevpath; /* url-decoded conn->path from the previous transfer */ @@ -139,7 +139,7 @@ struct ftp_conn { int count1; /* general purpose counter for the state machine */ int count2; /* general purpose counter for the state machine */ int count3; /* general purpose counter for the state machine */ - /* newhost is the (allocated) IP addr or host name to connect the data + /* newhost is the (allocated) IP addr or hostname to connect the data connection to */ unsigned short newport; ftpstate state; /* always use ftp.c:state() to change state! */ diff --git a/deps/curl/lib/ftplistparser.c b/deps/curl/lib/ftplistparser.c index 226d9bc033d..3088470abd3 100644 --- a/deps/curl/lib/ftplistparser.c +++ b/deps/curl/lib/ftplistparser.c @@ -55,9 +55,6 @@ /* The last #include file should be: */ #include "memdebug.h" -/* allocs buffer which will contain one line of LIST command response */ -#define FTP_BUFFER_ALLOCSIZE 160 - typedef enum { PL_UNIX_TOTALSIZE = 0, PL_UNIX_FILETYPE, @@ -337,7 +334,7 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, compare = Curl_fnmatch; /* filter pattern-corresponding filenames */ - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); if(compare(data->set.fnmatch_data, wc->pattern, finfo->filename) == 0) { /* discard symlink which is containing multiple " -> " */ @@ -349,10 +346,10 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, else { add = FALSE; } - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); if(add) { - Curl_llist_insert_next(llist, llist->tail, finfo, &infop->list); + Curl_llist_append(llist, finfo, &infop->list); } else { Curl_fileinfo_cleanup(infop); @@ -379,7 +376,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, /* scenario: * 1. call => OK.. * 2. call => OUT_OF_MEMORY (or other error) - * 3. (last) call => is skipped RIGHT HERE and the error is hadled later + * 3. (last) call => is skipped RIGHT HERE and the error is handled later * in wc_statemach() */ goto fail; diff --git a/deps/curl/lib/functypes.h b/deps/curl/lib/functypes.h index 075c02e54f6..ea66d3281d9 100644 --- a/deps/curl/lib/functypes.h +++ b/deps/curl/lib/functypes.h @@ -38,7 +38,7 @@ 2. For systems with config-*.h files, define them there. */ -#ifdef WIN32 +#ifdef _WIN32 /* int recv(SOCKET, char *, int, int) */ #define RECV_TYPE_ARG1 SOCKET #define RECV_TYPE_ARG2 char * diff --git a/deps/curl/lib/getenv.c b/deps/curl/lib/getenv.c index 80697847288..63eaeda0f26 100644 --- a/deps/curl/lib/getenv.c +++ b/deps/curl/lib/getenv.c @@ -31,12 +31,13 @@ static char *GetEnv(const char *variable) { -#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_APP) +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) || \ + defined(__ORBIS__) || defined(__PROSPERO__) /* PlayStation 4 and 5 */ (void)variable; return NULL; -#elif defined(WIN32) +#elif defined(_WIN32) /* This uses Windows API instead of C runtime getenv() to get the environment - variable since some changes aren't always visible to the latter. #4774 */ + variable since some changes are not always visible to the latter. #4774 */ char *buf = NULL; char *tmp; DWORD bufsize; @@ -53,8 +54,8 @@ static char *GetEnv(const char *variable) buf = tmp; bufsize = rc; - /* It's possible for rc to be 0 if the variable was found but empty. - Since getenv doesn't make that distinction we ignore it as well. */ + /* it is possible for rc to be 0 if the variable was found but empty. + Since getenv does not make that distinction we ignore it as well. */ rc = GetEnvironmentVariableA(variable, buf, bufsize); if(!rc || rc == bufsize || rc > max) { free(buf); @@ -69,7 +70,7 @@ static char *GetEnv(const char *variable) } #else char *env = getenv(variable); - return (env && env[0])?strdup(env):NULL; + return (env && env[0]) ? strdup(env) : NULL; #endif } diff --git a/deps/curl/lib/getinfo.c b/deps/curl/lib/getinfo.c index f1574e097b1..9144ad715df 100644 --- a/deps/curl/lib/getinfo.c +++ b/deps/curl/lib/getinfo.c @@ -53,10 +53,11 @@ CURLcode Curl_initinfo(struct Curl_easy *data) pro->t_connect = 0; pro->t_appconnect = 0; pro->t_pretransfer = 0; + pro->t_posttransfer = 0; pro->t_starttransfer = 0; pro->timespent = 0; pro->t_redirect = 0; - pro->is_t_startransfer_set = false; + pro->is_t_startransfer_set = FALSE; info->httpcode = 0; info->httpproxycode = 0; @@ -76,10 +77,9 @@ CURLcode Curl_initinfo(struct Curl_easy *data) free(info->wouldredirect); info->wouldredirect = NULL; - info->conn_primary_ip[0] = '\0'; - info->conn_local_ip[0] = '\0'; - info->conn_primary_port = 0; - info->conn_local_port = 0; + memset(&info->primary, 0, sizeof(info->primary)); + info->primary.remote_port = -1; + info->primary.local_port = -1; info->retry_after = 0; info->conn_scheme = 0; @@ -96,7 +96,7 @@ static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, { switch(info) { case CURLINFO_EFFECTIVE_URL: - *param_charp = data->state.url?data->state.url:(char *)""; + *param_charp = data->state.url ? data->state.url : (char *)""; break; case CURLINFO_EFFECTIVE_METHOD: { const char *m = data->set.str[STRING_CUSTOMREQUEST]; @@ -153,15 +153,19 @@ static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, break; case CURLINFO_PRIMARY_IP: /* Return the ip address of the most recent (primary) connection */ - *param_charp = data->info.conn_primary_ip; + *param_charp = data->info.primary.remote_ip; break; case CURLINFO_LOCAL_IP: /* Return the source/local ip address of the most recent (primary) connection */ - *param_charp = data->info.conn_local_ip; + *param_charp = data->info.primary.local_ip; break; case CURLINFO_RTSP_SESSION_ID: +#ifndef CURL_DISABLE_RTSP *param_charp = data->set.str[STRING_RTSP_SESSION_ID]; +#else + *param_charp = NULL; +#endif break; case CURLINFO_SCHEME: *param_charp = data->info.conn_scheme; @@ -180,7 +184,6 @@ static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, *param_charp = NULL; #endif break; - default: return CURLE_UNKNOWN_OPTION; } @@ -201,7 +204,7 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, #ifdef DEBUGBUILD char *timestr = getenv("CURL_TIME"); if(timestr) { - unsigned long val = strtol(timestr, NULL, 10); + unsigned long val = strtoul(timestr, NULL, 10); switch(info) { case CURLINFO_LOCAL_PORT: *param_longp = (long)val; @@ -213,7 +216,7 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, /* use another variable for this to allow different values */ timestr = getenv("CURL_DEBUG_SIZE"); if(timestr) { - unsigned long val = strtol(timestr, NULL, 10); + unsigned long val = strtoul(timestr, NULL, 10); switch(info) { case CURLINFO_HEADER_SIZE: case CURLINFO_REQUEST_SIZE: @@ -249,11 +252,13 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, case CURLINFO_SSL_VERIFYRESULT: *param_longp = data->set.ssl.certverifyresult; break; -#ifndef CURL_DISABLE_PROXY case CURLINFO_PROXY_SSL_VERIFYRESULT: +#ifndef CURL_DISABLE_PROXY *param_longp = data->set.proxy_ssl.certverifyresult; - break; +#else + *param_longp = 0; #endif + break; case CURLINFO_REDIRECT_COUNT: *param_longp = data->state.followlocation; break; @@ -274,8 +279,8 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, case CURLINFO_LASTSOCKET: sockfd = Curl_getconnectinfo(data, NULL); - /* note: this is not a good conversion for systems with 64 bit sockets and - 32 bit longs */ + /* note: this is not a good conversion for systems with 64-bit sockets and + 32-bit longs */ if(sockfd != CURL_SOCKET_BAD) *param_longp = (long)sockfd; else @@ -285,11 +290,11 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, break; case CURLINFO_PRIMARY_PORT: /* Return the (remote) port of the most recent (primary) connection */ - *param_longp = data->info.conn_primary_port; + *param_longp = data->info.primary.remote_port; break; case CURLINFO_LOCAL_PORT: /* Return the local port of the most recent (primary) connection */ - *param_longp = data->info.conn_local_port; + *param_longp = data->info.primary.local_port; break; case CURLINFO_PROXY_ERROR: *param_longp = (long)data->info.pxcode; @@ -311,6 +316,12 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, case CURLINFO_RTSP_CSEQ_RECV: *param_longp = data->state.rtsp_CSeq_recv; break; +#else + case CURLINFO_RTSP_CLIENT_CSEQ: + case CURLINFO_RTSP_SERVER_CSEQ: + case CURLINFO_RTSP_CSEQ_RECV: + *param_longp = 0; + break; #endif case CURLINFO_HTTP_VERSION: switch(data->info.httpversion) { @@ -332,7 +343,16 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, } break; case CURLINFO_PROTOCOL: - *param_longp = data->info.conn_protocol; + *param_longp = (long)data->info.conn_protocol; + break; + case CURLINFO_USED_PROXY: + *param_longp = +#ifdef CURL_DISABLE_PROXY + 0 +#else + data->info.used_proxy +#endif + ; break; default: return CURLE_UNKNOWN_OPTION; @@ -349,13 +369,14 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, #ifdef DEBUGBUILD char *timestr = getenv("CURL_TIME"); if(timestr) { - unsigned long val = strtol(timestr, NULL, 10); + unsigned long val = strtoul(timestr, NULL, 10); switch(info) { case CURLINFO_TOTAL_TIME_T: case CURLINFO_NAMELOOKUP_TIME_T: case CURLINFO_CONNECT_TIME_T: case CURLINFO_APPCONNECT_TIME_T: case CURLINFO_PRETRANSFER_TIME_T: + case CURLINFO_POSTTRANSFER_TIME_T: case CURLINFO_STARTTRANSFER_TIME_T: case CURLINFO_REDIRECT_TIME_T: case CURLINFO_SPEED_DOWNLOAD_T: @@ -372,24 +393,24 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, *param_offt = (curl_off_t)data->info.filetime; break; case CURLINFO_SIZE_UPLOAD_T: - *param_offt = data->progress.uploaded; + *param_offt = data->progress.ul.cur_size; break; case CURLINFO_SIZE_DOWNLOAD_T: - *param_offt = data->progress.downloaded; + *param_offt = data->progress.dl.cur_size; break; case CURLINFO_SPEED_DOWNLOAD_T: - *param_offt = data->progress.dlspeed; + *param_offt = data->progress.dl.speed; break; case CURLINFO_SPEED_UPLOAD_T: - *param_offt = data->progress.ulspeed; + *param_offt = data->progress.ul.speed; break; case CURLINFO_CONTENT_LENGTH_DOWNLOAD_T: - *param_offt = (data->progress.flags & PGRS_DL_SIZE_KNOWN)? - data->progress.size_dl:-1; + *param_offt = (data->progress.flags & PGRS_DL_SIZE_KNOWN) ? + data->progress.dl.total_size : -1; break; case CURLINFO_CONTENT_LENGTH_UPLOAD_T: - *param_offt = (data->progress.flags & PGRS_UL_SIZE_KNOWN)? - data->progress.size_ul:-1; + *param_offt = (data->progress.flags & PGRS_UL_SIZE_KNOWN) ? + data->progress.ul.total_size : -1; break; case CURLINFO_TOTAL_TIME_T: *param_offt = data->progress.timespent; @@ -406,9 +427,15 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, case CURLINFO_PRETRANSFER_TIME_T: *param_offt = data->progress.t_pretransfer; break; + case CURLINFO_POSTTRANSFER_TIME_T: + *param_offt = data->progress.t_posttransfer; + break; case CURLINFO_STARTTRANSFER_TIME_T: *param_offt = data->progress.t_starttransfer; break; + case CURLINFO_QUEUE_TIME_T: + *param_offt = data->progress.t_postqueue; + break; case CURLINFO_REDIRECT_TIME_T: *param_offt = data->progress.t_redirect; break; @@ -419,8 +446,11 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, *param_offt = data->id; break; case CURLINFO_CONN_ID: - *param_offt = data->conn? - data->conn->connection_id : data->state.recent_conn_id; + *param_offt = data->conn ? + data->conn->connection_id : data->state.recent_conn_id; + break; + case CURLINFO_EARLYDATA_SENT_T: + *param_offt = data->progress.earlydata_sent; break; default: return CURLE_UNKNOWN_OPTION; @@ -435,7 +465,7 @@ static CURLcode getinfo_double(struct Curl_easy *data, CURLINFO info, #ifdef DEBUGBUILD char *timestr = getenv("CURL_TIME"); if(timestr) { - unsigned long val = strtol(timestr, NULL, 10); + unsigned long val = strtoul(timestr, NULL, 10); switch(info) { case CURLINFO_TOTAL_TIME: case CURLINFO_NAMELOOKUP_TIME: @@ -473,24 +503,24 @@ static CURLcode getinfo_double(struct Curl_easy *data, CURLINFO info, *param_doublep = DOUBLE_SECS(data->progress.t_starttransfer); break; case CURLINFO_SIZE_UPLOAD: - *param_doublep = (double)data->progress.uploaded; + *param_doublep = (double)data->progress.ul.cur_size; break; case CURLINFO_SIZE_DOWNLOAD: - *param_doublep = (double)data->progress.downloaded; + *param_doublep = (double)data->progress.dl.cur_size; break; case CURLINFO_SPEED_DOWNLOAD: - *param_doublep = (double)data->progress.dlspeed; + *param_doublep = (double)data->progress.dl.speed; break; case CURLINFO_SPEED_UPLOAD: - *param_doublep = (double)data->progress.ulspeed; + *param_doublep = (double)data->progress.ul.speed; break; case CURLINFO_CONTENT_LENGTH_DOWNLOAD: - *param_doublep = (data->progress.flags & PGRS_DL_SIZE_KNOWN)? - (double)data->progress.size_dl:-1; + *param_doublep = (data->progress.flags & PGRS_DL_SIZE_KNOWN) ? + (double)data->progress.dl.total_size : -1; break; case CURLINFO_CONTENT_LENGTH_UPLOAD: - *param_doublep = (data->progress.flags & PGRS_UL_SIZE_KNOWN)? - (double)data->progress.size_ul:-1; + *param_doublep = (data->progress.flags & PGRS_UL_SIZE_KNOWN) ? + (double)data->progress.ul.total_size : -1; break; case CURLINFO_REDIRECT_TIME: *param_doublep = DOUBLE_SECS(data->progress.t_redirect); diff --git a/deps/curl/lib/gopher.c b/deps/curl/lib/gopher.c index 61e41b7e477..051e6e7ab5b 100644 --- a/deps/curl/lib/gopher.c +++ b/deps/curl/lib/gopher.c @@ -62,7 +62,7 @@ static CURLcode gopher_connecting(struct Curl_easy *data, bool *done); */ const struct Curl_handler Curl_handler_gopher = { - "GOPHER", /* scheme */ + "gopher", /* scheme */ ZERO_NULL, /* setup_connection */ gopher_do, /* do_it */ ZERO_NULL, /* done */ @@ -75,7 +75,8 @@ const struct Curl_handler Curl_handler_gopher = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_GOPHER, /* defport */ @@ -86,7 +87,7 @@ const struct Curl_handler Curl_handler_gopher = { #ifdef USE_SSL const struct Curl_handler Curl_handler_gophers = { - "GOPHERS", /* scheme */ + "gophers", /* scheme */ ZERO_NULL, /* setup_connection */ gopher_do, /* do_it */ ZERO_NULL, /* done */ @@ -99,7 +100,8 @@ const struct Curl_handler Curl_handler_gophers = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_GOPHER, /* defport */ @@ -139,8 +141,8 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done) char *sel = NULL; char *sel_org = NULL; timediff_t timeout_ms; - ssize_t amount, k; - size_t len; + ssize_t k; + size_t amount, len; int what; *done = TRUE; /* unconditionally */ @@ -185,7 +187,7 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done) if(strlen(sel) < 1) break; - result = Curl_nwrite(data, FIRSTSOCKET, sel, k, &amount); + result = Curl_xfer_send(data, sel, k, FALSE, &amount); if(!result) { /* Which may not have written it all! */ result = Curl_client_write(data, CLIENTWRITE_HEADER, sel, amount); if(result) @@ -207,9 +209,9 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done) if(!timeout_ms) timeout_ms = TIMEDIFF_T_MAX; - /* Don't busyloop. The entire loop thing is a work-around as it causes a + /* Do not busyloop. The entire loop thing is a work-around as it causes a BLOCKING behavior which is a NO-NO. This function should rather be - split up in a do and a doing piece where the pieces that aren't + split up in a do and a doing piece where the pieces that are not possible to send now will be sent in the doing function repeatedly until the entire request is sent. */ @@ -227,7 +229,7 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done) free(sel_org); if(!result) - result = Curl_nwrite(data, FIRSTSOCKET, "\r\n", 2, &amount); + result = Curl_xfer_send(data, "\r\n", 2, FALSE, &amount); if(result) { failf(data, "Failed sending Gopher request"); return result; @@ -236,7 +238,7 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done) if(result) return result; - Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); return CURLE_OK; } #endif /* CURL_DISABLE_GOPHER */ diff --git a/deps/curl/lib/hash.c b/deps/curl/lib/hash.c index 30f28e2352a..1910ac5dc49 100644 --- a/deps/curl/lib/hash.c +++ b/deps/curl/lib/hash.c @@ -33,6 +33,10 @@ /* The last #include file should be: */ #include "memdebug.h" +/* random patterns for API verification */ +#define HASHINIT 0x7017e781 +#define ITERINIT 0x5FEDCBA9 + static void hash_element_dtor(void *user, void *element) { @@ -40,7 +44,10 @@ hash_element_dtor(void *user, void *element) struct Curl_hash_element *e = (struct Curl_hash_element *) element; if(e->ptr) { - h->dtor(e->ptr); + if(e->dtor) + e->dtor(e->key, e->key_len, e->ptr); + else + h->dtor(e->ptr); e->ptr = NULL; } @@ -57,7 +64,7 @@ hash_element_dtor(void *user, void *element) */ void Curl_hash_init(struct Curl_hash *h, - int slots, + size_t slots, hash_function hfunc, comp_function comparator, Curl_hash_dtor dtor) @@ -74,10 +81,14 @@ Curl_hash_init(struct Curl_hash *h, h->dtor = dtor; h->size = 0; h->slots = slots; +#ifdef DEBUGBUILD + h->init = HASHINIT; +#endif } static struct Curl_hash_element * -mk_hash_element(const void *key, size_t key_len, const void *p) +mk_hash_element(const void *key, size_t key_len, const void *p, + Curl_hash_elem_dtor dtor) { /* allocate the struct plus memory after it to store the key */ struct Curl_hash_element *he = malloc(sizeof(struct Curl_hash_element) + @@ -87,31 +98,25 @@ mk_hash_element(const void *key, size_t key_len, const void *p) memcpy(he->key, key, key_len); he->key_len = key_len; he->ptr = (void *) p; + he->dtor = dtor; } return he; } #define FETCH_LIST(x,y,z) &x->table[x->hash_func(y, z, x->slots)] -/* Insert the data in the hash. If there already was a match in the hash, that - * data is replaced. This function also "lazily" allocates the table if - * needed, as it isn't done in the _init function (anymore). - * - * @unittest: 1305 - * @unittest: 1602 - * @unittest: 1603 - */ -void * -Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) +void *Curl_hash_add2(struct Curl_hash *h, void *key, size_t key_len, void *p, + Curl_hash_elem_dtor dtor) { struct Curl_hash_element *he; - struct Curl_llist_element *le; + struct Curl_llist_node *le; struct Curl_llist *l; DEBUGASSERT(h); DEBUGASSERT(h->slots); + DEBUGASSERT(h->init == HASHINIT); if(!h->table) { - int i; + size_t i; h->table = malloc(h->slots * sizeof(struct Curl_llist)); if(!h->table) return NULL; /* OOM */ @@ -121,18 +126,18 @@ Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) l = FETCH_LIST(h, key, key_len); - for(le = l->head; le; le = le->next) { - he = (struct Curl_hash_element *) le->ptr; + for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) { + he = (struct Curl_hash_element *) Curl_node_elem(le); if(h->comp_func(he->key, he->key_len, key, key_len)) { - Curl_llist_remove(l, le, (void *)h); + Curl_node_uremove(le, (void *)h); --h->size; break; } } - he = mk_hash_element(key, key_len, p); + he = mk_hash_element(key, key_len, p, dtor); if(he) { - Curl_llist_insert_next(l, l->tail, he, &he->list); + Curl_llist_append(l, he, &he->list); ++h->size; return p; /* return the new entry */ } @@ -140,6 +145,20 @@ Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) return NULL; /* failure */ } +/* Insert the data in the hash. If there already was a match in the hash, that + * data is replaced. This function also "lazily" allocates the table if + * needed, as it is not done in the _init function (anymore). + * + * @unittest: 1305 + * @unittest: 1602 + * @unittest: 1603 + */ +void * +Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) +{ + return Curl_hash_add2(h, key, key_len, p, NULL); +} + /* Remove the identified hash entry. * Returns non-zero on failure. * @@ -147,18 +166,17 @@ Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) */ int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len) { - struct Curl_llist_element *le; - struct Curl_llist *l; - DEBUGASSERT(h); DEBUGASSERT(h->slots); + DEBUGASSERT(h->init == HASHINIT); if(h->table) { - l = FETCH_LIST(h, key, key_len); + struct Curl_llist_node *le; + struct Curl_llist *l = FETCH_LIST(h, key, key_len); - for(le = l->head; le; le = le->next) { - struct Curl_hash_element *he = le->ptr; + for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) { + struct Curl_hash_element *he = Curl_node_elem(le); if(h->comp_func(he->key, he->key_len, key, key_len)) { - Curl_llist_remove(l, le, (void *) h); + Curl_node_uremove(le, (void *) h); --h->size; return 0; } @@ -174,15 +192,15 @@ int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len) void * Curl_hash_pick(struct Curl_hash *h, void *key, size_t key_len) { - struct Curl_llist_element *le; - struct Curl_llist *l; - DEBUGASSERT(h); + DEBUGASSERT(h->init == HASHINIT); if(h->table) { + struct Curl_llist_node *le; + struct Curl_llist *l; DEBUGASSERT(h->slots); l = FETCH_LIST(h, key, key_len); - for(le = l->head; le; le = le->next) { - struct Curl_hash_element *he = le->ptr; + for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) { + struct Curl_hash_element *he = Curl_node_elem(le); if(h->comp_func(he->key, he->key_len, key, key_len)) { return he->ptr; } @@ -192,25 +210,6 @@ Curl_hash_pick(struct Curl_hash *h, void *key, size_t key_len) return NULL; } -#if defined(DEBUGBUILD) && defined(AGGRESSIVE_TEST) -void -Curl_hash_apply(Curl_hash *h, void *user, - void (*cb)(void *user, void *ptr)) -{ - struct Curl_llist_element *le; - int i; - - for(i = 0; i < h->slots; ++i) { - for(le = (h->table[i])->head; - le; - le = le->next) { - Curl_hash_element *el = le->ptr; - cb(user, el->ptr); - } - } -} -#endif - /* Destroys all the entries in the given hash and resets its attributes, * prepping the given hash for [static|dynamic] deallocation. * @@ -221,8 +220,9 @@ Curl_hash_apply(Curl_hash *h, void *user, void Curl_hash_destroy(struct Curl_hash *h) { + DEBUGASSERT(h->init == HASHINIT); if(h->table) { - int i; + size_t i; for(i = 0; i < h->slots; ++i) { Curl_llist_destroy(&h->table[i], (void *) h); } @@ -242,28 +242,33 @@ Curl_hash_clean(struct Curl_hash *h) Curl_hash_clean_with_criterium(h, NULL, NULL); } +size_t Curl_hash_count(struct Curl_hash *h) +{ + DEBUGASSERT(h->init == HASHINIT); + return h->size; +} + /* Cleans all entries that pass the comp function criteria. */ void Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, int (*comp)(void *, void *)) { - struct Curl_llist_element *le; - struct Curl_llist_element *lnext; - struct Curl_llist *list; - int i; + size_t i; if(!h || !h->table) return; + DEBUGASSERT(h->init == HASHINIT); for(i = 0; i < h->slots; ++i) { - list = &h->table[i]; - le = list->head; /* get first list entry */ + struct Curl_llist *list = &h->table[i]; + struct Curl_llist_node *le = + Curl_llist_head(list); /* get first list entry */ while(le) { - struct Curl_hash_element *he = le->ptr; - lnext = le->next; + struct Curl_hash_element *he = Curl_node_elem(le); + struct Curl_llist_node *lnext = Curl_node_next(le); /* ask the callback function if we shall remove this entry or not */ if(!comp || comp(user, he->ptr)) { - Curl_llist_remove(list, le, (void *) h); + Curl_node_uremove(le, (void *) h); --h->size; /* one less entry in the hash now */ } le = lnext; @@ -278,8 +283,9 @@ size_t Curl_hash_str(void *key, size_t key_length, size_t slots_num) size_t h = 5381; while(key_str < end) { + size_t j = (size_t)*key_str++; h += h << 5; - h ^= *key_str++; + h ^= j; } return (h % slots_num); @@ -297,29 +303,34 @@ size_t Curl_str_key_compare(void *k1, size_t key1_len, void Curl_hash_start_iterate(struct Curl_hash *hash, struct Curl_hash_iterator *iter) { + DEBUGASSERT(hash->init == HASHINIT); iter->hash = hash; iter->slot_index = 0; iter->current_element = NULL; +#ifdef DEBUGBUILD + iter->init = ITERINIT; +#endif } struct Curl_hash_element * Curl_hash_next_element(struct Curl_hash_iterator *iter) { - struct Curl_hash *h = iter->hash; - + struct Curl_hash *h; + DEBUGASSERT(iter->init == ITERINIT); + h = iter->hash; if(!h->table) return NULL; /* empty hash, nothing to return */ /* Get the next element in the current list, if any */ if(iter->current_element) - iter->current_element = iter->current_element->next; + iter->current_element = Curl_node_next(iter->current_element); /* If we have reached the end of the list, find the next one */ if(!iter->current_element) { - int i; + size_t i; for(i = iter->slot_index; i < h->slots; i++) { - if(h->table[i].head) { - iter->current_element = h->table[i].head; + if(Curl_llist_head(&h->table[i])) { + iter->current_element = Curl_llist_head(&h->table[i]); iter->slot_index = i + 1; break; } @@ -327,7 +338,7 @@ Curl_hash_next_element(struct Curl_hash_iterator *iter) } if(iter->current_element) { - struct Curl_hash_element *he = iter->current_element->ptr; + struct Curl_hash_element *he = Curl_node_elem(iter->current_element); return he; } return NULL; @@ -339,7 +350,7 @@ void Curl_hash_print(struct Curl_hash *h, { struct Curl_hash_iterator iter; struct Curl_hash_element *he; - int last_index = -1; + size_t last_index = ~0; if(!h) return; @@ -352,7 +363,7 @@ void Curl_hash_print(struct Curl_hash *h, while(he) { if(iter.slot_index != last_index) { fprintf(stderr, "index %d:", iter.slot_index); - if(last_index >= 0) { + if(last_index != ~0) { fprintf(stderr, "\n"); } last_index = iter.slot_index; @@ -368,3 +379,25 @@ void Curl_hash_print(struct Curl_hash *h, fprintf(stderr, "\n"); } #endif + +void Curl_hash_offt_init(struct Curl_hash *h, + size_t slots, + Curl_hash_dtor dtor) +{ + Curl_hash_init(h, slots, Curl_hash_str, Curl_str_key_compare, dtor); +} + +void *Curl_hash_offt_set(struct Curl_hash *h, curl_off_t id, void *elem) +{ + return Curl_hash_add(h, &id, sizeof(id), elem); +} + +int Curl_hash_offt_remove(struct Curl_hash *h, curl_off_t id) +{ + return Curl_hash_delete(h, &id, sizeof(id)); +} + +void *Curl_hash_offt_get(struct Curl_hash *h, curl_off_t id) +{ + return Curl_hash_pick(h, &id, sizeof(id)); +} diff --git a/deps/curl/lib/hash.h b/deps/curl/lib/hash.h index 9cfffc25b08..b1603950245 100644 --- a/deps/curl/lib/hash.h +++ b/deps/curl/lib/hash.h @@ -54,36 +54,49 @@ struct Curl_hash { /* Comparator function to compare keys */ comp_function comp_func; Curl_hash_dtor dtor; - int slots; + size_t slots; size_t size; +#ifdef DEBUGBUILD + int init; +#endif }; +typedef void (*Curl_hash_elem_dtor)(void *key, size_t key_len, void *p); + struct Curl_hash_element { - struct Curl_llist_element list; + struct Curl_llist_node list; void *ptr; + Curl_hash_elem_dtor dtor; size_t key_len; +#ifdef DEBUGBUILD + int init; +#endif char key[1]; /* allocated memory following the struct */ }; struct Curl_hash_iterator { struct Curl_hash *hash; - int slot_index; - struct Curl_llist_element *current_element; + size_t slot_index; + struct Curl_llist_node *current_element; +#ifdef DEBUGBUILD + int init; +#endif }; void Curl_hash_init(struct Curl_hash *h, - int slots, + size_t slots, hash_function hfunc, comp_function comparator, Curl_hash_dtor dtor); void *Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p); +void *Curl_hash_add2(struct Curl_hash *h, void *key, size_t key_len, void *p, + Curl_hash_elem_dtor dtor); int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len); void *Curl_hash_pick(struct Curl_hash *, void *key, size_t key_len); -void Curl_hash_apply(struct Curl_hash *h, void *user, - void (*cb)(void *user, void *ptr)); -#define Curl_hash_count(h) ((h)->size) + void Curl_hash_destroy(struct Curl_hash *h); +size_t Curl_hash_count(struct Curl_hash *h); void Curl_hash_clean(struct Curl_hash *h); void Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, int (*comp)(void *, void *)); @@ -98,5 +111,13 @@ Curl_hash_next_element(struct Curl_hash_iterator *iter); void Curl_hash_print(struct Curl_hash *h, void (*func)(void *)); +/* Hash for `curl_off_t` as key */ +void Curl_hash_offt_init(struct Curl_hash *h, size_t slots, + Curl_hash_dtor dtor); + +void *Curl_hash_offt_set(struct Curl_hash *h, curl_off_t id, void *elem); +int Curl_hash_offt_remove(struct Curl_hash *h, curl_off_t id); +void *Curl_hash_offt_get(struct Curl_hash *h, curl_off_t id); + #endif /* HEADER_CURL_HASH_H */ diff --git a/deps/curl/lib/headers.c b/deps/curl/lib/headers.c index 3ff4d5eb073..2985e1e1854 100644 --- a/deps/curl/lib/headers.c +++ b/deps/curl/lib/headers.c @@ -27,6 +27,7 @@ #include "urldata.h" #include "strdup.h" #include "strcase.h" +#include "sendf.h" #include "headers.h" /* The last 3 #include files should be in this order */ @@ -41,7 +42,7 @@ static void copy_header_external(struct Curl_header_store *hs, size_t index, size_t amount, - struct Curl_llist_element *e, + struct Curl_llist_node *e, struct curl_header *hout) { struct curl_header *h = hout; @@ -53,7 +54,7 @@ static void copy_header_external(struct Curl_header_store *hs, impossible for applications to do == comparisons, as that would otherwise be very tempting and then lead to the reserved bits not being reserved anymore. */ - h->origin = hs->type | (1<<27); + h->origin = (unsigned int)(hs->type | (1 << 27)); h->anchor = e; } @@ -65,8 +66,8 @@ CURLHcode curl_easy_header(CURL *easy, int request, struct curl_header **hout) { - struct Curl_llist_element *e; - struct Curl_llist_element *e_pick = NULL; + struct Curl_llist_node *e; + struct Curl_llist_node *e_pick = NULL; struct Curl_easy *data = easy; size_t match = 0; size_t amount = 0; @@ -84,8 +85,8 @@ CURLHcode curl_easy_header(CURL *easy, request = data->state.requests; /* we need a first round to count amount of this header */ - for(e = data->state.httphdrs.head; e; e = e->next) { - hs = e->ptr; + for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { + hs = Curl_node_elem(e); if(strcasecompare(hs->name, name) && (hs->type & type) && (hs->request == request)) { @@ -103,8 +104,8 @@ CURLHcode curl_easy_header(CURL *easy, /* if the last or only occurrence is what's asked for, then we know it */ hs = pick; else { - for(e = data->state.httphdrs.head; e; e = e->next) { - hs = e->ptr; + for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { + hs = Curl_node_elem(e); if(strcasecompare(hs->name, name) && (hs->type & type) && (hs->request == request) && @@ -113,7 +114,7 @@ CURLHcode curl_easy_header(CURL *easy, break; } } - if(!e) /* this shouldn't happen */ + if(!e) /* this should not happen */ return CURLHE_MISSING; } /* this is the name we want */ @@ -130,8 +131,8 @@ struct curl_header *curl_easy_nextheader(CURL *easy, struct curl_header *prev) { struct Curl_easy *data = easy; - struct Curl_llist_element *pick; - struct Curl_llist_element *e; + struct Curl_llist_node *pick; + struct Curl_llist_node *e; struct Curl_header_store *hs; size_t amount = 0; size_t index = 0; @@ -146,18 +147,18 @@ struct curl_header *curl_easy_nextheader(CURL *easy, if(!pick) /* something is wrong */ return NULL; - pick = pick->next; + pick = Curl_node_next(pick); } else - pick = data->state.httphdrs.head; + pick = Curl_llist_head(&data->state.httphdrs); if(pick) { /* make sure it is the next header of the desired type */ do { - hs = pick->ptr; + hs = Curl_node_elem(pick); if((hs->type & type) && (hs->request == request)) break; - pick = pick->next; + pick = Curl_node_next(pick); } while(pick); } @@ -165,12 +166,12 @@ struct curl_header *curl_easy_nextheader(CURL *easy, /* no more headers available */ return NULL; - hs = pick->ptr; + hs = Curl_node_elem(pick); /* count number of occurrences of this name within the mask and figure out the index for the currently selected entry */ - for(e = data->state.httphdrs.head; e; e = e->next) { - struct Curl_header_store *check = e->ptr; + for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { + struct Curl_header_store *check = Curl_node_elem(e); if(strcasecompare(hs->name, check->name) && (check->request == request) && (check->type & type)) @@ -185,7 +186,7 @@ struct curl_header *curl_easy_nextheader(CURL *easy, } static CURLcode namevalue(char *header, size_t hlen, unsigned int type, - char **name, char **value) + char **name, char **value) { char *end = header + hlen - 1; /* point to the last byte */ DEBUGASSERT(hlen); @@ -246,13 +247,13 @@ static CURLcode unfold_value(struct Curl_easy *data, const char *value, /* since this header block might move in the realloc below, it needs to first be unlinked from the list and then re-added again after the realloc */ - Curl_llist_remove(&data->state.httphdrs, &hs->node, NULL); + Curl_node_remove(&hs->node); /* new size = struct + new value length + old name+value length */ newhs = Curl_saferealloc(hs, sizeof(*hs) + vlen + oalloc + 1); if(!newhs) return CURLE_OUT_OF_MEMORY; - /* ->name' and ->value point into ->buffer (to keep the header allocation + /* ->name and ->value point into ->buffer (to keep the header allocation in a single memory block), which now potentially have moved. Adjust them. */ newhs->name = newhs->buffer; @@ -263,8 +264,7 @@ static CURLcode unfold_value(struct Curl_easy *data, const char *value, newhs->value[olen + vlen] = 0; /* null-terminate at newline */ /* insert this node into the list of headers */ - Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail, - newhs, &newhs->node); + Curl_llist_append(&data->state.httphdrs, newhs, &newhs->node); data->state.prevhead = newhs; return CURLE_OK; } @@ -292,16 +292,17 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, if(!end) { end = strchr(header, '\n'); if(!end) - return CURLE_BAD_FUNCTION_ARGUMENT; + /* neither CR nor LF as terminator is not a valid header */ + return CURLE_WEIRD_SERVER_REPLY; } - hlen = end - header + 1; + hlen = end - header; if((header[0] == ' ') || (header[0] == '\t')) { if(data->state.prevhead) /* line folding, append value to the previous header's value */ return unfold_value(data, header, hlen); else { - /* Can't unfold without a previous header. Instead of erroring, just + /* cannot unfold without a previous header. Instead of erroring, just pass the leading blanks. */ while(hlen && ISBLANK(*header)) { header++; @@ -319,47 +320,100 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, hs->buffer[hlen] = 0; /* nul terminate */ result = namevalue(hs->buffer, hlen, type, &name, &value); - if(result) - goto fail; - - hs->name = name; - hs->value = value; - hs->type = type; - hs->request = data->state.requests; - - /* insert this node into the list of headers */ - Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail, - hs, &hs->node); - data->state.prevhead = hs; - return CURLE_OK; -fail: - free(hs); + if(!result) { + hs->name = name; + hs->value = value; + hs->type = type; + hs->request = data->state.requests; + + /* insert this node into the list of headers */ + Curl_llist_append(&data->state.httphdrs, hs, &hs->node); + data->state.prevhead = hs; + } + else + free(hs); return result; } /* - * Curl_headers_init(). Init the headers subsystem. + * Curl_headers_reset(). Reset the headers subsystem. */ -static void headers_init(struct Curl_easy *data) +static void headers_reset(struct Curl_easy *data) { Curl_llist_init(&data->state.httphdrs, NULL); data->state.prevhead = NULL; } +struct hds_cw_collect_ctx { + struct Curl_cwriter super; +}; + +static CURLcode hds_cw_collect_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) +{ + if((type & CLIENTWRITE_HEADER) && !(type & CLIENTWRITE_STATUS)) { + unsigned char htype = (unsigned char) + (type & CLIENTWRITE_CONNECT ? CURLH_CONNECT : + (type & CLIENTWRITE_1XX ? CURLH_1XX : + (type & CLIENTWRITE_TRAILER ? CURLH_TRAILER : + CURLH_HEADER))); + CURLcode result = Curl_headers_push(data, buf, htype); + CURL_TRC_WRITE(data, "header_collect pushed(type=%x, len=%zu) -> %d", + htype, blen, result); + if(result) + return result; + } + return Curl_cwriter_write(data, writer->next, type, buf, blen); +} + +static const struct Curl_cwtype hds_cw_collect = { + "hds-collect", + NULL, + Curl_cwriter_def_init, + hds_cw_collect_write, + Curl_cwriter_def_close, + sizeof(struct hds_cw_collect_ctx) +}; + +CURLcode Curl_headers_init(struct Curl_easy *data) +{ + struct Curl_cwriter *writer; + CURLcode result; + + if(data->conn && (data->conn->handler->protocol & PROTO_FAMILY_HTTP)) { + /* avoid installing it twice */ + if(Curl_cwriter_get_by_name(data, hds_cw_collect.name)) + return CURLE_OK; + + result = Curl_cwriter_create(&writer, data, &hds_cw_collect, + CURL_CW_PROTOCOL); + if(result) + return result; + + result = Curl_cwriter_add(data, writer); + if(result) { + Curl_cwriter_free(data, writer); + return result; + } + } + return CURLE_OK; +} + /* * Curl_headers_cleanup(). Free all stored headers and associated memory. */ CURLcode Curl_headers_cleanup(struct Curl_easy *data) { - struct Curl_llist_element *e; - struct Curl_llist_element *n; + struct Curl_llist_node *e; + struct Curl_llist_node *n; - for(e = data->state.httphdrs.head; e; e = n) { - struct Curl_header_store *hs = e->ptr; - n = e->next; + for(e = Curl_llist_head(&data->state.httphdrs); e; e = n) { + struct Curl_header_store *hs = Curl_node_elem(e); + n = Curl_node_next(e); free(hs); } - headers_init(data); + headers_reset(data); return CURLE_OK; } diff --git a/deps/curl/lib/headers.h b/deps/curl/lib/headers.h index a5229ea22f0..e11fe9804e5 100644 --- a/deps/curl/lib/headers.h +++ b/deps/curl/lib/headers.h @@ -28,7 +28,7 @@ #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HEADERS_API) struct Curl_header_store { - struct Curl_llist_element node; + struct Curl_llist_node node; char *name; /* points into 'buffer' */ char *value; /* points into 'buffer */ int request; /* 0 is the first request, then 1.. 2.. */ @@ -36,6 +36,12 @@ struct Curl_header_store { char buffer[1]; /* this is the raw header blob */ }; +/* + * Initialize header collecting for a transfer. + * Will add a client writer that catches CLIENTWRITE_HEADER writes. + */ +CURLcode Curl_headers_init(struct Curl_easy *data); + /* * Curl_headers_push() gets passed a full header to store. */ @@ -48,6 +54,7 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, CURLcode Curl_headers_cleanup(struct Curl_easy *data); #else +#define Curl_headers_init(x) CURLE_OK #define Curl_headers_push(x,y,z) CURLE_OK #define Curl_headers_cleanup(x) Curl_nop_stmt #endif diff --git a/deps/curl/lib/hmac.c b/deps/curl/lib/hmac.c index 87e7be8c653..088c9bdcec8 100644 --- a/deps/curl/lib/hmac.c +++ b/deps/curl/lib/hmac.c @@ -26,8 +26,8 @@ #include "curl_setup.h" -#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) \ - || !defined(CURL_DISABLE_AWS) +#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) \ + || !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) #include @@ -42,15 +42,13 @@ * Generic HMAC algorithm. * * This module computes HMAC digests based on any hash function. Parameters - * and computing procedures are set-up dynamically at HMAC computation context + * and computing procedures are setup dynamically at HMAC computation context * initialization. */ static const unsigned char hmac_ipad = 0x36; static const unsigned char hmac_opad = 0x5C; - - struct HMAC_context * Curl_HMAC_init(const struct HMAC_params *hashparams, const unsigned char *key, @@ -62,42 +60,40 @@ Curl_HMAC_init(const struct HMAC_params *hashparams, unsigned char b; /* Create HMAC context. */ - i = sizeof(*ctxt) + 2 * hashparams->hmac_ctxtsize + - hashparams->hmac_resultlen; + i = sizeof(*ctxt) + 2 * hashparams->ctxtsize + hashparams->resultlen; ctxt = malloc(i); if(!ctxt) return ctxt; - ctxt->hmac_hash = hashparams; - ctxt->hmac_hashctxt1 = (void *) (ctxt + 1); - ctxt->hmac_hashctxt2 = (void *) ((char *) ctxt->hmac_hashctxt1 + - hashparams->hmac_ctxtsize); + ctxt->hash = hashparams; + ctxt->hashctxt1 = (void *) (ctxt + 1); + ctxt->hashctxt2 = (void *) ((char *) ctxt->hashctxt1 + hashparams->ctxtsize); /* If the key is too long, replace it by its hash digest. */ - if(keylen > hashparams->hmac_maxkeylen) { - (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt1); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, key, keylen); - hkey = (unsigned char *) ctxt->hmac_hashctxt2 + hashparams->hmac_ctxtsize; - (*hashparams->hmac_hfinal)(hkey, ctxt->hmac_hashctxt1); + if(keylen > hashparams->maxkeylen) { + hashparams->hinit(ctxt->hashctxt1); + hashparams->hupdate(ctxt->hashctxt1, key, keylen); + hkey = (unsigned char *) ctxt->hashctxt2 + hashparams->ctxtsize; + hashparams->hfinal(hkey, ctxt->hashctxt1); key = hkey; - keylen = hashparams->hmac_resultlen; + keylen = hashparams->resultlen; } /* Prime the two hash contexts with the modified key. */ - (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt1); - (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt2); + hashparams->hinit(ctxt->hashctxt1); + hashparams->hinit(ctxt->hashctxt2); for(i = 0; i < keylen; i++) { b = (unsigned char)(*key ^ hmac_ipad); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, &b, 1); + hashparams->hupdate(ctxt->hashctxt1, &b, 1); b = (unsigned char)(*key++ ^ hmac_opad); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, &b, 1); + hashparams->hupdate(ctxt->hashctxt2, &b, 1); } - for(; i < hashparams->hmac_maxkeylen; i++) { - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, &hmac_ipad, 1); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, &hmac_opad, 1); + for(; i < hashparams->maxkeylen; i++) { + hashparams->hupdate(ctxt->hashctxt1, &hmac_ipad, 1); + hashparams->hupdate(ctxt->hashctxt2, &hmac_opad, 1); } /* Done, return pointer to HMAC context. */ @@ -105,31 +101,29 @@ Curl_HMAC_init(const struct HMAC_params *hashparams, } int Curl_HMAC_update(struct HMAC_context *ctxt, - const unsigned char *data, + const unsigned char *ptr, unsigned int len) { /* Update first hash calculation. */ - (*ctxt->hmac_hash->hmac_hupdate)(ctxt->hmac_hashctxt1, data, len); + ctxt->hash->hupdate(ctxt->hashctxt1, ptr, len); return 0; } -int Curl_HMAC_final(struct HMAC_context *ctxt, unsigned char *result) +int Curl_HMAC_final(struct HMAC_context *ctxt, unsigned char *output) { - const struct HMAC_params *hashparams = ctxt->hmac_hash; + const struct HMAC_params *hashparams = ctxt->hash; - /* Do not get result if called with a null parameter: only release + /* Do not get output if called with a null parameter: only release storage. */ - if(!result) - result = (unsigned char *) ctxt->hmac_hashctxt2 + - ctxt->hmac_hash->hmac_ctxtsize; + if(!output) + output = (unsigned char *) ctxt->hashctxt2 + ctxt->hash->ctxtsize; - (*hashparams->hmac_hfinal)(result, ctxt->hmac_hashctxt1); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, - result, hashparams->hmac_resultlen); - (*hashparams->hmac_hfinal)(result, ctxt->hmac_hashctxt2); - free((char *) ctxt); + hashparams->hfinal(output, ctxt->hashctxt1); + hashparams->hupdate(ctxt->hashctxt2, output, hashparams->resultlen); + hashparams->hfinal(output, ctxt->hashctxt2); + free(ctxt); return 0; } @@ -144,15 +138,15 @@ int Curl_HMAC_final(struct HMAC_context *ctxt, unsigned char *result) * hashparams [in] - The hash function (Curl_HMAC_MD5). * key [in] - The key to use. * keylen [in] - The length of the key. - * data [in] - The data to encrypt. - * datalen [in] - The length of the data. + * buf [in] - The data to encrypt. + * buflen [in] - The length of the data. * output [in/out] - The output buffer. * * Returns CURLE_OK on success. */ CURLcode Curl_hmacit(const struct HMAC_params *hashparams, const unsigned char *key, const size_t keylen, - const unsigned char *data, const size_t datalen, + const unsigned char *buf, const size_t buflen, unsigned char *output) { struct HMAC_context *ctxt = @@ -162,7 +156,7 @@ CURLcode Curl_hmacit(const struct HMAC_params *hashparams, return CURLE_OUT_OF_MEMORY; /* Update the digest with the given challenge */ - Curl_HMAC_update(ctxt, data, curlx_uztoui(datalen)); + Curl_HMAC_update(ctxt, buf, curlx_uztoui(buflen)); /* Finalise the digest */ Curl_HMAC_final(ctxt, output); diff --git a/deps/curl/lib/hostasyn.c b/deps/curl/lib/hostasyn.c index 2f6762ca4e1..4d6a8e85967 100644 --- a/deps/curl/lib/hostasyn.c +++ b/deps/curl/lib/hostasyn.c @@ -79,7 +79,7 @@ CURLcode Curl_addrinfo_callback(struct Curl_easy *data, dns = Curl_cache_addr(data, ai, data->state.async.hostname, 0, - data->state.async.port); + data->state.async.port, FALSE); if(data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); diff --git a/deps/curl/lib/hostip.c b/deps/curl/lib/hostip.c index acb4b5140b5..fe8cc5cb97f 100644 --- a/deps/curl/lib/hostip.c +++ b/deps/curl/lib/hostip.c @@ -41,12 +41,8 @@ #include #endif -#ifdef HAVE_SETJMP_H #include -#endif -#ifdef HAVE_SIGNAL_H #include -#endif #include "urldata.h" #include "sendf.h" @@ -88,8 +84,8 @@ * source file are these: * * CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use - * that. The host may not be able to resolve IPv6, but we don't really have to - * take that into account. Hosts that aren't IPv6-enabled have CURLRES_IPV4 + * that. The host may not be able to resolve IPv6, but we do not really have to + * take that into account. Hosts that are not IPv6-enabled have CURLRES_IPV4 * defined. * * CURLRES_ARES - is defined if libcurl is built to use c-ares for @@ -119,7 +115,14 @@ * CURLRES_* defines based on the config*.h and curl_setup.h defines. */ -static void freednsentry(void *freethis); +static void hostcache_unlink_entry(void *entry); + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void show_resolve_info(struct Curl_easy *data, + struct Curl_dns_entry *dns); +#else +#define show_resolve_info(x,y) Curl_nop_stmt +#endif /* * Curl_printable_address() stores a printable version of the 1st address @@ -141,7 +144,7 @@ void Curl_printable_address(const struct Curl_addrinfo *ai, char *buf, (void)Curl_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf, bufsize); break; } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: { const struct sockaddr_in6 *sa6 = (const void *)ai->ai_addr; const struct in6_addr *ipaddr6 = &sa6->sin6_addr; @@ -164,23 +167,18 @@ create_hostcache_id(const char *name, int port, char *ptr, size_t buflen) { size_t len = nlen ? nlen : strlen(name); - size_t olen = 0; DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN); if(len > (buflen - 7)) len = buflen - 7; /* store and lower case the name */ - while(len--) { - *ptr++ = Curl_raw_tolower(*name++); - olen++; - } - olen += msnprintf(ptr, 7, ":%u", port); - return olen; + Curl_strntolower(ptr, name, len); + return msnprintf(&ptr[len], 7, ":%u", port) + len; } struct hostcache_prune_data { time_t now; time_t oldest; /* oldest time in cache not pruned. */ - int cache_timeout; + int max_age_sec; }; /* @@ -191,16 +189,16 @@ struct hostcache_prune_data { * cache. */ static int -hostcache_timestamp_remove(void *datap, void *hc) +hostcache_entry_is_stale(void *datap, void *hc) { struct hostcache_prune_data *prune = (struct hostcache_prune_data *) datap; - struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc; + struct Curl_dns_entry *dns = (struct Curl_dns_entry *) hc; - if(c->timestamp) { + if(dns->timestamp) { /* age in seconds */ - time_t age = prune->now - c->timestamp; - if(age >= prune->cache_timeout) + time_t age = prune->now - dns->timestamp; + if(age >= prune->max_age_sec) return TRUE; if(age > prune->oldest) prune->oldest = age; @@ -218,13 +216,13 @@ hostcache_prune(struct Curl_hash *hostcache, int cache_timeout, { struct hostcache_prune_data user; - user.cache_timeout = cache_timeout; + user.max_age_sec = cache_timeout; user.now = now; user.oldest = 0; Curl_hash_clean_with_criterium(hostcache, (void *) &user, - hostcache_timestamp_remove); + hostcache_entry_is_stale); return user.oldest; } @@ -240,13 +238,13 @@ void Curl_hostcache_prune(struct Curl_easy *data) int timeout = data->set.dns_cache_timeout; if(!data->dns.hostcache) - /* NULL hostcache means we can't do it */ + /* NULL hostcache means we cannot do it */ return; if(data->share) Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - time(&now); + now = time(NULL); do { /* Remove outdated and unused entries from the hostcache */ @@ -259,7 +257,8 @@ void Curl_hostcache_prune(struct Curl_easy *data) /* if the cache size is still too big, use the oldest age as new prune limit */ - } while(timeout && (data->dns.hostcache->size > MAX_DNS_CACHE_SIZE)); + } while(timeout && + (Curl_hash_count(data->dns.hostcache) > MAX_DNS_CACHE_SIZE)); if(data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); @@ -285,14 +284,14 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, size_t entry_len = create_hostcache_id(hostname, 0, port, entry_id, sizeof(entry_id)); - /* See if its already in our dns cache */ + /* See if it is already in our dns cache */ dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1); /* No entry found in cache, check if we might have a wildcard entry */ if(!dns && data->state.wildcard_resolve) { entry_len = create_hostcache_id("*", 1, port, entry_id, sizeof(entry_id)); - /* See if it's already in our dns cache */ + /* See if it is already in our dns cache */ dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1); } @@ -300,11 +299,11 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, /* See whether the returned entry is stale. Done before we release lock */ struct hostcache_prune_data user; - time(&user.now); - user.cache_timeout = data->set.dns_cache_timeout; + user.now = time(NULL); + user.max_age_sec = data->set.dns_cache_timeout; user.oldest = 0; - if(hostcache_timestamp_remove(&user, dns)) { + if(hostcache_entry_is_stale(&user, dns)) { infof(data, "Hostname in DNS cache was stale, zapped"); dns = NULL; /* the memory deallocation is being handled by the hash */ Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); @@ -314,7 +313,7 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, /* See if the returned entry matches the required resolve mode */ if(dns && data->conn->ip_version != CURL_IPRESOLVE_WHATEVER) { int pf = PF_INET; - bool found = false; + bool found = FALSE; struct Curl_addrinfo *addr = dns->addr; #ifdef PF_INET6 @@ -324,14 +323,14 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, while(addr) { if(addr->ai_family == pf) { - found = true; + found = TRUE; break; } addr = addr->ai_next; } if(!found) { - infof(data, "Hostname in DNS cache doesn't have needed family, zapped"); + infof(data, "Hostname in DNS cache does not have needed family, zapped"); dns = NULL; /* the memory deallocation is being handled by the hash */ Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); } @@ -350,8 +349,8 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, * * Returns the Curl_dns_entry entry pointer or NULL if not in the cache. * - * The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after - * use, or we'll leak memory! + * The returned data *MUST* be "released" with Curl_resolv_unlink() after + * use, or we will leak memory! */ struct Curl_dns_entry * Curl_fetch_addr(struct Curl_easy *data, @@ -366,7 +365,7 @@ Curl_fetch_addr(struct Curl_easy *data, dns = fetch_addr(data, hostname, port); if(dns) - dns->inuse++; /* we use it! */ + dns->refcount++; /* we use it! */ if(data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); @@ -430,8 +429,8 @@ UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data, if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) { struct Curl_addrinfo *swap_tmp; for(i = num_addrs - 1; i > 0; i--) { - swap_tmp = nodes[rnd[i] % (i + 1)]; - nodes[rnd[i] % (i + 1)] = nodes[i]; + swap_tmp = nodes[rnd[i] % (unsigned int)(i + 1)]; + nodes[rnd[i] % (unsigned int)(i + 1)] = nodes[i]; nodes[i] = swap_tmp; } @@ -470,7 +469,8 @@ Curl_cache_addr(struct Curl_easy *data, struct Curl_addrinfo *addr, const char *hostname, size_t hostlen, /* length or zero */ - int port) + int port, + bool permanent) { char entry_id[MAX_HOSTCACHE_LEN]; size_t entry_len; @@ -485,9 +485,11 @@ Curl_cache_addr(struct Curl_easy *data, return NULL; } #endif + if(!hostlen) + hostlen = strlen(hostname); /* Create a new cache entry */ - dns = calloc(1, sizeof(struct Curl_dns_entry)); + dns = calloc(1, sizeof(struct Curl_dns_entry) + hostlen); if(!dns) { return NULL; } @@ -496,11 +498,18 @@ Curl_cache_addr(struct Curl_easy *data, entry_len = create_hostcache_id(hostname, hostlen, port, entry_id, sizeof(entry_id)); - dns->inuse = 1; /* the cache has the first reference */ + dns->refcount = 1; /* the cache has the first reference */ dns->addr = addr; /* this is the address(es) */ - time(&dns->timestamp); - if(dns->timestamp == 0) - dns->timestamp = 1; /* zero indicates permanent CURLOPT_RESOLVE entry */ + if(permanent) + dns->timestamp = 0; /* an entry that never goes stale */ + else { + dns->timestamp = time(NULL); + if(dns->timestamp == 0) + dns->timestamp = 1; + } + dns->hostport = port; + if(hostlen) + memcpy(dns->hostname, hostname, hostlen); /* Store the resolved data in our DNS cache. */ dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len + 1, @@ -511,11 +520,11 @@ Curl_cache_addr(struct Curl_easy *data, } dns = dns2; - dns->inuse++; /* mark entry as in-use */ + dns->refcount++; /* mark entry as in-use */ return dns; } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* return a static IPv6 ::1 for the name */ static struct Curl_addrinfo *get_localhost6(int port, const char *name) { @@ -525,7 +534,7 @@ static struct Curl_addrinfo *get_localhost6(int port, const char *name) struct sockaddr_in6 sa6; unsigned char ipv6[16]; unsigned short port16 = (unsigned short)(port & 0xffff); - ca = calloc(sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1, 1); + ca = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1); if(!ca) return NULL; @@ -533,8 +542,8 @@ static struct Curl_addrinfo *get_localhost6(int port, const char *name) sa6.sin6_port = htons(port16); sa6.sin6_flowinfo = 0; sa6.sin6_scope_id = 0; - if(Curl_inet_pton(AF_INET6, "::1", ipv6) < 1) - return NULL; + + (void)Curl_inet_pton(AF_INET6, "::1", ipv6); memcpy(&sa6.sin6_addr, ipv6, sizeof(ipv6)); ca->ai_flags = 0; @@ -572,7 +581,7 @@ static struct Curl_addrinfo *get_localhost(int port, const char *name) return NULL; memcpy(&sa.sin_addr, &ipv4, sizeof(ipv4)); - ca = calloc(sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1, 1); + ca = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1); if(!ca) return NULL; ca->ai_flags = 0; @@ -592,14 +601,14 @@ static struct Curl_addrinfo *get_localhost(int port, const char *name) return ca6; } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* * Curl_ipv6works() returns TRUE if IPv6 seems to work. */ bool Curl_ipv6works(struct Curl_easy *data) { if(data) { - /* the nature of most system is that IPv6 status doesn't come and go + /* the nature of most system is that IPv6 status does not come and go during a program's lifetime so we only probe the first time and then we have the info kept for fast reuse */ DEBUGASSERT(data); @@ -615,16 +624,16 @@ bool Curl_ipv6works(struct Curl_easy *data) /* probe to see if we have a working IPv6 stack */ curl_socket_t s = socket(PF_INET6, SOCK_DGRAM, 0); if(s == CURL_SOCKET_BAD) - /* an IPv6 address was requested but we can't get/use one */ + /* an IPv6 address was requested but we cannot get/use one */ ipv6_works = 0; else { ipv6_works = 1; sclose(s); } - return (ipv6_works>0)?TRUE:FALSE; + return (ipv6_works > 0); } } -#endif /* ENABLE_IPV6 */ +#endif /* USE_IPV6 */ /* * Curl_host_is_ipnum() returns TRUE if the given string is a numerical IPv4 @@ -633,11 +642,11 @@ bool Curl_ipv6works(struct Curl_easy *data) bool Curl_host_is_ipnum(const char *hostname) { struct in_addr in; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct in6_addr in6; #endif if(Curl_inet_pton(AF_INET, hostname, &in) > 0 -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 || Curl_inet_pton(AF_INET6, hostname, &in6) > 0 #endif ) @@ -659,12 +668,12 @@ static bool tailmatch(const char *full, const char *part) /* * Curl_resolv() is the main name resolve function within libcurl. It resolves * a name and returns a pointer to the entry in the 'entry' argument (if one - * is provided). This function might return immediately if we're using asynch + * is provided). This function might return immediately if we are using asynch * resolves. See the return codes. * * The cache entry we return will get its 'inuse' counter increased when this - * function is used. You MUST call Curl_resolv_unlock() later (when you're - * done using this struct) to decrease the counter again. + * function is used. You MUST call Curl_resolv_unlink() later (when you are + * done using this struct) to decrease the reference counter again. * * Return codes: * @@ -705,7 +714,7 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, if(dns) { infof(data, "Hostname %s was found in DNS cache", hostname); - dns->inuse++; /* we use it! */ + dns->refcount++; /* we use it! */ rc = CURLRESOLV_RESOLVED; } @@ -730,7 +739,7 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, /* notify the resolver start callback */ if(data->set.resolver_start) { int st; - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); st = data->set.resolver_start( #ifdef USE_CURL_ASYNC data->state.async.resolver, @@ -739,25 +748,31 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, #endif NULL, data->set.resolver_start_client); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); if(st) return CURLRESOLV_ERROR; } #ifndef USE_RESOLVE_ON_IPS /* First check if this is an IPv4 address string */ - if(Curl_inet_pton(AF_INET, hostname, &in) > 0) + if(Curl_inet_pton(AF_INET, hostname, &in) > 0) { /* This is a dotted IP address 123.123.123.123-style */ addr = Curl_ip2addr(AF_INET, &in, hostname, port); -#ifdef ENABLE_IPV6 - if(!addr) { + if(!addr) + return CURLRESOLV_ERROR; + } +#ifdef USE_IPV6 + else { struct in6_addr in6; /* check if this is an IPv6 address string */ - if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) + if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) { /* This is an IPv6 address literal */ addr = Curl_ip2addr(AF_INET6, &in6, hostname, port); + if(!addr) + return CURLRESOLV_ERROR; + } } -#endif /* ENABLE_IPV6 */ +#endif /* USE_IPV6 */ #else /* if USE_RESOLVE_ON_IPS */ #ifndef CURL_DISABLE_DOH @@ -765,7 +780,7 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, if(Curl_inet_pton(AF_INET, hostname, &in) > 0) /* This is a dotted IP address 123.123.123.123-style */ ipnum = TRUE; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else { struct in6_addr in6; /* check if this is an IPv6 address string */ @@ -773,7 +788,7 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, /* This is an IPv6 address literal */ ipnum = TRUE; } -#endif /* ENABLE_IPV6 */ +#endif /* USE_IPV6 */ #endif /* CURL_DISABLE_DOH */ #endif /* !USE_RESOLVE_ON_IPS */ @@ -804,7 +819,7 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, if(respwait) { /* the response to our resolve call will come asynchronously at a later time, good or bad */ - /* First, check that we haven't received the info by now */ + /* First, check that we have not received the info by now */ result = Curl_resolv_check(data, &dns); if(result) /* error detected */ return CURLRESOLV_ERROR; @@ -819,7 +834,7 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); /* we got a response, store it in the cache */ - dns = Curl_cache_addr(data, addr, hostname, 0, port); + dns = Curl_cache_addr(data, addr, hostname, 0, port, FALSE); if(data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); @@ -827,8 +842,10 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, if(!dns) /* returned failure, bail out nicely */ Curl_freeaddrinfo(addr); - else + else { rc = CURLRESOLV_RESOLVED; + show_resolve_info(data, dns); + } } } @@ -840,10 +857,10 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, #ifdef USE_ALARM_TIMEOUT /* * This signal handler jumps back into the main libcurl code and continues - * execution. This effectively causes the remainder of the application to run + * execution. This effectively causes the remainder of the application to run * within a signal handler which is nonportable and could lead to problems. */ -static +CURL_NORETURN static void alarmfunc(int sig) { (void)sig; @@ -853,12 +870,12 @@ void alarmfunc(int sig) /* * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a - * timeout. This function might return immediately if we're using asynch + * timeout. This function might return immediately if we are using asynch * resolves. See the return codes. * * The cache entry we return will get its 'inuse' counter increased when this - * function is used. You MUST call Curl_resolv_unlock() later (when you're - * done using this struct) to decrease the counter again. + * function is used. You MUST call Curl_resolv_unlink() later (when you are + * done using this struct) to decrease the reference counter again. * * If built with a synchronous resolver and use of signals is not * disabled by the application, then a nonzero timeout will cause a @@ -923,7 +940,7 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, will generate a signal and we will siglongjmp() from that here. This technique has problems (see alarmfunc). This should be the last thing we do before calling Curl_resolv(), - as otherwise we'd have to worry about variables that get modified + as otherwise we would have to worry about variables that get modified before we invoke Curl_resolv() (and thus use "volatile"). */ curl_simple_lock_lock(&curl_jmpenv_lock); @@ -944,7 +961,7 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, keep_copysig = TRUE; /* yes, we have a copy */ sigact.sa_handler = alarmfunc; #ifdef SA_RESTART - /* HPUX doesn't have SA_RESTART but defaults to that behavior! */ + /* HP-UX does not have SA_RESTART but defaults to that behavior! */ sigact.sa_flags &= ~SA_RESTART; #endif /* now set the new struct */ @@ -1011,7 +1028,7 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000)) ) { /* if the alarm time-left reached zero or turned "negative" (counted with unsigned values), we should fire off a SIGALRM here, but we - won't, and zero would be to switch it off so we never set it to + will not, and zero would be to switch it off so we never set it to less than 1! */ alarm(1); rc = CURLRESOLV_TIMEDOUT; @@ -1026,18 +1043,20 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, } /* - * Curl_resolv_unlock() unlocks the given cached DNS entry. When this has been - * made, the struct may be destroyed due to pruning. It is important that only - * one unlock is made for each Curl_resolv() call. + * Curl_resolv_unlink() releases a reference to the given cached DNS entry. + * When the reference count reaches 0, the entry is destroyed. It is important + * that only one unlink is made for each Curl_resolv() call. * * May be called with 'data' == NULL for global cache. */ -void Curl_resolv_unlock(struct Curl_easy *data, struct Curl_dns_entry *dns) +void Curl_resolv_unlink(struct Curl_easy *data, struct Curl_dns_entry **pdns) { + struct Curl_dns_entry *dns = *pdns; + *pdns = NULL; if(data && data->share) Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - freednsentry(dns); + hostcache_unlink_entry(dns); if(data && data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); @@ -1046,14 +1065,31 @@ void Curl_resolv_unlock(struct Curl_easy *data, struct Curl_dns_entry *dns) /* * File-internal: release cache dns entry reference, free if inuse drops to 0 */ -static void freednsentry(void *freethis) +static void hostcache_unlink_entry(void *entry) { - struct Curl_dns_entry *dns = (struct Curl_dns_entry *) freethis; - DEBUGASSERT(dns && (dns->inuse>0)); + struct Curl_dns_entry *dns = (struct Curl_dns_entry *) entry; + DEBUGASSERT(dns && (dns->refcount > 0)); - dns->inuse--; - if(dns->inuse == 0) { + dns->refcount--; + if(dns->refcount == 0) { Curl_freeaddrinfo(dns->addr); +#ifdef USE_HTTPSRR + if(dns->hinfo) { + if(dns->hinfo->target) + free(dns->hinfo->target); + if(dns->hinfo->alpns) + free(dns->hinfo->alpns); + if(dns->hinfo->ipv4hints) + free(dns->hinfo->ipv4hints); + if(dns->hinfo->echconfiglist) + free(dns->hinfo->echconfiglist); + if(dns->hinfo->ipv6hints) + free(dns->hinfo->ipv6hints); + if(dns->hinfo->val) + free(dns->hinfo->val); + free(dns->hinfo); + } +#endif free(dns); } } @@ -1061,10 +1097,10 @@ static void freednsentry(void *freethis) /* * Curl_init_dnscache() inits a new DNS cache. */ -void Curl_init_dnscache(struct Curl_hash *hash, int size) +void Curl_init_dnscache(struct Curl_hash *hash, size_t size) { Curl_hash_init(hash, size, Curl_hash_str, Curl_str_key_compare, - freednsentry); + hostcache_unlink_entry); } /* @@ -1093,7 +1129,7 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) char *host_end; /* Default is no wildcard found */ - data->state.wildcard_resolve = false; + data->state.wildcard_resolve = FALSE; for(hostp = data->state.resolve; hostp; hostp = hostp->next) { char entry_id[MAX_HOSTCACHE_LEN]; @@ -1122,7 +1158,7 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) if(data->share) Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - /* delete entry, ignore if it didn't exist */ + /* delete entry, ignore if it did not exist */ Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); if(data->share) @@ -1143,7 +1179,7 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) char *end_ptr; bool permanent = TRUE; unsigned long tmp_port; - bool error = true; + bool error = TRUE; char *host_begin = hostp->data; size_t hlen = 0; @@ -1194,7 +1230,7 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) memcpy(address, addr_begin, alen); address[alen] = '\0'; -#ifndef ENABLE_IPV6 +#ifndef USE_IPV6 if(strchr(address, ':')) { infof(data, "Ignoring resolve address '%s', missing IPv6 support.", address); @@ -1220,7 +1256,7 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) if(!head) goto err; - error = false; + error = FALSE; err: if(error) { failf(data, "Couldn't parse CURLOPT_RESOLVE entry '%s'", @@ -1236,7 +1272,7 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) if(data->share) Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - /* See if it's already in our dns cache */ + /* See if it is already in our dns cache */ dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1); if(dns) { @@ -1257,13 +1293,11 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) } /* put this new host in the cache */ - dns = Curl_cache_addr(data, head, host_begin, hlen, port); + dns = Curl_cache_addr(data, head, host_begin, hlen, port, permanent); if(dns) { - if(permanent) - dns->timestamp = 0; /* mark as permanent */ /* release the returned reference; the cache itself will keep the * entry alive: */ - dns->inuse--; + dns->refcount--; } if(data->share) @@ -1273,14 +1307,16 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) Curl_freeaddrinfo(head); return CURLE_OUT_OF_MEMORY; } +#ifndef CURL_DISABLE_VERBOSE_STRINGS infof(data, "Added %.*s:%d:%s to DNS cache%s", (int)hlen, host_begin, port, addresses, permanent ? "" : " (non-permanent)"); +#endif /* Wildcard hostname */ if((hlen == 1) && (host_begin[0] == '*')) { infof(data, "RESOLVE *:%d using wildcard", port); - data->state.wildcard_resolve = true; + data->state.wildcard_resolve = TRUE; } } } @@ -1289,18 +1325,89 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) return CURLE_OK; } +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void show_resolve_info(struct Curl_easy *data, + struct Curl_dns_entry *dns) +{ + struct Curl_addrinfo *a; + CURLcode result = CURLE_OK; +#ifdef CURLRES_IPV6 + struct dynbuf out[2]; +#else + struct dynbuf out[1]; +#endif + DEBUGASSERT(data); + DEBUGASSERT(dns); + + if(!data->set.verbose || + /* ignore no name or numerical IP addresses */ + !dns->hostname[0] || Curl_host_is_ipnum(dns->hostname)) + return; + + a = dns->addr; + + infof(data, "Host %s:%d was resolved.", + (dns->hostname[0] ? dns->hostname : "(none)"), dns->hostport); + + Curl_dyn_init(&out[0], 1024); +#ifdef CURLRES_IPV6 + Curl_dyn_init(&out[1], 1024); +#endif + + while(a) { + if( +#ifdef CURLRES_IPV6 + a->ai_family == PF_INET6 || +#endif + a->ai_family == PF_INET) { + char buf[MAX_IPADR_LEN]; + struct dynbuf *d = &out[(a->ai_family != PF_INET)]; + Curl_printable_address(a, buf, sizeof(buf)); + if(Curl_dyn_len(d)) + result = Curl_dyn_addn(d, ", ", 2); + if(!result) + result = Curl_dyn_add(d, buf); + if(result) { + infof(data, "too many IP, cannot show"); + goto fail; + } + } + a = a->ai_next; + } + +#ifdef CURLRES_IPV6 + infof(data, "IPv6: %s", + (Curl_dyn_len(&out[1]) ? Curl_dyn_ptr(&out[1]) : "(none)")); +#endif + infof(data, "IPv4: %s", + (Curl_dyn_len(&out[0]) ? Curl_dyn_ptr(&out[0]) : "(none)")); + +fail: + Curl_dyn_free(&out[0]); +#ifdef CURLRES_IPV6 + Curl_dyn_free(&out[1]); +#endif +} +#endif + CURLcode Curl_resolv_check(struct Curl_easy *data, struct Curl_dns_entry **dns) { + CURLcode result; #if defined(CURL_DISABLE_DOH) && !defined(CURLRES_ASYNCH) (void)data; (void)dns; #endif #ifndef CURL_DISABLE_DOH - if(data->conn->bits.doh) - return Curl_doh_is_resolved(data, dns); + if(data->conn->bits.doh) { + result = Curl_doh_is_resolved(data, dns); + } + else #endif - return Curl_resolver_is_resolved(data, dns); + result = Curl_resolver_is_resolved(data, dns); + if(*dns) + show_resolve_info(data, *dns); + return result; } int Curl_resolv_getsock(struct Curl_easy *data, @@ -1342,8 +1449,7 @@ CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_done) if(result) { Curl_detach_connection(data); - Curl_conncache_remove_conn(data, conn, TRUE); - Curl_disconnect(data, conn, TRUE); + Curl_cpool_disconnect(data, conn, TRUE); } return result; } diff --git a/deps/curl/lib/hostip.h b/deps/curl/lib/hostip.h index 06d08672777..b1c5ecb2e1b 100644 --- a/deps/curl/lib/hostip.h +++ b/deps/curl/lib/hostip.h @@ -30,8 +30,10 @@ #include "timeval.h" /* for timediff_t */ #include "asyn.h" -#ifdef HAVE_SETJMP_H #include + +#ifdef USE_HTTPSRR +# include #endif /* Allocate enough memory to hold the full name information structs and @@ -60,12 +62,49 @@ struct connectdata; */ struct Curl_hash *Curl_global_host_cache_init(void); +#ifdef USE_HTTPSRR + +#define CURL_MAXLEN_host_name 253 + +struct Curl_https_rrinfo { + size_t len; /* raw encoded length */ + unsigned char *val; /* raw encoded octets */ + /* + * fields from HTTPS RR, with the mandatory fields + * first (priority, target), then the others in the + * order of the keytag numbers defined at + * https://datatracker.ietf.org/doc/html/rfc9460#section-14.3.2 + */ + uint16_t priority; + char *target; + char *alpns; /* keytag = 1 */ + bool no_def_alpn; /* keytag = 2 */ + /* + * we do not support ports (keytag = 3) as we do not support + * port-switching yet + */ + unsigned char *ipv4hints; /* keytag = 4 */ + size_t ipv4hints_len; + unsigned char *echconfiglist; /* keytag = 5 */ + size_t echconfiglist_len; + unsigned char *ipv6hints; /* keytag = 6 */ + size_t ipv6hints_len; +}; +#endif + struct Curl_dns_entry { struct Curl_addrinfo *addr; - /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (doesn't time out) */ +#ifdef USE_HTTPSRR + struct Curl_https_rrinfo *hinfo; +#endif + /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (does not time out) */ time_t timestamp; - /* use-counter, use Curl_resolv_unlock to release reference */ - long inuse; + /* reference counter, entry is freed on reaching 0 */ + size_t refcount; + /* hostname port number that resolved to addr. */ + int hostport; + /* hostname that resolved to addr. may be NULL (Unix domain sockets). */ + char hostname[1]; }; bool Curl_host_is_ipnum(const char *hostname); @@ -74,8 +113,8 @@ bool Curl_host_is_ipnum(const char *hostname); * Curl_resolv() returns an entry with the info for the specified host * and port. * - * The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after - * use, or we'll leak memory! + * The returned data *MUST* be "released" with Curl_resolv_unlink() after + * use, or we will leak memory! */ /* return codes */ enum resolve_t { @@ -94,7 +133,7 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, struct Curl_dns_entry **dnsentry, timediff_t timeoutms); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* * Curl_ipv6works() returns TRUE if IPv6 seems to work. */ @@ -122,12 +161,12 @@ struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, int *waitp); -/* unlock a previously resolved dns entry */ -void Curl_resolv_unlock(struct Curl_easy *data, - struct Curl_dns_entry *dns); +/* unlink a dns entry, potentially shared with a cache */ +void Curl_resolv_unlink(struct Curl_easy *data, + struct Curl_dns_entry **pdns); /* init a new dns cache */ -void Curl_init_dnscache(struct Curl_hash *hash, int hashsize); +void Curl_init_dnscache(struct Curl_hash *hash, size_t hashsize); /* prune old entries from the DNS cache */ void Curl_hostcache_prune(struct Curl_easy *data); @@ -160,8 +199,8 @@ void Curl_printable_address(const struct Curl_addrinfo *ip, * * Returns the Curl_dns_entry entry pointer or NULL if not in the cache. * - * The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after - * use, or we'll leak memory! + * The returned data *MUST* be "released" with Curl_resolv_unlink() after + * use, or we will leak memory! */ struct Curl_dns_entry * Curl_fetch_addr(struct Curl_easy *data, @@ -170,12 +209,13 @@ Curl_fetch_addr(struct Curl_easy *data, /* * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache. - * + * @param permanent iff TRUE, entry will never become stale * Returns the Curl_dns_entry entry pointer or NULL if the storage failed. */ struct Curl_dns_entry * Curl_cache_addr(struct Curl_easy *data, struct Curl_addrinfo *addr, - const char *hostname, size_t hostlen, int port); + const char *hostname, size_t hostlen, int port, + bool permanent); #ifndef INADDR_NONE #define CURL_INADDR_NONE (in_addr_t) ~0 diff --git a/deps/curl/lib/hostip4.c b/deps/curl/lib/hostip4.c index 9140180ffd9..58333fbc607 100644 --- a/deps/curl/lib/hostip4.c +++ b/deps/curl/lib/hostip4.c @@ -62,7 +62,7 @@ bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn) { (void)data; if(conn->ip_version == CURL_IPRESOLVE_V6) - /* An IPv6 address was requested and we can't get/use one */ + /* An IPv6 address was requested and we cannot get/use one */ return FALSE; return TRUE; /* OK, proceed */ @@ -82,7 +82,7 @@ bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn) * detect which one this platform supports in the configure script and set up * the HAVE_GETHOSTBYNAME_R_3, HAVE_GETHOSTBYNAME_R_5 or * HAVE_GETHOSTBYNAME_R_6 defines accordingly. Note that HAVE_GETADDRBYNAME - * has the corresponding rules. This is primarily on *nix. Note that some unix + * has the corresponding rules. This is primarily on *nix. Note that some Unix * flavours have thread-safe versions of the plain gethostbyname() etc. * */ @@ -126,8 +126,10 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int res; #endif struct Curl_addrinfo *ai = NULL; +#if !(defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE)) struct hostent *h = NULL; struct hostent *buf = NULL; +#endif #if defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE) struct addrinfo hints; @@ -193,8 +195,8 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, * small. Previous versions are known to return ERANGE for the same * problem. * - * This wouldn't be such a big problem if older versions wouldn't - * sometimes return EAGAIN on a common failure case. Alas, we can't + * This would not be such a big problem if older versions would not + * sometimes return EAGAIN on a common failure case. Alas, we cannot * assume that EAGAIN *or* ERANGE means ERANGE for any given version of * glibc. * @@ -210,9 +212,9 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, * gethostbyname_r() in glibc: * * In glibc 2.2.5 the interface is different (this has also been - * discovered in glibc 2.1.1-6 as shipped by Redhat 6). What I can't + * discovered in glibc 2.1.1-6 as shipped by Redhat 6). What I cannot * explain, is that tests performed on glibc 2.2.4-34 and 2.2.4-32 - * (shipped/upgraded by Redhat 7.2) don't show this behavior! + * (shipped/upgraded by Redhat 7.2) do not show this behavior! * * In this "buggy" version, the return code is -1 on error and 'errno' * is set to the ERANGE or EAGAIN code. Note that 'errno' is not a @@ -221,9 +223,9 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, if(!h) /* failure */ #elif defined(HAVE_GETHOSTBYNAME_R_3) - /* AIX, Digital Unix/Tru64, HPUX 10, more? */ + /* AIX, Digital UNIX/Tru64, HP-UX 10, more? */ - /* For AIX 4.3 or later, we don't use gethostbyname_r() at all, because of + /* For AIX 4.3 or later, we do not use gethostbyname_r() at all, because of * the plain fact that it does not return unique full buffers on each * call, but instead several of the pointers in the hostent structs will * point to the same actual data! This have the unfortunate down-side that @@ -237,7 +239,7 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, * * Troels Walsted Hansen helped us work this out on March 3rd, 2003. * - * [*] = much later we've found out that it isn't at all "completely + * [*] = much later we have found out that it is not at all "completely * thread-safe", but at least the gethostbyname() function is. */ @@ -253,7 +255,7 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, (struct hostent *)buf, (struct hostent_data *)((char *)buf + sizeof(struct hostent))); - h_errnop = SOCKERRNO; /* we don't deal with this, but set it anyway */ + h_errnop = SOCKERRNO; /* we do not deal with this, but set it anyway */ } else res = -1; /* failure, too smallish buffer size */ @@ -263,8 +265,8 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, h = buf; /* result expected in h */ /* This is the worst kind of the different gethostbyname_r() interfaces. - * Since we don't know how big buffer this particular lookup required, - * we can't realloc down the huge alloc without doing closer analysis of + * Since we do not know how big buffer this particular lookup required, + * we cannot realloc down the huge alloc without doing closer analysis of * the returned data. Thus, we always use CURL_HOSTENT_SIZE for every * name lookup. Fixing this would require an extra malloc() and then * calling Curl_addrinfo_copy() that subsequent realloc()s down the new @@ -280,7 +282,7 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, #else /* (HAVE_GETADDRINFO && HAVE_GETADDRINFO_THREADSAFE) || HAVE_GETHOSTBYNAME_R */ /* - * Here is code for platforms that don't have a thread safe + * Here is code for platforms that do not have a thread safe * getaddrinfo() nor gethostbyname_r() function or for which * gethostbyname() is the preferred one. */ @@ -288,12 +290,14 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, #endif /* (HAVE_GETADDRINFO && HAVE_GETADDRINFO_THREADSAFE) || HAVE_GETHOSTBYNAME_R */ +#if !(defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE)) if(h) { ai = Curl_he2ai(h, port); if(buf) /* used a *_r() function */ free(buf); } +#endif return ai; } diff --git a/deps/curl/lib/hostip6.c b/deps/curl/lib/hostip6.c index 6b0ba55e9f3..c16ddfe58d1 100644 --- a/deps/curl/lib/hostip6.c +++ b/deps/curl/lib/hostip6.c @@ -71,8 +71,7 @@ bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn) #if defined(CURLRES_SYNCH) #ifdef DEBUG_ADDRINFO -static void dump_addrinfo(struct connectdata *conn, - const struct Curl_addrinfo *ai) +static void dump_addrinfo(const struct Curl_addrinfo *ai) { printf("dump_addrinfo:\n"); for(; ai; ai = ai->ai_next) { @@ -84,7 +83,7 @@ static void dump_addrinfo(struct connectdata *conn, } } #else -#define dump_addrinfo(x,y) Curl_nop_stmt +#define dump_addrinfo(x) Curl_nop_stmt #endif /* @@ -125,7 +124,7 @@ struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, #ifndef USE_RESOLVE_ON_IPS /* * The AI_NUMERICHOST must not be set to get synthesized IPv6 address from - * an IPv4 address on iOS and Mac OS X. + * an IPv4 address on iOS and macOS. */ if((1 == Curl_inet_pton(AF_INET, hostname, addrbuf)) || (1 == Curl_inet_pton(AF_INET6, hostname, addrbuf))) { @@ -149,7 +148,7 @@ struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, Curl_addrinfo_set_port(res, port); } - dump_addrinfo(conn, res); + dump_addrinfo(res); return res; } diff --git a/deps/curl/lib/hsts.c b/deps/curl/lib/hsts.c index 7ecf0042a59..5b0137263b0 100644 --- a/deps/curl/lib/hsts.c +++ b/deps/curl/lib/hsts.c @@ -40,6 +40,7 @@ #include "fopen.h" #include "rename.h" #include "share.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -53,7 +54,7 @@ #define MAX_HSTS_DATELENSTR "64" #define UNLIMITED "unlimited" -#ifdef DEBUGBUILD +#if defined(DEBUGBUILD) || defined(UNITTESTS) /* to play well with debug builds, we can *set* a fixed time this will return */ time_t deltatime; /* allow for "adjustments" for unit test purposes */ @@ -76,7 +77,7 @@ static time_t hsts_debugtime(void *unused) struct hsts *Curl_hsts_init(void) { - struct hsts *h = calloc(sizeof(struct hsts), 1); + struct hsts *h = calloc(1, sizeof(struct hsts)); if(h) { Curl_llist_init(&h->list, NULL); } @@ -93,11 +94,11 @@ void Curl_hsts_cleanup(struct hsts **hp) { struct hsts *h = *hp; if(h) { - struct Curl_llist_element *e; - struct Curl_llist_element *n; - for(e = h->list.head; e; e = n) { - struct stsentry *sts = e->ptr; - n = e->next; + struct Curl_llist_node *e; + struct Curl_llist_node *n; + for(e = Curl_llist_head(&h->list); e; e = n) { + struct stsentry *sts = Curl_node_elem(e); + n = Curl_node_next(e); hsts_free(sts); } free(h->filename); @@ -106,37 +107,36 @@ void Curl_hsts_cleanup(struct hsts **hp) } } -static struct stsentry *hsts_entry(void) -{ - return calloc(sizeof(struct stsentry), 1); -} - static CURLcode hsts_create(struct hsts *h, const char *hostname, bool subdomains, curl_off_t expires) { - struct stsentry *sts = hsts_entry(); - char *duphost; size_t hlen; - if(!sts) - return CURLE_OUT_OF_MEMORY; + DEBUGASSERT(h); + DEBUGASSERT(hostname); + + hlen = strlen(hostname); + if(hlen && (hostname[hlen - 1] == '.')) + /* strip off any trailing dot */ + --hlen; + if(hlen) { + char *duphost; + struct stsentry *sts = calloc(1, sizeof(struct stsentry)); + if(!sts) + return CURLE_OUT_OF_MEMORY; + + duphost = Curl_memdup0(hostname, hlen); + if(!duphost) { + free(sts); + return CURLE_OUT_OF_MEMORY; + } - duphost = strdup(hostname); - if(!duphost) { - free(sts); - return CURLE_OUT_OF_MEMORY; + sts->host = duphost; + sts->expires = expires; + sts->includeSubDomains = subdomains; + Curl_llist_append(&h->list, sts, &sts->node); } - - hlen = strlen(duphost); - if(duphost[hlen - 1] == '.') - /* strip off trailing any dot */ - duphost[--hlen] = 0; - - sts->host = duphost; - sts->expires = expires; - sts->includeSubDomains = subdomains; - Curl_llist_insert_next(&h->list, h->list.tail, sts, &sts->node); return CURLE_OK; } @@ -159,7 +159,7 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, do { while(*p && ISBLANK(*p)) p++; - if(strncasecompare("max-age=", p, 8)) { + if(strncasecompare("max-age", p, 7)) { bool quoted = FALSE; CURLofft offt; char *endp; @@ -167,9 +167,14 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, if(gotma) return CURLE_BAD_FUNCTION_ARGUMENT; - p += 8; + p += 7; + while(*p && ISBLANK(*p)) + p++; + if(*p++ != '=') + return CURLE_BAD_FUNCTION_ARGUMENT; while(*p && ISBLANK(*p)) p++; + if(*p == '\"') { p++; quoted = TRUE; @@ -215,7 +220,7 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, /* remove the entry if present verbatim (without subdomain match) */ sts = Curl_hsts(h, hostname, FALSE); if(sts) { - Curl_llist_remove(&h->list, &sts->node, NULL); + Curl_node_remove(&sts->node); hsts_free(sts); } return CURLE_OK; @@ -241,7 +246,7 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, } /* - * Return TRUE if the given host name is currently an HSTS one. + * Return TRUE if the given hostname is currently an HSTS one. * * The 'subdomain' argument tells the function if subdomain matching should be * attempted. @@ -249,45 +254,47 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, struct stsentry *Curl_hsts(struct hsts *h, const char *hostname, bool subdomain) { + struct stsentry *bestsub = NULL; if(h) { - char buffer[MAX_HSTS_HOSTLEN + 1]; time_t now = time(NULL); size_t hlen = strlen(hostname); - struct Curl_llist_element *e; - struct Curl_llist_element *n; + struct Curl_llist_node *e; + struct Curl_llist_node *n; + size_t blen = 0; if((hlen > MAX_HSTS_HOSTLEN) || !hlen) return NULL; - memcpy(buffer, hostname, hlen); if(hostname[hlen-1] == '.') /* remove the trailing dot */ --hlen; - buffer[hlen] = 0; - hostname = buffer; - for(e = h->list.head; e; e = n) { - struct stsentry *sts = e->ptr; - n = e->next; + for(e = Curl_llist_head(&h->list); e; e = n) { + struct stsentry *sts = Curl_node_elem(e); + size_t ntail; + n = Curl_node_next(e); if(sts->expires <= now) { /* remove expired entries */ - Curl_llist_remove(&h->list, &sts->node, NULL); + Curl_node_remove(&sts->node); hsts_free(sts); continue; } - if(subdomain && sts->includeSubDomains) { - size_t ntail = strlen(sts->host); - if(ntail < hlen) { - size_t offs = hlen - ntail; - if((hostname[offs-1] == '.') && - strncasecompare(&hostname[offs], sts->host, ntail)) - return sts; + ntail = strlen(sts->host); + if((subdomain && sts->includeSubDomains) && (ntail < hlen)) { + size_t offs = hlen - ntail; + if((hostname[offs-1] == '.') && + strncasecompare(&hostname[offs], sts->host, ntail) && + (ntail > blen)) { + /* save the tail match with the longest tail */ + bestsub = sts; + blen = ntail; } } - if(strcasecompare(hostname, sts->host)) + /* avoid strcasecompare because the host name is not null terminated */ + if((hlen == ntail) && strncasecompare(hostname, sts->host, hlen)) return sts; } } - return NULL; /* no match */ + return bestsub; } /* @@ -353,8 +360,8 @@ static CURLcode hsts_out(struct stsentry *sts, FILE *fp) CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, const char *file) { - struct Curl_llist_element *e; - struct Curl_llist_element *n; + struct Curl_llist_node *e; + struct Curl_llist_node *n; CURLcode result = CURLE_OK; FILE *out; char *tempstore = NULL; @@ -368,7 +375,7 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, file = h->filename; if((h->flags & CURLHSTS_READONLYFILE) || !file || !file[0]) - /* marked as read-only, no file or zero length file name */ + /* marked as read-only, no file or zero length filename */ goto skipsave; result = Curl_fopen(data, file, &out, &tempstore); @@ -376,9 +383,9 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, fputs("# Your HSTS cache. https://curl.se/docs/hsts.html\n" "# This file was generated by libcurl! Edit at your own risk.\n", out); - for(e = h->list.head; e; e = n) { - struct stsentry *sts = e->ptr; - n = e->next; + for(e = Curl_llist_head(&h->list); e; e = n) { + struct stsentry *sts = Curl_node_elem(e); + n = Curl_node_next(e); result = hsts_out(sts, out); if(result) break; @@ -393,14 +400,14 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, free(tempstore); skipsave: if(data->set.hsts_write) { - /* if there's a write callback */ + /* if there is a write callback */ struct curl_index i; /* count */ - i.total = h->list.size; + i.total = Curl_llist_count(&h->list); i.index = 0; - for(e = h->list.head; e; e = n) { - struct stsentry *sts = e->ptr; + for(e = Curl_llist_head(&h->list); e; e = n) { + struct stsentry *sts = Curl_node_elem(e); bool stop; - n = e->next; + n = Curl_node_next(e); result = hsts_push(data, &i, sts, &stop); if(result || stop) break; @@ -439,8 +446,8 @@ static CURLcode hsts_add(struct hsts *h, char *line) e = Curl_hsts(h, p, subdomain); if(!e) result = hsts_create(h, p, subdomain, expires); - else { - /* the same host name, use the largest expire time */ + else if(strcasecompare(p, e->host)) { + /* the same hostname, use the largest expire time */ if(expires > e->expires) e->expires = expires; } @@ -473,6 +480,7 @@ static CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h) if(sc == CURLSTS_OK) { time_t expires; CURLcode result; + DEBUGASSERT(e.name[0]); if(!e.name[0]) /* bail out if no name was stored */ return CURLE_BAD_FUNCTION_ARGUMENT; @@ -505,10 +513,9 @@ static CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h) static CURLcode hsts_load(struct hsts *h, const char *file) { CURLcode result = CURLE_OK; - char *line = NULL; FILE *fp; - /* we need a private copy of the file name so that the hsts cache file + /* we need a private copy of the filename so that the hsts cache file name survives an easy handle reset */ free(h->filename); h->filename = strdup(file); @@ -517,28 +524,25 @@ static CURLcode hsts_load(struct hsts *h, const char *file) fp = fopen(file, FOPEN_READTEXT); if(fp) { - line = malloc(MAX_HSTS_LINE); - if(!line) - goto fail; - while(Curl_get_line(line, MAX_HSTS_LINE, fp)) { - char *lineptr = line; + struct dynbuf buf; + Curl_dyn_init(&buf, MAX_HSTS_LINE); + while(Curl_get_line(&buf, fp)) { + char *lineptr = Curl_dyn_ptr(&buf); while(*lineptr && ISBLANK(*lineptr)) lineptr++; - if(*lineptr == '#') - /* skip commented lines */ + /* + * Skip empty or commented lines, since we know the line will have a + * trailing newline from Curl_get_line we can treat length 1 as empty. + */ + if((*lineptr == '#') || strlen(lineptr) <= 1) continue; hsts_add(h, lineptr); } - free(line); /* free the line buffer */ + Curl_dyn_free(&buf); /* free the line buffer */ fclose(fp); } return result; - -fail: - Curl_safefree(h->filename); - fclose(fp); - return CURLE_OUT_OF_MEMORY; } /* @@ -564,7 +568,7 @@ CURLcode Curl_hsts_loadcb(struct Curl_easy *data, struct hsts *h) void Curl_hsts_loadfiles(struct Curl_easy *data) { - struct curl_slist *l = data->set.hstslist; + struct curl_slist *l = data->state.hstslist; if(l) { Curl_share_lock(data, CURL_LOCK_DATA_HSTS, CURL_LOCK_ACCESS_SINGLE); diff --git a/deps/curl/lib/hsts.h b/deps/curl/lib/hsts.h index d3431a5d7a3..1c544f97bd8 100644 --- a/deps/curl/lib/hsts.h +++ b/deps/curl/lib/hsts.h @@ -29,18 +29,18 @@ #include #include "llist.h" -#ifdef DEBUGBUILD +#if defined(DEBUGBUILD) || defined(UNITTESTS) extern time_t deltatime; #endif struct stsentry { - struct Curl_llist_element node; + struct Curl_llist_node node; const char *host; bool includeSubDomains; curl_off_t expires; /* the timestamp of this entry's expiry */ }; -/* The HSTS cache. Needs to be able to tailmatch host names. */ +/* The HSTS cache. Needs to be able to tailmatch hostnames. */ struct hsts { struct Curl_llist list; char *filename; diff --git a/deps/curl/lib/http.c b/deps/curl/lib/http.c index 4344b9dae58..a94edd61c23 100644 --- a/deps/curl/lib/http.c +++ b/deps/curl/lib/http.c @@ -65,7 +65,6 @@ #include "vquic/vquic.h" #include "http_digest.h" #include "http_ntlm.h" -#include "curl_ntlm_wb.h" #include "http_negotiate.h" #include "http_aws_sigv4.h" #include "url.h" @@ -73,6 +72,7 @@ #include "hostip.h" #include "dynhds.h" #include "http.h" +#include "headers.h" #include "select.h" #include "parsedate.h" /* for the week day and month names */ #include "strtoofft.h" @@ -100,24 +100,17 @@ * Forward declarations. */ -static int http_getsock_do(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks); -static bool http_should_fail(struct Curl_easy *data); - -static CURLcode http_setup_conn(struct Curl_easy *data, - struct connectdata *conn); -#ifdef USE_WEBSOCKETS -static CURLcode ws_setup_conn(struct Curl_easy *data, - struct connectdata *conn); -#endif +static bool http_should_fail(struct Curl_easy *data, int httpcode); +static bool http_exp100_is_waiting(struct Curl_easy *data); +static CURLcode http_exp100_add_reader(struct Curl_easy *data); +static void http_exp100_send_anyway(struct Curl_easy *data); /* * HTTP handler interface. */ const struct Curl_handler Curl_handler_http = { - "HTTP", /* scheme */ - http_setup_conn, /* setup_connection */ + "http", /* scheme */ + Curl_http_setup_conn, /* setup_connection */ Curl_http, /* do_it */ Curl_http_done, /* done */ ZERO_NULL, /* do_more */ @@ -125,11 +118,12 @@ const struct Curl_handler Curl_handler_http = { ZERO_NULL, /* connecting */ ZERO_NULL, /* doing */ ZERO_NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ + Curl_http_getsock_do, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_HTTP, /* defport */ @@ -139,39 +133,13 @@ const struct Curl_handler Curl_handler_http = { PROTOPT_USERPWDCTRL }; -#ifdef USE_WEBSOCKETS -const struct Curl_handler Curl_handler_ws = { - "WS", /* scheme */ - ws_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - Curl_http_connect, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - Curl_ws_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTP, /* defport */ - CURLPROTO_WS, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_CREDSPERREQUEST | /* flags */ - PROTOPT_USERPWDCTRL -}; -#endif - #ifdef USE_SSL /* * HTTPS handler interface. */ const struct Curl_handler Curl_handler_https = { - "HTTPS", /* scheme */ - http_setup_conn, /* setup_connection */ + "https", /* scheme */ + Curl_http_setup_conn, /* setup_connection */ Curl_http, /* do_it */ Curl_http_done, /* done */ ZERO_NULL, /* do_more */ @@ -179,11 +147,12 @@ const struct Curl_handler Curl_handler_https = { NULL, /* connecting */ ZERO_NULL, /* doing */ NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ + Curl_http_getsock_do, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_HTTPS, /* defport */ @@ -193,47 +162,13 @@ const struct Curl_handler Curl_handler_https = { PROTOPT_USERPWDCTRL }; -#ifdef USE_WEBSOCKETS -const struct Curl_handler Curl_handler_wss = { - "WSS", /* scheme */ - ws_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - Curl_http_connect, /* connect_it */ - NULL, /* connecting */ - ZERO_NULL, /* doing */ - NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - Curl_ws_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTPS, /* defport */ - CURLPROTO_WSS, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */ - PROTOPT_USERPWDCTRL -}; -#endif - #endif -static CURLcode http_setup_conn(struct Curl_easy *data, - struct connectdata *conn) +CURLcode Curl_http_setup_conn(struct Curl_easy *data, + struct connectdata *conn) { /* allocate the HTTP-specific struct for the Curl_easy, only to survive during this request */ - struct HTTP *http; - DEBUGASSERT(data->req.p.http == NULL); - - http = calloc(1, sizeof(struct HTTP)); - if(!http) - return CURLE_OUT_OF_MEMORY; - - data->req.p.http = http; connkeep(conn, "HTTP default"); if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) { @@ -245,16 +180,6 @@ static CURLcode http_setup_conn(struct Curl_easy *data, return CURLE_OK; } -#ifdef USE_WEBSOCKETS -static CURLcode ws_setup_conn(struct Curl_easy *data, - struct connectdata *conn) -{ - /* websockets is 1.1 only (for now) */ - data->state.httpwant = CURL_HTTP_VERSION_1_1; - return http_setup_conn(data, conn); -} -#endif - #ifndef CURL_DISABLE_PROXY /* * checkProxyHeaders() checks the linked list of custom proxy headers @@ -297,7 +222,6 @@ char *Curl_copy_header_value(const char *header) { const char *start; const char *end; - char *value; size_t len; /* Find the end of the header name */ @@ -313,8 +237,6 @@ char *Curl_copy_header_value(const char *header) while(*start && ISSPACE(*start)) start++; - /* data is in the host encoding so - use '\r' and '\n' instead of 0x0d and 0x0a */ end = strchr(start, '\r'); if(!end) end = strchr(start, '\n'); @@ -330,14 +252,7 @@ char *Curl_copy_header_value(const char *header) /* get length of the type */ len = end - start + 1; - value = malloc(len + 1); - if(!value) - return NULL; - - memcpy(value, start, len); - value[len] = 0; /* null-terminate */ - - return value; + return Curl_memdup0(start, len); } #ifndef CURL_DISABLE_HTTP_AUTH @@ -462,8 +377,6 @@ static bool pickoneauth(struct auth *pick, unsigned long mask) #endif else if(avail & CURLAUTH_NTLM) pick->picked = CURLAUTH_NTLM; - else if(avail & CURLAUTH_NTLM_WB) - pick->picked = CURLAUTH_NTLM_WB; #ifndef CURL_DISABLE_BASIC_AUTH else if(avail & CURLAUTH_BASIC) pick->picked = CURLAUTH_BASIC; @@ -484,150 +397,85 @@ static bool pickoneauth(struct auth *pick, unsigned long mask) /* * http_perhapsrewind() * - * If we are doing POST or PUT { - * If we have more data to send { - * If we are doing NTLM { - * Keep sending since we must not disconnect - * } - * else { - * If there is more than just a little data left to send, close - * the current connection by force. - * } - * } - * If we have sent any data { - * If we don't have track of all the data { - * call app to tell it to rewind - * } - * else { - * rewind internally so that the operation can restart fine - * } - * } - * } + * The current request needs to be done again - maybe due to a follow + * or authentication negotiation. Check if: + * 1) a rewind of the data sent to the server is necessary + * 2) the current transfer should continue or be stopped early */ static CURLcode http_perhapsrewind(struct Curl_easy *data, struct connectdata *conn) { - struct HTTP *http = data->req.p.http; - curl_off_t bytessent; - curl_off_t expectsend = -1; /* default is unknown */ - - if(!http) - /* If this is still NULL, we have not reach very far and we can safely - skip this rewinding stuff */ - return CURLE_OK; - - switch(data->state.httpreq) { - case HTTPREQ_GET: - case HTTPREQ_HEAD: + curl_off_t bytessent = data->req.writebytecount; + curl_off_t expectsend = Curl_creader_total_length(data); + curl_off_t upload_remain = (expectsend >= 0) ? (expectsend - bytessent) : -1; + bool little_upload_remains = (upload_remain >= 0 && upload_remain < 2000); + bool needs_rewind = Curl_creader_needs_rewind(data); + /* By default, we would like to abort the transfer when little or unknown + * amount remains. This may be overridden by authentications further + * below! */ + bool abort_upload = (!data->req.upload_done && !little_upload_remains); + const char *ongoing_auth = NULL; + + /* We need a rewind before uploading client read data again. The + * checks below just influence of the upload is to be continued + * or aborted early. + * This depends on how much remains to be sent and in what state + * the authentication is. Some auth schemes such as NTLM do not work + * for a new connection. */ + if(needs_rewind) { + infof(data, "Need to rewind upload for next request"); + Curl_creader_set_rewind(data, TRUE); + } + + if(conn->bits.close) + /* If we already decided to close this connection, we cannot veto. */ return CURLE_OK; - default: - break; - } - - bytessent = data->req.writebytecount; - - if(conn->bits.authneg) { - /* This is a state where we are known to be negotiating and we don't send - any data then. */ - expectsend = 0; - } - else if(!conn->bits.protoconnstart) { - /* HTTP CONNECT in progress: there is no body */ - expectsend = 0; - } - else { - /* figure out how much data we are expected to send */ - switch(data->state.httpreq) { - case HTTPREQ_POST: - case HTTPREQ_PUT: - if(data->state.infilesize != -1) - expectsend = data->state.infilesize; - break; - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - expectsend = http->postsize; - break; - default: - break; - } - } - data->state.rewindbeforesend = FALSE; /* default */ - - if((expectsend == -1) || (expectsend > bytessent)) { + if(abort_upload) { + /* We'd like to abort the upload - but should we? */ #if defined(USE_NTLM) - /* There is still data left to send */ if((data->state.authproxy.picked == CURLAUTH_NTLM) || - (data->state.authhost.picked == CURLAUTH_NTLM) || - (data->state.authproxy.picked == CURLAUTH_NTLM_WB) || - (data->state.authhost.picked == CURLAUTH_NTLM_WB)) { - if(((expectsend - bytessent) < 2000) || - (conn->http_ntlm_state != NTLMSTATE_NONE) || + (data->state.authhost.picked == CURLAUTH_NTLM)) { + ongoing_auth = "NTML"; + if((conn->http_ntlm_state != NTLMSTATE_NONE) || (conn->proxy_ntlm_state != NTLMSTATE_NONE)) { - /* The NTLM-negotiation has started *OR* there is just a little (<2K) - data left to send, keep on sending. */ - - /* rewind data when completely done sending! */ - if(!conn->bits.authneg && (conn->writesockfd != CURL_SOCKET_BAD)) { - data->state.rewindbeforesend = TRUE; - infof(data, "Rewind stream before next send"); - } - - return CURLE_OK; + /* The NTLM-negotiation has started, keep on sending. + * Need to do further work on same connection */ + abort_upload = FALSE; } - - if(conn->bits.close) - /* this is already marked to get closed */ - return CURLE_OK; - - infof(data, "NTLM send, close instead of sending %" - CURL_FORMAT_CURL_OFF_T " bytes", - (curl_off_t)(expectsend - bytessent)); } #endif #if defined(USE_SPNEGO) /* There is still data left to send */ if((data->state.authproxy.picked == CURLAUTH_NEGOTIATE) || (data->state.authhost.picked == CURLAUTH_NEGOTIATE)) { - if(((expectsend - bytessent) < 2000) || - (conn->http_negotiate_state != GSS_AUTHNONE) || + ongoing_auth = "NEGOTIATE"; + if((conn->http_negotiate_state != GSS_AUTHNONE) || (conn->proxy_negotiate_state != GSS_AUTHNONE)) { - /* The NEGOTIATE-negotiation has started *OR* - there is just a little (<2K) data left to send, keep on sending. */ - - /* rewind data when completely done sending! */ - if(!conn->bits.authneg && (conn->writesockfd != CURL_SOCKET_BAD)) { - data->state.rewindbeforesend = TRUE; - infof(data, "Rewind stream before next send"); - } - - return CURLE_OK; + /* The NEGOTIATE-negotiation has started, keep on sending. + * Need to do further work on same connection */ + abort_upload = FALSE; } - - if(conn->bits.close) - /* this is already marked to get closed */ - return CURLE_OK; - - infof(data, "NEGOTIATE send, close instead of sending %" - CURL_FORMAT_CURL_OFF_T " bytes", - (curl_off_t)(expectsend - bytessent)); } #endif - - /* This is not NEGOTIATE/NTLM or many bytes left to send: close */ - streamclose(conn, "Mid-auth HTTP and much data left to send"); - data->req.size = 0; /* don't download any more than 0 bytes */ - - /* There still is data left to send, but this connection is marked for - closure so we can safely do the rewind right now */ } - if(bytessent) { - /* mark for rewind since if we already sent something */ - data->state.rewindbeforesend = TRUE; - infof(data, "Please rewind output before next send"); + if(abort_upload) { + if(upload_remain >= 0) + infof(data, "%s%sclose instead of sending %" FMT_OFF_T " more bytes", + ongoing_auth ? ongoing_auth : "", + ongoing_auth ? " send, " : "", + upload_remain); + else + infof(data, "%s%sclose instead of sending unknown amount " + "of more bytes", + ongoing_auth ? ongoing_auth : "", + ongoing_auth ? " send, " : ""); + /* We decided to abort the ongoing transfer */ + streamclose(conn, "Mid-auth HTTP and much data left to send"); + /* FIXME: questionable manipulation here, can we do this differently? */ + data->req.size = 0; /* do not download any more than 0 bytes */ } - return CURLE_OK; } @@ -654,11 +502,11 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) return CURLE_OK; if(data->state.authproblem) - return data->set.http_fail_on_error?CURLE_HTTP_RETURNED_ERROR:CURLE_OK; + return data->set.http_fail_on_error ? CURLE_HTTP_RETURNED_ERROR : CURLE_OK; if((data->state.aptr.user || data->set.str[STRING_BEARER]) && ((data->req.httpcode == 401) || - (conn->bits.authneg && data->req.httpcode < 300))) { + (data->req.authneg && data->req.httpcode < 300))) { pickhost = pickoneauth(&data->state.authhost, authmask); if(!pickhost) data->state.authproblem = TRUE; @@ -672,7 +520,7 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) #ifndef CURL_DISABLE_PROXY if(conn->bits.proxy_user_passwd && ((data->req.httpcode == 407) || - (conn->bits.authneg && data->req.httpcode < 300))) { + (data->req.authneg && data->req.httpcode < 300))) { pickproxy = pickoneauth(&data->state.authproxy, authmask & ~CURLAUTH_BEARER); if(!pickproxy) @@ -681,13 +529,10 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) #endif if(pickhost || pickproxy) { - if((data->state.httpreq != HTTPREQ_GET) && - (data->state.httpreq != HTTPREQ_HEAD) && - !data->state.rewindbeforesend) { - result = http_perhapsrewind(data, conn); - if(result) - return result; - } + result = http_perhapsrewind(data, conn); + if(result) + return result; + /* In case this is GSS auth, the newurl field is already allocated so we must make sure to free it before allocating a new one. As figured out in bug #2284386 */ @@ -698,11 +543,11 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) } else if((data->req.httpcode < 300) && (!data->state.authhost.done) && - conn->bits.authneg) { + data->req.authneg) { /* no (known) authentication available, authentication is not "done" yet and no authentication seems to be required and - we didn't try HEAD or GET */ + we did not try HEAD or GET */ if((data->state.httpreq != HTTPREQ_GET) && (data->state.httpreq != HTTPREQ_HEAD)) { data->req.newurl = strdup(data->state.url); /* clone URL */ @@ -711,7 +556,7 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) data->state.authhost.done = TRUE; } } - if(http_should_fail(data)) { + if(http_should_fail(data, data->req.httpcode)) { failf(data, "The requested URL returned error: %d", data->req.httpcode); result = CURLE_HTTP_RETURNED_ERROR; @@ -768,15 +613,6 @@ output_auth_headers(struct Curl_easy *data, } else #endif -#if defined(USE_NTLM) && defined(NTLM_WB_ENABLED) - if(authstatus->picked == CURLAUTH_NTLM_WB) { - auth = "NTLM_WB"; - result = Curl_output_ntlm_wb(data, conn, proxy); - if(result) - return result; - } - else -#endif #ifndef CURL_DISABLE_DIGEST_AUTH if(authstatus->picked == CURLAUTH_DIGEST) { auth = "Digest"; @@ -836,16 +672,17 @@ output_auth_headers(struct Curl_easy *data, (data->state.aptr.user ? data->state.aptr.user : "")); #else + (void)proxy; infof(data, "Server auth using %s with user '%s'", auth, data->state.aptr.user ? data->state.aptr.user : ""); #endif - authstatus->multipass = (!authstatus->done) ? TRUE : FALSE; + authstatus->multipass = !authstatus->done; } else authstatus->multipass = FALSE; - return CURLE_OK; + return result; } /** @@ -900,13 +737,13 @@ Curl_http_output_auth(struct Curl_easy *data, if(authhost->want && !authhost->picked) /* The app has selected one or more methods, but none has been picked so far by a server round-trip. Then we set the picked one to the - want one, and if this is one single bit it'll be used instantly. */ + want one, and if this is one single bit it will be used instantly. */ authhost->picked = authhost->want; if(authproxy->want && !authproxy->picked) /* The app has selected one or more methods, but none has been picked so far by a proxy round-trip. Then we set the picked one to the want one, - and if this is one single bit it'll be used instantly. */ + and if this is one single bit it will be used instantly. */ authproxy->picked = authproxy->want; #ifndef CURL_DISABLE_PROXY @@ -921,7 +758,7 @@ Curl_http_output_auth(struct Curl_easy *data, #else (void)proxytunnel; #endif /* CURL_DISABLE_PROXY */ - /* we have no proxy so let's pretend we're done authenticating + /* we have no proxy so let's pretend we are done authenticating with it */ authproxy->done = TRUE; @@ -942,10 +779,10 @@ Curl_http_output_auth(struct Curl_easy *data, (httpreq != HTTPREQ_HEAD)) { /* Auth is required and we are not authenticated yet. Make a PUT or POST with content-length zero as a "probe". */ - conn->bits.authneg = TRUE; + data->req.authneg = TRUE; } else - conn->bits.authneg = FALSE; + data->req.authneg = FALSE; return result; } @@ -970,17 +807,21 @@ Curl_http_output_auth(struct Curl_easy *data, } #endif -/* - * Curl_http_input_auth() deals with Proxy-Authenticate: and WWW-Authenticate: - * headers. They are dealt with both in the transfer.c main loop and in the - * proxy CONNECT loop. - */ - +#if defined(USE_SPNEGO) || defined(USE_NTLM) || \ + !defined(CURL_DISABLE_DIGEST_AUTH) || \ + !defined(CURL_DISABLE_BASIC_AUTH) || \ + !defined(CURL_DISABLE_BEARER_AUTH) static int is_valid_auth_separator(char ch) { return ch == '\0' || ch == ',' || ISSPACE(ch); } +#endif +/* + * Curl_http_input_auth() deals with Proxy-Authenticate: and WWW-Authenticate: + * headers. They are dealt with both in the transfer.c main loop and in the + * proxy CONNECT loop. + */ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, const char *auth) /* the first non-space */ { @@ -992,11 +833,15 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, curlnegotiate *negstate = proxy ? &conn->proxy_negotiate_state : &conn->http_negotiate_state; #endif +#if defined(USE_SPNEGO) || \ + defined(USE_NTLM) || \ + !defined(CURL_DISABLE_DIGEST_AUTH) || \ + !defined(CURL_DISABLE_BASIC_AUTH) || \ + !defined(CURL_DISABLE_BEARER_AUTH) + unsigned long *availp; struct auth *authp; - (void) conn; /* In case conditionals make it unused. */ - if(proxy) { availp = &data->info.proxyauthavail; authp = &data->state.authproxy; @@ -1005,6 +850,11 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, availp = &data->info.httpauthavail; authp = &data->state.authhost; } +#else + (void) proxy; +#endif + + (void) conn; /* In case conditionals make it unused. */ /* * Here we check if we want the specific single authentication (using ==) and @@ -1052,31 +902,15 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, /* NTLM support requires the SSL crypto libs */ if(checkprefix("NTLM", auth) && is_valid_auth_separator(auth[4])) { if((authp->avail & CURLAUTH_NTLM) || - (authp->avail & CURLAUTH_NTLM_WB) || Curl_auth_is_ntlm_supported()) { *availp |= CURLAUTH_NTLM; authp->avail |= CURLAUTH_NTLM; - if(authp->picked == CURLAUTH_NTLM || - authp->picked == CURLAUTH_NTLM_WB) { + if(authp->picked == CURLAUTH_NTLM) { /* NTLM authentication is picked and activated */ CURLcode result = Curl_input_ntlm(data, proxy, auth); if(!result) { data->state.authproblem = FALSE; -#ifdef NTLM_WB_ENABLED - if(authp->picked == CURLAUTH_NTLM_WB) { - *availp &= ~CURLAUTH_NTLM; - authp->avail &= ~CURLAUTH_NTLM; - *availp |= CURLAUTH_NTLM_WB; - authp->avail |= CURLAUTH_NTLM_WB; - - result = Curl_input_ntlm_wb(data, conn, proxy, auth); - if(result) { - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } -#endif } else { infof(data, "Authentication problem. Ignoring this."); @@ -1098,7 +932,7 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, authp->avail |= CURLAUTH_DIGEST; /* We call this function on input Digest headers even if Digest - * authentication isn't activated yet, as we need to store the + * authentication is not activated yet, as we need to store the * incoming data from this header in case we are going to use * Digest */ result = Curl_input_digest(data, proxy, auth); @@ -1117,7 +951,7 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, authp->avail |= CURLAUTH_BASIC; if(authp->picked == CURLAUTH_BASIC) { /* We asked for Basic authentication but got a 40X back - anyway, which basically means our name+password isn't + anyway, which basically means our name+password is not valid. */ authp->avail = CURLAUTH_NONE; infof(data, "Authentication problem. Ignoring this."); @@ -1133,18 +967,27 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, authp->avail |= CURLAUTH_BEARER; if(authp->picked == CURLAUTH_BEARER) { /* We asked for Bearer authentication but got a 40X back - anyway, which basically means our token isn't valid. */ + anyway, which basically means our token is not valid. */ authp->avail = CURLAUTH_NONE; infof(data, "Authentication problem. Ignoring this."); data->state.authproblem = TRUE; } } +#else + { + /* + * Empty block to terminate the if-else chain correctly. + * + * A semicolon would yield the same result here, but can cause a + * compiler warning when -Wextra is enabled. + */ + } #endif /* there may be multiple methods on one line, so keep reading */ while(*auth && *auth != ',') /* read up to the next comma */ auth++; - if(*auth == ',') /* if we're on a comma, skip it */ + if(*auth == ',') /* if we are on a comma, skip it */ auth++; while(*auth && ISSPACE(*auth)) auth++; @@ -1154,26 +997,21 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, } /** - * http_should_fail() determines whether an HTTP response has gotten us + * http_should_fail() determines whether an HTTP response code has gotten us * into an error state or not. * - * @param conn all information about the current connection - * * @retval FALSE communications should continue * * @retval TRUE communications should not continue */ -static bool http_should_fail(struct Curl_easy *data) +static bool http_should_fail(struct Curl_easy *data, int httpcode) { - int httpcode; DEBUGASSERT(data); DEBUGASSERT(data->conn); - httpcode = data->req.httpcode; - /* - ** If we haven't been asked to fail on error, - ** don't fail. + ** If we have not been asked to fail on error, + ** do not fail. */ if(!data->set.http_fail_on_error) return FALSE; @@ -1193,7 +1031,7 @@ static bool http_should_fail(struct Curl_easy *data) return FALSE; /* - ** Any code >= 400 that's not 401 or 407 is always + ** Any code >= 400 that is not 401 or 407 is always ** a terminal error */ if((httpcode != 401) && (httpcode != 407)) @@ -1205,22 +1043,19 @@ static bool http_should_fail(struct Curl_easy *data) DEBUGASSERT((httpcode == 401) || (httpcode == 407)); /* - ** Examine the current authentication state to see if this - ** is an error. The idea is for this function to get - ** called after processing all the headers in a response - ** message. So, if we've been to asked to authenticate a - ** particular stage, and we've done it, we're OK. But, if - ** we're already completely authenticated, it's not OK to - ** get another 401 or 407. + ** Examine the current authentication state to see if this is an error. The + ** idea is for this function to get called after processing all the headers + ** in a response message. So, if we have been to asked to authenticate a + ** particular stage, and we have done it, we are OK. If we are already + ** completely authenticated, it is not OK to get another 401 or 407. ** - ** It is possible for authentication to go stale such that - ** the client needs to reauthenticate. Once that info is - ** available, use it here. + ** It is possible for authentication to go stale such that the client needs + ** to reauthenticate. Once that info is available, use it here. */ /* - ** Either we're not authenticating, or we're supposed to - ** be authenticating something else. This is an error. + ** Either we are not authenticating, or we are supposed to be authenticating + ** something else. This is an error. */ if((httpcode == 401) && !data->state.aptr.user) return TRUE; @@ -1232,274 +1067,6 @@ static bool http_should_fail(struct Curl_easy *data) return data->state.authproblem; } -/* - * readmoredata() is a "fread() emulation" to provide POST and/or request - * data. It is used when a huge POST is to be made and the entire chunk wasn't - * sent in the first send(). This function will then be called from the - * transfer.c loop when more data is to be sent to the peer. - * - * Returns the amount of bytes it filled the buffer with. - */ -static size_t readmoredata(char *buffer, - size_t size, - size_t nitems, - void *userp) -{ - struct HTTP *http = (struct HTTP *)userp; - struct Curl_easy *data = http->backup.data; - size_t fullsize = size * nitems; - - if(!http->postsize) - /* nothing to return */ - return 0; - - /* make sure that an HTTP request is never sent away chunked! */ - data->req.forbidchunk = (http->sending == HTTPSEND_REQUEST)?TRUE:FALSE; - - if(data->set.max_send_speed && - (data->set.max_send_speed < (curl_off_t)fullsize) && - (data->set.max_send_speed < http->postsize)) - /* speed limit */ - fullsize = (size_t)data->set.max_send_speed; - - else if(http->postsize <= (curl_off_t)fullsize) { - memcpy(buffer, http->postdata, (size_t)http->postsize); - fullsize = (size_t)http->postsize; - - if(http->backup.postsize) { - /* move backup data into focus and continue on that */ - http->postdata = http->backup.postdata; - http->postsize = http->backup.postsize; - data->state.fread_func = http->backup.fread_func; - data->state.in = http->backup.fread_in; - - http->sending++; /* move one step up */ - - http->backup.postsize = 0; - } - else - http->postsize = 0; - - return fullsize; - } - - memcpy(buffer, http->postdata, fullsize); - http->postdata += fullsize; - http->postsize -= fullsize; - - return fullsize; -} - -/* - * Curl_buffer_send() sends a header buffer and frees all associated - * memory. Body data may be appended to the header data if desired. - * - * Returns CURLcode - */ -CURLcode Curl_buffer_send(struct dynbuf *in, - struct Curl_easy *data, - struct HTTP *http, - /* add the number of sent bytes to this - counter */ - curl_off_t *bytes_written, - /* how much of the buffer contains body data */ - curl_off_t included_body_bytes, - int sockindex) -{ - ssize_t amount; - CURLcode result; - char *ptr; - size_t size; - struct connectdata *conn = data->conn; - size_t sendsize; - size_t headersize; - - DEBUGASSERT(sockindex <= SECONDARYSOCKET && sockindex >= 0); - - /* The looping below is required since we use non-blocking sockets, but due - to the circumstances we will just loop and try again and again etc */ - - ptr = Curl_dyn_ptr(in); - size = Curl_dyn_len(in); - - headersize = size - (size_t)included_body_bytes; /* the initial part that - isn't body is header */ - - DEBUGASSERT(size > (size_t)included_body_bytes); - - if((conn->handler->flags & PROTOPT_SSL -#ifndef CURL_DISABLE_PROXY - || IS_HTTPS_PROXY(conn->http_proxy.proxytype) -#endif - ) - && conn->httpversion < 20) { - /* Make sure this doesn't send more body bytes than what the max send - speed says. The request bytes do not count to the max speed. - */ - if(data->set.max_send_speed && - (included_body_bytes > data->set.max_send_speed)) { - curl_off_t overflow = included_body_bytes - data->set.max_send_speed; - DEBUGASSERT((size_t)overflow < size); - sendsize = size - (size_t)overflow; - } - else - sendsize = size; - - /* OpenSSL is very picky and we must send the SAME buffer pointer to the - library when we attempt to re-send this buffer. Sending the same data - is not enough, we must use the exact same address. For this reason, we - must copy the data to the uploadbuffer first, since that is the buffer - we will be using if this send is retried later. - */ - result = Curl_get_upload_buffer(data); - if(result) { - /* malloc failed, free memory and return to the caller */ - Curl_dyn_free(in); - return result; - } - /* We never send more than upload_buffer_size bytes in one single chunk - when we speak HTTPS, as if only a fraction of it is sent now, this data - needs to fit into the normal read-callback buffer later on and that - buffer is using this size. - */ - if(sendsize > (size_t)data->set.upload_buffer_size) - sendsize = (size_t)data->set.upload_buffer_size; - - memcpy(data->state.ulbuf, ptr, sendsize); - ptr = data->state.ulbuf; - } - else { -#ifdef CURLDEBUG - /* Allow debug builds to override this logic to force short initial - sends - */ - char *p = getenv("CURL_SMALLREQSEND"); - if(p) { - size_t altsize = (size_t)strtoul(p, NULL, 10); - if(altsize) - sendsize = CURLMIN(size, altsize); - else - sendsize = size; - } - else -#endif - { - /* Make sure this doesn't send more body bytes than what the max send - speed says. The request bytes do not count to the max speed. - */ - if(data->set.max_send_speed && - (included_body_bytes > data->set.max_send_speed)) { - curl_off_t overflow = included_body_bytes - data->set.max_send_speed; - DEBUGASSERT((size_t)overflow < size); - sendsize = size - (size_t)overflow; - } - else - sendsize = size; - } - - /* We currently cannot send more that this for http here: - * - if sending blocks, it return 0 as amount - * - we then whisk aside the `in` into the `http` struct - * and install our own `data->state.fread_func` that - * on subsequent calls reads `in` empty. - * - when the whisked away `in` is empty, the `fread_func` - * is restored ot its original state. - * The problem is that `fread_func` can only return - * `upload_buffer_size` lengths. If the send we do here - * is larger and blocks, we do re-sending with smaller - * amounts of data and connection filters do not like - * that. - */ - if(http && (sendsize > (size_t)data->set.upload_buffer_size)) - sendsize = (size_t)data->set.upload_buffer_size; - } - - result = Curl_nwrite(data, sockindex, ptr, sendsize, &amount); - - if(!result) { - /* - * Note that we may not send the entire chunk at once, and we have a set - * number of data bytes at the end of the big buffer (out of which we may - * only send away a part). - */ - /* how much of the header that was sent */ - size_t headlen = (size_t)amount>headersize ? headersize : (size_t)amount; - size_t bodylen = amount - headlen; - - /* this data _may_ contain binary stuff */ - Curl_debug(data, CURLINFO_HEADER_OUT, ptr, headlen); - if(bodylen) - /* there was body data sent beyond the initial header part, pass that on - to the debug callback too */ - Curl_debug(data, CURLINFO_DATA_OUT, ptr + headlen, bodylen); - - /* 'amount' can never be a very large value here so typecasting it so a - signed 31 bit value should not cause problems even if ssize_t is - 64bit */ - *bytes_written += (long)amount; - - if(http) { - /* if we sent a piece of the body here, up the byte counter for it - accordingly */ - data->req.writebytecount += bodylen; - Curl_pgrsSetUploadCounter(data, data->req.writebytecount); - - if((size_t)amount != size) { - /* The whole request could not be sent in one system call. We must - queue it up and send it later when we get the chance. We must not - loop here and wait until it might work again. */ - - size -= amount; - - ptr = Curl_dyn_ptr(in) + amount; - - /* backup the currently set pointers */ - http->backup.fread_func = data->state.fread_func; - http->backup.fread_in = data->state.in; - http->backup.postdata = http->postdata; - http->backup.postsize = http->postsize; - http->backup.data = data; - - /* set the new pointers for the request-sending */ - data->state.fread_func = (curl_read_callback)readmoredata; - data->state.in = (void *)http; - http->postdata = ptr; - http->postsize = (curl_off_t)size; - - /* this much data is remaining header: */ - data->req.pendingheader = headersize - headlen; - - http->send_buffer = *in; /* copy the whole struct */ - http->sending = HTTPSEND_REQUEST; - return CURLE_OK; - } - http->sending = HTTPSEND_BODY; - /* the full buffer was sent, clean up and return */ - } - else { - if((size_t)amount != size) - /* We have no continue-send mechanism now, fail. This can only happen - when this function is used from the CONNECT sending function. We - currently (stupidly) assume that the whole request is always sent - away in the first single chunk. - - This needs FIXing. - */ - return CURLE_SEND_ERROR; - } - } - Curl_dyn_free(in); - - /* no remaining header data */ - data->req.pendingheader = 0; - return result; -} - -/* end of the add_buffer functions */ -/* ------------------------------------------------------------------------- */ - - - /* * Curl_compareheader() * @@ -1527,7 +1094,7 @@ Curl_compareheader(const char *headerline, /* line to check */ DEBUGASSERT(content); if(!strncasecompare(headerline, header, hlen)) - return FALSE; /* doesn't start with header */ + return FALSE; /* does not start with header */ /* pass the header */ start = &headerline[hlen]; @@ -1539,11 +1106,11 @@ Curl_compareheader(const char *headerline, /* line to check */ /* find the end of the header line */ end = strchr(start, '\r'); /* lines end with CRLF */ if(!end) { - /* in case there's a non-standard compliant line here */ + /* in case there is a non-standard compliant line here */ end = strchr(start, '\n'); if(!end) - /* hm, there's no line ending here, use the zero byte! */ + /* hm, there is no line ending here, use the zero byte! */ end = strchr(start, '\0'); } @@ -1574,11 +1141,11 @@ CURLcode Curl_http_connect(struct Curl_easy *data, bool *done) } /* this returns the socket to wait for in the DO and DOING state for the multi - interface and then we're always _sending_ a request and thus we wait for + interface and then we are always _sending_ a request and thus we wait for the single socket to become writable only */ -static int http_getsock_do(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) +int Curl_http_getsock_do(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks) { /* write mode */ (void)conn; @@ -1595,26 +1162,14 @@ CURLcode Curl_http_done(struct Curl_easy *data, CURLcode status, bool premature) { struct connectdata *conn = data->conn; - struct HTTP *http = data->req.p.http; - /* Clear multipass flag. If authentication isn't done yet, then it will get + /* Clear multipass flag. If authentication is not done yet, then it will get * a chance to be set back to true when we output the next auth header */ data->state.authhost.multipass = FALSE; data->state.authproxy.multipass = FALSE; - Curl_unencode_cleanup(data); - - /* set the proper values (possibly modified on POST) */ - conn->seek_func = data->set.seek_func; /* restore */ - conn->seek_client = data->set.seek_client; /* restore */ - - if(!http) - return CURLE_OK; - - Curl_dyn_free(&http->send_buffer); Curl_dyn_reset(&data->state.headerb); Curl_hyper_done(data); - Curl_ws_done(data); if(status) return status; @@ -1626,8 +1181,8 @@ CURLcode Curl_http_done(struct Curl_easy *data, (data->req.bytecount + data->req.headerbytecount - data->req.deductheadercount) <= 0) { - /* If this connection isn't simply closed to be retried, AND nothing was - read from the HTTP server (that counts), this can't be right so we + /* If this connection is not simply closed to be retried, AND nothing was + read from the HTTP server (that counts), this cannot be right so we return an error here */ failf(data, "Empty reply from server"); /* Mark it as closed to avoid the "left intact" message */ @@ -1674,85 +1229,12 @@ static const char *get_http_string(const struct Curl_easy *data, } #endif -/* check and possibly add an Expect: header */ -static CURLcode expect100(struct Curl_easy *data, - struct connectdata *conn, - struct dynbuf *req) -{ - CURLcode result = CURLE_OK; - data->state.expect100header = FALSE; /* default to false unless it is set - to TRUE below */ - if(!data->state.disableexpect && Curl_use_http_1_1plus(data, conn) && - (conn->httpversion < 20)) { - /* if not doing HTTP 1.0 or version 2, or disabled explicitly, we add an - Expect: 100-continue to the headers which actually speeds up post - operations (as there is one packet coming back from the web server) */ - const char *ptr = Curl_checkheaders(data, STRCONST("Expect")); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue")); - } - else { - result = Curl_dyn_addn(req, STRCONST("Expect: 100-continue\r\n")); - if(!result) - data->state.expect100header = TRUE; - } - } - - return result; -} - enum proxy_use { HEADER_SERVER, /* direct to server */ HEADER_PROXY, /* regular request to proxy */ HEADER_CONNECT /* sending CONNECT to a proxy */ }; -/* used to compile the provided trailers into one buffer - will return an error code if one of the headers is - not formatted correctly */ -CURLcode Curl_http_compile_trailers(struct curl_slist *trailers, - struct dynbuf *b, - struct Curl_easy *handle) -{ - char *ptr = NULL; - CURLcode result = CURLE_OK; - const char *endofline_native = NULL; - const char *endofline_network = NULL; - - if( -#ifdef CURL_DO_LINEEND_CONV - (handle->state.prefer_ascii) || -#endif - (handle->set.crlf)) { - /* \n will become \r\n later on */ - endofline_native = "\n"; - endofline_network = "\x0a"; - } - else { - endofline_native = "\r\n"; - endofline_network = "\x0d\x0a"; - } - - while(trailers) { - /* only add correctly formatted trailers */ - ptr = strchr(trailers->data, ':'); - if(ptr && *(ptr + 1) == ' ') { - result = Curl_dyn_add(b, trailers->data); - if(result) - return result; - result = Curl_dyn_add(b, endofline_native); - if(result) - return result; - } - else - infof(handle, "Malformatted trailing header, skipping trailer"); - trailers = trailers->next; - } - result = Curl_dyn_add(b, endofline_network); - return result; -} - static bool hd_name_eq(const char *n1, size_t n1len, const char *n2, size_t n2len) { @@ -1779,8 +1261,8 @@ CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, if(is_connect) proxy = HEADER_CONNECT; else - proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy? - HEADER_PROXY:HEADER_SERVER; + proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy ? + HEADER_PROXY : HEADER_SERVER; switch(proxy) { case HEADER_SERVER: @@ -1859,7 +1341,7 @@ CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, DEBUGASSERT(name && value); if(data->state.aptr.host && - /* a Host: header was sent already, don't pass on any custom Host: + /* a Host: header was sent already, do not pass on any custom Host: header as that will produce *two* in the same request! */ hd_name_eq(name, namelen, STRCONST("Host:"))) ; @@ -1871,19 +1353,19 @@ CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, /* this header is sent later */ hd_name_eq(name, namelen, STRCONST("Content-Type:"))) ; - else if(conn->bits.authneg && - /* while doing auth neg, don't allow the custom length since + else if(data->req.authneg && + /* while doing auth neg, do not allow the custom length since we will force length zero then */ hd_name_eq(name, namelen, STRCONST("Content-Length:"))) ; else if(data->state.aptr.te && - /* when asking for Transfer-Encoding, don't pass on a custom + /* when asking for Transfer-Encoding, do not pass on a custom Connection: */ hd_name_eq(name, namelen, STRCONST("Connection:"))) ; else if((conn->httpversion >= 20) && hd_name_eq(name, namelen, STRCONST("Transfer-Encoding:"))) - /* HTTP/2 doesn't support chunked requests */ + /* HTTP/2 does not support chunked requests */ ; else if((hd_name_eq(name, namelen, STRCONST("Authorization:")) || hd_name_eq(name, namelen, STRCONST("Cookie:"))) && @@ -1926,8 +1408,8 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, if(is_connect) proxy = HEADER_CONNECT; else - proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy? - HEADER_PROXY:HEADER_SERVER; + proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy ? + HEADER_PROXY : HEADER_SERVER; switch(proxy) { case HEADER_SERVER: @@ -2005,8 +1487,9 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, char *compare = semicolonp ? semicolonp : headers->data; if(data->state.aptr.host && - /* a Host: header was sent already, don't pass on any custom Host: - header as that will produce *two* in the same request! */ + /* a Host: header was sent already, do not pass on any custom + Host: header as that will produce *two* in the same + request! */ checkprefix("Host:", compare)) ; else if(data->state.httpreq == HTTPREQ_POST_FORM && @@ -2017,19 +1500,19 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, /* this header is sent later */ checkprefix("Content-Type:", compare)) ; - else if(conn->bits.authneg && - /* while doing auth neg, don't allow the custom length since + else if(data->req.authneg && + /* while doing auth neg, do not allow the custom length since we will force length zero then */ checkprefix("Content-Length:", compare)) ; else if(data->state.aptr.te && - /* when asking for Transfer-Encoding, don't pass on a custom + /* when asking for Transfer-Encoding, do not pass on a custom Connection: */ checkprefix("Connection:", compare)) ; else if((conn->httpversion >= 20) && checkprefix("Transfer-Encoding:", compare)) - /* HTTP/2 doesn't support chunked requests */ + /* HTTP/2 does not support chunked requests */ ; else if((checkprefix("Authorization:", compare) || checkprefix("Cookie:", compare)) && @@ -2086,6 +1569,7 @@ CURLcode Curl_add_timecondition(struct Curl_easy *data, switch(data->set.timecondition) { default: + DEBUGF(infof(data, "invalid time condition")); return CURLE_BAD_FUNCTION_ARGUMENT; case CURL_TIMECOND_IFMODSINCE: @@ -2118,7 +1602,7 @@ CURLcode Curl_add_timecondition(struct Curl_easy *data, msnprintf(datestr, sizeof(datestr), "%s: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", condp, - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + Curl_wkday[tm->tm_wday ? tm->tm_wday-1 : 6], tm->tm_mday, Curl_month[tm->tm_mon], tm->tm_year + 1900, @@ -2220,10 +1704,10 @@ CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn) if(ptr && (!data->state.this_is_a_follow || strcasecompare(data->state.first_host, conn->host.name))) { #if !defined(CURL_DISABLE_COOKIES) - /* If we have a given custom Host: header, we extract the host name in + /* If we have a given custom Host: header, we extract the hostname in order to possibly use it for cookie reasons later on. We only allow the custom Host: header if this is NOT a redirect, as setting Host: in the - redirected request is being out on thin ice. Except if the host name + redirected request is being out on thin ice. Except if the hostname is the same as the first one! */ char *cookiehost = Curl_copy_header_value(ptr); if(!cookiehost) @@ -2254,32 +1738,33 @@ CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn) } #endif - if(strcmp("Host:", ptr)) { + if(!strcasecompare("Host:", ptr)) { aptr->host = aprintf("Host:%s\r\n", &ptr[5]); if(!aptr->host) return CURLE_OUT_OF_MEMORY; } } else { - /* When building Host: headers, we must put the host name within - [brackets] if the host name is a plain IPv6-address. RFC2732-style. */ + /* When building Host: headers, we must put the hostname within + [brackets] if the hostname is a plain IPv6-address. RFC2732-style. */ const char *host = conn->host.name; if(((conn->given->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS)) && (conn->remote_port == PORT_HTTPS)) || ((conn->given->protocol&(CURLPROTO_HTTP|CURLPROTO_WS)) && (conn->remote_port == PORT_HTTP)) ) - /* if(HTTPS on port 443) OR (HTTP on port 80) then don't include + /* if(HTTPS on port 443) OR (HTTP on port 80) then do not include the port number in the host string */ - aptr->host = aprintf("Host: %s%s%s\r\n", conn->bits.ipv6_ip?"[":"", - host, conn->bits.ipv6_ip?"]":""); + aptr->host = aprintf("Host: %s%s%s\r\n", conn->bits.ipv6_ip ? "[" : "", + host, conn->bits.ipv6_ip ? "]" : ""); else - aptr->host = aprintf("Host: %s%s%s:%d\r\n", conn->bits.ipv6_ip?"[":"", - host, conn->bits.ipv6_ip?"]":"", + aptr->host = aprintf("Host: %s%s%s:%d\r\n", + conn->bits.ipv6_ip ? "[" : "", + host, conn->bits.ipv6_ip ? "]" : "", conn->remote_port); if(!aptr->host) - /* without Host: we can't make a nice request */ + /* without Host: we cannot make a nice request */ return CURLE_OUT_OF_MEMORY; } return CURLE_OK; @@ -2307,7 +1792,7 @@ CURLcode Curl_http_target(struct Curl_easy *data, /* The path sent to the proxy is in fact the entire URL. But if the remote host is a IDN-name, we must make sure that the request we produce only - uses the encoded host name! */ + uses the encoded hostname! */ /* and no fragment part */ CURLUcode uc; @@ -2330,7 +1815,7 @@ CURLcode Curl_http_target(struct Curl_easy *data, } if(strcasecompare("http", data->state.up.scheme)) { - /* when getting HTTP, we don't want the userinfo the URL */ + /* when getting HTTP, we do not want the userinfo the URL */ uc = curl_url_set(h, CURLUPART_USER, NULL, 0); if(uc) { curl_url_cleanup(h); @@ -2342,9 +1827,7 @@ CURLcode Curl_http_target(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } } - /* Extract the URL to use in the request. Store in STRING_TEMP_URL for - clean-up reasons if the function returns before the free() further - down. */ + /* Extract the URL to use in the request. */ uc = curl_url_get(h, CURLUPART_URL, &url, CURLU_NO_DEFAULT_PORT); if(uc) { curl_url_cleanup(h); @@ -2353,9 +1836,9 @@ CURLcode Curl_http_target(struct Curl_easy *data, curl_url_cleanup(h); - /* target or url */ - result = Curl_dyn_add(r, data->set.str[STRING_TARGET]? - data->set.str[STRING_TARGET]:url); + /* target or URL */ + result = Curl_dyn_add(r, data->set.str[STRING_TARGET] ? + data->set.str[STRING_TARGET] : url); free(url); if(result) return (result); @@ -2399,65 +1882,193 @@ CURLcode Curl_http_target(struct Curl_easy *data, return result; } -CURLcode Curl_http_body(struct Curl_easy *data, struct connectdata *conn, - Curl_HttpReq httpreq, const char **tep) +#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) +static CURLcode set_post_reader(struct Curl_easy *data, Curl_HttpReq httpreq) { - CURLcode result = CURLE_OK; - const char *ptr; - struct HTTP *http = data->req.p.http; - http->postsize = 0; + CURLcode result; switch(httpreq) { +#ifndef CURL_DISABLE_MIME case HTTPREQ_POST_MIME: data->state.mimepost = &data->set.mimepost; break; +#endif #ifndef CURL_DISABLE_FORM_API case HTTPREQ_POST_FORM: /* Convert the form structure into a mime structure, then keep the conversion */ if(!data->state.formp) { - data->state.formp = calloc(sizeof(curl_mimepart), 1); + data->state.formp = calloc(1, sizeof(curl_mimepart)); if(!data->state.formp) return CURLE_OUT_OF_MEMORY; Curl_mime_cleanpart(data->state.formp); result = Curl_getformdata(data, data->state.formp, data->set.httppost, data->state.fread_func); - if(result) + if(result) { + Curl_safefree(data->state.formp); return result; + } data->state.mimepost = data->state.formp; } break; #endif default: data->state.mimepost = NULL; + break; } + switch(httpreq) { + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + /* This is form posting using mime data. */ #ifndef CURL_DISABLE_MIME - if(data->state.mimepost) { - const char *cthdr = Curl_checkheaders(data, STRCONST("Content-Type")); - - /* Read and seek body only. */ - data->state.mimepost->flags |= MIME_BODY_ONLY; + if(data->state.mimepost) { + const char *cthdr = Curl_checkheaders(data, STRCONST("Content-Type")); - /* Prepare the mime structure headers & set content type. */ + /* Read and seek body only. */ + data->state.mimepost->flags |= MIME_BODY_ONLY; - if(cthdr) - for(cthdr += 13; *cthdr == ' '; cthdr++) - ; - else if(data->state.mimepost->kind == MIMEKIND_MULTIPART) - cthdr = "multipart/form-data"; + /* Prepare the mime structure headers & set content type. */ - curl_mime_headers(data->state.mimepost, data->set.headers, 0); - result = Curl_mime_prepare_headers(data, data->state.mimepost, cthdr, - NULL, MIMESTRATEGY_FORM); - curl_mime_headers(data->state.mimepost, NULL, 0); - if(!result) - result = Curl_mime_rewind(data->state.mimepost); - if(result) - return result; - http->postsize = Curl_mime_size(data->state.mimepost); - } -#endif + if(cthdr) + for(cthdr += 13; *cthdr == ' '; cthdr++) + ; + else if(data->state.mimepost->kind == MIMEKIND_MULTIPART) + cthdr = "multipart/form-data"; + + curl_mime_headers(data->state.mimepost, data->set.headers, 0); + result = Curl_mime_prepare_headers(data, data->state.mimepost, cthdr, + NULL, MIMESTRATEGY_FORM); + if(result) + return result; + curl_mime_headers(data->state.mimepost, NULL, 0); + result = Curl_creader_set_mime(data, data->state.mimepost); + if(result) + return result; + } + else +#endif + { + result = Curl_creader_set_null(data); + } + data->state.infilesize = Curl_creader_total_length(data); + return result; + + default: + return Curl_creader_set_null(data); + } + /* never reached */ +} +#endif + +static CURLcode set_reader(struct Curl_easy *data, Curl_HttpReq httpreq) +{ + CURLcode result = CURLE_OK; + curl_off_t postsize = data->state.infilesize; + + DEBUGASSERT(data->conn); + + if(data->req.authneg) { + return Curl_creader_set_null(data); + } + + switch(httpreq) { + case HTTPREQ_PUT: /* Let's PUT the data to the server! */ + return postsize ? Curl_creader_set_fread(data, postsize) : + Curl_creader_set_null(data); + +#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + return set_post_reader(data, httpreq); +#endif + + case HTTPREQ_POST: + /* this is the simple POST, using x-www-form-urlencoded style */ + /* the size of the post body */ + if(!postsize) { + result = Curl_creader_set_null(data); + } + else if(data->set.postfields) { + if(postsize > 0) + result = Curl_creader_set_buf(data, data->set.postfields, + (size_t)postsize); + else + result = Curl_creader_set_null(data); + } + else { + /* we read the bytes from the callback. In case "chunked" encoding + * is forced by the application, we disregard `postsize`. This is + * a backward compatibility decision to earlier versions where + * chunking disregarded this. See issue #13229. */ + bool chunked = FALSE; + char *ptr = Curl_checkheaders(data, STRCONST("Transfer-Encoding")); + if(ptr) { + /* Some kind of TE is requested, check if 'chunked' is chosen */ + chunked = Curl_compareheader(ptr, STRCONST("Transfer-Encoding:"), + STRCONST("chunked")); + } + result = Curl_creader_set_fread(data, chunked ? -1 : postsize); + } + return result; + + default: + /* HTTP GET/HEAD download, has no body, needs no Content-Length */ + data->state.infilesize = 0; + return Curl_creader_set_null(data); + } + /* not reached */ +} + +static CURLcode http_resume(struct Curl_easy *data, Curl_HttpReq httpreq) +{ + if((HTTPREQ_POST == httpreq || HTTPREQ_PUT == httpreq) && + data->state.resume_from) { + /********************************************************************** + * Resuming upload in HTTP means that we PUT or POST and that we have + * got a resume_from value set. The resume value has already created + * a Range: header that will be passed along. We need to "fast forward" + * the file the given number of bytes and decrease the assume upload + * file size before we continue this venture in the dark lands of HTTP. + * Resuming mime/form posting at an offset > 0 has no sense and is ignored. + *********************************************************************/ + + if(data->state.resume_from < 0) { + /* + * This is meant to get the size of the present remote-file by itself. + * We do not support this now. Bail out! + */ + data->state.resume_from = 0; + } + + if(data->state.resume_from && !data->req.authneg) { + /* only act on the first request */ + CURLcode result; + result = Curl_creader_resume_from(data, data->state.resume_from); + if(result) { + failf(data, "Unable to resume from offset %" FMT_OFF_T, + data->state.resume_from); + return result; + } + } + } + return CURLE_OK; +} + +CURLcode Curl_http_req_set_reader(struct Curl_easy *data, + Curl_HttpReq httpreq, + const char **tep) +{ + CURLcode result = CURLE_OK; + const char *ptr; + + result = set_reader(data, httpreq); + if(result) + return result; + + result = http_resume(data, httpreq); + if(result) + return result; ptr = Curl_checkheaders(data, STRCONST("Transfer-Encoding")); if(ptr) { @@ -2465,20 +2076,23 @@ CURLcode Curl_http_body(struct Curl_easy *data, struct connectdata *conn, data->req.upload_chunky = Curl_compareheader(ptr, STRCONST("Transfer-Encoding:"), STRCONST("chunked")); + if(data->req.upload_chunky && + Curl_use_http_1_1plus(data, data->conn) && + (data->conn->httpversion >= 20)) { + infof(data, "suppressing chunked transfer encoding on connection " + "using HTTP version 2 or higher"); + data->req.upload_chunky = FALSE; + } } else { - if((conn->handler->protocol & PROTO_FAMILY_HTTP) && - (((httpreq == HTTPREQ_POST_MIME || httpreq == HTTPREQ_POST_FORM) && - http->postsize < 0) || - ((data->state.upload || httpreq == HTTPREQ_POST) && - data->state.infilesize == -1))) { - if(conn->bits.authneg) - /* don't enable chunked during auth neg */ - ; - else if(Curl_use_http_1_1plus(data, conn)) { - if(conn->httpversion < 20) - /* HTTP, upload, unknown file size and not HTTP 1.0 */ - data->req.upload_chunky = TRUE; + curl_off_t req_clen = Curl_creader_total_length(data); + + if(req_clen < 0) { + /* indeterminate request content length */ + if(Curl_use_http_1_1plus(data, data->conn)) { + /* On HTTP/1.1, enable chunked, on HTTP/2 and later we do not + * need it */ + data->req.upload_chunky = (data->conn->httpversion < 20); } else { failf(data, "Chunky upload is not supported by HTTP 1.0"); @@ -2496,350 +2110,127 @@ CURLcode Curl_http_body(struct Curl_easy *data, struct connectdata *conn, return result; } -CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, - struct dynbuf *r, Curl_HttpReq httpreq) +static CURLcode addexpect(struct Curl_easy *data, struct dynbuf *r, + bool *announced_exp100) { -#ifndef USE_HYPER - /* Hyper always handles the body separately */ - curl_off_t included_body = 0; -#else - /* from this point down, this function should not be used */ -#define Curl_buffer_send(a,b,c,d,e,f) CURLE_OK -#endif - CURLcode result = CURLE_OK; - struct HTTP *http = data->req.p.http; - const char *ptr; - - /* If 'authdone' is FALSE, we must not set the write socket index to the - Curl_transfer() call below, as we're not ready to actually upload any - data yet. */ - - switch(httpreq) { - - case HTTPREQ_PUT: /* Let's PUT the data to the server! */ - - if(conn->bits.authneg) - http->postsize = 0; - else - http->postsize = data->state.infilesize; + CURLcode result; + char *ptr; - if((http->postsize != -1) && !data->req.upload_chunky && - (conn->bits.authneg || - !Curl_checkheaders(data, STRCONST("Content-Length")))) { - /* only add Content-Length if not uploading chunked */ - result = Curl_dyn_addf(r, "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", http->postsize); - if(result) - return result; - } + *announced_exp100 = FALSE; + /* Avoid Expect: 100-continue if Upgrade: is used */ + if(data->req.upgr101 != UPGR101_INIT) + return CURLE_OK; - /* For really small puts we don't use Expect: headers at all, and for - the somewhat bigger ones we allow the app to disable it. Just make - sure that the expect100header is always set to the preferred value - here. */ - ptr = Curl_checkheaders(data, STRCONST("Expect")); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue")); - } - else if(http->postsize > EXPECT_100_THRESHOLD || http->postsize < 0) { - result = expect100(data, conn, r); + /* For really small puts we do not use Expect: headers at all, and for + the somewhat bigger ones we allow the app to disable it. Just make + sure that the expect100header is always set to the preferred value + here. */ + ptr = Curl_checkheaders(data, STRCONST("Expect")); + if(ptr) { + *announced_exp100 = + Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue")); + } + else if(!data->state.disableexpect && + Curl_use_http_1_1plus(data, data->conn) && + (data->conn->httpversion < 20)) { + /* if not doing HTTP 1.0 or version 2, or disabled explicitly, we add an + Expect: 100-continue to the headers which actually speeds up post + operations (as there is one packet coming back from the web server) */ + curl_off_t client_len = Curl_creader_client_length(data); + if(client_len > EXPECT_100_THRESHOLD || client_len < 0) { + result = Curl_dyn_addn(r, STRCONST("Expect: 100-continue\r\n")); if(result) return result; + *announced_exp100 = TRUE; } + } + return CURLE_OK; +} - /* end of headers */ - result = Curl_dyn_addn(r, STRCONST("\r\n")); - if(result) - return result; - - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize); +CURLcode Curl_http_req_complete(struct Curl_easy *data, + struct dynbuf *r, Curl_HttpReq httpreq) +{ + CURLcode result = CURLE_OK; + curl_off_t req_clen; + bool announced_exp100 = FALSE; - /* this sends the buffer and frees all the buffer resources */ - result = Curl_buffer_send(r, data, data->req.p.http, - &data->info.request_size, 0, - FIRSTSOCKET); - if(result) - failf(data, "Failed sending PUT request"); - else - /* prepare for transfer */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, - http->postsize?FIRSTSOCKET:-1); + DEBUGASSERT(data->conn); +#ifndef USE_HYPER + if(data->req.upload_chunky) { + result = Curl_httpchunk_add_reader(data); if(result) return result; - break; + } +#endif + /* Get the request body length that has been set up */ + req_clen = Curl_creader_total_length(data); + switch(httpreq) { + case HTTPREQ_PUT: + case HTTPREQ_POST: +#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) case HTTPREQ_POST_FORM: case HTTPREQ_POST_MIME: - /* This is form posting using mime data. */ - if(conn->bits.authneg) { - /* nothing to post! */ - result = Curl_dyn_addn(r, STRCONST("Content-Length: 0\r\n\r\n")); - if(result) - return result; - - result = Curl_buffer_send(r, data, data->req.p.http, - &data->info.request_size, 0, - FIRSTSOCKET); - if(result) - failf(data, "Failed sending POST request"); - else - /* setup variables for the upcoming transfer */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); - break; - } - - data->state.infilesize = http->postsize; - +#endif /* We only set Content-Length and allow a custom Content-Length if - we don't upload data chunked, as RFC2616 forbids us to set both - kinds of headers (Transfer-Encoding: chunked and Content-Length) */ - if(http->postsize != -1 && !data->req.upload_chunky && - (!Curl_checkheaders(data, STRCONST("Content-Length")))) { + we do not upload data chunked, as RFC2616 forbids us to set both + kinds of headers (Transfer-Encoding: chunked and Content-Length). + We do not override a custom "Content-Length" header, but during + authentication negotiation that header is suppressed. + */ + if(req_clen >= 0 && !data->req.upload_chunky && + (data->req.authneg || + !Curl_checkheaders(data, STRCONST("Content-Length")))) { /* we allow replacing this header if not during auth negotiation, - although it isn't very wise to actually set your own */ - result = Curl_dyn_addf(r, - "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", http->postsize); - if(result) - return result; + although it is not very wise to actually set your own */ + result = Curl_dyn_addf(r, "Content-Length: %" FMT_OFF_T "\r\n", + req_clen); } + if(result) + goto out; #ifndef CURL_DISABLE_MIME /* Output mime-generated headers. */ - { + if(data->state.mimepost && + ((httpreq == HTTPREQ_POST_FORM) || (httpreq == HTTPREQ_POST_MIME))) { struct curl_slist *hdr; for(hdr = data->state.mimepost->curlheaders; hdr; hdr = hdr->next) { result = Curl_dyn_addf(r, "%s\r\n", hdr->data); if(result) - return result; - } - } -#endif - - /* For really small posts we don't use Expect: headers at all, and for - the somewhat bigger ones we allow the app to disable it. Just make - sure that the expect100header is always set to the preferred value - here. */ - ptr = Curl_checkheaders(data, STRCONST("Expect")); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue")); - } - else if(http->postsize > EXPECT_100_THRESHOLD || http->postsize < 0) { - result = expect100(data, conn, r); - if(result) - return result; - } - else - data->state.expect100header = FALSE; - - /* make the request end in a true CRLF */ - result = Curl_dyn_addn(r, STRCONST("\r\n")); - if(result) - return result; - - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize); - - /* Read from mime structure. */ - data->state.fread_func = (curl_read_callback) Curl_mime_read; - data->state.in = (void *) data->state.mimepost; - http->sending = HTTPSEND_BODY; - - /* this sends the buffer and frees all the buffer resources */ - result = Curl_buffer_send(r, data, data->req.p.http, - &data->info.request_size, 0, - FIRSTSOCKET); - if(result) - failf(data, "Failed sending POST request"); - else - /* prepare for transfer */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, - http->postsize?FIRSTSOCKET:-1); - if(result) - return result; - - break; - - case HTTPREQ_POST: - /* this is the simple POST, using x-www-form-urlencoded style */ - - if(conn->bits.authneg) - http->postsize = 0; - else - /* the size of the post body */ - http->postsize = data->state.infilesize; - - /* We only set Content-Length and allow a custom Content-Length if - we don't upload data chunked, as RFC2616 forbids us to set both - kinds of headers (Transfer-Encoding: chunked and Content-Length) */ - if((http->postsize != -1) && !data->req.upload_chunky && - (conn->bits.authneg || - !Curl_checkheaders(data, STRCONST("Content-Length")))) { - /* we allow replacing this header if not during auth negotiation, - although it isn't very wise to actually set your own */ - result = Curl_dyn_addf(r, "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", http->postsize); - if(result) - return result; - } - - if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { - result = Curl_dyn_addn(r, STRCONST("Content-Type: application/" - "x-www-form-urlencoded\r\n")); - if(result) - return result; - } - - /* For really small posts we don't use Expect: headers at all, and for - the somewhat bigger ones we allow the app to disable it. Just make - sure that the expect100header is always set to the preferred value - here. */ - ptr = Curl_checkheaders(data, STRCONST("Expect")); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue")); - } - else if(http->postsize > EXPECT_100_THRESHOLD || http->postsize < 0) { - result = expect100(data, conn, r); - if(result) - return result; - } - else - data->state.expect100header = FALSE; - -#ifndef USE_HYPER - /* With Hyper the body is always passed on separately */ - if(data->set.postfields) { - if(!data->state.expect100header && - (http->postsize < MAX_INITIAL_POST_SIZE)) { - /* if we don't use expect: 100 AND - postsize is less than MAX_INITIAL_POST_SIZE - - then append the post data to the HTTP request header. This limit - is no magic limit but only set to prevent really huge POSTs to - get the data duplicated with malloc() and family. */ - - /* end of headers! */ - result = Curl_dyn_addn(r, STRCONST("\r\n")); - if(result) - return result; - - if(!data->req.upload_chunky) { - /* We're not sending it 'chunked', append it to the request - already now to reduce the number of send() calls */ - result = Curl_dyn_addn(r, data->set.postfields, - (size_t)http->postsize); - included_body = http->postsize; - } - else { - if(http->postsize) { - char chunk[16]; - /* Append the POST data chunky-style */ - msnprintf(chunk, sizeof(chunk), "%x\r\n", (int)http->postsize); - result = Curl_dyn_add(r, chunk); - if(!result) { - included_body = http->postsize + strlen(chunk); - result = Curl_dyn_addn(r, data->set.postfields, - (size_t)http->postsize); - if(!result) - result = Curl_dyn_addn(r, STRCONST("\r\n")); - included_body += 2; - } - } - if(!result) { - result = Curl_dyn_addn(r, STRCONST("\x30\x0d\x0a\x0d\x0a")); - /* 0 CR LF CR LF */ - included_body += 5; - } - } - if(result) - return result; - /* Make sure the progress information is accurate */ - Curl_pgrsSetUploadSize(data, http->postsize); - } - else { - /* A huge POST coming up, do data separate from the request */ - http->postdata = data->set.postfields; - http->sending = HTTPSEND_BODY; - http->backup.data = data; - data->state.fread_func = (curl_read_callback)readmoredata; - data->state.in = (void *)http; - - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize); - - /* end of headers! */ - result = Curl_dyn_addn(r, STRCONST("\r\n")); - if(result) - return result; + goto out; } } - else #endif - { - /* end of headers! */ - result = Curl_dyn_addn(r, STRCONST("\r\n")); - if(result) - return result; - - if(data->req.upload_chunky && conn->bits.authneg) { - /* Chunky upload is selected and we're negotiating auth still, send - end-of-data only */ - result = Curl_dyn_addn(r, (char *)STRCONST("\x30\x0d\x0a\x0d\x0a")); - /* 0 CR LF CR LF */ + if(httpreq == HTTPREQ_POST) { + if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { + result = Curl_dyn_addn(r, STRCONST("Content-Type: application/" + "x-www-form-urlencoded\r\n")); if(result) - return result; - } - - else if(data->state.infilesize) { - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize?http->postsize:-1); - - /* set the pointer to mark that we will send the post body using the - read callback, but only if we're not in authenticate negotiation */ - if(!conn->bits.authneg) - http->postdata = (char *)&http->postdata; + goto out; } } - /* issue the request */ - result = Curl_buffer_send(r, data, data->req.p.http, - &data->info.request_size, included_body, - FIRSTSOCKET); - + result = addexpect(data, r, &announced_exp100); if(result) - failf(data, "Failed sending HTTP POST request"); - else - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, - http->postdata?FIRSTSOCKET:-1); + goto out; break; - default: - result = Curl_dyn_addn(r, STRCONST("\r\n")); - if(result) - return result; + break; + } - /* issue the request */ - result = Curl_buffer_send(r, data, data->req.p.http, - &data->info.request_size, 0, - FIRSTSOCKET); - if(result) - failf(data, "Failed sending HTTP request"); -#ifdef USE_WEBSOCKETS - else if((conn->handler->protocol & (CURLPROTO_WS|CURLPROTO_WSS)) && - !(data->set.connect_only)) - /* Set up the transfer for two-way since without CONNECT_ONLY set, this - request probably wants to send data too post upgrade */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, FIRSTSOCKET); -#endif - else - /* HTTP GET/HEAD download: */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); + /* end of headers */ + result = Curl_dyn_addn(r, STRCONST("\r\n")); + if(!result) { + Curl_pgrsSetUploadSize(data, req_clen); + if(announced_exp100) + result = http_exp100_add_reader(data); } +out: + if(!result) { + /* setup variables for the upcoming transfer */ + Curl_xfer_setup1(data, CURL_XFER_SENDRECV, -1, TRUE); + } return result; } @@ -2857,8 +2248,9 @@ CURLcode Curl_http_cookies(struct Curl_easy *data, addcookies = data->set.str[STRING_COOKIE]; if(data->cookies || addcookies) { - struct Cookie *co = NULL; /* no cookies from start */ + struct Curl_llist list; int count = 0; + int rc = 1; if(data->cookies && data->state.cookie_engine) { const char *host = data->state.aptr.cookiehost ? @@ -2867,17 +2259,19 @@ CURLcode Curl_http_cookies(struct Curl_easy *data, conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) || strcasecompare("localhost", host) || !strcmp(host, "127.0.0.1") || - !strcmp(host, "::1") ? TRUE : FALSE; + !strcmp(host, "::1"); Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - co = Curl_cookie_getlist(data, data->cookies, host, data->state.up.path, - secure_context); + rc = Curl_cookie_getlist(data, data->cookies, host, data->state.up.path, + secure_context, &list); Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); } - if(co) { - struct Cookie *store = co; + if(!rc) { + struct Curl_llist_node *n; size_t clen = 8; /* hold the size of the generated Cookie: header */ - /* now loop through all cookies that matched */ - while(co) { + + /* loop through all cookies that matched */ + for(n = Curl_llist_head(&list); n; n = Curl_node_next(n)) { + struct Cookie *co = Curl_node_elem(n); if(co->value) { size_t add; if(!count) { @@ -2892,22 +2286,21 @@ CURLcode Curl_http_cookies(struct Curl_easy *data, linecap = TRUE; break; } - result = Curl_dyn_addf(r, "%s%s=%s", count?"; ":"", + result = Curl_dyn_addf(r, "%s%s=%s", count ? "; " : "", co->name, co->value); if(result) break; clen += add + (count ? 2 : 0); count++; } - co = co->next; /* next cookie please */ } - Curl_cookie_freelist(store); + Curl_llist_destroy(&list, NULL); } if(addcookies && !result && !linecap) { if(!count) result = Curl_dyn_addn(r, STRCONST("Cookie: ")); if(!result) { - result = Curl_dyn_addf(r, "%s%s", count?"; ":"", addcookies); + result = Curl_dyn_addf(r, "%s%s", count ? "; " : "", addcookies); count++; } } @@ -2926,7 +2319,7 @@ CURLcode Curl_http_range(struct Curl_easy *data, { if(data->state.use_range) { /* - * A range is selected. We use different headers whether we're downloading + * A range is selected. We use different headers whether we are downloading * or uploading and we always let customized headers override our internal * ones if any such are specified. */ @@ -2939,36 +2332,37 @@ CURLcode Curl_http_range(struct Curl_easy *data, } else if((httpreq == HTTPREQ_POST || httpreq == HTTPREQ_PUT) && !Curl_checkheaders(data, STRCONST("Content-Range"))) { - + curl_off_t req_clen = Curl_creader_total_length(data); /* if a line like this was already allocated, free the previous one */ free(data->state.aptr.rangeline); if(data->set.set_resume_from < 0) { - /* Upload resume was asked for, but we don't know the size of the + /* Upload resume was asked for, but we do not know the size of the remote part so we tell the server (and act accordingly) that we upload the whole file (again) */ data->state.aptr.rangeline = - aprintf("Content-Range: bytes 0-%" CURL_FORMAT_CURL_OFF_T - "/%" CURL_FORMAT_CURL_OFF_T "\r\n", - data->state.infilesize - 1, data->state.infilesize); + aprintf("Content-Range: bytes 0-%" FMT_OFF_T "/%" FMT_OFF_T "\r\n", + req_clen - 1, req_clen); } else if(data->state.resume_from) { /* This is because "resume" was selected */ - curl_off_t total_expected_size = - data->state.resume_from + data->state.infilesize; + /* TODO: not sure if we want to send this header during authentication + * negotiation, but test1084 checks for it. In which case we have a + * "null" client reader installed that gives an unexpected length. */ + curl_off_t total_len = data->req.authneg ? + data->state.infilesize : + (data->state.resume_from + req_clen); data->state.aptr.rangeline = - aprintf("Content-Range: bytes %s%" CURL_FORMAT_CURL_OFF_T - "/%" CURL_FORMAT_CURL_OFF_T "\r\n", - data->state.range, total_expected_size-1, - total_expected_size); + aprintf("Content-Range: bytes %s%" FMT_OFF_T "/%" FMT_OFF_T "\r\n", + data->state.range, total_len-1, total_len); } else { /* Range was selected and then we just pass the incoming range and append total size */ data->state.aptr.rangeline = - aprintf("Content-Range: bytes %s/%" CURL_FORMAT_CURL_OFF_T "\r\n", - data->state.range, data->state.infilesize); + aprintf("Content-Range: bytes %s/%" FMT_OFF_T "\r\n", + data->state.range, req_clen); } if(!data->state.aptr.rangeline) return CURLE_OUT_OF_MEMORY; @@ -2977,101 +2371,20 @@ CURLcode Curl_http_range(struct Curl_easy *data, return CURLE_OK; } -CURLcode Curl_http_resume(struct Curl_easy *data, - struct connectdata *conn, - Curl_HttpReq httpreq) +CURLcode Curl_http_firstwrite(struct Curl_easy *data) { - if((HTTPREQ_POST == httpreq || HTTPREQ_PUT == httpreq) && - data->state.resume_from) { - /********************************************************************** - * Resuming upload in HTTP means that we PUT or POST and that we have - * got a resume_from value set. The resume value has already created - * a Range: header that will be passed along. We need to "fast forward" - * the file the given number of bytes and decrease the assume upload - * file size before we continue this venture in the dark lands of HTTP. - * Resuming mime/form posting at an offset > 0 has no sense and is ignored. - *********************************************************************/ - - if(data->state.resume_from < 0) { - /* - * This is meant to get the size of the present remote-file by itself. - * We don't support this now. Bail out! - */ - data->state.resume_from = 0; - } - - if(data->state.resume_from && !data->state.followlocation) { - /* only act on the first request */ - - /* Now, let's read off the proper amount of bytes from the - input. */ - int seekerr = CURL_SEEKFUNC_CANTSEEK; - if(conn->seek_func) { - Curl_set_in_callback(data, true); - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - Curl_set_in_callback(data, false); - } - - if(seekerr != CURL_SEEKFUNC_OK) { - curl_off_t passed = 0; - - if(seekerr != CURL_SEEKFUNC_CANTSEEK) { - failf(data, "Could not seek stream"); - return CURLE_READ_ERROR; - } - /* when seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ - do { - size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : - curlx_sotouz(data->state.resume_from - passed); - - size_t actuallyread = - data->state.fread_func(data->state.buffer, 1, readthisamountnow, - data->state.in); - - passed += actuallyread; - if((actuallyread == 0) || (actuallyread > readthisamountnow)) { - /* this checks for greater-than only to make sure that the - CURL_READFUNC_ABORT return code still aborts */ - failf(data, "Could only read %" CURL_FORMAT_CURL_OFF_T - " bytes from the input", passed); - return CURLE_READ_ERROR; - } - } while(passed < data->state.resume_from); - } - - /* now, decrease the size of the read */ - if(data->state.infilesize>0) { - data->state.infilesize -= data->state.resume_from; - - if(data->state.infilesize <= 0) { - failf(data, "File already completely uploaded"); - return CURLE_PARTIAL_FILE; - } - } - /* we've passed, proceed as normal */ - } - } - return CURLE_OK; -} - -CURLcode Curl_http_firstwrite(struct Curl_easy *data, - struct connectdata *conn, - bool *done) -{ - struct SingleRequest *k = &data->req; + struct connectdata *conn = data->conn; + struct SingleRequest *k = &data->req; if(data->req.newurl) { if(conn->bits.close) { /* Abort after the headers if "follow Location" is set - and we're set to close anyway. */ + and we are set to close anyway. */ k->keepon &= ~KEEP_RECV; - *done = TRUE; + k->done = TRUE; return CURLE_OK; } - /* We have a new url to load, but since we want to be able to reuse this + /* We have a new URL to load, but since we want to be able to reuse this connection properly, we read the full response in "ignore more" */ k->ignorebody = TRUE; infof(data, "Ignoring the response-body"); @@ -3082,19 +2395,19 @@ CURLcode Curl_http_firstwrite(struct Curl_easy *data, if(k->size == data->state.resume_from) { /* The resume point is at the end of file, consider this fine even if it - doesn't allow resume from here. */ + does not allow resume from here. */ infof(data, "The entire document is already downloaded"); streamclose(conn, "already downloaded"); /* Abort download */ k->keepon &= ~KEEP_RECV; - *done = TRUE; + k->done = TRUE; return CURLE_OK; } - /* we wanted to resume a download, although the server doesn't seem to - * support this and we did this with a GET (if it wasn't a GET we did a + /* we wanted to resume a download, although the server does not seem to + * support this and we did this with a GET (if it was not a GET we did a * POST or PUT resume) */ - failf(data, "HTTP server doesn't seem to support " + failf(data, "HTTP server does not seem to support " "byte ranges. Cannot resume."); return CURLE_RANGE_ERROR; } @@ -3105,8 +2418,8 @@ CURLcode Curl_http_firstwrite(struct Curl_easy *data, action for an HTTP/1.1 client */ if(!Curl_meets_timecondition(data, k->timeofdoc)) { - *done = TRUE; - /* We're simulating an HTTP 304 from server so we return + k->done = TRUE; + /* We are simulating an HTTP 304 from server so we return what should have been returned from the server */ data->info.httpcode = 304; infof(data, "Simulate an HTTP 304 response"); @@ -3128,7 +2441,7 @@ CURLcode Curl_transferencode(struct Curl_easy *data) /* When we are to insert a TE: header in the request, we must also insert TE in a Connection: header, so we need to merge the custom provided Connection: header and prevent the original to get sent. Note that if - the user has inserted his/her own TE: header we don't do this magic + the user has inserted his/her own TE: header we do not do this magic but then assume that the user will handle it all! */ char *cptr = Curl_checkheaders(data, STRCONST("Connection")); #define TE_HEADER "TE: gzip\r\n" @@ -3163,7 +2476,6 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; - struct HTTP *http; Curl_HttpReq httpreq; const char *te = ""; /* transfer-encoding */ const char *request; @@ -3188,14 +2500,14 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) ) { result = Curl_http2_switch(data, conn, FIRSTSOCKET); if(result) - return result; + goto fail; } else #endif DEBUGASSERT(Curl_conn_is_http2(data, conn, FIRSTSOCKET)); break; case CURL_HTTP_VERSION_1_1: - /* continue with HTTP/1.1 when explicitly requested */ + /* continue with HTTP/1.x when explicitly requested */ break; default: /* Check if user wants to use HTTP/2 with clear TCP */ @@ -3203,21 +2515,25 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) DEBUGF(infof(data, "HTTP/2 over clean TCP")); result = Curl_http2_switch(data, conn, FIRSTSOCKET); if(result) - return result; + goto fail; } break; } - http = data->req.p.http; - DEBUGASSERT(http); + /* Add collecting of headers written to client. For a new connection, + * we might have done that already, but reuse + * or multiplex needs it here as well. */ + result = Curl_headers_init(data); + if(result) + goto fail; result = Curl_http_host(data, conn); if(result) - return result; + goto fail; result = Curl_http_useragent(data); if(result) - return result; + goto fail; Curl_http_method(data, conn, &request, &httpreq); @@ -3233,7 +2549,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) (pq ? pq : data->state.up.path), FALSE); free(pq); if(result) - return result; + goto fail; } Curl_safefree(data->state.aptr.ref); @@ -3258,23 +2574,19 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) /* we only consider transfer-encoding magic if libz support is built-in */ result = Curl_transferencode(data); if(result) - return result; + goto fail; #endif - result = Curl_http_body(data, conn, httpreq, &te); + result = Curl_http_req_set_reader(data, httpreq, &te); if(result) - return result; + goto fail; p_accept = Curl_checkheaders(data, - STRCONST("Accept"))?NULL:"Accept: */*\r\n"; - - result = Curl_http_resume(data, conn, httpreq); - if(result) - return result; + STRCONST("Accept")) ? NULL : "Accept: */*\r\n"; result = Curl_http_range(data, httpreq); if(result) - return result; + goto fail; httpstring = get_http_string(data, conn); @@ -3292,7 +2604,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) result = Curl_http_target(data, conn, &req); if(result) { Curl_dyn_free(&req); - return result; + goto fail; } #ifndef CURL_DISABLE_ALTSVC @@ -3322,31 +2634,34 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) "%s",/* Alt-Used */ httpstring, - (data->state.aptr.host?data->state.aptr.host:""), - data->state.aptr.proxyuserpwd? - data->state.aptr.proxyuserpwd:"", - data->state.aptr.userpwd?data->state.aptr.userpwd:"", - (data->state.use_range && data->state.aptr.rangeline)? - data->state.aptr.rangeline:"", + (data->state.aptr.host ? data->state.aptr.host : ""), +#ifndef CURL_DISABLE_PROXY + data->state.aptr.proxyuserpwd ? + data->state.aptr.proxyuserpwd : "", +#else + "", +#endif + data->state.aptr.userpwd ? data->state.aptr.userpwd : "", + (data->state.use_range && data->state.aptr.rangeline) ? + data->state.aptr.rangeline : "", (data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT] && - data->state.aptr.uagent)? - data->state.aptr.uagent:"", - p_accept?p_accept:"", - data->state.aptr.te?data->state.aptr.te:"", + data->state.aptr.uagent) ? + data->state.aptr.uagent : "", + p_accept ? p_accept : "", + data->state.aptr.te ? data->state.aptr.te : "", (data->set.str[STRING_ENCODING] && *data->set.str[STRING_ENCODING] && - data->state.aptr.accept_encoding)? - data->state.aptr.accept_encoding:"", - (data->state.referer && data->state.aptr.ref)? - data->state.aptr.ref:"" /* Referer: */, + data->state.aptr.accept_encoding) ? + data->state.aptr.accept_encoding : "", + (data->state.referer && data->state.aptr.ref) ? + data->state.aptr.ref : "" /* Referer: */, #ifndef CURL_DISABLE_PROXY (conn->bits.httpproxy && !conn->bits.tunnel_proxy && !Curl_checkheaders(data, STRCONST("Proxy-Connection")) && - !Curl_checkProxyheaders(data, - conn, - STRCONST("Proxy-Connection")))? + !Curl_checkProxyheaders(data, conn, + STRCONST("Proxy-Connection"))) ? "Proxy-Connection: Keep-Alive\r\n":"", #else "", @@ -3358,18 +2673,20 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) /* clear userpwd and proxyuserpwd to avoid reusing old credentials * from reused connections */ Curl_safefree(data->state.aptr.userpwd); +#ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuserpwd); +#endif free(altused); if(result) { Curl_dyn_free(&req); - return result; + goto fail; } if(!(conn->handler->flags&PROTOPT_SSL) && conn->httpversion < 20 && (data->state.httpwant == CURL_HTTP_VERSION_2)) { - /* append HTTP2 upgrade magic stuff to the HTTP request if it isn't done + /* append HTTP2 upgrade magic stuff to the HTTP request if it is not done over SSL */ result = Curl_http2_request_upgrade(&req, data); if(result) { @@ -3379,7 +2696,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) } result = Curl_http_cookies(data, conn, &req); -#ifdef USE_WEBSOCKETS +#ifndef CURL_DISABLE_WEBSOCKETS if(!result && conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS)) result = Curl_ws_request(data, &req); #endif @@ -3389,52 +2706,23 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) result = Curl_add_custom_headers(data, FALSE, &req); if(!result) { - http->postdata = NULL; /* nothing to post at this point */ - if((httpreq == HTTPREQ_GET) || - (httpreq == HTTPREQ_HEAD)) - Curl_pgrsSetUploadSize(data, 0); /* nothing */ - - /* bodysend takes ownership of the 'req' memory on success */ - result = Curl_http_bodysend(data, conn, &req, httpreq); - } - if(result) { - Curl_dyn_free(&req); - return result; - } - - if((http->postsize > -1) && - (http->postsize <= data->req.writebytecount) && - (http->sending != HTTPSEND_REQUEST)) - data->req.upload_done = TRUE; - - if(data->req.writebytecount) { - /* if a request-body has been sent off, we make sure this progress is noted - properly */ - Curl_pgrsSetUploadCounter(data, data->req.writebytecount); - if(Curl_pgrsUpdate(data)) - result = CURLE_ABORTED_BY_CALLBACK; - - if(!http->postsize) { - /* already sent the entire request body, mark the "upload" as - complete */ - infof(data, "upload completely sent off: %" CURL_FORMAT_CURL_OFF_T - " out of %" CURL_FORMAT_CURL_OFF_T " bytes", - data->req.writebytecount, http->postsize); - data->req.upload_done = TRUE; - data->req.keepon &= ~KEEP_SEND; /* we're done writing */ - data->req.exp100 = EXP100_SEND_DATA; /* already sent */ - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - } + /* req_send takes ownership of the 'req' memory on success */ + result = Curl_http_req_complete(data, &req, httpreq); + if(!result) + result = Curl_req_send(data, &req); } - - if(data->req.upload_done) - Curl_conn_ev_data_done_send(data); + Curl_dyn_free(&req); + if(result) + goto fail; if((conn->httpversion >= 20) && data->req.upload_chunky) /* upload_chunky was set above to set up the request in a chunky fashion, but is disabled here again to avoid that the chunked encoded version is actually used when sending the request body over h2 */ data->req.upload_chunky = FALSE; +fail: + if(CURLE_TOO_LARGE == result) + failf(data, "HTTP request too large"); return result; } @@ -3465,7 +2753,7 @@ checkhttpprefix(struct Curl_easy *data, { struct curl_slist *head = data->set.http200aliases; statusline rc = STATUS_BAD; - statusline onmatch = len >= 5? STATUS_DONE : STATUS_UNKNOWN; + statusline onmatch = len >= 5 ? STATUS_DONE : STATUS_UNKNOWN; while(head) { if(checkprefixmax(head->data, s, len)) { @@ -3487,7 +2775,7 @@ checkrtspprefix(struct Curl_easy *data, const char *s, size_t len) { statusline result = STATUS_BAD; - statusline onmatch = len >= 5? STATUS_DONE : STATUS_UNKNOWN; + statusline onmatch = len >= 5 ? STATUS_DONE : STATUS_UNKNOWN; (void)data; /* unused */ if(checkprefixmax("RTSP/", s, len)) result = onmatch; @@ -3510,325 +2798,377 @@ checkprotoprefix(struct Curl_easy *data, struct connectdata *conn, return checkhttpprefix(data, s, len); } +/* HTTP header has field name `n` (a string constant) */ +#define HD_IS(hd, hdlen, n) \ + (((hdlen) >= (sizeof(n)-1)) && curl_strnequal((n), (hd), (sizeof(n)-1))) + +#define HD_VAL(hd, hdlen, n) \ + ((((hdlen) >= (sizeof(n)-1)) && \ + curl_strnequal((n), (hd), (sizeof(n)-1)))? (hd + (sizeof(n)-1)) : NULL) + +/* HTTP header has field name `n` (a string constant) and contains `v` + * (a string constant) in its value(s) */ +#define HD_IS_AND_SAYS(hd, hdlen, n, v) \ + (HD_IS(hd, hdlen, n) && \ + ((hdlen) > ((sizeof(n)-1) + (sizeof(v)-1))) && \ + Curl_compareheader(hd, STRCONST(n), STRCONST(v))) + /* * Curl_http_header() parses a single response header. */ -CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, - char *headp) +CURLcode Curl_http_header(struct Curl_easy *data, + const char *hd, size_t hdlen) { + struct connectdata *conn = data->conn; CURLcode result; struct SingleRequest *k = &data->req; - /* Check for Content-Length: header lines to get size */ - if(!k->http_bodyless && - !data->set.ignorecl && checkprefix("Content-Length:", headp)) { - curl_off_t contentlength; - CURLofft offt = curlx_strtoofft(headp + strlen("Content-Length:"), - NULL, 10, &contentlength); - - if(offt == CURL_OFFT_OK) { - k->size = contentlength; - k->maxdownload = k->size; + const char *v; + + switch(hd[0]) { + case 'a': + case 'A': +#ifndef CURL_DISABLE_ALTSVC + v = (data->asi && + ((data->conn->handler->flags & PROTOPT_SSL) || +#ifdef DEBUGBUILD + /* allow debug builds to circumvent the HTTPS restriction */ + getenv("CURL_ALTSVC_HTTP") +#else + 0 +#endif + )) ? HD_VAL(hd, hdlen, "Alt-Svc:") : NULL; + if(v) { + /* the ALPN of the current request */ + enum alpnid id = (conn->httpversion == 30) ? ALPN_h3 : + (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1; + return Curl_altsvc_parse(data, data->asi, v, id, conn->host.name, + curlx_uitous((unsigned int)conn->remote_port)); } - else if(offt == CURL_OFFT_FLOW) { - /* out of range */ - if(data->set.max_filesize) { - failf(data, "Maximum file size exceeded"); - return CURLE_FILESIZE_EXCEEDED; +#endif + break; + case 'c': + case 'C': + /* Check for Content-Length: header lines to get size */ + v = (!k->http_bodyless && !data->set.ignorecl) ? + HD_VAL(hd, hdlen, "Content-Length:") : NULL; + if(v) { + curl_off_t contentlength; + CURLofft offt = curlx_strtoofft(v, NULL, 10, &contentlength); + + if(offt == CURL_OFFT_OK) { + k->size = contentlength; + k->maxdownload = k->size; + } + else if(offt == CURL_OFFT_FLOW) { + /* out of range */ + if(data->set.max_filesize) { + failf(data, "Maximum file size exceeded"); + return CURLE_FILESIZE_EXCEEDED; + } + streamclose(conn, "overflow content-length"); + infof(data, "Overflow Content-Length: value"); } - streamclose(conn, "overflow content-length"); - infof(data, "Overflow Content-Length: value"); + else { + /* negative or just rubbish - bad HTTP */ + failf(data, "Invalid Content-Length: value"); + return CURLE_WEIRD_SERVER_REPLY; + } + return CURLE_OK; } - else { - /* negative or just rubbish - bad HTTP */ - failf(data, "Invalid Content-Length: value"); - return CURLE_WEIRD_SERVER_REPLY; + v = (!k->http_bodyless && data->set.str[STRING_ENCODING]) ? + HD_VAL(hd, hdlen, "Content-Encoding:") : NULL; + if(v) { + /* + * Process Content-Encoding. Look for the values: identity, + * gzip, deflate, compress, x-gzip and x-compress. x-gzip and + * x-compress are the same as gzip and compress. (Sec 3.5 RFC + * 2616). zlib cannot handle compress. However, errors are + * handled further down when the response body is processed + */ + return Curl_build_unencoding_stack(data, v, FALSE); } - } - /* check for Content-Type: header lines to get the MIME-type */ - else if(checkprefix("Content-Type:", headp)) { - char *contenttype = Curl_copy_header_value(headp); - if(!contenttype) - return CURLE_OUT_OF_MEMORY; - if(!*contenttype) - /* ignore empty data */ - free(contenttype); - else { - Curl_safefree(data->info.contenttype); - data->info.contenttype = contenttype; + /* check for Content-Type: header lines to get the MIME-type */ + v = HD_VAL(hd, hdlen, "Content-Type:"); + if(v) { + char *contenttype = Curl_copy_header_value(hd); + if(!contenttype) + return CURLE_OUT_OF_MEMORY; + if(!*contenttype) + /* ignore empty data */ + free(contenttype); + else { + Curl_safefree(data->info.contenttype); + data->info.contenttype = contenttype; + } + return CURLE_OK; } - } -#ifndef CURL_DISABLE_PROXY - else if((conn->httpversion == 10) && - conn->bits.httpproxy && - Curl_compareheader(headp, - STRCONST("Proxy-Connection:"), - STRCONST("keep-alive"))) { - /* - * When an HTTP/1.0 reply comes when using a proxy, the - * 'Proxy-Connection: keep-alive' line tells us the - * connection will be kept alive for our pleasure. - * Default action for 1.0 is to close. - */ - connkeep(conn, "Proxy-Connection keep-alive"); /* don't close */ - infof(data, "HTTP/1.0 proxy connection set to keep alive"); - } - else if((conn->httpversion == 11) && - conn->bits.httpproxy && - Curl_compareheader(headp, - STRCONST("Proxy-Connection:"), - STRCONST("close"))) { - /* - * We get an HTTP/1.1 response from a proxy and it says it'll - * close down after this transfer. - */ - connclose(conn, "Proxy-Connection: asked to close after done"); - infof(data, "HTTP/1.1 proxy connection set close"); - } -#endif - else if((conn->httpversion == 10) && - Curl_compareheader(headp, - STRCONST("Connection:"), - STRCONST("keep-alive"))) { - /* - * An HTTP/1.0 reply with the 'Connection: keep-alive' line - * tells us the connection will be kept alive for our - * pleasure. Default action for 1.0 is to close. - * - * [RFC2068, section 19.7.1] */ - connkeep(conn, "Connection keep-alive"); - infof(data, "HTTP/1.0 connection set to keep alive"); - } - else if(Curl_compareheader(headp, - STRCONST("Connection:"), STRCONST("close"))) { - /* - * [RFC 2616, section 8.1.2.1] - * "Connection: close" is HTTP/1.1 language and means that - * the connection will close when this request has been - * served. - */ - streamclose(conn, "Connection: close used"); - } - else if(!k->http_bodyless && checkprefix("Transfer-Encoding:", headp)) { - /* One or more encodings. We check for chunked and/or a compression - algorithm. */ - /* - * [RFC 2616, section 3.6.1] A 'chunked' transfer encoding - * means that the server will send a series of "chunks". Each - * chunk starts with line with info (including size of the - * coming block) (terminated with CRLF), then a block of data - * with the previously mentioned size. There can be any amount - * of chunks, and a chunk-data set to zero signals the - * end-of-chunks. */ - - result = Curl_build_unencoding_stack(data, - headp + strlen("Transfer-Encoding:"), - TRUE); - if(result) - return result; - if(!k->chunk && data->set.http_transfer_encoding) { - /* if this isn't chunked, only close can signal the end of this transfer - as Content-Length is said not to be trusted for transfer-encoding! */ - connclose(conn, "HTTP/1.1 transfer-encoding without chunks"); - k->ignore_cl = TRUE; + if(HD_IS_AND_SAYS(hd, hdlen, "Connection:", "close")) { + /* + * [RFC 2616, section 8.1.2.1] + * "Connection: close" is HTTP/1.1 language and means that + * the connection will close when this request has been + * served. + */ + streamclose(conn, "Connection: close used"); + return CURLE_OK; } - } - else if(!k->http_bodyless && checkprefix("Content-Encoding:", headp) && - data->set.str[STRING_ENCODING]) { - /* - * Process Content-Encoding. Look for the values: identity, - * gzip, deflate, compress, x-gzip and x-compress. x-gzip and - * x-compress are the same as gzip and compress. (Sec 3.5 RFC - * 2616). zlib cannot handle compress. However, errors are - * handled further down when the response body is processed - */ - result = Curl_build_unencoding_stack(data, - headp + strlen("Content-Encoding:"), - FALSE); - if(result) - return result; - } - else if(checkprefix("Retry-After:", headp)) { - /* Retry-After = HTTP-date / delay-seconds */ - curl_off_t retry_after = 0; /* zero for unknown or "now" */ - /* Try it as a decimal number, if it works it is not a date */ - (void)curlx_strtoofft(headp + strlen("Retry-After:"), - NULL, 10, &retry_after); - if(!retry_after) { - time_t date = Curl_getdate_capped(headp + strlen("Retry-After:")); - if(-1 != date) - /* convert date to number of seconds into the future */ - retry_after = date - time(NULL); + if((conn->httpversion == 10) && + HD_IS_AND_SAYS(hd, hdlen, "Connection:", "keep-alive")) { + /* + * An HTTP/1.0 reply with the 'Connection: keep-alive' line + * tells us the connection will be kept alive for our + * pleasure. Default action for 1.0 is to close. + * + * [RFC2068, section 19.7.1] */ + connkeep(conn, "Connection keep-alive"); + infof(data, "HTTP/1.0 connection set to keep alive"); + return CURLE_OK; } - data->info.retry_after = retry_after; /* store it */ - } - else if(!k->http_bodyless && checkprefix("Content-Range:", headp)) { - /* Content-Range: bytes [num]- - Content-Range: bytes: [num]- - Content-Range: [num]- - Content-Range: [asterisk]/[total] - - The second format was added since Sun's webserver - JavaWebServer/1.1.1 obviously sends the header this way! - The third added since some servers use that! - The fourth means the requested range was unsatisfied. - */ - - char *ptr = headp + strlen("Content-Range:"); - - /* Move forward until first digit or asterisk */ - while(*ptr && !ISDIGIT(*ptr) && *ptr != '*') - ptr++; - - /* if it truly stopped on a digit */ - if(ISDIGIT(*ptr)) { - if(!curlx_strtoofft(ptr, NULL, 10, &k->offset)) { - if(data->state.resume_from == k->offset) - /* we asked for a resume and we got it */ - k->content_range = TRUE; + v = !k->http_bodyless ? HD_VAL(hd, hdlen, "Content-Range:") : NULL; + if(v) { + /* Content-Range: bytes [num]- + Content-Range: bytes: [num]- + Content-Range: [num]- + Content-Range: [asterisk]/[total] + + The second format was added since Sun's webserver + JavaWebServer/1.1.1 obviously sends the header this way! + The third added since some servers use that! + The fourth means the requested range was unsatisfied. + */ + + const char *ptr = v; + + /* Move forward until first digit or asterisk */ + while(*ptr && !ISDIGIT(*ptr) && *ptr != '*') + ptr++; + + /* if it truly stopped on a digit */ + if(ISDIGIT(*ptr)) { + if(!curlx_strtoofft(ptr, NULL, 10, &k->offset)) { + if(data->state.resume_from == k->offset) + /* we asked for a resume and we got it */ + k->content_range = TRUE; + } } + else if(k->httpcode < 300) + data->state.resume_from = 0; /* get everything */ } - else - data->state.resume_from = 0; /* get everything */ - } -#if !defined(CURL_DISABLE_COOKIES) - else if(data->cookies && data->state.cookie_engine && - checkprefix("Set-Cookie:", headp)) { - /* If there is a custom-set Host: name, use it here, or else use real peer - host name. */ - const char *host = data->state.aptr.cookiehost? - data->state.aptr.cookiehost:conn->host.name; - const bool secure_context = - conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) || - strcasecompare("localhost", host) || - !strcmp(host, "127.0.0.1") || - !strcmp(host, "::1") ? TRUE : FALSE; - - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, - CURL_LOCK_ACCESS_SINGLE); - Curl_cookie_add(data, data->cookies, TRUE, FALSE, - headp + strlen("Set-Cookie:"), host, - data->state.up.path, secure_context); - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - } -#endif - else if(!k->http_bodyless && checkprefix("Last-Modified:", headp) && - (data->set.timecondition || data->set.get_filetime) ) { - k->timeofdoc = Curl_getdate_capped(headp + strlen("Last-Modified:")); - if(data->set.get_filetime) - data->info.filetime = k->timeofdoc; - } - else if((checkprefix("WWW-Authenticate:", headp) && - (401 == k->httpcode)) || - (checkprefix("Proxy-authenticate:", headp) && - (407 == k->httpcode))) { - - bool proxy = (k->httpcode == 407) ? TRUE : FALSE; - char *auth = Curl_copy_header_value(headp); - if(!auth) - return CURLE_OUT_OF_MEMORY; + break; + case 'l': + case 'L': + v = (!k->http_bodyless && + (data->set.timecondition || data->set.get_filetime)) ? + HD_VAL(hd, hdlen, "Last-Modified:") : NULL; + if(v) { + k->timeofdoc = Curl_getdate_capped(v); + if(data->set.get_filetime) + data->info.filetime = k->timeofdoc; + return CURLE_OK; + } + if((k->httpcode >= 300 && k->httpcode < 400) && + HD_IS(hd, hdlen, "Location:") && + !data->req.location) { + /* this is the URL that the server advises us to use instead */ + char *location = Curl_copy_header_value(hd); + if(!location) + return CURLE_OUT_OF_MEMORY; + if(!*location) + /* ignore empty data */ + free(location); + else { + data->req.location = location; - result = Curl_http_input_auth(data, proxy, auth); + if(data->set.http_follow_location) { + DEBUGASSERT(!data->req.newurl); + data->req.newurl = strdup(data->req.location); /* clone */ + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; - free(auth); + /* some cases of POST and PUT etc needs to rewind the data + stream at this point */ + result = http_perhapsrewind(data, conn); + if(result) + return result; - if(result) + /* mark the next request as a followed location: */ + data->state.this_is_a_follow = TRUE; + } + } + } + break; + case 'p': + case 'P': +#ifndef CURL_DISABLE_PROXY + v = HD_VAL(hd, hdlen, "Proxy-Connection:"); + if(v) { + if((conn->httpversion == 10) && conn->bits.httpproxy && + HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "keep-alive")) { + /* + * When an HTTP/1.0 reply comes when using a proxy, the + * 'Proxy-Connection: keep-alive' line tells us the + * connection will be kept alive for our pleasure. + * Default action for 1.0 is to close. + */ + connkeep(conn, "Proxy-Connection keep-alive"); /* do not close */ + infof(data, "HTTP/1.0 proxy connection set to keep alive"); + } + else if((conn->httpversion == 11) && conn->bits.httpproxy && + HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "close")) { + /* + * We get an HTTP/1.1 response from a proxy and it says it will + * close down after this transfer. + */ + connclose(conn, "Proxy-Connection: asked to close after done"); + infof(data, "HTTP/1.1 proxy connection set close"); + } + return CURLE_OK; + } +#endif + if((407 == k->httpcode) && HD_IS(hd, hdlen, "Proxy-authenticate:")) { + char *auth = Curl_copy_header_value(hd); + if(!auth) + return CURLE_OUT_OF_MEMORY; + result = Curl_http_input_auth(data, TRUE, auth); + free(auth); return result; - } + } #ifdef USE_SPNEGO - else if(checkprefix("Persistent-Auth:", headp)) { - struct negotiatedata *negdata = &conn->negotiate; - struct auth *authp = &data->state.authhost; - if(authp->picked == CURLAUTH_NEGOTIATE) { - char *persistentauth = Curl_copy_header_value(headp); - if(!persistentauth) - return CURLE_OUT_OF_MEMORY; - negdata->noauthpersist = checkprefix("false", persistentauth)? - TRUE:FALSE; - negdata->havenoauthpersist = TRUE; - infof(data, "Negotiate: noauthpersist -> %d, header part: %s", - negdata->noauthpersist, persistentauth); - free(persistentauth); + if(HD_IS(hd, hdlen, "Persistent-Auth:")) { + struct negotiatedata *negdata = &conn->negotiate; + struct auth *authp = &data->state.authhost; + if(authp->picked == CURLAUTH_NEGOTIATE) { + char *persistentauth = Curl_copy_header_value(hd); + if(!persistentauth) + return CURLE_OUT_OF_MEMORY; + negdata->noauthpersist = !!checkprefix("false", persistentauth); + negdata->havenoauthpersist = TRUE; + infof(data, "Negotiate: noauthpersist -> %d, header part: %s", + negdata->noauthpersist, persistentauth); + free(persistentauth); + } } - } #endif - else if((k->httpcode >= 300 && k->httpcode < 400) && - checkprefix("Location:", headp) && - !data->req.location) { - /* this is the URL that the server advises us to use instead */ - char *location = Curl_copy_header_value(headp); - if(!location) - return CURLE_OUT_OF_MEMORY; - if(!*location) - /* ignore empty data */ - free(location); - else { - data->req.location = location; - - if(data->set.http_follow_location) { - DEBUGASSERT(!data->req.newurl); - data->req.newurl = strdup(data->req.location); /* clone */ - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - - /* some cases of POST and PUT etc needs to rewind the data - stream at this point */ - result = http_perhapsrewind(data, conn); - if(result) - return result; - - /* mark the next request as a followed location: */ - data->state.this_is_a_follow = TRUE; + break; + case 'r': + case 'R': + v = HD_VAL(hd, hdlen, "Retry-After:"); + if(v) { + /* Retry-After = HTTP-date / delay-seconds */ + curl_off_t retry_after = 0; /* zero for unknown or "now" */ + /* Try it as a decimal number, if it works it is not a date */ + (void)curlx_strtoofft(v, NULL, 10, &retry_after); + if(!retry_after) { + time_t date = Curl_getdate_capped(v); + if(-1 != date) + /* convert date to number of seconds into the future */ + retry_after = date - time(NULL); } + data->info.retry_after = retry_after; /* store it */ + return CURLE_OK; } - } + break; + case 's': + case 'S': +#if !defined(CURL_DISABLE_COOKIES) + v = (data->cookies && data->state.cookie_engine) ? + HD_VAL(hd, hdlen, "Set-Cookie:") : NULL; + if(v) { + /* If there is a custom-set Host: name, use it here, or else use + * real peer hostname. */ + const char *host = data->state.aptr.cookiehost ? + data->state.aptr.cookiehost : conn->host.name; + const bool secure_context = + conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) || + strcasecompare("localhost", host) || + !strcmp(host, "127.0.0.1") || + !strcmp(host, "::1"); + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, + CURL_LOCK_ACCESS_SINGLE); + Curl_cookie_add(data, data->cookies, TRUE, FALSE, v, host, + data->state.up.path, secure_context); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + return CURLE_OK; + } +#endif #ifndef CURL_DISABLE_HSTS - /* If enabled, the header is incoming and this is over HTTPS */ - else if(data->hsts && checkprefix("Strict-Transport-Security:", headp) && - ((conn->handler->flags & PROTOPT_SSL) || -#ifdef CURLDEBUG + /* If enabled, the header is incoming and this is over HTTPS */ + v = (data->hsts && + ((conn->handler->flags & PROTOPT_SSL) || +#ifdef DEBUGBUILD /* allow debug builds to circumvent the HTTPS restriction */ getenv("CURL_HSTS_HTTP") #else 0 #endif - )) { - CURLcode check = - Curl_hsts_parse(data->hsts, conn->host.name, - headp + strlen("Strict-Transport-Security:")); - if(check) - infof(data, "Illegal STS header skipped"); + ) + ) ? HD_VAL(hd, hdlen, "Strict-Transport-Security:") : NULL; + if(v) { + CURLcode check = + Curl_hsts_parse(data->hsts, conn->host.name, v); + if(check) + infof(data, "Illegal STS header skipped"); #ifdef DEBUGBUILD - else - infof(data, "Parsed STS header fine (%zu entries)", - data->hsts->list.size); -#endif - } + else + infof(data, "Parsed STS header fine (%zu entries)", + Curl_llist_count(&data->hsts->list)); #endif -#ifndef CURL_DISABLE_ALTSVC - /* If enabled, the header is incoming and this is over HTTPS */ - else if(data->asi && checkprefix("Alt-Svc:", headp) && - ((conn->handler->flags & PROTOPT_SSL) || -#ifdef CURLDEBUG - /* allow debug builds to circumvent the HTTPS restriction */ - getenv("CURL_ALTSVC_HTTP") -#else - 0 + } #endif - )) { - /* the ALPN of the current request */ - enum alpnid id = (conn->httpversion == 30)? ALPN_h3 : - (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1; - result = Curl_altsvc_parse(data, data->asi, - headp + strlen("Alt-Svc:"), - id, conn->host.name, - curlx_uitous((unsigned int)conn->remote_port)); - if(result) + break; + case 't': + case 'T': + /* RFC 9112, ch. 6.1 + * "Transfer-Encoding MAY be sent in a response to a HEAD request or + * in a 304 (Not Modified) response (Section 15.4.5 of [HTTP]) to a + * GET request, neither of which includes a message body, to indicate + * that the origin server would have applied a transfer coding to the + * message body if the request had been an unconditional GET." + * + * Read: in these cases the 'Transfer-Encoding' does not apply + * to any data following the response headers. Do not add any decoders. + */ + v = (!k->http_bodyless && + (data->state.httpreq != HTTPREQ_HEAD) && + (k->httpcode != 304)) ? + HD_VAL(hd, hdlen, "Transfer-Encoding:") : NULL; + if(v) { + /* One or more encodings. We check for chunked and/or a compression + algorithm. */ + result = Curl_build_unencoding_stack(data, v, TRUE); + if(result) + return result; + if(!k->chunk && data->set.http_transfer_encoding) { + /* if this is not chunked, only close can signal the end of this + * transfer as Content-Length is said not to be trusted for + * transfer-encoding! */ + connclose(conn, "HTTP/1.1 transfer-encoding without chunks"); + k->ignore_cl = TRUE; + } + return CURLE_OK; + } + v = HD_VAL(hd, hdlen, "Trailer:"); + if(v) { + data->req.resp_trailer = TRUE; + return CURLE_OK; + } + break; + case 'w': + case 'W': + if((401 == k->httpcode) && HD_IS(hd, hdlen, "WWW-Authenticate:")) { + char *auth = Curl_copy_header_value(hd); + if(!auth) + return CURLE_OUT_OF_MEMORY; + result = Curl_http_input_auth(data, FALSE, auth); + free(auth); return result; + } + break; } -#endif - else if(conn->handler->protocol & CURLPROTO_RTSP) { - result = Curl_rtsp_parseheader(data, headp); + + if(conn->handler->protocol & CURLPROTO_RTSP) { + result = Curl_rtsp_parseheader(data, hd); if(result) return result; } @@ -3839,25 +3179,48 @@ CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, * Called after the first HTTP response line (the status line) has been * received and parsed. */ - CURLcode Curl_http_statusline(struct Curl_easy *data, struct connectdata *conn) { struct SingleRequest *k = &data->req; + + switch(k->httpversion) { + case 10: + case 11: +#ifdef USE_HTTP2 + case 20: +#endif +#ifdef USE_HTTP3 + case 30: +#endif + /* no major version switch mid-connection */ + if(conn->httpversion && + (k->httpversion/10 != conn->httpversion/10)) { + failf(data, "Version mismatch (from HTTP/%u to HTTP/%u)", + conn->httpversion/10, k->httpversion/10); + return CURLE_UNSUPPORTED_PROTOCOL; + } + break; + default: + failf(data, "Unsupported HTTP version (%u.%d) in response", + k->httpversion/10, k->httpversion%10); + return CURLE_UNSUPPORTED_PROTOCOL; + } + data->info.httpcode = k->httpcode; + data->info.httpversion = k->httpversion; + conn->httpversion = (unsigned char)k->httpversion; - data->info.httpversion = conn->httpversion; - if(!data->state.httpversion || - data->state.httpversion > conn->httpversion) + if(!data->state.httpversion || data->state.httpversion > k->httpversion) /* store the lowest server version we encounter */ - data->state.httpversion = conn->httpversion; + data->state.httpversion = (unsigned char)k->httpversion; /* - * This code executes as part of processing the header. As a - * result, it's not totally clear how to interpret the + * This code executes as part of processing the header. As a + * result, it is not totally clear how to interpret the * response code yet as that depends on what other headers may - * be present. 401 and 407 may be errors, but may be OK - * depending on how authentication is working. Other codes + * be present. 401 and 407 may be errors, but may be OK + * depending on how authentication is working. Other codes * are definitely errors, so give up here. */ if(data->state.resume_from && data->state.httpreq == HTTPREQ_GET && @@ -3867,25 +3230,16 @@ CURLcode Curl_http_statusline(struct Curl_easy *data, k->ignorebody = TRUE; /* Avoid appending error msg to good data. */ } - if(conn->httpversion == 10) { + if(k->httpversion == 10) { /* Default action for HTTP/1.0 must be to close, unless we get one of those fancy headers that tell us the server keeps it open for us! */ infof(data, "HTTP 1.0, assume close after body"); connclose(conn, "HTTP/1.0 close after body"); } - else if(conn->httpversion == 20 || + else if(k->httpversion == 20 || (k->upgr101 == UPGR101_H2 && k->httpcode == 101)) { DEBUGF(infof(data, "HTTP/2 found, allow multiplexing")); - /* HTTP/2 cannot avoid multiplexing since it is a core functionality - of the protocol */ - conn->bundle->multiuse = BUNDLE_MULTIPLEX; - } - else if(conn->httpversion >= 11 && - !conn->bits.close) { - /* If HTTP version is >= 1.1 and connection is persistent */ - DEBUGF(infof(data, - "HTTP 1.1 or later with persistent connection")); } k->http_bodyless = k->httpcode >= 100 && k->httpcode < 200; @@ -3897,7 +3251,7 @@ CURLcode Curl_http_statusline(struct Curl_easy *data, * fields. */ if(data->set.timecondition) data->info.timecond = TRUE; - /* FALLTHROUGH */ + FALLTHROUGH(); case 204: /* (quote from RFC2616, section 10.2.5): The server has * fulfilled the request but does not need to return an @@ -3915,7 +3269,7 @@ CURLcode Curl_http_statusline(struct Curl_easy *data, } /* Content-Length must be ignored if any Transfer-Encoding is present in the - response. Refer to RFC 7230 section 3.3.3 and RFC2616 section 4.4. This is + response. Refer to RFC 7230 section 3.3.3 and RFC2616 section 4.4. This is figured out here after all headers have been received but before the final call to the user's header callback, so that a valid content length can be retrieved by the user in the final call. */ @@ -3927,22 +3281,24 @@ CURLcode Curl_http_size(struct Curl_easy *data) } else if(k->size != -1) { if(data->set.max_filesize && - k->size > data->set.max_filesize) { + !k->ignorebody && + (k->size > data->set.max_filesize)) { failf(data, "Maximum file size exceeded"); return CURLE_FILESIZE_EXCEEDED; } + if(k->ignorebody) + infof(data, "setting size while ignoring"); Curl_pgrsSetDownloadSize(data, k->size); k->maxdownload = k->size; } return CURLE_OK; } -static CURLcode verify_header(struct Curl_easy *data) +static CURLcode verify_header(struct Curl_easy *data, + const char *hd, size_t hdlen) { struct SingleRequest *k = &data->req; - const char *header = Curl_dyn_ptr(&data->state.headerb); - size_t hlen = Curl_dyn_len(&data->state.headerb); - char *ptr = memchr(header, 0x00, hlen); + char *ptr = memchr(hd, 0x00, hdlen); if(ptr) { /* this is bad, bail out */ failf(data, "Nul byte in header"); @@ -3951,11 +3307,11 @@ static CURLcode verify_header(struct Curl_easy *data) if(k->headerline < 2) /* the first "header" is the status-line and it has no colon */ return CURLE_OK; - if(((header[0] == ' ') || (header[0] == '\t')) && k->headerline > 2) - /* line folding, can't happen on line 2 */ + if(((hd[0] == ' ') || (hd[0] == '\t')) && k->headerline > 2) + /* line folding, cannot happen on line 2 */ ; else { - ptr = memchr(header, ':', hlen); + ptr = memchr(hd, ':', hdlen); if(!ptr) { /* this is bad, bail out */ failf(data, "Header without colon"); @@ -3970,58 +3326,545 @@ CURLcode Curl_bump_headersize(struct Curl_easy *data, bool connect_only) { size_t bad = 0; + unsigned int max = MAX_HTTP_RESP_HEADER_SIZE; if(delta < MAX_HTTP_RESP_HEADER_SIZE) { + data->info.header_size += (unsigned int)delta; + data->req.allheadercount += (unsigned int)delta; if(!connect_only) data->req.headerbytecount += (unsigned int)delta; - data->info.header_size += (unsigned int)delta; - if(data->info.header_size > MAX_HTTP_RESP_HEADER_SIZE) + if(data->req.allheadercount > max) + bad = data->req.allheadercount; + else if(data->info.header_size > (max * 20)) { bad = data->info.header_size; + max *= 20; + } } else - bad = data->info.header_size + delta; + bad = data->req.allheadercount + delta; if(bad) { - failf(data, "Too large response headers: %zu > %u", - bad, MAX_HTTP_RESP_HEADER_SIZE); + failf(data, "Too large response headers: %zu > %u", bad, max); return CURLE_RECV_ERROR; } return CURLE_OK; } - +static CURLcode http_write_header(struct Curl_easy *data, + const char *hd, size_t hdlen) +{ + CURLcode result; + int writetype; + + /* now, only output this if the header AND body are requested: + */ + Curl_debug(data, CURLINFO_HEADER_IN, (char *)hd, hdlen); + + writetype = CLIENTWRITE_HEADER | + ((data->req.httpcode/100 == 1) ? CLIENTWRITE_1XX : 0); + + result = Curl_client_write(data, writetype, hd, hdlen); + if(result) + return result; + + result = Curl_bump_headersize(data, hdlen, FALSE); + if(result) + return result; + + data->req.deductheadercount = (100 <= data->req.httpcode && + 199 >= data->req.httpcode) ? + data->req.headerbytecount : 0; + return result; +} + +static CURLcode http_on_response(struct Curl_easy *data, + const char *last_hd, size_t last_hd_len, + const char *buf, size_t blen, + size_t *pconsumed) +{ + struct connectdata *conn = data->conn; + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + + (void)buf; /* not used without HTTP2 enabled */ + *pconsumed = 0; + + if(k->upgr101 == UPGR101_RECEIVED) { + /* supposedly upgraded to http2 now */ + if(conn->httpversion != 20) + infof(data, "Lying server, not serving HTTP/2"); + } + + if(k->httpcode < 200 && last_hd) { + /* Intermediate responses might trigger processing of more + * responses, write the last header to the client before + * proceeding. */ + result = http_write_header(data, last_hd, last_hd_len); + last_hd = NULL; /* handled it */ + if(result) + goto out; + } + + if(k->httpcode < 100) { + failf(data, "Unsupported response code in HTTP response"); + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; + } + else if(k->httpcode < 200) { + /* "A user agent MAY ignore unexpected 1xx status responses." + * By default, we expect to get more responses after this one. */ + k->header = TRUE; + k->headerline = 0; /* restart the header line counter */ + + switch(k->httpcode) { + case 100: + /* + * We have made an HTTP PUT or POST and this is 1.1-lingo + * that tells us that the server is OK with this and ready + * to receive the data. + */ + Curl_http_exp100_got100(data); + break; + case 101: + /* Switching Protocols only allowed from HTTP/1.1 */ + + if(conn->httpversion != 11) { + /* invalid for other HTTP versions */ + failf(data, "unexpected 101 response code"); + result = CURLE_WEIRD_SERVER_REPLY; + goto out; + } + if(k->upgr101 == UPGR101_H2) { + /* Switching to HTTP/2, where we will get more responses */ + infof(data, "Received 101, Switching to HTTP/2"); + k->upgr101 = UPGR101_RECEIVED; + data->conn->bits.asks_multiplex = FALSE; + /* We expect more response from HTTP/2 later */ + k->header = TRUE; + k->headerline = 0; /* restart the header line counter */ + /* Any remaining `buf` bytes are already HTTP/2 and passed to + * be processed. */ + result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen); + if(result) + goto out; + *pconsumed += blen; + } +#ifndef CURL_DISABLE_WEBSOCKETS + else if(k->upgr101 == UPGR101_WS) { + /* verify the response. Any passed `buf` bytes are already in + * WebSockets format and taken in by the protocol handler. */ + result = Curl_ws_accept(data, buf, blen); + if(result) + goto out; + *pconsumed += blen; /* ws accept handled the data */ + k->header = FALSE; /* we will not get more responses */ + if(data->set.connect_only) + k->keepon &= ~KEEP_RECV; /* read no more content */ + } +#endif + else { + /* We silently accept this as the final response. + * TODO: this looks, uhm, wrong. What are we switching to if we + * did not ask for an Upgrade? Maybe the application provided an + * `Upgrade: xxx` header? */ + k->header = FALSE; + } + break; + default: + /* The server may send us other 1xx responses, like informative + * 103. This have no influence on request processing and we expect + * to receive a final response eventually. */ + break; + } + goto out; + } + + /* k->httpcode >= 200, final response */ + k->header = FALSE; + + if(k->upgr101 == UPGR101_H2) { + /* A requested upgrade was denied, poke the multi handle to possibly + allow a pending pipewait to continue */ + data->conn->bits.asks_multiplex = FALSE; + Curl_multi_connchanged(data->multi); + } + + if((k->size == -1) && !k->chunk && !conn->bits.close && + (conn->httpversion == 11) && + !(conn->handler->protocol & CURLPROTO_RTSP) && + data->state.httpreq != HTTPREQ_HEAD) { + /* On HTTP 1.1, when connection is not to get closed, but no + Content-Length nor Transfer-Encoding chunked have been + received, according to RFC2616 section 4.4 point 5, we + assume that the server will close the connection to + signal the end of the document. */ + infof(data, "no chunk, no close, no size. Assume close to " + "signal end"); + streamclose(conn, "HTTP: No end-of-message indicator"); + } + + /* At this point we have some idea about the fate of the connection. + If we are closing the connection it may result auth failure. */ +#if defined(USE_NTLM) + if(conn->bits.close && + (((data->req.httpcode == 401) && + (conn->http_ntlm_state == NTLMSTATE_TYPE2)) || + ((data->req.httpcode == 407) && + (conn->proxy_ntlm_state == NTLMSTATE_TYPE2)))) { + infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); + data->state.authproblem = TRUE; + } +#endif +#if defined(USE_SPNEGO) + if(conn->bits.close && + (((data->req.httpcode == 401) && + (conn->http_negotiate_state == GSS_AUTHRECV)) || + ((data->req.httpcode == 407) && + (conn->proxy_negotiate_state == GSS_AUTHRECV)))) { + infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); + data->state.authproblem = TRUE; + } + if((conn->http_negotiate_state == GSS_AUTHDONE) && + (data->req.httpcode != 401)) { + conn->http_negotiate_state = GSS_AUTHSUCC; + } + if((conn->proxy_negotiate_state == GSS_AUTHDONE) && + (data->req.httpcode != 407)) { + conn->proxy_negotiate_state = GSS_AUTHSUCC; + } +#endif + +#ifndef CURL_DISABLE_WEBSOCKETS + /* All >=200 HTTP status codes are errors when wanting WebSockets */ + if(data->req.upgr101 == UPGR101_WS) { + failf(data, "Refused WebSockets upgrade: %d", k->httpcode); + result = CURLE_HTTP_RETURNED_ERROR; + goto out; + } +#endif + + /* Check if this response means the transfer errored. */ + if(http_should_fail(data, data->req.httpcode)) { + failf(data, "The requested URL returned error: %d", + k->httpcode); + result = CURLE_HTTP_RETURNED_ERROR; + goto out; + } + + /* Curl_http_auth_act() checks what authentication methods + * that are available and decides which one (if any) to + * use. It will set 'newurl' if an auth method was picked. */ + result = Curl_http_auth_act(data); + if(result) + goto out; + + if(k->httpcode >= 300) { + if((!data->req.authneg) && !conn->bits.close && + !Curl_creader_will_rewind(data)) { + /* + * General treatment of errors when about to send data. Including : + * "417 Expectation Failed", while waiting for 100-continue. + * + * The check for close above is done simply because of something + * else has already deemed the connection to get closed then + * something else should've considered the big picture and we + * avoid this check. + * + */ + + switch(data->state.httpreq) { + case HTTPREQ_PUT: + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + /* We got an error response. If this happened before the whole + * request body has been sent we stop sending and mark the + * connection for closure after we have read the entire response. + */ + if(!Curl_req_done_sending(data)) { + if((k->httpcode == 417) && Curl_http_exp100_is_selected(data)) { + /* 417 Expectation Failed - try again without the Expect + header */ + if(!k->writebytecount && http_exp100_is_waiting(data)) { + infof(data, "Got HTTP failure 417 while waiting for a 100"); + } + else { + infof(data, "Got HTTP failure 417 while sending data"); + streamclose(conn, + "Stop sending data before everything sent"); + result = http_perhapsrewind(data, conn); + if(result) + goto out; + } + data->state.disableexpect = TRUE; + DEBUGASSERT(!data->req.newurl); + data->req.newurl = strdup(data->state.url); + Curl_req_abort_sending(data); + } + else if(data->set.http_keep_sending_on_error) { + infof(data, "HTTP error before end of send, keep sending"); + http_exp100_send_anyway(data); + } + else { + infof(data, "HTTP error before end of send, stop sending"); + streamclose(conn, "Stop sending data before everything sent"); + result = Curl_req_abort_sending(data); + if(result) + goto out; + } + } + break; + + default: /* default label present to avoid compiler warnings */ + break; + } + } + + if(Curl_creader_will_rewind(data) && !Curl_req_done_sending(data)) { + /* We rewind before next send, continue sending now */ + infof(data, "Keep sending data to get tossed away"); + k->keepon |= KEEP_SEND; + } + + } + + /* If we requested a "no body", this is a good time to get + * out and return home. + */ + if(data->req.no_body) + k->download_done = TRUE; + + /* If max download size is *zero* (nothing) we already have + nothing and can safely return ok now! But for HTTP/2, we would + like to call http2_handle_stream_close to properly close a + stream. In order to do this, we keep reading until we + close the stream. */ + if(0 == k->maxdownload + && !Curl_conn_is_http2(data, conn, FIRSTSOCKET) + && !Curl_conn_is_http3(data, conn, FIRSTSOCKET)) + k->download_done = TRUE; + + /* final response without error, prepare to receive the body */ + result = Curl_http_firstwrite(data); + + if(!result) + /* This is the last response that we get for the current request. + * Check on the body size and determine if the response is complete. + */ + result = Curl_http_size(data); + +out: + if(last_hd) { + /* if not written yet, write it now */ + CURLcode r2 = http_write_header(data, last_hd, last_hd_len); + if(!result) + result = r2; + } + return result; +} + +static CURLcode http_rw_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + const char *buf_remain, size_t blen, + size_t *pconsumed) +{ + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + int writetype; + + *pconsumed = 0; + if((0x0a == *hd) || (0x0d == *hd)) { + /* Empty header line means end of headers! */ + struct dynbuf last_header; + size_t consumed; + + Curl_dyn_init(&last_header, hdlen + 1); + result = Curl_dyn_addn(&last_header, hd, hdlen); + if(result) + return result; + + /* analyze the response to find out what to do. */ + /* Caveat: we clear anything in the header brigade, because a + * response might switch HTTP version which may call use recursively. + * Not nice, but that is currently the way of things. */ + Curl_dyn_reset(&data->state.headerb); + result = http_on_response(data, Curl_dyn_ptr(&last_header), + Curl_dyn_len(&last_header), + buf_remain, blen, &consumed); + *pconsumed += consumed; + Curl_dyn_free(&last_header); + return result; + } + + /* + * Checks for special headers coming up. + */ + + writetype = CLIENTWRITE_HEADER; + if(!k->headerline++) { + /* This is the first header, it MUST be the error code line + or else we consider this to be the body right away! */ + bool fine_statusline = FALSE; + + k->httpversion = 0; /* Do not know yet */ + if(data->conn->handler->protocol & PROTO_FAMILY_HTTP) { + /* + * https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 + * + * The response code is always a three-digit number in HTTP as the spec + * says. We allow any three-digit number here, but we cannot make + * guarantees on future behaviors since it is not within the protocol. + */ + const char *p = hd; + + while(*p && ISBLANK(*p)) + p++; + if(!strncmp(p, "HTTP/", 5)) { + p += 5; + switch(*p) { + case '1': + p++; + if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) { + if(ISBLANK(p[2])) { + k->httpversion = 10 + (p[1] - '0'); + p += 3; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + p += 3; + if(ISSPACE(*p)) + fine_statusline = TRUE; + } + } + } + if(!fine_statusline) { + failf(data, "Unsupported HTTP/1 subversion in response"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + break; + case '2': + case '3': + if(!ISBLANK(p[1])) + break; + k->httpversion = (*p - '0') * 10; + p += 2; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + p += 3; + if(!ISSPACE(*p)) + break; + fine_statusline = TRUE; + } + break; + default: /* unsupported */ + failf(data, "Unsupported HTTP version in response"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + } + + if(!fine_statusline) { + /* If user has set option HTTP200ALIASES, + compare header line against list of aliases + */ + statusline check = checkhttpprefix(data, hd, hdlen); + if(check == STATUS_DONE) { + fine_statusline = TRUE; + k->httpcode = 200; + k->httpversion = 10; + } + } + } + else if(data->conn->handler->protocol & CURLPROTO_RTSP) { + const char *p = hd; + while(*p && ISBLANK(*p)) + p++; + if(!strncmp(p, "RTSP/", 5)) { + p += 5; + if(ISDIGIT(*p)) { + p++; + if((p[0] == '.') && ISDIGIT(p[1])) { + if(ISBLANK(p[2])) { + p += 3; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + p += 3; + if(ISSPACE(*p)) { + fine_statusline = TRUE; + k->httpversion = 11; /* RTSP acts like HTTP 1.1 */ + } + } + } + } + } + if(!fine_statusline) + return CURLE_WEIRD_SERVER_REPLY; + } + } + + if(fine_statusline) { + result = Curl_http_statusline(data, data->conn); + if(result) + return result; + writetype |= CLIENTWRITE_STATUS; + } + else { + k->header = FALSE; /* this is not a header line */ + return CURLE_WEIRD_SERVER_REPLY; + } + } + + result = verify_header(data, hd, hdlen); + if(result) + return result; + + result = Curl_http_header(data, hd, hdlen); + if(result) + return result; + + /* + * Taken in one (more) header. Write it to the client. + */ + Curl_debug(data, CURLINFO_HEADER_IN, (char *)hd, hdlen); + + if(k->httpcode/100 == 1) + writetype |= CLIENTWRITE_1XX; + result = Curl_client_write(data, writetype, hd, hdlen); + if(result) + return result; + + result = Curl_bump_headersize(data, hdlen, FALSE); + if(result) + return result; + + return CURLE_OK; +} + /* * Read any HTTP header lines from the server and pass them to the client app. */ -CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, - struct connectdata *conn, - ssize_t *nread, - bool *stop_reading) +static CURLcode http_parse_headers(struct Curl_easy *data, + const char *buf, size_t blen, + size_t *pconsumed) { - CURLcode result; + struct connectdata *conn = data->conn; + CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; - ssize_t onread = *nread; - char *ostr = k->str; - char *headp; - char *str_start; char *end_ptr; + bool leftover_body = FALSE; /* header line within buffer loop */ - do { - size_t rest_length; - size_t full_length; - int writetype; - - /* str_start is start of line within buf */ - str_start = k->str; - - /* data is in network encoding so use 0x0a instead of '\n' */ - end_ptr = memchr(str_start, 0x0a, *nread); + *pconsumed = 0; + while(blen && k->header) { + size_t consumed; + end_ptr = memchr(buf, '\n', blen); if(!end_ptr) { /* Not a complete header line within buffer, append the data to the end of the headerbuff. */ - result = Curl_dyn_addn(&data->state.headerb, str_start, *nread); + result = Curl_dyn_addn(&data->state.headerb, buf, blen); if(result) return result; + *pconsumed += blen; if(!k->headerline) { /* check if this looks like a protocol header */ @@ -4033,30 +3876,30 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, if(st == STATUS_BAD) { /* this is not the beginning of a protocol first header line */ k->header = FALSE; - k->badheader = HEADER_ALLBAD; streamclose(conn, "bad HTTP: No end-of-message indicator"); + if(conn->httpversion >= 10) { + failf(data, "Invalid status line"); + return CURLE_WEIRD_SERVER_REPLY; + } if(!data->set.http09_allowed) { failf(data, "Received HTTP/0.9 when not allowed"); return CURLE_UNSUPPORTED_PROTOCOL; } - break; + leftover_body = TRUE; + goto out; } } - - break; /* read more and try again */ + goto out; /* read more and try again */ } /* decrease the size of the remaining (supposed) header line */ - rest_length = (end_ptr - k->str) + 1; - *nread -= (ssize_t)rest_length; - - k->str = end_ptr + 1; /* move past new line */ - - full_length = k->str - str_start; - - result = Curl_dyn_addn(&data->state.headerb, str_start, full_length); + consumed = (end_ptr - buf) + 1; + result = Curl_dyn_addn(&data->state.headerb, buf, consumed); if(result) return result; + blen -= consumed; + buf += consumed; + *pconsumed += consumed; /**** * We now have a FULL header line in 'headerb'. @@ -4070,529 +3913,122 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, if(st == STATUS_BAD) { streamclose(conn, "bad HTTP: No end-of-message indicator"); /* this is not the beginning of a protocol first header line */ + if(conn->httpversion >= 10) { + failf(data, "Invalid status line"); + return CURLE_WEIRD_SERVER_REPLY; + } if(!data->set.http09_allowed) { failf(data, "Received HTTP/0.9 when not allowed"); return CURLE_UNSUPPORTED_PROTOCOL; } k->header = FALSE; - if(*nread) - /* since there's more, this is a partial bad header */ - k->badheader = HEADER_PARTHEADER; - else { - /* this was all we read so it's all a bad header */ - k->badheader = HEADER_ALLBAD; - *nread = onread; - k->str = ostr; - return CURLE_OK; - } - break; + leftover_body = TRUE; + goto out; } } - /* headers are in network encoding so use 0x0a and 0x0d instead of '\n' - and '\r' */ - headp = Curl_dyn_ptr(&data->state.headerb); - if((0x0a == *headp) || (0x0d == *headp)) { - size_t headerlen; - /* Zero-length header line means end of headers! */ - - if('\r' == *headp) - headp++; /* pass the \r byte */ - if('\n' == *headp) - headp++; /* pass the \n byte */ - - if(100 <= k->httpcode && 199 >= k->httpcode) { - /* "A user agent MAY ignore unexpected 1xx status responses." */ - switch(k->httpcode) { - case 100: - /* - * We have made an HTTP PUT or POST and this is 1.1-lingo - * that tells us that the server is OK with this and ready - * to receive the data. - * However, we'll get more headers now so we must get - * back into the header-parsing state! - */ - k->header = TRUE; - k->headerline = 0; /* restart the header line counter */ - - /* if we did wait for this do enable write now! */ - if(k->exp100 > EXP100_SEND_DATA) { - k->exp100 = EXP100_SEND_DATA; - k->keepon |= KEEP_SEND; - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - } - break; - case 101: - /* Switching Protocols */ - if(k->upgr101 == UPGR101_H2) { - /* Switching to HTTP/2 */ - DEBUGASSERT(conn->httpversion < 20); - infof(data, "Received 101, Switching to HTTP/2"); - k->upgr101 = UPGR101_RECEIVED; - - /* we'll get more headers (HTTP/2 response) */ - k->header = TRUE; - k->headerline = 0; /* restart the header line counter */ - - /* switch to http2 now. The bytes after response headers - are also processed here, otherwise they are lost. */ - result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, - k->str, *nread); - if(result) - return result; - *nread = 0; - } -#ifdef USE_WEBSOCKETS - else if(k->upgr101 == UPGR101_WS) { - /* verify the response */ - result = Curl_ws_accept(data, k->str, *nread); - if(result) - return result; - k->header = FALSE; /* no more header to parse! */ - if(data->set.connect_only) { - k->keepon &= ~KEEP_RECV; /* read no more content */ - *nread = 0; - } - } -#endif - else { - /* Not switching to another protocol */ - k->header = FALSE; /* no more header to parse! */ - } - break; - default: - /* the status code 1xx indicates a provisional response, so - we'll get another set of headers */ - k->header = TRUE; - k->headerline = 0; /* restart the header line counter */ - break; - } - } - else { - if(k->upgr101 == UPGR101_H2) { - /* A requested upgrade was denied, poke the multi handle to possibly - allow a pending pipewait to continue */ - Curl_multi_connchanged(data->multi); - } - k->header = FALSE; /* no more header to parse! */ - - if((k->size == -1) && !k->chunk && !conn->bits.close && - (conn->httpversion == 11) && - !(conn->handler->protocol & CURLPROTO_RTSP) && - data->state.httpreq != HTTPREQ_HEAD) { - /* On HTTP 1.1, when connection is not to get closed, but no - Content-Length nor Transfer-Encoding chunked have been - received, according to RFC2616 section 4.4 point 5, we - assume that the server will close the connection to - signal the end of the document. */ - infof(data, "no chunk, no close, no size. Assume close to " - "signal end"); - streamclose(conn, "HTTP: No end-of-message indicator"); - } - } - - if(!k->header) { - result = Curl_http_size(data); - if(result) - return result; - } - - /* At this point we have some idea about the fate of the connection. - If we are closing the connection it may result auth failure. */ -#if defined(USE_NTLM) - if(conn->bits.close && - (((data->req.httpcode == 401) && - (conn->http_ntlm_state == NTLMSTATE_TYPE2)) || - ((data->req.httpcode == 407) && - (conn->proxy_ntlm_state == NTLMSTATE_TYPE2)))) { - infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); - data->state.authproblem = TRUE; - } -#endif -#if defined(USE_SPNEGO) - if(conn->bits.close && - (((data->req.httpcode == 401) && - (conn->http_negotiate_state == GSS_AUTHRECV)) || - ((data->req.httpcode == 407) && - (conn->proxy_negotiate_state == GSS_AUTHRECV)))) { - infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); - data->state.authproblem = TRUE; - } - if((conn->http_negotiate_state == GSS_AUTHDONE) && - (data->req.httpcode != 401)) { - conn->http_negotiate_state = GSS_AUTHSUCC; - } - if((conn->proxy_negotiate_state == GSS_AUTHDONE) && - (data->req.httpcode != 407)) { - conn->proxy_negotiate_state = GSS_AUTHSUCC; - } -#endif - - /* now, only output this if the header AND body are requested: - */ - writetype = CLIENTWRITE_HEADER | - (data->set.include_header ? CLIENTWRITE_BODY : 0) | - ((k->httpcode/100 == 1) ? CLIENTWRITE_1XX : 0); - - headerlen = Curl_dyn_len(&data->state.headerb); - result = Curl_client_write(data, writetype, - Curl_dyn_ptr(&data->state.headerb), - headerlen); - if(result) - return result; - - result = Curl_bump_headersize(data, headerlen, FALSE); - if(result) - return result; - - /* - * When all the headers have been parsed, see if we should give - * up and return an error. - */ - if(http_should_fail(data)) { - failf(data, "The requested URL returned error: %d", - k->httpcode); - return CURLE_HTTP_RETURNED_ERROR; - } - -#ifdef USE_WEBSOCKETS - /* All non-101 HTTP status codes are bad when wanting to upgrade to - websockets */ - if(data->req.upgr101 == UPGR101_WS) { - failf(data, "Refused WebSockets upgrade: %d", k->httpcode); - return CURLE_HTTP_RETURNED_ERROR; - } -#endif - - - data->req.deductheadercount = - (100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0; - - /* Curl_http_auth_act() checks what authentication methods - * that are available and decides which one (if any) to - * use. It will set 'newurl' if an auth method was picked. */ - result = Curl_http_auth_act(data); - - if(result) - return result; - - if(k->httpcode >= 300) { - if((!conn->bits.authneg) && !conn->bits.close && - !data->state.rewindbeforesend) { - /* - * General treatment of errors when about to send data. Including : - * "417 Expectation Failed", while waiting for 100-continue. - * - * The check for close above is done simply because of something - * else has already deemed the connection to get closed then - * something else should've considered the big picture and we - * avoid this check. - * - * rewindbeforesend indicates that something has told libcurl to - * continue sending even if it gets discarded - */ - - switch(data->state.httpreq) { - case HTTPREQ_PUT: - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - /* We got an error response. If this happened before the whole - * request body has been sent we stop sending and mark the - * connection for closure after we've read the entire response. - */ - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - if(!k->upload_done) { - if((k->httpcode == 417) && data->state.expect100header) { - /* 417 Expectation Failed - try again without the Expect - header */ - if(!k->writebytecount && - k->exp100 == EXP100_AWAITING_CONTINUE) { - infof(data, "Got HTTP failure 417 while waiting for a 100"); - } - else { - infof(data, "Got HTTP failure 417 while sending data"); - streamclose(conn, - "Stop sending data before everything sent"); - result = http_perhapsrewind(data, conn); - if(result) - return result; - } - data->state.disableexpect = TRUE; - DEBUGASSERT(!data->req.newurl); - data->req.newurl = strdup(data->state.url); - Curl_done_sending(data, k); - } - else if(data->set.http_keep_sending_on_error) { - infof(data, "HTTP error before end of send, keep sending"); - if(k->exp100 > EXP100_SEND_DATA) { - k->exp100 = EXP100_SEND_DATA; - k->keepon |= KEEP_SEND; - } - } - else { - infof(data, "HTTP error before end of send, stop sending"); - streamclose(conn, "Stop sending data before everything sent"); - result = Curl_done_sending(data, k); - if(result) - return result; - k->upload_done = TRUE; - if(data->state.expect100header) - k->exp100 = EXP100_FAILED; - } - } - break; - - default: /* default label present to avoid compiler warnings */ - break; - } - } - - if(data->state.rewindbeforesend && - (conn->writesockfd != CURL_SOCKET_BAD)) { - /* We rewind before next send, continue sending now */ - infof(data, "Keep sending data to get tossed away"); - k->keepon |= KEEP_SEND; - } - } - - if(!k->header) { - /* - * really end-of-headers. - * - * If we requested a "no body", this is a good time to get - * out and return home. - */ - if(data->req.no_body) - *stop_reading = TRUE; -#ifndef CURL_DISABLE_RTSP - else if((conn->handler->protocol & CURLPROTO_RTSP) && - (data->set.rtspreq == RTSPREQ_DESCRIBE) && - (k->size <= -1)) - /* Respect section 4.4 of rfc2326: If the Content-Length header is - absent, a length 0 must be assumed. It will prevent libcurl from - hanging on DESCRIBE request that got refused for whatever - reason */ - *stop_reading = TRUE; -#endif - - /* If max download size is *zero* (nothing) we already have - nothing and can safely return ok now! But for HTTP/2, we'd - like to call http2_handle_stream_close to properly close a - stream. In order to do this, we keep reading until we - close the stream. */ - if(0 == k->maxdownload - && !Curl_conn_is_http2(data, conn, FIRSTSOCKET) - && !Curl_conn_is_http3(data, conn, FIRSTSOCKET)) - *stop_reading = TRUE; - - if(*stop_reading) { - /* we make sure that this socket isn't read more now */ - k->keepon &= ~KEEP_RECV; - } - - Curl_debug(data, CURLINFO_HEADER_IN, str_start, headerlen); - break; /* exit header line loop */ - } - - /* We continue reading headers, reset the line-based header */ - Curl_dyn_reset(&data->state.headerb); - continue; + result = http_rw_hd(data, Curl_dyn_ptr(&data->state.headerb), + Curl_dyn_len(&data->state.headerb), + buf, blen, &consumed); + /* We are done with this line. We reset because response + * processing might switch to HTTP/2 and that might call us + * directly again. */ + Curl_dyn_reset(&data->state.headerb); + if(consumed) { + blen -= consumed; + buf += consumed; + *pconsumed += consumed; } + if(result) + return result; + } - /* - * Checks for special headers coming up. - */ - - writetype = CLIENTWRITE_HEADER; - if(!k->headerline++) { - /* This is the first header, it MUST be the error code line - or else we consider this to be the body right away! */ - bool fine_statusline = FALSE; - if(conn->handler->protocol & PROTO_FAMILY_HTTP) { - /* - * https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 - * - * The response code is always a three-digit number in HTTP as the spec - * says. We allow any three-digit number here, but we cannot make - * guarantees on future behaviors since it isn't within the protocol. - */ - int httpversion = 0; - char *p = headp; - - while(*p && ISBLANK(*p)) - p++; - if(!strncmp(p, "HTTP/", 5)) { - p += 5; - switch(*p) { - case '1': - p++; - if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) { - if(ISBLANK(p[2])) { - httpversion = 10 + (p[1] - '0'); - p += 3; - if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { - k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + - (p[2] - '0'); - p += 3; - if(ISSPACE(*p)) - fine_statusline = TRUE; - } - } - } - if(!fine_statusline) { - failf(data, "Unsupported HTTP/1 subversion in response"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - break; - case '2': - case '3': - if(!ISBLANK(p[1])) - break; - httpversion = (*p - '0') * 10; - p += 2; - if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { - k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + - (p[2] - '0'); - p += 3; - if(!ISSPACE(*p)) - break; - fine_statusline = TRUE; - } - break; - default: /* unsupported */ - failf(data, "Unsupported HTTP version in response"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - } + /* We might have reached the end of the header part here, but + there might be a non-header part left in the end of the read + buffer. */ +out: + if(!k->header && !leftover_body) { + Curl_dyn_free(&data->state.headerb); + } + return CURLE_OK; +} - if(fine_statusline) { - if(k->httpcode < 100) { - failf(data, "Unsupported response code in HTTP response"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - switch(httpversion) { - case 10: - case 11: -#ifdef USE_HTTP2 - case 20: -#endif -#ifdef ENABLE_QUIC - case 30: -#endif - conn->httpversion = (unsigned char)httpversion; - break; - default: - failf(data, "Unsupported HTTP version (%u.%d) in response", - httpversion/10, httpversion%10); - return CURLE_UNSUPPORTED_PROTOCOL; - } +CURLcode Curl_http_write_resp_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + bool is_eos) +{ + CURLcode result; + size_t consumed; + char tmp = 0; - if(k->upgr101 == UPGR101_RECEIVED) { - /* supposedly upgraded to http2 now */ - if(conn->httpversion != 20) - infof(data, "Lying server, not serving HTTP/2"); - } - if(conn->httpversion < 20) { - conn->bundle->multiuse = BUNDLE_NO_MULTIUSE; - } - } - else { - /* If user has set option HTTP200ALIASES, - compare header line against list of aliases - */ - statusline check = - checkhttpprefix(data, - Curl_dyn_ptr(&data->state.headerb), - Curl_dyn_len(&data->state.headerb)); - if(check == STATUS_DONE) { - fine_statusline = TRUE; - k->httpcode = 200; - conn->httpversion = 10; - } - } - } - else if(conn->handler->protocol & CURLPROTO_RTSP) { - char *p = headp; - while(*p && ISBLANK(*p)) - p++; - if(!strncmp(p, "RTSP/", 5)) { - p += 5; - if(ISDIGIT(*p)) { - p++; - if((p[0] == '.') && ISDIGIT(p[1])) { - if(ISBLANK(p[2])) { - p += 3; - if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { - k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + - (p[2] - '0'); - p += 3; - if(ISSPACE(*p)) { - fine_statusline = TRUE; - conn->httpversion = 11; /* RTSP acts like HTTP 1.1 */ - } - } - } - } - } - if(!fine_statusline) - return CURLE_WEIRD_SERVER_REPLY; - } - } + result = http_rw_hd(data, hd, hdlen, &tmp, 0, &consumed); + if(!result && is_eos) { + result = Curl_client_write(data, (CLIENTWRITE_BODY|CLIENTWRITE_EOS), + &tmp, 0); + } + return result; +} - if(fine_statusline) { - result = Curl_http_statusline(data, conn); - if(result) - return result; - writetype |= CLIENTWRITE_STATUS; - } - else { - k->header = FALSE; /* this is not a header line */ - break; +/* + * HTTP protocol `write_resp` implementation. Will parse headers + * when not done yet and otherwise return without consuming data. + */ +CURLcode Curl_http_write_resp_hds(struct Curl_easy *data, + const char *buf, size_t blen, + size_t *pconsumed) +{ + if(!data->req.header) { + *pconsumed = 0; + return CURLE_OK; + } + else { + CURLcode result; + + result = http_parse_headers(data, buf, blen, pconsumed); + if(!result && !data->req.header) { + if(!data->req.no_body && Curl_dyn_len(&data->state.headerb)) { + /* leftover from parsing something that turned out not + * to be a header, only happens if we allow for + * HTTP/0.9 like responses */ + result = Curl_client_write(data, CLIENTWRITE_BODY, + Curl_dyn_ptr(&data->state.headerb), + Curl_dyn_len(&data->state.headerb)); } + Curl_dyn_free(&data->state.headerb); } + return result; + } +} - result = verify_header(data); - if(result) - return result; - - result = Curl_http_header(data, conn, headp); - if(result) - return result; - - /* - * End of header-checks. Write them to the client. - */ - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; - if(k->httpcode/100 == 1) - writetype |= CLIENTWRITE_1XX; - - Curl_debug(data, CURLINFO_HEADER_IN, headp, - Curl_dyn_len(&data->state.headerb)); - - result = Curl_client_write(data, writetype, headp, - Curl_dyn_len(&data->state.headerb)); - if(result) - return result; +CURLcode Curl_http_write_resp(struct Curl_easy *data, + const char *buf, size_t blen, + bool is_eos) +{ + CURLcode result; + size_t consumed; + int flags; - result = Curl_bump_headersize(data, Curl_dyn_len(&data->state.headerb), - FALSE); - if(result) - return result; + result = Curl_http_write_resp_hds(data, buf, blen, &consumed); + if(result || data->req.done) + goto out; - Curl_dyn_reset(&data->state.headerb); + DEBUGASSERT(consumed <= blen); + blen -= consumed; + buf += consumed; + /* either all was consumed in header parsing, or we have data left + * and are done with headers, e.g. it is BODY data */ + DEBUGASSERT(!blen || !data->req.header); + if(!data->req.header && (blen || is_eos)) { + /* BODY data after header been parsed, write and consume */ + flags = CLIENTWRITE_BODY; + if(is_eos) + flags |= CLIENTWRITE_EOS; + result = Curl_client_write(data, flags, (char *)buf, blen); } - while(*k->str); /* header line within buffer */ - - /* We might have reached the end of the header part here, but - there might be a non-header part left in the end of the read - buffer. */ - - return CURLE_OK; +out: + return result; } - /* Decode HTTP status code string. */ CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len) { @@ -4614,21 +4050,10 @@ CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len) } result = CURLE_OK; out: - *pstatus = result? -1 : status; + *pstatus = result ? -1 : status; return result; } -/* simple implementation of strndup(), which isn't portable */ -static char *my_strndup(const char *ptr, size_t len) -{ - char *copy = malloc(len + 1); - if(!copy) - return NULL; - memcpy(copy, ptr, len); - copy[len] = '\0'; - return copy; -} - CURLcode Curl_http_req_make(struct httpreq **preq, const char *method, size_t m_len, const char *scheme, size_t s_len, @@ -4639,7 +4064,7 @@ CURLcode Curl_http_req_make(struct httpreq **preq, CURLcode result = CURLE_OUT_OF_MEMORY; DEBUGASSERT(method); - if(m_len + 1 >= sizeof(req->method)) + if(m_len + 1 > sizeof(req->method)) return CURLE_BAD_FUNCTION_ARGUMENT; req = calloc(1, sizeof(*req)); @@ -4647,17 +4072,17 @@ CURLcode Curl_http_req_make(struct httpreq **preq, goto out; memcpy(req->method, method, m_len); if(scheme) { - req->scheme = my_strndup(scheme, s_len); + req->scheme = Curl_memdup0(scheme, s_len); if(!req->scheme) goto out; } if(authority) { - req->authority = my_strndup(authority, a_len); + req->authority = Curl_memdup0(authority, a_len); if(!req->authority) goto out; } if(path) { - req->path = my_strndup(path, p_len); + req->path = Curl_memdup0(path, p_len); if(!req->path) goto out; } @@ -4668,7 +4093,7 @@ CURLcode Curl_http_req_make(struct httpreq **preq, out: if(result && req) Curl_http_req_free(req); - *preq = result? NULL : req; + *preq = result ? NULL : req; return result; } @@ -4795,7 +4220,7 @@ CURLcode Curl_http_req_make2(struct httpreq **preq, CURLUcode uc; DEBUGASSERT(method); - if(m_len + 1 >= sizeof(req->method)) + if(m_len + 1 > sizeof(req->method)) return CURLE_BAD_FUNCTION_ARGUMENT; req = calloc(1, sizeof(*req)); @@ -4826,7 +4251,7 @@ CURLcode Curl_http_req_make2(struct httpreq **preq, out: if(result && req) Curl_http_req_free(req); - *preq = result? NULL : req; + *preq = result ? NULL : req; return result; } @@ -4892,8 +4317,8 @@ CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers, infof(data, "set pseudo header %s to %s", HTTP_PSEUDO_SCHEME, scheme); } else { - scheme = (data->conn && data->conn->handler->flags & PROTOPT_SSL)? - "https" : "http"; + scheme = (data->conn && data->conn->handler->flags & PROTOPT_SSL) ? + "https" : "http"; } } @@ -4957,7 +4382,7 @@ CURLcode Curl_http_resp_make(struct http_resp **presp, out: if(result && resp) Curl_http_resp_free(resp); - *presp = result? NULL : resp; + *presp = result ? NULL : resp; return result; } @@ -4973,4 +4398,152 @@ void Curl_http_resp_free(struct http_resp *resp) } } +struct cr_exp100_ctx { + struct Curl_creader super; + struct curltime start; /* time started waiting */ + enum expect100 state; +}; + +/* Expect: 100-continue client reader, blocking uploads */ + +static void http_exp100_continue(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_exp100_ctx *ctx = reader->ctx; + if(ctx->state > EXP100_SEND_DATA) { + ctx->state = EXP100_SEND_DATA; + data->req.keepon |= KEEP_SEND; + data->req.keepon &= ~KEEP_SEND_TIMED; + Curl_expire_done(data, EXPIRE_100_TIMEOUT); + } +} + +static CURLcode cr_exp100_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *nread, bool *eos) +{ + struct cr_exp100_ctx *ctx = reader->ctx; + timediff_t ms; + + switch(ctx->state) { + case EXP100_SENDING_REQUEST: + if(!Curl_req_sendbuf_empty(data)) { + /* The initial request data has not been fully sent yet. Do + * not start the timer yet. */ + DEBUGF(infof(data, "cr_exp100_read, request not full sent yet")); + *nread = 0; + *eos = FALSE; + return CURLE_OK; + } + /* We are now waiting for a reply from the server or + * a timeout on our side IFF the request has been fully sent. */ + DEBUGF(infof(data, "cr_exp100_read, start AWAITING_CONTINUE, " + "timeout %ldms", data->set.expect_100_timeout)); + ctx->state = EXP100_AWAITING_CONTINUE; + ctx->start = Curl_now(); + Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT); + data->req.keepon &= ~KEEP_SEND; + data->req.keepon |= KEEP_SEND_TIMED; + *nread = 0; + *eos = FALSE; + return CURLE_OK; + case EXP100_FAILED: + DEBUGF(infof(data, "cr_exp100_read, expectation failed, error")); + *nread = 0; + *eos = FALSE; + return CURLE_READ_ERROR; + case EXP100_AWAITING_CONTINUE: + ms = Curl_timediff(Curl_now(), ctx->start); + if(ms < data->set.expect_100_timeout) { + DEBUGF(infof(data, "cr_exp100_read, AWAITING_CONTINUE, not expired")); + data->req.keepon &= ~KEEP_SEND; + data->req.keepon |= KEEP_SEND_TIMED; + *nread = 0; + *eos = FALSE; + return CURLE_OK; + } + /* we have waited long enough, continue anyway */ + http_exp100_continue(data, reader); + infof(data, "Done waiting for 100-continue"); + FALLTHROUGH(); + default: + DEBUGF(infof(data, "cr_exp100_read, pass through")); + return Curl_creader_read(data, reader->next, buf, blen, nread, eos); + } +} + +static void cr_exp100_done(struct Curl_easy *data, + struct Curl_creader *reader, int premature) +{ + struct cr_exp100_ctx *ctx = reader->ctx; + ctx->state = premature ? EXP100_FAILED : EXP100_SEND_DATA; + data->req.keepon &= ~KEEP_SEND_TIMED; + Curl_expire_done(data, EXPIRE_100_TIMEOUT); +} + +static const struct Curl_crtype cr_exp100 = { + "cr-exp100", + Curl_creader_def_init, + cr_exp100_read, + Curl_creader_def_close, + Curl_creader_def_needs_rewind, + Curl_creader_def_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + cr_exp100_done, + sizeof(struct cr_exp100_ctx) +}; + +static CURLcode http_exp100_add_reader(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; + + result = Curl_creader_create(&reader, data, &cr_exp100, + CURL_CR_PROTOCOL); + if(!result) + result = Curl_creader_add(data, reader); + if(!result) { + struct cr_exp100_ctx *ctx = reader->ctx; + ctx->state = EXP100_SENDING_REQUEST; + } + + if(result && reader) + Curl_creader_free(data, reader); + return result; +} + +void Curl_http_exp100_got100(struct Curl_easy *data) +{ + struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100); + if(r) + http_exp100_continue(data, r); +} + +static bool http_exp100_is_waiting(struct Curl_easy *data) +{ + struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100); + if(r) { + struct cr_exp100_ctx *ctx = r->ctx; + return (ctx->state == EXP100_AWAITING_CONTINUE); + } + return FALSE; +} + +static void http_exp100_send_anyway(struct Curl_easy *data) +{ + struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100); + if(r) + http_exp100_continue(data, r); +} + +bool Curl_http_exp100_is_selected(struct Curl_easy *data) +{ + struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100); + return !!r; +} + #endif /* CURL_DISABLE_HTTP */ diff --git a/deps/curl/lib/http.h b/deps/curl/lib/http.h index 9ee3c6537ce..bb5974d94da 100644 --- a/deps/curl/lib/http.h +++ b/deps/curl/lib/http.h @@ -44,7 +44,7 @@ typedef enum { #ifndef CURL_DISABLE_HTTP -#if defined(ENABLE_QUIC) +#if defined(USE_HTTP3) #include #endif @@ -54,14 +54,6 @@ extern const struct Curl_handler Curl_handler_http; extern const struct Curl_handler Curl_handler_https; #endif -#ifdef USE_WEBSOCKETS -extern const struct Curl_handler Curl_handler_ws; - -#ifdef USE_SSL -extern const struct Curl_handler Curl_handler_wss; -#endif -#endif /* websockets */ - struct dynhds; CURLcode Curl_bump_headersize(struct Curl_easy *data, @@ -81,13 +73,6 @@ char *Curl_checkProxyheaders(struct Curl_easy *data, const struct connectdata *conn, const char *thisheader, const size_t thislen); -struct HTTP; /* see below */ -CURLcode Curl_buffer_send(struct dynbuf *in, - struct Curl_easy *data, - struct HTTP *http, - curl_off_t *bytes_written, - curl_off_t included_body_bytes, - int socketindex); CURLcode Curl_add_timecondition(struct Curl_easy *data, #ifndef USE_HYPER @@ -108,10 +93,6 @@ CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, bool is_connect, struct dynhds *hds); -CURLcode Curl_http_compile_trailers(struct curl_slist *trailers, - struct dynbuf *buf, - struct Curl_easy *handle); - void Curl_http_method(struct Curl_easy *data, struct connectdata *conn, const char **method, Curl_HttpReq *); CURLcode Curl_http_useragent(struct Curl_easy *data); @@ -120,14 +101,14 @@ CURLcode Curl_http_target(struct Curl_easy *data, struct connectdata *conn, struct dynbuf *req); CURLcode Curl_http_statusline(struct Curl_easy *data, struct connectdata *conn); -CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, - char *headp); +CURLcode Curl_http_header(struct Curl_easy *data, + const char *hd, size_t hdlen); CURLcode Curl_transferencode(struct Curl_easy *data); -CURLcode Curl_http_body(struct Curl_easy *data, struct connectdata *conn, - Curl_HttpReq httpreq, - const char **teep); -CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, - struct dynbuf *r, Curl_HttpReq httpreq); +CURLcode Curl_http_req_set_reader(struct Curl_easy *data, + Curl_HttpReq httpreq, + const char **tep); +CURLcode Curl_http_req_complete(struct Curl_easy *data, + struct dynbuf *r, Curl_HttpReq httpreq); bool Curl_use_http_1_1plus(const struct Curl_easy *data, const struct connectdata *conn); #ifndef CURL_DISABLE_COOKIES @@ -137,19 +118,24 @@ CURLcode Curl_http_cookies(struct Curl_easy *data, #else #define Curl_http_cookies(a,b,c) CURLE_OK #endif -CURLcode Curl_http_resume(struct Curl_easy *data, - struct connectdata *conn, - Curl_HttpReq httpreq); CURLcode Curl_http_range(struct Curl_easy *data, Curl_HttpReq httpreq); -CURLcode Curl_http_firstwrite(struct Curl_easy *data, - struct connectdata *conn, - bool *done); +CURLcode Curl_http_firstwrite(struct Curl_easy *data); /* protocol-specific functions set up to be called by the main engine */ +CURLcode Curl_http_setup_conn(struct Curl_easy *data, + struct connectdata *conn); CURLcode Curl_http(struct Curl_easy *data, bool *done); CURLcode Curl_http_done(struct Curl_easy *data, CURLcode, bool premature); CURLcode Curl_http_connect(struct Curl_easy *data, bool *done); +int Curl_http_getsock_do(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t *socks); +CURLcode Curl_http_write_resp(struct Curl_easy *data, + const char *buf, size_t blen, + bool is_eos); +CURLcode Curl_http_write_resp_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + bool is_eos); /* These functions are in http.c */ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, @@ -160,7 +146,7 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data); selected to use no auth at all. Ie, we actively select no auth, as opposed to not having one selected. The other CURLAUTH_* defines are present in the public curl/curl.h header. */ -#define CURLAUTH_PICKNONE (1<<30) /* don't use auth */ +#define CURLAUTH_PICKNONE (1<<30) /* do not use auth */ /* MAX_INITIAL_POST_SIZE indicates the number of bytes that will make the POST data get included in the initial data chunk sent to the server. If the @@ -192,43 +178,20 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data); version. This count includes CONNECT response headers. */ #define MAX_HTTP_RESP_HEADER_SIZE (300*1024) +bool Curl_http_exp100_is_selected(struct Curl_easy *data); +void Curl_http_exp100_got100(struct Curl_easy *data); + #endif /* CURL_DISABLE_HTTP */ /**************************************************************************** * HTTP unique setup ***************************************************************************/ -struct HTTP { - curl_off_t postsize; /* off_t to handle large file sizes */ - const char *postdata; - struct back { - curl_read_callback fread_func; /* backup storage for fread pointer */ - void *fread_in; /* backup storage for fread_in pointer */ - const char *postdata; - curl_off_t postsize; - struct Curl_easy *data; - } backup; - - enum { - HTTPSEND_NADA, /* init */ - HTTPSEND_REQUEST, /* sending a request */ - HTTPSEND_BODY /* sending body */ - } sending; - -#ifndef CURL_DISABLE_HTTP - void *h2_ctx; /* HTTP/2 implementation context */ - void *h3_ctx; /* HTTP/3 implementation context */ - struct dynbuf send_buffer; /* used if the request couldn't be sent in one - chunk, points to an allocated send_buffer - struct */ -#endif -}; CURLcode Curl_http_size(struct Curl_easy *data); -CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, - struct connectdata *conn, - ssize_t *nread, - bool *stop_reading); +CURLcode Curl_http_write_resp_hds(struct Curl_easy *data, + const char *buf, size_t blen, + size_t *pconsumed); /** * Curl_http_output_auth() setups the authentication headers for the @@ -263,7 +226,7 @@ CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len); * All about a core HTTP request, excluding body and trailers */ struct httpreq { - char method[12]; + char method[24]; char *scheme; char *authority; char *path; @@ -272,7 +235,7 @@ struct httpreq { }; /** - * Create a HTTP request struct. + * Create an HTTP request struct. */ CURLcode Curl_http_req_make(struct httpreq **preq, const char *method, size_t m_len, @@ -322,7 +285,7 @@ struct http_resp { }; /** - * Create a HTTP response struct. + * Create an HTTP response struct. */ CURLcode Curl_http_resp_make(struct http_resp **presp, int status, diff --git a/deps/curl/lib/http1.c b/deps/curl/lib/http1.c index 1ca7d41e8a2..f135b2072a2 100644 --- a/deps/curl/lib/http1.c +++ b/deps/curl/lib/http1.c @@ -128,7 +128,7 @@ static ssize_t next_line(struct h1_req_parser *parser, else if(*err == CURLE_AGAIN) { /* no line end in `buf`, add it to our scratch */ *err = Curl_dyn_addn(&parser->scratch, (const unsigned char *)buf, buflen); - nread = (*err)? -1 : (ssize_t)buflen; + nread = (*err) ? -1 : (ssize_t)buflen; } return nread; } @@ -217,7 +217,7 @@ static CURLcode start_req(struct h1_req_parser *parser, tmp[target_len] = '\0'; /* See if treating TARGET as an absolute URL makes sense */ if(Curl_is_absolute_url(tmp, NULL, 0, FALSE)) { - int url_options; + unsigned int url_options; url = curl_url(); if(!url) { @@ -318,5 +318,29 @@ ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser, return nread; } +CURLcode Curl_h1_req_write_head(struct httpreq *req, int http_minor, + struct dynbuf *dbuf) +{ + CURLcode result; + + result = Curl_dyn_addf(dbuf, "%s %s%s%s%s HTTP/1.%d\r\n", + req->method, + req->scheme ? req->scheme : "", + req->scheme ? "://" : "", + req->authority ? req->authority : "", + req->path ? req->path : "", + http_minor); + if(result) + goto out; + + result = Curl_dynhds_h1_dprint(&req->headers, dbuf); + if(result) + goto out; + + result = Curl_dyn_addn(dbuf, STRCONST("\r\n")); + +out: + return result; +} #endif /* !CURL_DISABLE_HTTP */ diff --git a/deps/curl/lib/http1.h b/deps/curl/lib/http1.h index b1eaa969d9e..2de302f1f6c 100644 --- a/deps/curl/lib/http1.h +++ b/deps/curl/lib/http1.h @@ -56,6 +56,8 @@ ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser, CURLcode Curl_h1_req_dprint(const struct httpreq *req, struct dynbuf *dbuf); +CURLcode Curl_h1_req_write_head(struct httpreq *req, int http_minor, + struct dynbuf *dbuf); #endif /* !CURL_DISABLE_HTTP */ #endif /* HEADER_CURL_HTTP1_H */ diff --git a/deps/curl/lib/http2.c b/deps/curl/lib/http2.c index e0cda76d287..f1ee0cf9ec1 100644 --- a/deps/curl/lib/http2.c +++ b/deps/curl/lib/http2.c @@ -29,6 +29,7 @@ #include #include "urldata.h" #include "bufq.h" +#include "hash.h" #include "http1.h" #include "http2.h" #include "http.h" @@ -68,38 +69,44 @@ /* buffer dimensioning: * use 16K as chunk size, as that fits H2 DATA frames well */ #define H2_CHUNK_SIZE (16 * 1024) -/* this is how much we want "in flight" for a stream */ -#define H2_STREAM_WINDOW_SIZE (10 * 1024 * 1024) +/* connection window size */ +#define H2_CONN_WINDOW_SIZE (10 * 1024 * 1024) /* on receiving from TLS, we prep for holding a full stream window */ -#define H2_NW_RECV_CHUNKS (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) +#define H2_NW_RECV_CHUNKS (H2_CONN_WINDOW_SIZE / H2_CHUNK_SIZE) /* on send into TLS, we just want to accumulate small frames */ #define H2_NW_SEND_CHUNKS 1 -/* stream recv/send chunks are a result of window / chunk sizes */ -#define H2_STREAM_RECV_CHUNKS (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) +/* this is how much we want "in flight" for a stream, unthrottled */ +#define H2_STREAM_WINDOW_SIZE_MAX (10 * 1024 * 1024) +/* this is how much we want "in flight" for a stream, initially, IFF + * nghttp2 allows us to tweak the local window size. */ +#if NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE +#define H2_STREAM_WINDOW_SIZE_INITIAL (64 * 1024) +#else +#define H2_STREAM_WINDOW_SIZE_INITIAL H2_STREAM_WINDOW_SIZE_MAX +#endif /* keep smaller stream upload buffer (default h2 window size) to have * our progress bars and "upload done" reporting closer to reality */ #define H2_STREAM_SEND_CHUNKS ((64 * 1024) / H2_CHUNK_SIZE) /* spare chunks we keep for a full window */ -#define H2_STREAM_POOL_SPARES (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) +#define H2_STREAM_POOL_SPARES (H2_CONN_WINDOW_SIZE / H2_CHUNK_SIZE) -/* We need to accommodate the max number of streams with their window - * sizes on the overall connection. Streams might become PAUSED which - * will block their received QUOTA in the connection window. And if we - * run out of space, the server is blocked from sending us any data. - * See #10988 for an issue with this. */ -#define HTTP2_HUGE_WINDOW_SIZE (100 * H2_STREAM_WINDOW_SIZE) +/* We need to accommodate the max number of streams with their window sizes on + * the overall connection. Streams might become PAUSED which will block their + * received QUOTA in the connection window. If we run out of space, the server + * is blocked from sending us any data. See #10988 for an issue with this. */ +#define HTTP2_HUGE_WINDOW_SIZE (100 * H2_STREAM_WINDOW_SIZE_MAX) #define H2_SETTINGS_IV_LEN 3 #define H2_BINSETTINGS_LEN 80 -static int populate_settings(nghttp2_settings_entry *iv, - struct Curl_easy *data) +static size_t populate_settings(nghttp2_settings_entry *iv, + struct Curl_easy *data) { iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; iv[0].value = Curl_multi_max_concurrent_streams(data->multi); iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; - iv[1].value = H2_STREAM_WINDOW_SIZE; + iv[1].value = H2_STREAM_WINDOW_SIZE_INITIAL; iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; iv[2].value = data->multi->push_cb != NULL; @@ -107,33 +114,39 @@ static int populate_settings(nghttp2_settings_entry *iv, return 3; } -static size_t populate_binsettings(uint8_t *binsettings, - struct Curl_easy *data) +static ssize_t populate_binsettings(uint8_t *binsettings, + struct Curl_easy *data) { nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN]; - int ivlen; + size_t ivlen; ivlen = populate_settings(iv, data); - /* this returns number of bytes it wrote */ + /* this returns number of bytes it wrote or a negative number on error. */ return nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN, iv, ivlen); } struct cf_h2_ctx { nghttp2_session *h2; - uint32_t max_concurrent_streams; /* The easy handle used in the current filter call, cleared at return */ struct cf_call_data call_data; struct bufq inbufq; /* network input */ struct bufq outbufq; /* network output */ struct bufc_pool stream_bufcp; /* spares for stream buffers */ + struct dynbuf scratch; /* scratch buffer for temp use */ + struct Curl_hash streams; /* hash of `data->mid` to `h2_stream_ctx` */ size_t drain_total; /* sum of all stream's UrlState drain */ - int32_t goaway_error; - int32_t last_stream_id; + uint32_t max_concurrent_streams; + uint32_t goaway_error; /* goaway error code from server */ + int32_t remote_max_sid; /* max id processed by server */ + int32_t local_max_sid; /* max id processed by us */ + BIT(initialized); + BIT(via_h1_upgrade); BIT(conn_closed); - BIT(goaway); + BIT(rcvd_goaway); + BIT(sent_goaway); BIT(enable_push); BIT(nw_out_blocked); }; @@ -143,25 +156,38 @@ struct cf_h2_ctx { #define CF_CTX_CALL_DATA(cf) \ ((struct cf_h2_ctx *)(cf)->ctx)->call_data -static void cf_h2_ctx_clear(struct cf_h2_ctx *ctx) +static void h2_stream_hash_free(void *stream); + +static void cf_h2_ctx_init(struct cf_h2_ctx *ctx, bool via_h1_upgrade) { - struct cf_call_data save = ctx->call_data; + Curl_bufcp_init(&ctx->stream_bufcp, H2_CHUNK_SIZE, H2_STREAM_POOL_SPARES); + Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0); + Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0); + Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); + Curl_hash_offt_init(&ctx->streams, 63, h2_stream_hash_free); + ctx->remote_max_sid = 2147483647; + ctx->via_h1_upgrade = via_h1_upgrade; + ctx->initialized = TRUE; +} - if(ctx->h2) { - nghttp2_session_del(ctx->h2); +static void cf_h2_ctx_free(struct cf_h2_ctx *ctx) +{ + if(ctx && ctx->initialized) { + Curl_bufq_free(&ctx->inbufq); + Curl_bufq_free(&ctx->outbufq); + Curl_bufcp_free(&ctx->stream_bufcp); + Curl_dyn_free(&ctx->scratch); + Curl_hash_clean(&ctx->streams); + Curl_hash_destroy(&ctx->streams); + memset(ctx, 0, sizeof(*ctx)); } - Curl_bufq_free(&ctx->inbufq); - Curl_bufq_free(&ctx->outbufq); - Curl_bufcp_free(&ctx->stream_bufcp); - memset(ctx, 0, sizeof(*ctx)); - ctx->call_data = save; + free(ctx); } -static void cf_h2_ctx_free(struct cf_h2_ctx *ctx) +static void cf_h2_ctx_close(struct cf_h2_ctx *ctx) { - if(ctx) { - cf_h2_ctx_clear(ctx); - free(ctx); + if(ctx->h2) { + nghttp2_session_del(ctx->h2); } } @@ -169,18 +195,15 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf, struct Curl_easy *data); /** - * All about the H3 internals of a stream + * All about the H2 internals of a stream */ -struct stream_ctx { - /*********** for HTTP/2 we store stream-local data here *************/ - int32_t id; /* HTTP/2 protocol identifier for stream */ +struct h2_stream_ctx { struct bufq recvbuf; /* response buffer */ struct bufq sendbuf; /* request buffer */ struct h1_req_parser h1; /* parsing the request */ struct dynhds resp_trailers; /* response trailer fields */ size_t resp_hds_len; /* amount of response header bytes in recvbuf */ - size_t upload_blocked_len; - curl_off_t upload_left; /* number of request bytes left to upload */ + curl_off_t nrcvd_data; /* number of DATA bytes received */ char **push_headers; /* allocated array */ size_t push_headers_used; /* number of entries filled in */ @@ -188,147 +211,221 @@ struct stream_ctx { int status_code; /* HTTP response status code */ uint32_t error; /* stream error code */ - uint32_t local_window_size; /* the local recv window size */ - bool resp_hds_complete; /* we have a complete, final response */ - bool closed; /* TRUE on stream close */ - bool reset; /* TRUE on stream reset */ - bool close_handled; /* TRUE if stream closure is handled by libcurl */ - bool bodystarted; - bool send_closed; /* transfer is done sending, we might have still - buffered data in stream->sendbuf to upload. */ + CURLcode xfer_result; /* Result of writing out response */ + int32_t local_window_size; /* the local recv window size */ + int32_t id; /* HTTP/2 protocol identifier for stream */ + BIT(resp_hds_complete); /* we have a complete, final response */ + BIT(closed); /* TRUE on stream close */ + BIT(reset); /* TRUE on stream reset */ + BIT(close_handled); /* TRUE if stream closure is handled by libcurl */ + BIT(bodystarted); + BIT(body_eos); /* the complete body has been added to `sendbuf` and + * is being/has been processed from there. */ }; -#define H2_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->h2_ctx \ - : NULL)) -#define H2_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h2_ctx -#define H2_STREAM_ID(d) (H2_STREAM_CTX(d)? \ - H2_STREAM_CTX(d)->id : -2) +#define H2_STREAM_CTX(ctx,data) ((struct h2_stream_ctx *)(\ + data? Curl_hash_offt_get(&(ctx)->streams, (data)->mid) : NULL)) + +static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx) +{ + struct h2_stream_ctx *stream; + + (void)ctx; + stream = calloc(1, sizeof(*stream)); + if(!stream) + return NULL; + + stream->id = -1; + Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, + H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST); + stream->resp_hds_len = 0; + stream->bodystarted = FALSE; + stream->status_code = -1; + stream->closed = FALSE; + stream->close_handled = FALSE; + stream->error = NGHTTP2_NO_ERROR; + stream->local_window_size = H2_STREAM_WINDOW_SIZE_INITIAL; + stream->nrcvd_data = 0; + return stream; +} + +static void free_push_headers(struct h2_stream_ctx *stream) +{ + size_t i; + for(i = 0; i < stream->push_headers_used; i++) + free(stream->push_headers[i]); + Curl_safefree(stream->push_headers); + stream->push_headers_used = 0; +} + +static void h2_stream_ctx_free(struct h2_stream_ctx *stream) +{ + Curl_bufq_free(&stream->sendbuf); + Curl_h1_req_parse_free(&stream->h1); + Curl_dynhds_free(&stream->resp_trailers); + free_push_headers(stream); + free(stream); +} + +static void h2_stream_hash_free(void *stream) +{ + DEBUGASSERT(stream); + h2_stream_ctx_free((struct h2_stream_ctx *)stream); +} + +#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE +static int32_t cf_h2_get_desired_local_win(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + (void)cf; + if(data->set.max_recv_speed && data->set.max_recv_speed < INT32_MAX) { + /* The transfer should only receive `max_recv_speed` bytes per second. + * We restrict the stream's local window size, so that the server cannot + * send us "too much" at a time. + * This gets less precise the higher the latency. */ + return (int32_t)data->set.max_recv_speed; + } + return H2_STREAM_WINDOW_SIZE_MAX; +} + +static CURLcode cf_h2_update_local_win(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + bool paused) +{ + struct cf_h2_ctx *ctx = cf->ctx; + int32_t dwsize; + int rv; + + dwsize = paused ? 0 : cf_h2_get_desired_local_win(cf, data); + if(dwsize != stream->local_window_size) { + int32_t wsize = nghttp2_session_get_stream_effective_local_window_size( + ctx->h2, stream->id); + if(dwsize > wsize) { + rv = nghttp2_submit_window_update(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, dwsize - wsize); + if(rv) { + failf(data, "[%d] nghttp2_submit_window_update() failed: " + "%s(%d)", stream->id, nghttp2_strerror(rv), rv); + return CURLE_HTTP2; + } + stream->local_window_size = dwsize; + CURL_TRC_CF(data, cf, "[%d] local window update by %d", + stream->id, dwsize - wsize); + } + else { + rv = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, dwsize); + if(rv) { + failf(data, "[%d] nghttp2_session_set_local_window_size() failed: " + "%s(%d)", stream->id, nghttp2_strerror(rv), rv); + return CURLE_HTTP2; + } + stream->local_window_size = dwsize; + CURL_TRC_CF(data, cf, "[%d] local window size now %d", + stream->id, dwsize); + } + } + return CURLE_OK; +} + +#else /* NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE */ + +static CURLcode cf_h2_update_local_win(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + bool paused) +{ + (void)cf; + (void)data; + (void)stream; + (void)paused; + return CURLE_OK; +} +#endif /* !NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE */ /* * Mark this transfer to get "drained". */ static void drain_stream(struct Curl_cfilter *cf, struct Curl_easy *data, - struct stream_ctx *stream) + struct h2_stream_ctx *stream) { unsigned char bits; (void)cf; bits = CURL_CSELECT_IN; - if(!stream->send_closed && - (stream->upload_left || stream->upload_blocked_len)) + if(!stream->closed && + (!stream->body_eos || !Curl_bufq_is_empty(&stream->sendbuf))) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - CURL_TRC_CF(data, cf, "[%d] DRAIN dselect_bits=%x", + if(stream->closed || (data->state.select_bits != bits)) { + CURL_TRC_CF(data, cf, "[%d] DRAIN select_bits=%x", stream->id, bits); - data->state.dselect_bits = bits; + data->state.select_bits = bits; Curl_expire(data, 0, EXPIRE_RUN_NOW); } } static CURLcode http2_data_setup(struct Curl_cfilter *cf, struct Curl_easy *data, - struct stream_ctx **pstream) + struct h2_stream_ctx **pstream) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream; + struct h2_stream_ctx *stream; (void)cf; DEBUGASSERT(data); - if(!data->req.p.http) { - failf(data, "initialization failure, transfer not http initialized"); - return CURLE_FAILED_INIT; - } - stream = H2_STREAM_CTX(data); + stream = H2_STREAM_CTX(ctx, data); if(stream) { *pstream = stream; return CURLE_OK; } - stream = calloc(1, sizeof(*stream)); + stream = h2_stream_ctx_create(ctx); if(!stream) return CURLE_OUT_OF_MEMORY; - stream->id = -1; - Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, - H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); - Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, - H2_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); - Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST); - stream->resp_hds_len = 0; - stream->bodystarted = FALSE; - stream->status_code = -1; - stream->closed = FALSE; - stream->close_handled = FALSE; - stream->error = NGHTTP2_NO_ERROR; - stream->local_window_size = H2_STREAM_WINDOW_SIZE; - stream->upload_left = 0; + if(!Curl_hash_offt_set(&ctx->streams, data->mid, stream)) { + h2_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } - H2_STREAM_LCTX(data) = stream; *pstream = stream; return CURLE_OK; } -static void http2_data_done(struct Curl_cfilter *cf, - struct Curl_easy *data, bool premature) +static void http2_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); DEBUGASSERT(ctx); - (void)premature; - if(!stream) + if(!stream || !ctx->initialized) return; if(ctx->h2) { + bool flush_egress = FALSE; + /* returns error if stream not known, which is fine here */ + (void)nghttp2_session_set_stream_user_data(ctx->h2, stream->id, NULL); + if(!stream->closed && stream->id > 0) { /* RST_STREAM */ CURL_TRC_CF(data, cf, "[%d] premature DATA_DONE, RST stream", stream->id); - if(!nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, - stream->id, NGHTTP2_STREAM_CLOSED)) - (void)nghttp2_session_send(ctx->h2); - } - if(!Curl_bufq_is_empty(&stream->recvbuf)) { - /* Anything in the recvbuf is still being counted - * in stream and connection window flow control. Need - * to free that space or the connection window might get - * exhausted eventually. */ - nghttp2_session_consume(ctx->h2, stream->id, - Curl_bufq_len(&stream->recvbuf)); - /* give WINDOW_UPATE a chance to be sent, but ignore any error */ - (void)h2_progress_egress(cf, data); - } - - /* -1 means unassigned and 0 means cleared */ - if(nghttp2_session_get_stream_user_data(ctx->h2, stream->id)) { - int rv = nghttp2_session_set_stream_user_data(ctx->h2, - stream->id, 0); - if(rv) { - infof(data, "http/2: failed to clear user_data for stream %u", - stream->id); - DEBUGASSERT(0); - } + stream->closed = TRUE; + stream->reset = TRUE; + nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, NGHTTP2_STREAM_CLOSED); + flush_egress = TRUE; } - } - Curl_bufq_free(&stream->sendbuf); - Curl_bufq_free(&stream->recvbuf); - Curl_h1_req_parse_free(&stream->h1); - Curl_dynhds_free(&stream->resp_trailers); - if(stream->push_headers) { - /* if they weren't used and then freed before */ - for(; stream->push_headers_used > 0; --stream->push_headers_used) { - free(stream->push_headers[stream->push_headers_used - 1]); - } - free(stream->push_headers); - stream->push_headers = NULL; + if(flush_egress) + nghttp2_session_send(ctx->h2); } - free(stream); - H2_STREAM_LCTX(data) = NULL; + Curl_hash_offt_remove(&ctx->streams, data->mid); } static int h2_client_new(struct Curl_cfilter *cf, @@ -369,12 +466,15 @@ static ssize_t nw_out_writer(void *writer_ctx, { struct Curl_cfilter *cf = writer_ctx; struct Curl_easy *data = CF_DATA_CURRENT(cf); - ssize_t nwritten; - nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf, buflen, err); - if(nwritten > 0) - CURL_TRC_CF(data, cf, "[0] egress: wrote %zd bytes", nwritten); - return nwritten; + if(data) { + ssize_t nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf, + buflen, FALSE, err); + if(nwritten > 0) + CURL_TRC_CF(data, cf, "[0] egress: wrote %zd bytes", nwritten); + return nwritten; + } + return 0; } static ssize_t send_callback(nghttp2_session *h2, @@ -398,27 +498,21 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *value, size_t valuelen, uint8_t flags, void *userp); +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) static int error_callback(nghttp2_session *session, const char *msg, size_t len, void *userp); - -/* - * Initialize the cfilter context - */ -static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool via_h1_upgrade) +#endif +static CURLcode cf_h2_ctx_open(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream; + struct h2_stream_ctx *stream; CURLcode result = CURLE_OUT_OF_MEMORY; int rc; nghttp2_session_callbacks *cbs = NULL; DEBUGASSERT(!ctx->h2); - Curl_bufcp_init(&ctx->stream_bufcp, H2_CHUNK_SIZE, H2_STREAM_POOL_SPARES); - Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0); - Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0); - ctx->last_stream_id = 2147483647; + DEBUGASSERT(ctx->initialized); rc = nghttp2_session_callbacks_new(&cbs); if(rc) { @@ -437,7 +531,9 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, nghttp2_session_callbacks_set_on_begin_headers_callback( cbs, on_begin_headers); nghttp2_session_callbacks_set_on_header_callback(cbs, on_header); +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) nghttp2_session_callbacks_set_error_callback(cbs, error_callback); +#endif /* The nghttp2 session is not yet setup, do it */ rc = h2_client_new(cf, cbs); @@ -447,14 +543,19 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, } ctx->max_concurrent_streams = DEFAULT_MAX_CONCURRENT_STREAMS; - if(via_h1_upgrade) { + if(ctx->via_h1_upgrade) { /* HTTP/1.1 Upgrade issued. H2 Settings have already been submitted * in the H1 request and we upgrade from there. This stream * is opened implicitly as #1. */ uint8_t binsettings[H2_BINSETTINGS_LEN]; - size_t binlen; /* length of the binsettings data */ + ssize_t binlen; /* length of the binsettings data */ binlen = populate_binsettings(binsettings, data); + if(binlen <= 0) { + failf(data, "nghttp2 unexpectedly failed on pack_settings_payload"); + result = CURLE_FAILED_INIT; + goto out; + } result = http2_data_setup(cf, data, &stream); if(result) @@ -462,7 +563,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, DEBUGASSERT(stream); stream->id = 1; /* queue SETTINGS frame (again) */ - rc = nghttp2_session_upgrade2(ctx->h2, binsettings, binlen, + rc = nghttp2_session_upgrade2(ctx->h2, binsettings, (size_t)binlen, data->state.httpreq == HTTPREQ_HEAD, NULL); if(rc) { @@ -483,7 +584,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, } else { nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN]; - int ivlen; + size_t ivlen; ivlen = populate_settings(iv, data); rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, @@ -508,7 +609,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, /* all set, traffic will be send on connect */ result = CURLE_OK; CURL_TRC_CF(data, cf, "[0] created h2 session%s", - via_h1_upgrade? " (via h1 upgrade)" : ""); + ctx->via_h1_upgrade ? " (via h1 upgrade)" : ""); out: if(cbs) @@ -588,8 +689,8 @@ static bool http2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data, return FALSE; if(*input_pending) { - /* This happens before we've sent off a request and the connection is - not in use by any other transfer, there shouldn't be any data here, + /* This happens before we have sent off a request and the connection is + not in use by any other transfer, there should not be any data here, only "protocol frames" */ CURLcode result; ssize_t nread = -1; @@ -666,7 +767,7 @@ static CURLcode nw_out_flush(struct Curl_cfilter *cf, } return result; } - return Curl_bufq_is_empty(&ctx->outbufq)? CURLE_OK: CURLE_AGAIN; + return Curl_bufq_is_empty(&ctx->outbufq) ? CURLE_OK : CURLE_AGAIN; } /* @@ -688,8 +789,11 @@ static ssize_t send_callback(nghttp2_session *h2, (void)flags; DEBUGASSERT(data); - nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen, - nw_out_writer, cf, &result); + if(!cf->connected) + nwritten = Curl_bufq_write(&ctx->outbufq, buf, blen, &result); + else + nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen, + nw_out_writer, cf, &result); if(nwritten < 0) { if(result == CURLE_AGAIN) { ctx->nw_out_blocked = 1; @@ -711,6 +815,7 @@ static ssize_t send_callback(nghttp2_session *h2, the struct are hidden from the user. */ struct curl_pushheaders { struct Curl_easy *data; + struct h2_stream_ctx *stream; const nghttp2_push_promise *frame; }; @@ -724,9 +829,8 @@ char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) if(!h || !GOOD_EASY_HANDLE(h->data)) return NULL; else { - struct stream_ctx *stream = H2_STREAM_CTX(h->data); - if(stream && num < stream->push_headers_used) - return stream->push_headers[num]; + if(h->stream && num < h->stream->push_headers_used) + return h->stream->push_headers[num]; } return NULL; } @@ -736,7 +840,7 @@ char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) */ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) { - struct stream_ctx *stream; + struct h2_stream_ctx *stream; size_t len; size_t i; /* Verify that we got a good easy handle in the push header struct, @@ -749,12 +853,12 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) !strcmp(header, ":") || strchr(header + 1, ':')) return NULL; - stream = H2_STREAM_CTX(h->data); + stream = h->stream; if(!stream) return NULL; len = strlen(header); - for(i = 0; ipush_headers_used; i++) { + for(i = 0; i < stream->push_headers_used; i++) { if(!strncmp(header, stream->push_headers[i], len)) { /* sub-match, make sure that it is followed by a colon */ if(stream->push_headers[i][len] != ':') @@ -770,18 +874,9 @@ static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf, { struct Curl_easy *second = curl_easy_duphandle(data); if(second) { - /* setup the request struct */ - struct HTTP *http = calloc(1, sizeof(struct HTTP)); - if(!http) { - (void)Curl_close(&second); - } - else { - struct stream_ctx *second_stream; - - second->req.p.http = http; - http2_data_setup(cf, second, &second_stream); - second->state.priority.weight = data->state.priority.weight; - } + struct h2_stream_ctx *second_stream; + http2_data_setup(cf, second, &second_stream); + second->state.priority.weight = data->state.priority.weight; } return second; } @@ -809,7 +904,7 @@ static int set_transfer_url(struct Curl_easy *data, v = curl_pushheader_byname(hp, HTTP_PSEUDO_AUTHORITY); if(v) { - uc = Curl_url_set_authority(u, v, CURLU_DISALLOW_USER); + uc = Curl_url_set_authority(u, v); if(uc) { rc = 2; goto fail; @@ -843,10 +938,7 @@ static int set_transfer_url(struct Curl_easy *data, static void discard_newhandle(struct Curl_cfilter *cf, struct Curl_easy *newhandle) { - if(!newhandle->req.p.http) { - http2_data_done(cf, newhandle, TRUE); - newhandle->req.p.http = NULL; - } + http2_data_done(cf, newhandle); (void)Curl_close(&newhandle); } @@ -860,12 +952,11 @@ static int push_promise(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "[%d] PUSH_PROMISE received", frame->promised_stream_id); if(data->multi->push_cb) { - struct stream_ctx *stream; - struct stream_ctx *newstream; + struct h2_stream_ctx *stream; + struct h2_stream_ctx *newstream; struct curl_pushheaders heads; CURLMcode rc; CURLcode result; - size_t i; /* clone the parent */ struct Curl_easy *newhandle = h2_duphandle(cf, data); if(!newhandle) { @@ -874,12 +965,10 @@ static int push_promise(struct Curl_cfilter *cf, goto fail; } - heads.data = data; - heads.frame = frame; /* ask the application */ CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ask application"); - stream = H2_STREAM_CTX(data); + stream = H2_STREAM_CTX(ctx, data); if(!stream) { failf(data, "Internal NULL stream"); discard_newhandle(cf, newhandle); @@ -887,6 +976,10 @@ static int push_promise(struct Curl_cfilter *cf, goto fail; } + heads.data = data; + heads.stream = stream; + heads.frame = frame; + rv = set_transfer_url(newhandle, &heads); if(rv) { discard_newhandle(cf, newhandle); @@ -903,18 +996,14 @@ static int push_promise(struct Curl_cfilter *cf, } DEBUGASSERT(stream); - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); rv = data->multi->push_cb(data, newhandle, stream->push_headers_used, &heads, data->multi->push_userp); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); /* free the headers again */ - for(i = 0; ipush_headers_used; i++) - free(stream->push_headers[i]); - free(stream->push_headers); - stream->push_headers = NULL; - stream->push_headers_used = 0; + free_push_headers(stream); if(rv) { DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT)); @@ -947,6 +1036,10 @@ static int push_promise(struct Curl_cfilter *cf, rv = CURL_PUSH_DENY; goto fail; } + + /* success, remember max stream id processed */ + if(newstream->id > ctx->local_max_sid) + ctx->local_max_sid = newstream->id; } else { CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ignore it"); @@ -956,22 +1049,43 @@ static int push_promise(struct Curl_cfilter *cf, return rv; } -static CURLcode recvbuf_write_hds(struct Curl_cfilter *cf, +static void h2_xfer_write_resp_hd(struct Curl_cfilter *cf, struct Curl_easy *data, - const char *buf, size_t blen) + struct h2_stream_ctx *stream, + const char *buf, size_t blen, bool eos) { - struct stream_ctx *stream = H2_STREAM_CTX(data); - ssize_t nwritten; - CURLcode result; - (void)cf; - nwritten = Curl_bufq_write(&stream->recvbuf, - (const unsigned char *)buf, blen, &result); - if(nwritten < 0) - return result; - stream->resp_hds_len += (size_t)nwritten; - DEBUGASSERT((size_t)nwritten == blen); - return CURLE_OK; + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) { + stream->xfer_result = Curl_xfer_write_resp_hd(data, buf, blen, eos); + if(!stream->xfer_result && !eos) + stream->xfer_result = cf_h2_update_local_win(cf, data, stream, FALSE); + if(stream->xfer_result) + CURL_TRC_CF(data, cf, "[%d] error %d writing %zu bytes of headers", + stream->id, stream->xfer_result, blen); + } +} + +static void h2_xfer_write_resp(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + const char *buf, size_t blen, bool eos) +{ + + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) + stream->xfer_result = Curl_xfer_write_resp(data, buf, blen, eos); + if(!stream->xfer_result && !eos) + stream->xfer_result = cf_h2_update_local_win(cf, data, stream, FALSE); + /* If the transfer write is errored, we do not want any more data */ + if(stream->xfer_result) { + struct cf_h2_ctx *ctx = cf->ctx; + CURL_TRC_CF(data, cf, "[%d] error %d writing %zu bytes of data, " + "RST-ing stream", + stream->id, stream->xfer_result, blen); + nghttp2_submit_rst_stream(ctx->h2, 0, stream->id, + (uint32_t)NGHTTP2_ERR_CALLBACK_FAILURE); + } } static CURLcode on_stream_frame(struct Curl_cfilter *cf, @@ -979,10 +1093,8 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf, const nghttp2_frame *frame) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); int32_t stream_id = frame->hd.stream_id; - CURLcode result; - size_t rbuflen; int rv; if(!stream) { @@ -992,9 +1104,8 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf, switch(frame->hd.type) { case NGHTTP2_DATA: - rbuflen = Curl_bufq_len(&stream->recvbuf); - CURL_TRC_CF(data, cf, "[%d] DATA, buffered=%zu, window=%d/%d", - stream_id, rbuflen, + CURL_TRC_CF(data, cf, "[%d] DATA, window=%d/%d", + stream_id, nghttp2_session_get_stream_effective_recv_data_length( ctx->h2, stream->id), nghttp2_session_get_stream_effective_local_window_size( @@ -1008,27 +1119,10 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf, return CURLE_RECV_ERROR; } } - if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - drain_stream(cf, data, stream); - } - else if(rbuflen > stream->local_window_size) { - int32_t wsize = nghttp2_session_get_stream_local_window_size( - ctx->h2, stream->id); - if(wsize > 0 && (uint32_t)wsize != stream->local_window_size) { - /* H2 flow control is not absolute, as the server might not have the - * same view, yet. When we receive more than we want, we enforce - * the local window size again to make nghttp2 send WINDOW_UPATEs - * accordingly. */ - nghttp2_session_set_local_window_size(ctx->h2, - NGHTTP2_FLAG_NONE, - stream->id, - stream->local_window_size); - } - } break; case NGHTTP2_HEADERS: if(stream->bodystarted) { - /* Only valid HEADERS after body started is trailer HEADERS. We + /* Only valid HEADERS after body started is trailer HEADERS. We buffer them in on_header callback. */ break; } @@ -1040,14 +1134,12 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf, return CURLE_RECV_ERROR; /* Only final status code signals the end of header */ - if(stream->status_code / 100 != 1) { + if(stream->status_code / 100 != 1) stream->bodystarted = TRUE; + else stream->status_code = -1; - } - result = recvbuf_write_hds(cf, data, STRCONST("\r\n")); - if(result) - return result; + h2_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), stream->closed); if(stream->status_code / 100 != 1) { stream->resp_hds_complete = TRUE; @@ -1075,22 +1167,39 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf, if(frame->rst_stream.error_code) { stream->reset = TRUE; } - stream->send_closed = TRUE; - data->req.keepon &= ~KEEP_SEND_HOLD; drain_stream(cf, data, stream); break; case NGHTTP2_WINDOW_UPDATE: - if((data->req.keepon & KEEP_SEND_HOLD) && - (data->req.keepon & KEEP_SEND)) { - data->req.keepon &= ~KEEP_SEND_HOLD; + if(CURL_WANT_SEND(data) && Curl_bufq_is_empty(&stream->sendbuf)) { + /* need more data, force processing of transfer */ drain_stream(cf, data, stream); - CURL_TRC_CF(data, cf, "[%d] un-holding after win update", - stream_id); + } + else if(!Curl_bufq_is_empty(&stream->sendbuf)) { + /* resume the potentially suspended stream */ + rv = nghttp2_session_resume_data(ctx->h2, stream->id); + if(nghttp2_is_fatal(rv)) + return CURLE_SEND_ERROR; } break; default: break; } + + if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + if(!stream->closed && !stream->body_eos && + ((stream->status_code >= 400) || (stream->status_code < 200))) { + /* The server did not give us a positive response and we are not + * done uploading the request body. We need to stop doing that and + * also inform the server that we aborted our side. */ + CURL_TRC_CF(data, cf, "[%d] EOS frame with unfinished upload and " + "HTTP status %d, abort upload by RST", + stream_id, stream->status_code); + nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, NGHTTP2_STREAM_CLOSED); + stream->closed = TRUE; + } + drain_stream(cf, data, stream); + } return CURLE_OK; } @@ -1145,14 +1254,14 @@ static int fr_print(const nghttp2_frame *frame, char *buffer, size_t blen) case NGHTTP2_GOAWAY: { char scratch[128]; size_t s_len = sizeof(scratch)/sizeof(scratch[0]); - size_t len = (frame->goaway.opaque_data_len < s_len)? - frame->goaway.opaque_data_len : s_len-1; - if(len) - memcpy(scratch, frame->goaway.opaque_data, len); - scratch[len] = '\0'; - return msnprintf(buffer, blen, "FRAME[GOAWAY, error=%d, reason='%s', " - "last_stream=%d]", frame->goaway.error_code, - scratch, frame->goaway.last_stream_id); + size_t len = (frame->goaway.opaque_data_len < s_len) ? + frame->goaway.opaque_data_len : s_len-1; + if(len) + memcpy(scratch, frame->goaway.opaque_data, len); + scratch[len] = '\0'; + return msnprintf(buffer, blen, "FRAME[GOAWAY, error=%d, reason='%s', " + "last_stream=%d]", frame->goaway.error_code, + scratch, frame->goaway.last_stream_id); } case NGHTTP2_WINDOW_UPDATE: { return msnprintf(buffer, blen, @@ -1230,26 +1339,21 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, * window and *assume* that we treat this like a WINDOW_UPDATE. Some * servers send an explicit WINDOW_UPDATE, but not all seem to do that. * To be safe, we UNHOLD a stream in order not to stall. */ - if((data->req.keepon & KEEP_SEND_HOLD) && - (data->req.keepon & KEEP_SEND)) { - struct stream_ctx *stream = H2_STREAM_CTX(data); - data->req.keepon &= ~KEEP_SEND_HOLD; - if(stream) { + if(CURL_WANT_SEND(data)) { + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + if(stream) drain_stream(cf, data, stream); - CURL_TRC_CF(data, cf, "[%d] un-holding after SETTINGS", - stream_id); - } } } break; } case NGHTTP2_GOAWAY: - ctx->goaway = TRUE; + ctx->rcvd_goaway = TRUE; ctx->goaway_error = frame->goaway.error_code; - ctx->last_stream_id = frame->goaway.last_stream_id; + ctx->remote_max_sid = frame->goaway.last_stream_id; if(data) { - infof(data, "received GOAWAY, error=%d, last_stream=%u", - ctx->goaway_error, ctx->last_stream_id); + infof(data, "received GOAWAY, error=%u, last_stream=%u", + ctx->goaway_error, ctx->remote_max_sid); Curl_multi_connchanged(data->multi); } break; @@ -1265,7 +1369,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, return 0; } - return on_stream_frame(cf, data_s, frame)? NGHTTP2_ERR_CALLBACK_FAILURE : 0; + return on_stream_frame(cf, data_s, frame) ? NGHTTP2_ERR_CALLBACK_FAILURE : 0; } static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, @@ -1273,10 +1377,9 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, const uint8_t *mem, size_t len, void *userp) { struct Curl_cfilter *cf = userp; - struct stream_ctx *stream; + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream; struct Curl_easy *data_s; - ssize_t nwritten; - CURLcode result; (void)flags; DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ @@ -1295,22 +1398,14 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, return 0; } - stream = H2_STREAM_CTX(data_s); + stream = H2_STREAM_CTX(ctx, data_s); if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; - nwritten = Curl_bufq_write(&stream->recvbuf, mem, len, &result); - if(nwritten < 0) { - if(result != CURLE_AGAIN) - return NGHTTP2_ERR_CALLBACK_FAILURE; + h2_xfer_write_resp(cf, data_s, stream, (char *)mem, len, FALSE); - nwritten = 0; - } - - /* if we receive data for another handle, wake that up */ - drain_stream(cf, data_s, stream); - - DEBUGASSERT((size_t)nwritten == len); + nghttp2_session_consume(ctx->h2, stream_id, len); + stream->nrcvd_data += (curl_off_t)len; return 0; } @@ -1318,27 +1413,42 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *userp) { struct Curl_cfilter *cf = userp; - struct Curl_easy *data_s; - struct stream_ctx *stream; + struct cf_h2_ctx *ctx = cf->ctx; + struct Curl_easy *data_s, *call_data = CF_DATA_CURRENT(cf); + struct h2_stream_ctx *stream; int rv; (void)session; - /* get the stream from the hash based on Stream ID, stream ID zero is for - connection-oriented stuff */ - data_s = stream_id? - nghttp2_session_get_stream_user_data(session, stream_id) : NULL; + DEBUGASSERT(call_data); + /* stream id 0 is the connection, do not look there for streams. */ + data_s = stream_id ? + nghttp2_session_get_stream_user_data(session, stream_id) : NULL; if(!data_s) { + CURL_TRC_CF(call_data, cf, + "[%d] on_stream_close, no easy set on stream", stream_id); return 0; } - stream = H2_STREAM_CTX(data_s); - if(!stream) + if(!GOOD_EASY_HANDLE(data_s)) { + /* nghttp2 still has an easy registered for the stream which has + * been freed be libcurl. This points to a code path that does not + * trigger DONE or DETACH events as it must. */ + CURL_TRC_CF(call_data, cf, + "[%d] on_stream_close, not a GOOD easy on stream", stream_id); + (void)nghttp2_session_set_stream_user_data(session, stream_id, 0); return NGHTTP2_ERR_CALLBACK_FAILURE; + } + stream = H2_STREAM_CTX(ctx, data_s); + if(!stream) { + CURL_TRC_CF(data_s, cf, + "[%d] on_stream_close, GOOD easy but no stream", stream_id); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } stream->closed = TRUE; stream->error = error_code; - if(stream->error) + if(stream->error) { stream->reset = TRUE; - data_s->req.keepon &= ~KEEP_SEND_HOLD; + } if(stream->error) CURL_TRC_CF(data_s, cf, "[%d] RESET: %s (err %d)", @@ -1361,7 +1471,8 @@ static int on_begin_headers(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct Curl_cfilter *cf = userp; - struct stream_ctx *stream; + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream; struct Curl_easy *data_s = NULL; (void)cf; @@ -1374,7 +1485,7 @@ static int on_begin_headers(nghttp2_session *session, return 0; } - stream = H2_STREAM_CTX(data_s); + stream = H2_STREAM_CTX(ctx, data_s); if(!stream || !stream->bodystarted) { return 0; } @@ -1390,7 +1501,8 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct Curl_cfilter *cf = userp; - struct stream_ctx *stream; + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream; struct Curl_easy *data_s; int32_t stream_id = frame->hd.stream_id; CURLcode result; @@ -1405,7 +1517,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; - stream = H2_STREAM_CTX(data_s); + stream = H2_STREAM_CTX(ctx, data_s); if(!stream) { failf(data_s, "Internal NULL stream"); return NGHTTP2_ERR_CALLBACK_FAILURE; @@ -1446,7 +1558,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, stream->push_headers = malloc(stream->push_headers_alloc * sizeof(char *)); if(!stream->push_headers) - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + return NGHTTP2_ERR_CALLBACK_FAILURE; stream->push_headers_used = 0; } else if(stream->push_headers_used == @@ -1455,15 +1567,15 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(stream->push_headers_alloc > 1000) { /* this is beyond crazy many headers, bail out */ failf(data_s, "Too many PUSH_PROMISE headers"); - Curl_safefree(stream->push_headers); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + free_push_headers(stream); + return NGHTTP2_ERR_CALLBACK_FAILURE; } stream->push_headers_alloc *= 2; - headp = Curl_saferealloc(stream->push_headers, - stream->push_headers_alloc * sizeof(char *)); + headp = realloc(stream->push_headers, + stream->push_headers_alloc * sizeof(char *)); if(!headp) { - stream->push_headers = NULL; - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + free_push_headers(stream); + return NGHTTP2_ERR_CALLBACK_FAILURE; } stream->push_headers = headp; } @@ -1499,14 +1611,15 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, result = Curl_headers_push(data_s, buffer, CURLH_PSEUDO); if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, STRCONST("HTTP/2 ")); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - /* the space character after the status code is mandatory */ - result = recvbuf_write_hds(cf, data_s, STRCONST(" \r\n")); + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, STRCONST("HTTP/2 ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, value, valuelen); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(" \r\n")); + if(!result) + h2_xfer_write_resp_hd(cf, data_s, stream, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; /* if we receive data for another handle, wake that up */ @@ -1521,16 +1634,17 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, /* nghttp2 guarantees that namelen > 0, and :status was already received, and this is not pseudo-header field . */ /* convert to an HTTP1-style header */ - result = recvbuf_write_hds(cf, data_s, (const char *)name, namelen); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, STRCONST(": ")); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen); - if(result) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = recvbuf_write_hds(cf, data_s, STRCONST("\r\n")); + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, (const char *)name, namelen); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(": ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, (const char *)value, valuelen); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST("\r\n")); + if(!result) + h2_xfer_write_resp_hd(cf, data_s, stream, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; /* if we receive data for another handle, wake that up */ @@ -1551,29 +1665,29 @@ static ssize_t req_body_read_callback(nghttp2_session *session, void *userp) { struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data_s; - struct stream_ctx *stream = NULL; + struct h2_stream_ctx *stream = NULL; CURLcode result; ssize_t nread; (void)source; (void)cf; - if(stream_id) { - /* get the stream from the hash based on Stream ID, stream ID zero is for - connection-oriented stuff */ - data_s = nghttp2_session_get_stream_user_data(session, stream_id); - if(!data_s) - /* Receiving a Stream ID not in the hash should not happen, this is an - internal error more than anything else! */ - return NGHTTP2_ERR_CALLBACK_FAILURE; - - stream = H2_STREAM_CTX(data_s); - if(!stream) - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - else + if(!stream_id) return NGHTTP2_ERR_INVALID_ARGUMENT; + /* get the stream from the hash based on Stream ID, stream ID zero is for + connection-oriented stuff */ + data_s = nghttp2_session_get_stream_user_data(session, stream_id); + if(!data_s) + /* Receiving a Stream ID not in the hash should not happen, this is an + internal error more than anything else! */ + return NGHTTP2_ERR_CALLBACK_FAILURE; + + stream = H2_STREAM_CTX(ctx, data_s); + if(!stream) + return NGHTTP2_ERR_CALLBACK_FAILURE; + nread = Curl_bufq_read(&stream->sendbuf, buf, length, &result); if(nread < 0) { if(result != CURLE_AGAIN) @@ -1581,19 +1695,14 @@ static ssize_t req_body_read_callback(nghttp2_session *session, nread = 0; } - if(nread > 0 && stream->upload_left != -1) - stream->upload_left -= nread; - - CURL_TRC_CF(data_s, cf, "[%d] req_body_read(len=%zu) left=%" - CURL_FORMAT_CURL_OFF_T " -> %zd, %d", - stream_id, length, stream->upload_left, nread, result); + CURL_TRC_CF(data_s, cf, "[%d] req_body_read(len=%zu) eos=%d -> %zd, %d", + stream_id, length, stream->body_eos, nread, result); - if(stream->upload_left == 0) + if(stream->body_eos && Curl_bufq_is_empty(&stream->sendbuf)) { *data_flags = NGHTTP2_DATA_FLAG_EOF; - else if(nread == 0) - return NGHTTP2_ERR_DEFERRED; - - return nread; + return nread; + } + return (nread == 0) ? NGHTTP2_ERR_DEFERRED : nread; } #if !defined(CURL_DISABLE_VERBOSE_STRINGS) @@ -1602,10 +1711,10 @@ static int error_callback(nghttp2_session *session, size_t len, void *userp) { + struct Curl_cfilter *cf = userp; + struct Curl_easy *data = CF_DATA_CURRENT(cf); (void)session; - (void)msg; - (void)len; - (void)userp; + failf(data, "%.*s", (int)len, msg); return 0; } #endif @@ -1621,7 +1730,7 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req, size_t blen; struct SingleRequest *k = &data->req; uint8_t binsettings[H2_BINSETTINGS_LEN]; - size_t binlen; /* length of the binsettings data */ + ssize_t binlen; /* length of the binsettings data */ binlen = populate_binsettings(binsettings, data); if(binlen <= 0) { @@ -1630,7 +1739,7 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req, return CURLE_FAILED_INIT; } - result = Curl_base64url_encode((const char *)binsettings, binlen, + result = Curl_base64url_encode((const char *)binsettings, (size_t)binlen, &base64, &blen); if(result) { Curl_dyn_free(req); @@ -1645,40 +1754,14 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req, free(base64); k->upgr101 = UPGR101_H2; + data->conn->bits.asks_multiplex = TRUE; return result; } -static CURLcode http2_data_done_send(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_h2_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - struct stream_ctx *stream = H2_STREAM_CTX(data); - - if(!ctx || !ctx->h2 || !stream) - goto out; - - CURL_TRC_CF(data, cf, "[%d] data done send", stream->id); - if(!stream->send_closed) { - stream->send_closed = TRUE; - if(stream->upload_left) { - /* we now know that everything that is buffered is all there is. */ - stream->upload_left = Curl_bufq_len(&stream->sendbuf); - /* resume sending here to trigger the callback to get called again so - that it can signal EOF to nghttp2 */ - (void)nghttp2_session_resume_data(ctx->h2, stream->id); - drain_stream(cf, data, stream); - } - } - -out: - return result; -} - static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, struct Curl_easy *data, - struct stream_ctx *stream, + struct h2_stream_ctx *stream, CURLcode *err) { ssize_t rv = 0; @@ -1686,12 +1769,21 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, if(stream->error == NGHTTP2_REFUSED_STREAM) { CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new " "connection", stream->id); - connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */ + connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */ data->state.refused_stream = TRUE; - *err = CURLE_SEND_ERROR; /* trigger Curl_retry_request() later */ + *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ return -1; } else if(stream->error != NGHTTP2_NO_ERROR) { + if(stream->resp_hds_complete && data->req.no_body) { + CURL_TRC_CF(data, cf, "[%d] error after response headers, but we did " + "not want a body anyway, ignore: %s (err %u)", + stream->id, nghttp2_http2_strerror(stream->error), + stream->error); + stream->close_handled = TRUE; + *err = CURLE_OK; + goto out; + } failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)", stream->id, nghttp2_http2_strerror(stream->error), stream->error); @@ -1700,7 +1792,7 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, } else if(stream->reset) { failf(data, "HTTP/2 stream %u was reset", stream->id); - *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP2; return -1; } @@ -1753,14 +1845,14 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, static int sweight_wanted(const struct Curl_easy *data) { /* 0 weight is not set by user and we take the nghttp2 default one */ - return data->set.priority.weight? + return data->set.priority.weight ? data->set.priority.weight : NGHTTP2_DEFAULT_WEIGHT; } static int sweight_in_effect(const struct Curl_easy *data) { /* 0 weight is not set by user and we take the nghttp2 default one */ - return data->state.priority.weight? + return data->state.priority.weight ? data->state.priority.weight : NGHTTP2_DEFAULT_WEIGHT; } @@ -1770,12 +1862,13 @@ static int sweight_in_effect(const struct Curl_easy *data) * struct. */ -static void h2_pri_spec(struct Curl_easy *data, +static void h2_pri_spec(struct cf_h2_ctx *ctx, + struct Curl_easy *data, nghttp2_priority_spec *pri_spec) { struct Curl_data_priority *prio = &data->set.priority; - struct stream_ctx *depstream = H2_STREAM_CTX(prio->parent); - int32_t depstream_id = depstream? depstream->id:0; + struct h2_stream_ctx *depstream = H2_STREAM_CTX(ctx, prio->parent); + int32_t depstream_id = depstream ? depstream->id : 0; nghttp2_priority_spec_init(pri_spec, depstream_id, sweight_wanted(data), data->set.priority.exclusive); @@ -1783,7 +1876,7 @@ static void h2_pri_spec(struct Curl_easy *data, } /* - * Check if there's been an update in the priority / + * Check if there is been an update in the priority / * dependency settings and if so it submits a PRIORITY frame with the updated * info. * Flush any out data pending in the network buffer. @@ -1792,7 +1885,7 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); int rv = 0; if(stream && stream->id > 0 && @@ -1802,7 +1895,7 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf, /* send new weight and/or dependency */ nghttp2_priority_spec pri_spec; - h2_pri_spec(data, &pri_spec); + h2_pri_spec(ctx, data, &pri_spec); CURL_TRC_CF(data, cf, "[%d] Queuing PRIORITY", stream->id); DEBUGASSERT(stream->id != -1); rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE, @@ -1821,44 +1914,40 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf, nghttp2_strerror(rv), rv); return CURLE_SEND_ERROR; } + /* Defer flushing during the connect phase so that the SETTINGS and + * other initial frames are sent together with the first request. + * Unless we are 'connect_only' where the request will never come. */ + if(!cf->connected && !cf->conn->connect_only) + return CURLE_OK; return nw_out_flush(cf, data); } static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data, - struct stream_ctx *stream, + struct h2_stream_ctx *stream, char *buf, size_t len, CURLcode *err) { struct cf_h2_ctx *ctx = cf->ctx; ssize_t nread = -1; + (void)buf; *err = CURLE_AGAIN; - if(!Curl_bufq_is_empty(&stream->recvbuf)) { - nread = Curl_bufq_read(&stream->recvbuf, - (unsigned char *)buf, len, err); - if(nread < 0) - goto out; - DEBUGASSERT(nread > 0); + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%d] xfer write failed", stream->id); + *err = stream->xfer_result; + nread = -1; } - - if(nread < 0) { - if(stream->closed) { - CURL_TRC_CF(data, cf, "[%d] returning CLOSE", stream->id); - nread = http2_handle_stream_close(cf, data, stream, err); - } - else if(stream->reset || - (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) || - (ctx->goaway && ctx->last_stream_id < stream->id)) { - CURL_TRC_CF(data, cf, "[%d] returning ERR", stream->id); - *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; - nread = -1; - } + else if(stream->closed) { + CURL_TRC_CF(data, cf, "[%d] returning CLOSE", stream->id); + nread = http2_handle_stream_close(cf, data, stream, err); } - else if(nread == 0) { - *err = CURLE_AGAIN; + else if(stream->reset || + (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) || + (ctx->rcvd_goaway && ctx->remote_max_sid < stream->id)) { + CURL_TRC_CF(data, cf, "[%d] returning ERR", stream->id); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP2; nread = -1; } -out: if(nread < 0 && *err != CURLE_AGAIN) CURL_TRC_CF(data, cf, "[%d] stream_recv(len=%zu) -> %zd, %d", stream->id, len, nread, *err); @@ -1866,10 +1955,11 @@ static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + size_t data_max_bytes) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream; + struct h2_stream_ctx *stream; CURLcode result = CURLE_OK; ssize_t nread; @@ -1885,17 +1975,18 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, * it is time to stop due to connection close or us not processing * all network input */ while(!ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { - stream = H2_STREAM_CTX(data); - if(stream && (stream->closed || Curl_bufq_is_full(&stream->recvbuf))) { + stream = H2_STREAM_CTX(ctx, data); + if(stream && (stream->closed || !data_max_bytes)) { /* We would like to abort here and stop processing, so that * the transfer loop can handle the data/close here. However, * this may leave data in underlying buffers that will not * be consumed. */ if(!cf->next || !cf->next->cft->has_data_pending(cf->next, data)) - break; + drain_stream(cf, data, stream); + break; } - nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result); + nread = Curl_bufq_sipn(&ctx->inbufq, 0, nw_in_reader, cf, &result); if(nread < 0) { if(result != CURLE_AGAIN) { failf(data, "Failed receiving HTTP2 data: %d(%s)", result, @@ -1910,18 +2001,22 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, break; } else { - CURL_TRC_CF(data, cf, "[0] ingress: read %zd bytes", - nread); + CURL_TRC_CF(data, cf, "[0] ingress: read %zd bytes", nread); + data_max_bytes = (data_max_bytes > (size_t)nread) ? + (data_max_bytes - (size_t)nread) : 0; } if(h2_process_pending_input(cf, data, &result)) return result; + CURL_TRC_CF(data, cf, "[0] progress ingress: inbufg=%zu", + Curl_bufq_len(&ctx->inbufq)); } if(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { connclose(cf->conn, "GOAWAY received"); } + CURL_TRC_CF(data, cf, "[0] progress ingress: done"); return CURLE_OK; } @@ -1929,7 +2024,7 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); ssize_t nread = -1; CURLcode result; struct cf_call_data save; @@ -1939,9 +2034,8 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, * (unlikely) or the transfer has been done, cleaned up its resources, but * a read() is called anyway. It is not clear what the calling sequence * is for such a case. */ - failf(data, "[%zd-%zd], http/2 recv on a transfer never opened " - "or already cleared", (ssize_t)data->id, - (ssize_t)cf->conn->connection_id); + failf(data, "http/2 recv on a transfer never opened " + "or already cleared, mid=%" FMT_OFF_T, data->mid); *err = CURLE_HTTP2; return -1; } @@ -1953,7 +2047,7 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, goto out; if(nread < 0) { - *err = h2_progress_ingress(cf, data); + *err = h2_progress_ingress(cf, data, len); if(*err) goto out; @@ -1987,20 +2081,19 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, out: result = h2_progress_egress(cf, data); if(result == CURLE_AGAIN) { - /* pending data to send, need to be called again. Ideally, we'd - * monitor the socket for POLLOUT, but we might not be in SENDING - * transfer state any longer and are unable to make this happen. - */ - drain_stream(cf, data, stream); + /* pending data to send, need to be called again. Ideally, we + * monitor the socket for POLLOUT, but when not SENDING + * any more, we force processing of the transfer. */ + if(!CURL_WANT_SEND(data)) + drain_stream(cf, data, stream); } else if(result) { *err = result; nread = -1; } CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d, " - "buffered=%zu, window=%d/%d, connection %d/%d", + "window=%d/%d, connection %d/%d", stream->id, len, nread, *err, - Curl_bufq_len(&stream->recvbuf), nghttp2_session_get_stream_effective_recv_data_length( ctx->h2, stream->id), nghttp2_session_get_stream_effective_local_window_size( @@ -2012,12 +2105,60 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, return nread; } -static ssize_t h2_submit(struct stream_ctx **pstream, +static ssize_t cf_h2_body_send(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + const void *buf, size_t blen, bool eos, + CURLcode *err) +{ + struct cf_h2_ctx *ctx = cf->ctx; + ssize_t nwritten; + + if(stream->closed) { + if(stream->resp_hds_complete) { + /* Server decided to close the stream after having sent us a final + * response. This is valid if it is not interested in the request + * body. This happens on 30x or 40x responses. + * We silently discard the data sent, since this is not a transport + * error situation. */ + CURL_TRC_CF(data, cf, "[%d] discarding data" + "on closed stream with response", stream->id); + if(eos) + stream->body_eos = TRUE; + *err = CURLE_OK; + return (ssize_t)blen; + } + /* Server closed before we got a response, this is an error */ + infof(data, "stream %u closed", stream->id); + *err = CURLE_SEND_ERROR; + return -1; + } + + nwritten = Curl_bufq_write(&stream->sendbuf, buf, blen, err); + if(nwritten < 0) + return -1; + + if(eos && (blen == (size_t)nwritten)) + stream->body_eos = TRUE; + + if(eos || !Curl_bufq_is_empty(&stream->sendbuf)) { + /* resume the potentially suspended stream */ + int rv = nghttp2_session_resume_data(ctx->h2, stream->id); + if(nghttp2_is_fatal(rv)) { + *err = CURLE_SEND_ERROR; + return -1; + } + } + return nwritten; +} + +static ssize_t h2_submit(struct h2_stream_ctx **pstream, struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, + bool eos, CURLcode *err) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = NULL; + struct h2_stream_ctx *stream = NULL; struct dynhds h2_headers; nghttp2_nv *nva = NULL; const void *body = NULL; @@ -2052,24 +2193,14 @@ static ssize_t h2_submit(struct stream_ctx **pstream, /* no longer needed */ Curl_h1_req_parse_free(&stream->h1); - nheader = Curl_dynhds_count(&h2_headers); - nva = malloc(sizeof(nghttp2_nv) * nheader); + nva = Curl_dynhds_to_nva(&h2_headers, &nheader); if(!nva) { *err = CURLE_OUT_OF_MEMORY; nwritten = -1; goto out; } - for(i = 0; i < nheader; ++i) { - struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i); - nva[i].name = (unsigned char *)e->name; - nva[i].namelen = e->namelen; - nva[i].value = (unsigned char *)e->value; - nva[i].valuelen = e->valuelen; - nva[i].flags = NGHTTP2_NV_FLAG_NONE; - } - - h2_pri_spec(data, &pri_spec); + h2_pri_spec(ctx, data, &pri_spec); if(!nghttp2_session_check_request_allowed(ctx->h2)) CURL_TRC_CF(data, cf, "send request NOT allowed (via nghttp2)"); @@ -2078,19 +2209,12 @@ static ssize_t h2_submit(struct stream_ctx **pstream, case HTTPREQ_POST_FORM: case HTTPREQ_POST_MIME: case HTTPREQ_PUT: - if(data->state.infilesize != -1) - stream->upload_left = data->state.infilesize; - else - /* data sending without specifying the data amount up front */ - stream->upload_left = -1; /* unknown */ - data_prd.read_callback = req_body_read_callback; data_prd.source.ptr = NULL; stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, &data_prd, data); break; default: - stream->upload_left = 0; /* no request body */ stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, NULL, data); } @@ -2125,37 +2249,26 @@ static ssize_t h2_submit(struct stream_ctx **pstream, } stream->id = stream_id; - stream->local_window_size = H2_STREAM_WINDOW_SIZE; - if(data->set.max_recv_speed) { - /* We are asked to only receive `max_recv_speed` bytes per second. - * Let's limit our stream window size around that, otherwise the server - * will send in large bursts only. We make the window 50% larger to - * allow for data in flight and avoid stalling. */ - curl_off_t n = (((data->set.max_recv_speed - 1) / H2_CHUNK_SIZE) + 1); - n += CURLMAX((n/2), 1); - if(n < (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) && - n < (UINT_MAX / H2_CHUNK_SIZE)) { - stream->local_window_size = (uint32_t)n * H2_CHUNK_SIZE; - } - } body = (const char *)buf + nwritten; bodylen = len - nwritten; - if(bodylen) { - /* We have request body to send in DATA frame */ - ssize_t n = Curl_bufq_write(&stream->sendbuf, body, bodylen, err); - if(n < 0) { + if(bodylen || eos) { + ssize_t n = cf_h2_body_send(cf, data, stream, body, bodylen, eos, err); + if(n >= 0) + nwritten += n; + else if(*err == CURLE_AGAIN) + *err = CURLE_OK; + else if(*err != CURLE_AGAIN) { *err = CURLE_SEND_ERROR; nwritten = -1; goto out; } - nwritten += n; } out: CURL_TRC_CF(data, cf, "[%d] submit -> %zd, %d", - stream? stream->id : -1, nwritten, *err); + stream ? stream->id : -1, nwritten, *err); Curl_safefree(nva); *pstream = stream; Curl_dynhds_free(&h2_headers); @@ -2163,137 +2276,63 @@ static ssize_t h2_submit(struct stream_ctx **pstream, } static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); struct cf_call_data save; - int rv; ssize_t nwritten; CURLcode result; - int blocked = 0, was_blocked = 0; CF_DATA_SAVE(save, cf, data); - if(stream && stream->id != -1) { - if(stream->upload_blocked_len) { - /* the data in `buf` has already been submitted or added to the - * buffers, but have been EAGAINed on the last invocation. */ - /* TODO: this assertion triggers in OSSFuzz runs and it is not - * clear why. Disable for now to let OSSFuzz continue its tests. */ - DEBUGASSERT(len >= stream->upload_blocked_len); - if(len < stream->upload_blocked_len) { - /* Did we get called again with a smaller `len`? This should not - * happen. We are not prepared to handle that. */ - failf(data, "HTTP/2 send again with decreased length (%zd vs %zd)", - len, stream->upload_blocked_len); - *err = CURLE_HTTP2; - nwritten = -1; - goto out; - } - nwritten = (ssize_t)stream->upload_blocked_len; - stream->upload_blocked_len = 0; - was_blocked = 1; - } - else if(stream->closed) { - if(stream->resp_hds_complete) { - /* Server decided to close the stream after having sent us a findl - * response. This is valid if it is not interested in the request - * body. This happens on 30x or 40x responses. - * We silently discard the data sent, since this is not a transport - * error situation. */ - CURL_TRC_CF(data, cf, "[%d] discarding data" - "on closed stream with response", stream->id); - *err = CURLE_OK; - nwritten = (ssize_t)len; - goto out; - } - infof(data, "stream %u closed", stream->id); - *err = CURLE_SEND_ERROR; - nwritten = -1; + if(!stream || stream->id == -1) { + nwritten = h2_submit(&stream, cf, data, buf, len, eos, err); + if(nwritten < 0) { goto out; } - else { - /* If stream_id != -1, we have dispatched request HEADERS and - * optionally request body, and now are going to send or sending - * more request body in DATA frame */ - nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err); - if(nwritten < 0 && *err != CURLE_AGAIN) - goto out; - } - - if(!Curl_bufq_is_empty(&stream->sendbuf)) { - /* req body data is buffered, resume the potentially suspended stream */ - rv = nghttp2_session_resume_data(ctx->h2, stream->id); - if(nghttp2_is_fatal(rv)) { - *err = CURLE_SEND_ERROR; - nwritten = -1; - goto out; - } - } + DEBUGASSERT(stream); } - else { - nwritten = h2_submit(&stream, cf, data, buf, len, err); + else if(stream->body_eos) { + /* We already wrote this, but CURLE_AGAINed the call due to not + * being able to flush stream->sendbuf. Make a 0-length write + * to trigger flushing again. + * If this works, we report to have written `len` bytes. */ + DEBUGASSERT(eos); + nwritten = cf_h2_body_send(cf, data, stream, buf, 0, eos, err); + CURL_TRC_CF(data, cf, "[%d] cf_body_send last CHUNK -> %zd, %d, eos=%d", + stream->id, nwritten, *err, eos); if(nwritten < 0) { goto out; } - DEBUGASSERT(stream); + nwritten = len; + } + else { + nwritten = cf_h2_body_send(cf, data, stream, buf, len, eos, err); + CURL_TRC_CF(data, cf, "[%d] cf_body_send(len=%zu) -> %zd, %d, eos=%d", + stream->id, len, nwritten, *err, eos); } /* Call the nghttp2 send loop and flush to write ALL buffered data, * headers and/or request body completely out to the network */ result = h2_progress_egress(cf, data); + /* if the stream has been closed in egress handling (nghttp2 does that * when it does not like the headers, for example */ - if(stream && stream->closed && !was_blocked) { + if(stream && stream->closed) { infof(data, "stream %u closed", stream->id); *err = CURLE_SEND_ERROR; nwritten = -1; goto out; } - else if(result == CURLE_AGAIN) { - blocked = 1; - } - else if(result) { + else if(result && (result != CURLE_AGAIN)) { *err = result; nwritten = -1; goto out; } - else if(stream && !Curl_bufq_is_empty(&stream->sendbuf)) { - /* although we wrote everything that nghttp2 wants to send now, - * there is data left in our stream send buffer unwritten. This may - * be due to the stream's HTTP/2 flow window being exhausted. */ - blocked = 1; - } - - if(stream && blocked && nwritten > 0) { - /* Unable to send all data, due to connection blocked or H2 window - * exhaustion. Data is left in our stream buffer, or nghttp2's internal - * frame buffer or our network out buffer. */ - size_t rwin = nghttp2_session_get_stream_remote_window_size(ctx->h2, - stream->id); - if(rwin == 0) { - /* H2 flow window exhaustion. We need to HOLD upload until we get - * a WINDOW_UPDATE from the server. */ - data->req.keepon |= KEEP_SEND_HOLD; - CURL_TRC_CF(data, cf, "[%d] holding send as remote flow " - "window is exhausted", stream->id); - } - - /* Whatever the cause, we need to return CURL_EAGAIN for this call. - * We have unwritten state that needs us being invoked again and EAGAIN - * is the only way to ensure that. */ - stream->upload_blocked_len = nwritten; - CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) BLOCK: win %u/%zu " - "blocked_len=%zu", - stream->id, len, - nghttp2_session_get_remote_window_size(ctx->h2), rwin, - nwritten); - *err = CURLE_AGAIN; - nwritten = -1; - goto out; - } - else if(should_close_session(ctx)) { + + if(should_close_session(ctx)) { /* nghttp2 thinks this session is done. If the stream has not been * closed, this is an error state for out transfer */ if(stream->closed) { @@ -2309,11 +2348,10 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, out: if(stream) { CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %zd, %d, " - "upload_left=%" CURL_FORMAT_CURL_OFF_T ", " - "h2 windows %d-%d (stream-conn), " + "eos=%d, h2 windows %d-%d (stream-conn), " "buffers %zu-%zu (stream-conn)", stream->id, len, nwritten, *err, - (ssize_t)stream->upload_left, + stream->body_eos, nghttp2_session_get_stream_remote_window_size( ctx->h2, stream->id), nghttp2_session_get_remote_window_size(ctx->h2), @@ -2331,37 +2369,89 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, return nwritten; } -static int cf_h2_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *sock) +static CURLcode cf_h2_flush(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct SingleRequest *k = &data->req; - struct stream_ctx *stream = H2_STREAM_CTX(data); - int bitmap = GETSOCK_BLANK; + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); struct cf_call_data save; + CURLcode result = CURLE_OK; CF_DATA_SAVE(save, cf, data); - sock[0] = Curl_conn_cf_get_socket(cf, data); - - if(!(k->keepon & (KEEP_RECV_PAUSE|KEEP_RECV_HOLD))) - /* Unless paused - in an HTTP/2 connection we can basically always get a - frame so we should always be ready for one */ - bitmap |= GETSOCK_READSOCK(0); - - /* we're (still uploading OR the HTTP/2 layer wants to send data) AND - there's a window to send data in */ - if((((k->keepon & KEEP_SENDBITS) == KEEP_SEND) || - nghttp2_session_want_write(ctx->h2)) && - (nghttp2_session_get_remote_window_size(ctx->h2) && - nghttp2_session_get_stream_remote_window_size(ctx->h2, - stream->id))) - bitmap |= GETSOCK_WRITESOCK(0); + if(stream && !Curl_bufq_is_empty(&stream->sendbuf)) { + /* resume the potentially suspended stream */ + int rv = nghttp2_session_resume_data(ctx->h2, stream->id); + if(nghttp2_is_fatal(rv)) { + result = CURLE_SEND_ERROR; + goto out; + } + } + + result = h2_progress_egress(cf, data); +out: + if(stream) { + CURL_TRC_CF(data, cf, "[%d] flush -> %d, " + "h2 windows %d-%d (stream-conn), " + "buffers %zu-%zu (stream-conn)", + stream->id, result, + nghttp2_session_get_stream_remote_window_size( + ctx->h2, stream->id), + nghttp2_session_get_remote_window_size(ctx->h2), + Curl_bufq_len(&stream->sendbuf), + Curl_bufq_len(&ctx->outbufq)); + } + else { + CURL_TRC_CF(data, cf, "flush -> %d, " + "connection-window=%d, nw_send_buffer(%zu)", + result, nghttp2_session_get_remote_window_size(ctx->h2), + Curl_bufq_len(&ctx->outbufq)); + } CF_DATA_RESTORE(cf, save); - return bitmap; + return result; } +static void cf_h2_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct cf_call_data save; + curl_socket_t sock; + bool want_recv, want_send; + + if(!ctx->h2) + return; + + sock = Curl_conn_cf_get_socket(cf, data); + Curl_pollset_check(data, ps, sock, &want_recv, &want_send); + if(want_recv || want_send) { + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + bool c_exhaust, s_exhaust; + + CF_DATA_SAVE(save, cf, data); + c_exhaust = want_send && !nghttp2_session_get_remote_window_size(ctx->h2); + s_exhaust = want_send && stream && stream->id >= 0 && + !nghttp2_session_get_stream_remote_window_size(ctx->h2, + stream->id); + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + (!c_exhaust && nghttp2_session_want_write(ctx->h2)) || + !Curl_bufq_is_empty(&ctx->outbufq); + + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } + else if(ctx->sent_goaway && !cf->shutdown) { + /* shutdown in progress */ + CF_DATA_SAVE(save, cf, data); + want_send = nghttp2_session_want_write(ctx->h2) || + !Curl_bufq_is_empty(&ctx->outbufq); + want_recv = nghttp2_session_want_read(ctx->h2); + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } +} static CURLcode cf_h2_connect(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -2370,6 +2460,7 @@ static CURLcode cf_h2_connect(struct Curl_cfilter *cf, struct cf_h2_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; struct cf_call_data save; + bool first_time = FALSE; if(cf->connected) { *done = TRUE; @@ -2386,15 +2477,19 @@ static CURLcode cf_h2_connect(struct Curl_cfilter *cf, *done = FALSE; CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(ctx->initialized); if(!ctx->h2) { - result = cf_h2_ctx_init(cf, data, FALSE); + result = cf_h2_ctx_open(cf, data); if(result) goto out; + first_time = TRUE; } - result = h2_progress_ingress(cf, data); - if(result) - goto out; + if(!first_time) { + result = h2_progress_ingress(cf, data, H2_CHUNK_SIZE); + if(result) + goto out; + } /* Send out our SETTINGS and ACKs and such. If that blocks, we * have it buffered and can count this filter as being connected */ @@ -2422,9 +2517,12 @@ static void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data) struct cf_call_data save; CF_DATA_SAVE(save, cf, data); - cf_h2_ctx_clear(ctx); + cf_h2_ctx_close(ctx); CF_DATA_RESTORE(cf, save); + cf->connected = FALSE; } + if(cf->next) + cf->next->cft->do_close(cf->next, data); } static void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -2438,30 +2536,68 @@ static void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) } } -static CURLcode http2_data_pause(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool pause) +static CURLcode cf_h2_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) { -#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); + struct cf_call_data save; + CURLcode result; + int rv; - DEBUGASSERT(data); - if(ctx && ctx->h2 && stream) { - uint32_t window = pause? 0 : stream->local_window_size; + if(!cf->connected || !ctx->h2 || cf->shutdown || ctx->conn_closed) { + *done = TRUE; + return CURLE_OK; + } + + CF_DATA_SAVE(save, cf, data); - int rv = nghttp2_session_set_local_window_size(ctx->h2, - NGHTTP2_FLAG_NONE, - stream->id, - window); + if(!ctx->sent_goaway) { + rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE, + ctx->local_max_sid, 0, + (const uint8_t *)"shutdown", + sizeof("shutdown")); if(rv) { - failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)", + failf(data, "nghttp2_submit_goaway() failed: %s(%d)", nghttp2_strerror(rv), rv); - return CURLE_HTTP2; + result = CURLE_SEND_ERROR; + goto out; } + ctx->sent_goaway = TRUE; + } + /* GOAWAY submitted, process egress and ingress until nghttp2 is done. */ + result = CURLE_OK; + if(nghttp2_session_want_write(ctx->h2) || + !Curl_bufq_is_empty(&ctx->outbufq)) + result = h2_progress_egress(cf, data); + if(!result && nghttp2_session_want_read(ctx->h2)) + result = h2_progress_ingress(cf, data, 0); - if(!pause) - drain_stream(cf, data, stream); + if(result == CURLE_AGAIN) + result = CURLE_OK; + + *done = (ctx->conn_closed || + (!result && !nghttp2_session_want_write(ctx->h2) && + !nghttp2_session_want_read(ctx->h2) && + Curl_bufq_is_empty(&ctx->outbufq))); + +out: + CF_DATA_RESTORE(cf, save); + cf->shutdown = (result || *done); + return result; +} + +static CURLcode http2_data_pause(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool pause) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + + DEBUGASSERT(data); + if(ctx && ctx->h2 && stream) { + CURLcode result = cf_h2_update_local_win(cf, data, stream, pause); + if(result) + return result; /* attempt to send the window update */ (void)h2_progress_egress(cf, data); @@ -2475,21 +2611,9 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf, drain_stream(cf, data, stream); Curl_expire(data, 0, EXPIRE_RUN_NOW); } - DEBUGF(infof(data, "Set HTTP/2 window size to %u for stream %u", - window, stream->id)); - -#ifdef DEBUGBUILD - { - /* read out the stream local window again */ - uint32_t window2 = - nghttp2_session_get_stream_local_window_size(ctx->h2, - stream->id); - DEBUGF(infof(data, "HTTP/2 window size is now %u for stream %u", - window2, stream->id)); - } -#endif + CURL_TRC_CF(data, cf, "[%d] stream now %spaused", stream->id, + pause ? "" : "un"); } -#endif return CURLE_OK; } @@ -2509,14 +2633,15 @@ static CURLcode cf_h2_cntrl(struct Curl_cfilter *cf, case CF_CTRL_DATA_PAUSE: result = http2_data_pause(cf, data, (arg1 != 0)); break; - case CF_CTRL_DATA_DONE_SEND: { - result = http2_data_done_send(cf, data); + case CF_CTRL_FLUSH: + result = cf_h2_flush(cf, data); break; - } - case CF_CTRL_DATA_DONE: { - http2_data_done(cf, data, arg1 != 0); + case CF_CTRL_DATA_DETACH: + http2_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE: + http2_data_done(cf, data); break; - } default: break; } @@ -2528,13 +2653,12 @@ static bool cf_h2_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H2_STREAM_CTX(data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); if(ctx && (!Curl_bufq_is_empty(&ctx->inbufq) - || (stream && !Curl_bufq_is_empty(&stream->sendbuf)) - || (stream && !Curl_bufq_is_empty(&stream->recvbuf)))) + || (stream && !Curl_bufq_is_empty(&stream->sendbuf)))) return TRUE; - return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE; + return cf->next ? cf->next->cft->has_data_pending(cf->next, data) : FALSE; } static bool cf_h2_is_alive(struct Curl_cfilter *cf, @@ -2585,13 +2709,27 @@ static CURLcode cf_h2_query(struct Curl_cfilter *cf, else { effective_max = ctx->max_concurrent_streams; } - *pres1 = (effective_max > INT_MAX)? INT_MAX : (int)effective_max; + *pres1 = (effective_max > INT_MAX) ? INT_MAX : (int)effective_max; CF_DATA_RESTORE(cf, save); return CURLE_OK; + case CF_QUERY_STREAM_ERROR: { + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + *pres1 = stream ? (int)stream->error : 0; + return CURLE_OK; + } + case CF_QUERY_NEED_FLUSH: { + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + if(!Curl_bufq_is_empty(&ctx->outbufq) || + (stream && !Curl_bufq_is_empty(&stream->sendbuf))) { + *pres1 = TRUE; + return CURLE_OK; + } + break; + } default: break; } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -2603,8 +2741,9 @@ struct Curl_cftype Curl_cft_nghttp2 = { cf_h2_destroy, cf_h2_connect, cf_h2_close, + cf_h2_shutdown, Curl_cf_def_get_host, - cf_h2_get_select_socks, + cf_h2_adjust_pollset, cf_h2_data_pending, cf_h2_send, cf_h2_recv, @@ -2617,49 +2756,53 @@ struct Curl_cftype Curl_cft_nghttp2 = { static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf, struct Curl_easy *data, struct connectdata *conn, - int sockindex) + int sockindex, + bool via_h1_upgrade) { struct Curl_cfilter *cf = NULL; struct cf_h2_ctx *ctx; CURLcode result = CURLE_OUT_OF_MEMORY; DEBUGASSERT(data->conn); - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) goto out; + cf_h2_ctx_init(ctx, via_h1_upgrade); result = Curl_cf_create(&cf, &Curl_cft_nghttp2, ctx); if(result) goto out; + ctx = NULL; Curl_conn_cf_add(data, conn, sockindex, cf); - result = CURLE_OK; out: if(result) cf_h2_ctx_free(ctx); - *pcf = result? NULL : cf; + *pcf = result ? NULL : cf; return result; } static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + bool via_h1_upgrade) { struct Curl_cfilter *cf_h2 = NULL; struct cf_h2_ctx *ctx; CURLcode result = CURLE_OUT_OF_MEMORY; (void)data; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) goto out; + cf_h2_ctx_init(ctx, via_h1_upgrade); result = Curl_cf_create(&cf_h2, &Curl_cft_nghttp2, ctx); if(result) goto out; + ctx = NULL; Curl_conn_cf_insert_after(cf, cf_h2); - result = CURLE_OK; out: if(result) @@ -2667,8 +2810,8 @@ static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf, return result; } -static bool Curl_cf_is_http2(struct Curl_cfilter *cf, - const struct Curl_easy *data) +static bool cf_is_http2(struct Curl_cfilter *cf, + const struct Curl_easy *data) { (void)data; for(; cf; cf = cf->next) { @@ -2684,7 +2827,7 @@ bool Curl_conn_is_http2(const struct Curl_easy *data, const struct connectdata *conn, int sockindex) { - return conn? Curl_cf_is_http2(conn->cfilter[sockindex], data) : FALSE; + return conn ? cf_is_http2(conn->cfilter[sockindex], data) : FALSE; } bool Curl_http2_may_switch(struct Curl_easy *data, @@ -2696,7 +2839,7 @@ bool Curl_http2_may_switch(struct Curl_easy *data, data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { #ifndef CURL_DISABLE_PROXY if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { - /* We don't support HTTP/2 proxies yet. Also it's debatable + /* We do not support HTTP/2 proxies yet. Also it is debatable whether or not this setting should apply to HTTP/2 proxies. */ infof(data, "Ignoring HTTP/2 prior knowledge due to proxy"); return FALSE; @@ -2714,19 +2857,14 @@ CURLcode Curl_http2_switch(struct Curl_easy *data, CURLcode result; DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex)); - DEBUGF(infof(data, "switching to HTTP/2")); - result = http2_cfilter_add(&cf, data, conn, sockindex); + result = http2_cfilter_add(&cf, data, conn, sockindex, FALSE); if(result) return result; + CURL_TRC_CF(data, cf, "switching connection to HTTP/2"); - result = cf_h2_ctx_init(cf, data, FALSE); - if(result) - return result; - - conn->httpversion = 20; /* we know we're on HTTP/2 now */ + conn->httpversion = 20; /* we know we are on HTTP/2 now */ conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - conn->bundle->multiuse = BUNDLE_MULTIPLEX; Curl_multi_connchanged(data->multi); if(cf->next) { @@ -2741,20 +2879,15 @@ CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data) struct Curl_cfilter *cf_h2; CURLcode result; - DEBUGASSERT(!Curl_cf_is_http2(cf, data)); + DEBUGASSERT(!cf_is_http2(cf, data)); - result = http2_cfilter_insert_after(cf, data); + result = http2_cfilter_insert_after(cf, data, FALSE); if(result) return result; cf_h2 = cf->next; - result = cf_h2_ctx_init(cf_h2, data, FALSE); - if(result) - return result; - - cf->conn->httpversion = 20; /* we know we're on HTTP/2 now */ + cf->conn->httpversion = 20; /* we know we are on HTTP/2 now */ cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; Curl_multi_connchanged(data->multi); if(cf_h2->next) { @@ -2773,20 +2906,16 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data, CURLcode result; DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex)); - DEBUGF(infof(data, "upgrading to HTTP/2")); DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED); - result = http2_cfilter_add(&cf, data, conn, sockindex); + result = http2_cfilter_add(&cf, data, conn, sockindex, TRUE); if(result) return result; + CURL_TRC_CF(data, cf, "upgrading connection to HTTP/2"); DEBUGASSERT(cf->cft == &Curl_cft_nghttp2); ctx = cf->ctx; - result = cf_h2_ctx_init(cf, data, TRUE); - if(result) - return result; - if(nread > 0) { /* Remaining data from the protocol switch reply is already using * the switched protocol, ie. HTTP/2. We add that to the network @@ -2809,9 +2938,8 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data, " after upgrade: len=%zu", nread); } - conn->httpversion = 20; /* we know we're on HTTP/2 now */ + conn->httpversion = 20; /* we know we are on HTTP/2 now */ conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - conn->bundle->multiuse = BUNDLE_MULTIPLEX; Curl_multi_connchanged(data->multi); if(cf->next) { @@ -2825,8 +2953,11 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data, CURLE_HTTP2_STREAM error! */ bool Curl_h2_http_1_1_error(struct Curl_easy *data) { - struct stream_ctx *stream = H2_STREAM_CTX(data); - return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED); + if(Curl_conn_is_http2(data, data->conn, FIRSTSOCKET)) { + int err = Curl_conn_get_stream_error(data, data->conn, FIRSTSOCKET); + return (err == NGHTTP2_HTTP_1_1_REQUIRED); + } + return FALSE; } #else /* !USE_NGHTTP2 */ diff --git a/deps/curl/lib/http_aws_sigv4.c b/deps/curl/lib/http_aws_sigv4.c index f39d02ccedb..5d4848fed2b 100644 --- a/deps/curl/lib/http_aws_sigv4.c +++ b/deps/curl/lib/http_aws_sigv4.c @@ -34,6 +34,7 @@ #include "transfer.h" #include "parsedate.h" #include "sendf.h" +#include "escape.h" #include @@ -46,7 +47,7 @@ #define HMAC_SHA256(k, kl, d, dl, o) \ do { \ - result = Curl_hmacit(Curl_HMAC_SHA256, \ + result = Curl_hmacit(&Curl_HMAC_SHA256, \ (unsigned char *)k, \ kl, \ (unsigned char *)d, \ @@ -59,15 +60,12 @@ #define TIMESTAMP_SIZE 17 /* hex-encoded with trailing null */ -#define SHA256_HEX_LENGTH (2 * SHA256_DIGEST_LENGTH + 1) +#define SHA256_HEX_LENGTH (2 * CURL_SHA256_DIGEST_LENGTH + 1) static void sha256_to_hex(char *dst, unsigned char *sha) { - int i; - - for(i = 0; i < SHA256_DIGEST_LENGTH; ++i) { - msnprintf(dst + (i * 2), SHA256_HEX_LENGTH - (i * 2), "%02x", sha[i]); - } + Curl_hexencode(sha, CURL_SHA256_DIGEST_LENGTH, + (unsigned char *)dst, SHA256_HEX_LENGTH); } static char *find_date_hdr(struct Curl_easy *data, const char *sig_hdr) @@ -124,13 +122,40 @@ static void trim_headers(struct curl_slist *head) #define DATE_HDR_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Date")) -#define MAX_HOST_LEN 255 -/* FQDN + host: */ -#define FULL_HOST_LEN (MAX_HOST_LEN + sizeof("host:")) - /* string been x-PROVIDER-date:TIMESTAMP, I need +1 for ':' */ #define DATE_FULL_HDR_LEN (DATE_HDR_KEY_LEN + TIMESTAMP_SIZE + 1) +/* alphabetically compare two headers by their name, expecting + headers to use ':' at this point */ +static int compare_header_names(const char *a, const char *b) +{ + const char *colon_a; + const char *colon_b; + size_t len_a; + size_t len_b; + size_t min_len; + int cmp; + + colon_a = strchr(a, ':'); + colon_b = strchr(b, ':'); + + DEBUGASSERT(colon_a); + DEBUGASSERT(colon_b); + + len_a = colon_a ? (size_t)(colon_a - a) : strlen(a); + len_b = colon_b ? (size_t)(colon_b - b) : strlen(b); + + min_len = (len_a < len_b) ? len_a : len_b; + + cmp = strncmp(a, b, min_len); + + /* return the shorter of the two if one is shorter */ + if(!cmp) + return (int)(len_a - len_b); + + return cmp; +} + /* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */ static CURLcode make_headers(struct Curl_easy *data, const char *hostname, @@ -147,7 +172,7 @@ static CURLcode make_headers(struct Curl_easy *data, struct curl_slist *tmp_head = NULL; CURLcode ret = CURLE_OUT_OF_MEMORY; struct curl_slist *l; - int again = 1; + bool again = TRUE; /* provider1 mid */ Curl_strntolower(provider1, provider1, strlen(provider1)); @@ -160,35 +185,23 @@ static CURLcode make_headers(struct Curl_easy *data, msnprintf(date_full_hdr, DATE_FULL_HDR_LEN, "x-%s-date:%s", provider1, timestamp); - if(Curl_checkheaders(data, STRCONST("Host"))) { - head = NULL; - } - else { - char full_host[FULL_HOST_LEN + 1]; + if(!Curl_checkheaders(data, STRCONST("Host"))) { + char *fullhost; if(data->state.aptr.host) { - size_t pos; - - if(strlen(data->state.aptr.host) > FULL_HOST_LEN) { - ret = CURLE_URL_MALFORMAT; - goto fail; - } - strcpy(full_host, data->state.aptr.host); /* remove /r/n as the separator for canonical request must be '\n' */ - pos = strcspn(full_host, "\n\r"); - full_host[pos] = 0; - } - else { - if(strlen(hostname) > MAX_HOST_LEN) { - ret = CURLE_URL_MALFORMAT; - goto fail; - } - msnprintf(full_host, FULL_HOST_LEN, "host:%s", hostname); + size_t pos = strcspn(data->state.aptr.host, "\n\r"); + fullhost = Curl_memdup0(data->state.aptr.host, pos); } + else + fullhost = aprintf("host:%s", hostname); - head = curl_slist_append(NULL, full_host); - if(!head) + if(fullhost) + head = Curl_slist_append_nodup(NULL, fullhost); + if(!head) { + free(fullhost); goto fail; + } } @@ -245,11 +258,11 @@ static CURLcode make_headers(struct Curl_easy *data, if(!tmp_head) goto fail; head = tmp_head; - *date_header = curl_maprintf("%s: %s\r\n", date_hdr_key, timestamp); + *date_header = aprintf("%s: %s\r\n", date_hdr_key, timestamp); } else { char *value; - + char *endp; value = strchr(*date_header, ':'); if(!value) { *date_header = NULL; @@ -258,23 +271,32 @@ static CURLcode make_headers(struct Curl_easy *data, ++value; while(ISBLANK(*value)) ++value; - strncpy(timestamp, value, TIMESTAMP_SIZE - 1); - timestamp[TIMESTAMP_SIZE - 1] = 0; + endp = value; + while(*endp && ISALNUM(*endp)) + ++endp; + /* 16 bytes => "19700101T000000Z" */ + if((endp - value) == TIMESTAMP_SIZE - 1) { + memcpy(timestamp, value, TIMESTAMP_SIZE - 1); + timestamp[TIMESTAMP_SIZE - 1] = 0; + } + else + /* bad timestamp length */ + timestamp[0] = 0; *date_header = NULL; } - /* alpha-sort in a case sensitive manner */ + /* alpha-sort by header name in a case sensitive manner */ do { - again = 0; + again = FALSE; for(l = head; l; l = l->next) { struct curl_slist *next = l->next; - if(next && strcmp(l->data, next->data) > 0) { + if(next && compare_header_names(l->data, next->data) > 0) { char *tmp = l->data; l->data = next->data; next->data = tmp; - again = 1; + again = TRUE; } } } while(again); @@ -409,11 +431,86 @@ static int compare_func(const void *a, const void *b) { const struct pair *aa = a; const struct pair *bb = b; + /* If one element is empty, the other is always sorted higher */ + if(aa->len == 0) + return -1; + if(bb->len == 0) + return 1; return strncmp(aa->p, bb->p, aa->len < bb->len ? aa->len : bb->len); } #define MAX_QUERYPAIRS 64 +/** + * found_equals have a double meaning, + * detect if an equal have been found when called from canon_query, + * and mark that this function is called to compute the path, + * if found_equals is NULL. + */ +static CURLcode canon_string(const char *q, size_t len, + struct dynbuf *dq, bool *found_equals) +{ + CURLcode result = CURLE_OK; + + for(; len && !result; q++, len--) { + if(ISALNUM(*q)) + result = Curl_dyn_addn(dq, q, 1); + else { + switch(*q) { + case '-': + case '.': + case '_': + case '~': + /* allowed as-is */ + result = Curl_dyn_addn(dq, q, 1); + break; + case '%': + /* uppercase the following if hexadecimal */ + if(ISXDIGIT(q[1]) && ISXDIGIT(q[2])) { + char tmp[3]="%"; + tmp[1] = Curl_raw_toupper(q[1]); + tmp[2] = Curl_raw_toupper(q[2]); + result = Curl_dyn_addn(dq, tmp, 3); + q += 2; + len -= 2; + } + else + /* '%' without a following two-digit hex, encode it */ + result = Curl_dyn_addn(dq, "%25", 3); + break; + default: { + const char hex[] = "0123456789ABCDEF"; + char out[3]={'%'}; + + if(!found_equals) { + /* if found_equals is NULL assuming, been in path */ + if(*q == '/') { + /* allowed as if */ + result = Curl_dyn_addn(dq, q, 1); + break; + } + } + else { + /* allowed as-is */ + if(*q == '=') { + result = Curl_dyn_addn(dq, q, 1); + *found_equals = TRUE; + break; + } + } + /* URL encode */ + out[1] = hex[((unsigned char)*q) >> 4]; + out[2] = hex[*q & 0xf]; + result = Curl_dyn_addn(dq, out, 3); + break; + } + } + } + } + return result; +} + + static CURLcode canon_query(struct Curl_easy *data, const char *query, struct dynbuf *dq) { @@ -451,50 +548,16 @@ static CURLcode canon_query(struct Curl_easy *data, ap = &array[0]; for(i = 0; !result && (i < entry); i++, ap++) { - size_t len; const char *q = ap->p; + bool found_equals = FALSE; if(!ap->len) continue; - for(len = ap->len; len && !result; q++, len--) { - if(ISALNUM(*q)) - result = Curl_dyn_addn(dq, q, 1); - else { - switch(*q) { - case '-': - case '.': - case '_': - case '~': - case '=': - /* allowed as-is */ - result = Curl_dyn_addn(dq, q, 1); - break; - case '%': - /* uppercase the following if hexadecimal */ - if(ISXDIGIT(q[1]) && ISXDIGIT(q[2])) { - char tmp[3]="%"; - tmp[1] = Curl_raw_toupper(q[1]); - tmp[2] = Curl_raw_toupper(q[2]); - result = Curl_dyn_addn(dq, tmp, 3); - q += 2; - len -= 2; - } - else - /* '%' without a following two-digit hex, encode it */ - result = Curl_dyn_addn(dq, "%25", 3); - break; - default: { - /* URL encode */ - const char hex[] = "0123456789ABCDEF"; - char out[3]={'%'}; - out[1] = hex[((unsigned char)*q)>>4]; - out[2] = hex[*q & 0xf]; - result = Curl_dyn_addn(dq, out, 3); - break; - } - } - } + result = canon_string(q, ap->len, dq, &found_equals); + if(!result && !found_equals) { + /* queries without value still need an equals */ + result = Curl_dyn_addn(dq, "=", 1); } - if(i < entry - 1) { + if(!result && i < entry - 1) { /* insert ampersands between query pairs */ result = Curl_dyn_addn(dq, "&", 1); } @@ -513,7 +576,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) char provider1[MAX_SIGV4_LEN + 1]=""; char region[MAX_SIGV4_LEN + 1]=""; char service[MAX_SIGV4_LEN + 1]=""; - bool sign_as_s3 = false; + bool sign_as_s3 = FALSE; const char *hostname = conn->host.name; time_t clock; struct tm tm; @@ -522,12 +585,13 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) struct dynbuf canonical_headers; struct dynbuf signed_headers; struct dynbuf canonical_query; + struct dynbuf canonical_path; char *date_header = NULL; Curl_HttpReq httpreq; const char *method = NULL; char *payload_hash = NULL; size_t payload_hash_len = 0; - unsigned char sha_hash[SHA256_DIGEST_LENGTH]; + unsigned char sha_hash[CURL_SHA256_DIGEST_LENGTH]; char sha_hex[SHA256_HEX_LENGTH]; char content_sha256_hdr[CONTENT_SHA256_HDR_LEN + 2] = ""; /* add \r\n */ char *canonical_request = NULL; @@ -536,8 +600,8 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) char *str_to_sign = NULL; const char *user = data->state.aptr.user ? data->state.aptr.user : ""; char *secret = NULL; - unsigned char sign0[SHA256_DIGEST_LENGTH] = {0}; - unsigned char sign1[SHA256_DIGEST_LENGTH] = {0}; + unsigned char sign0[CURL_SHA256_DIGEST_LENGTH] = {0}; + unsigned char sign1[CURL_SHA256_DIGEST_LENGTH] = {0}; char *auth_headers = NULL; DEBUGASSERT(!proxy); @@ -552,6 +616,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) Curl_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER); Curl_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER); Curl_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER); + Curl_dyn_init(&canonical_path, CURL_MAX_HTTP_HEADER); /* * Parameters parsing @@ -573,7 +638,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) ":%" MAX_SIGV4_LEN_TXT "s", provider0, provider1, region, service); if(!provider0[0]) { - failf(data, "first aws-sigv4 provider can't be empty"); + failf(data, "first aws-sigv4 provider cannot be empty"); result = CURLE_BAD_FUNCTION_ARGUMENT; goto fail; } @@ -593,7 +658,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) result = CURLE_URL_MALFORMAT; goto fail; } - strncpy(service, hostname, len); + memcpy(service, hostname, len); service[len] = '\0'; infof(data, "aws_sigv4: picked service %s from host", service); @@ -612,7 +677,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) result = CURLE_URL_MALFORMAT; goto fail; } - strncpy(region, reg, len); + memcpy(region, reg, len); region[len] = '\0'; infof(data, "aws_sigv4: picked region %s from host", region); } @@ -647,10 +712,10 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) if(force_timestamp) clock = 0; else - time(&clock); + clock = time(NULL); } #else - time(&clock); + clock = time(NULL); #endif result = Curl_gmtime(clock, &tm); if(result) { @@ -680,22 +745,27 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) result = canon_query(data, data->state.up.query, &canonical_query); if(result) goto fail; + + result = canon_string(data->state.up.path, strlen(data->state.up.path), + &canonical_path, NULL); + if(result) + goto fail; result = CURLE_OUT_OF_MEMORY; canonical_request = - curl_maprintf("%s\n" /* HTTPRequestMethod */ - "%s\n" /* CanonicalURI */ - "%s\n" /* CanonicalQueryString */ - "%s\n" /* CanonicalHeaders */ - "%s\n" /* SignedHeaders */ - "%.*s", /* HashedRequestPayload in hex */ - method, - data->state.up.path, - Curl_dyn_ptr(&canonical_query) ? - Curl_dyn_ptr(&canonical_query) : "", - Curl_dyn_ptr(&canonical_headers), - Curl_dyn_ptr(&signed_headers), - (int)payload_hash_len, payload_hash); + aprintf("%s\n" /* HTTPRequestMethod */ + "%s\n" /* CanonicalURI */ + "%s\n" /* CanonicalQueryString */ + "%s\n" /* CanonicalHeaders */ + "%s\n" /* SignedHeaders */ + "%.*s", /* HashedRequestPayload in hex */ + method, + Curl_dyn_ptr(&canonical_path), + Curl_dyn_ptr(&canonical_query) ? + Curl_dyn_ptr(&canonical_query) : "", + Curl_dyn_ptr(&canonical_headers), + Curl_dyn_ptr(&signed_headers), + (int)payload_hash_len, payload_hash); if(!canonical_request) goto fail; @@ -703,12 +773,12 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) /* provider 0 lowercase */ Curl_strntolower(provider0, provider0, strlen(provider0)); - request_type = curl_maprintf("%s4_request", provider0); + request_type = aprintf("%s4_request", provider0); if(!request_type) goto fail; - credential_scope = curl_maprintf("%s/%s/%s/%s", - date, region, service, request_type); + credential_scope = aprintf("%s/%s/%s/%s", + date, region, service, request_type); if(!credential_scope) goto fail; @@ -725,22 +795,22 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) * Google allows using RSA key instead of HMAC, so this code might change * in the future. For now we only support HMAC. */ - str_to_sign = curl_maprintf("%s4-HMAC-SHA256\n" /* Algorithm */ - "%s\n" /* RequestDateTime */ - "%s\n" /* CredentialScope */ - "%s", /* HashedCanonicalRequest in hex */ - provider0, - timestamp, - credential_scope, - sha_hex); + str_to_sign = aprintf("%s4-HMAC-SHA256\n" /* Algorithm */ + "%s\n" /* RequestDateTime */ + "%s\n" /* CredentialScope */ + "%s", /* HashedCanonicalRequest in hex */ + provider0, + timestamp, + credential_scope, + sha_hex); if(!str_to_sign) { goto fail; } /* provider 0 uppercase */ - secret = curl_maprintf("%s4%s", provider0, - data->state.aptr.passwd ? - data->state.aptr.passwd : ""); + secret = aprintf("%s4%s", provider0, + data->state.aptr.passwd ? + data->state.aptr.passwd : ""); if(!secret) goto fail; @@ -753,24 +823,24 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) sha256_to_hex(sha_hex, sign0); /* provider 0 uppercase */ - auth_headers = curl_maprintf("Authorization: %s4-HMAC-SHA256 " - "Credential=%s/%s, " - "SignedHeaders=%s, " - "Signature=%s\r\n" - /* - * date_header is added here, only if it wasn't - * user-specified (using CURLOPT_HTTPHEADER). - * date_header includes \r\n - */ - "%s" - "%s", /* optional sha256 header includes \r\n */ - provider0, - user, - credential_scope, - Curl_dyn_ptr(&signed_headers), - sha_hex, - date_header ? date_header : "", - content_sha256_hdr); + auth_headers = aprintf("Authorization: %s4-HMAC-SHA256 " + "Credential=%s/%s, " + "SignedHeaders=%s, " + "Signature=%s\r\n" + /* + * date_header is added here, only if it was not + * user-specified (using CURLOPT_HTTPHEADER). + * date_header includes \r\n + */ + "%s" + "%s", /* optional sha256 header includes \r\n */ + provider0, + user, + credential_scope, + Curl_dyn_ptr(&signed_headers), + sha_hex, + date_header ? date_header : "", + content_sha256_hdr); if(!auth_headers) { goto fail; } @@ -782,6 +852,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) fail: Curl_dyn_free(&canonical_query); + Curl_dyn_free(&canonical_path); Curl_dyn_free(&canonical_headers); Curl_dyn_free(&signed_headers); free(canonical_request); diff --git a/deps/curl/lib/http_chunks.c b/deps/curl/lib/http_chunks.c index bda00d38338..aea84be9863 100644 --- a/deps/curl/lib/http_chunks.c +++ b/deps/curl/lib/http_chunks.c @@ -27,10 +27,13 @@ #ifndef CURL_DISABLE_HTTP #include "urldata.h" /* it includes http_chunks.h */ +#include "curl_printf.h" +#include "curl_trc.h" #include "sendf.h" /* for the client write stuff */ #include "dynbuf.h" #include "content_encoding.h" #include "http.h" +#include "multiif.h" #include "strtoofft.h" #include "warnless.h" @@ -75,121 +78,156 @@ */ -#define isxdigit_ascii(x) Curl_isxdigit(x) +void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body) +{ + (void)data; + ch->hexindex = 0; /* start at 0 */ + ch->state = CHUNK_HEX; /* we get hex first! */ + ch->last_code = CHUNKE_OK; + Curl_dyn_init(&ch->trailer, DYN_H1_TRAILER); + ch->ignore_body = ignore_body; +} -void Curl_httpchunk_init(struct Curl_easy *data) +void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body) { - struct connectdata *conn = data->conn; - struct Curl_chunker *chunk = &conn->chunk; - chunk->hexindex = 0; /* start at 0 */ - chunk->state = CHUNK_HEX; /* we get hex first! */ - Curl_dyn_init(&conn->trailer, DYN_H1_TRAILER); + (void)data; + ch->hexindex = 0; /* start at 0 */ + ch->state = CHUNK_HEX; /* we get hex first! */ + ch->last_code = CHUNKE_OK; + Curl_dyn_reset(&ch->trailer); + ch->ignore_body = ignore_body; } -/* - * chunk_read() returns a OK for normal operations, or a positive return code - * for errors. STOP means this sequence of chunks is complete. The 'wrote' - * argument is set to tell the caller how many bytes we actually passed to the - * client (for byte-counting and whatever). - * - * The states and the state-machine is further explained in the header file. - * - * This function always uses ASCII hex values to accommodate non-ASCII hosts. - * For example, 0x0d and 0x0a are used instead of '\r' and '\n'. - */ -CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, - char *datap, - ssize_t datalen, - ssize_t *wrote, - CURLcode *extrap) +void Curl_httpchunk_free(struct Curl_easy *data, struct Curl_chunker *ch) +{ + (void)data; + Curl_dyn_free(&ch->trailer); +} + +bool Curl_httpchunk_is_done(struct Curl_easy *data, struct Curl_chunker *ch) +{ + (void)data; + return ch->state == CHUNK_DONE; +} + +static CURLcode httpchunk_readwrite(struct Curl_easy *data, + struct Curl_chunker *ch, + struct Curl_cwriter *cw_next, + const char *buf, size_t blen, + size_t *pconsumed) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct Curl_chunker *ch = &conn->chunk; - struct SingleRequest *k = &data->req; size_t piece; - curl_off_t length = (curl_off_t)datalen; - *wrote = 0; /* nothing's written yet */ + *pconsumed = 0; /* nothing's written yet */ + /* first check terminal states that will not progress anywhere */ + if(ch->state == CHUNK_DONE) + return CURLE_OK; + if(ch->state == CHUNK_FAILED) + return CURLE_RECV_ERROR; /* the original data is written to the client, but we go on with the chunk read process, to properly calculate the content length */ - if(data->set.http_te_skip && !k->ignorebody) { - result = Curl_client_write(data, CLIENTWRITE_BODY, datap, datalen); + if(data->set.http_te_skip && !ch->ignore_body) { + if(cw_next) + result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY, buf, blen); + else + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)buf, blen); if(result) { - *extrap = result; - return CHUNKE_PASSTHRU_ERROR; + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_PASSTHRU_ERROR; + return result; } } - while(length) { + while(blen) { switch(ch->state) { case CHUNK_HEX: - if(ISXDIGIT(*datap)) { - if(ch->hexindex < CHUNK_MAXNUM_LEN) { - ch->hexbuffer[ch->hexindex] = *datap; - datap++; - length--; - ch->hexindex++; - } - else { - return CHUNKE_TOO_LONG_HEX; /* longer hex than we support */ + if(ISXDIGIT(*buf)) { + if(ch->hexindex >= CHUNK_MAXNUM_LEN) { + failf(data, "chunk hex-length longer than %d", CHUNK_MAXNUM_LEN); + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_TOO_LONG_HEX; /* longer than we support */ + return CURLE_RECV_ERROR; } + ch->hexbuffer[ch->hexindex++] = *buf; + buf++; + blen--; + (*pconsumed)++; } else { - char *endptr; - if(0 == ch->hexindex) + if(0 == ch->hexindex) { /* This is illegal data, we received junk where we expected a hexadecimal digit. */ - return CHUNKE_ILLEGAL_HEX; + failf(data, "chunk hex-length char not a hex digit: 0x%x", *buf); + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_ILLEGAL_HEX; + return CURLE_RECV_ERROR; + } - /* length and datap are unmodified */ + /* blen and buf are unmodified */ ch->hexbuffer[ch->hexindex] = 0; - - if(curlx_strtoofft(ch->hexbuffer, &endptr, 16, &ch->datasize)) - return CHUNKE_ILLEGAL_HEX; + if(curlx_strtoofft(ch->hexbuffer, NULL, 16, &ch->datasize)) { + failf(data, "chunk hex-length not valid: '%s'", ch->hexbuffer); + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_ILLEGAL_HEX; + return CURLE_RECV_ERROR; + } ch->state = CHUNK_LF; /* now wait for the CRLF */ } break; case CHUNK_LF: /* waiting for the LF after a chunk size */ - if(*datap == 0x0a) { - /* we're now expecting data to come, unless size was zero! */ + if(*buf == 0x0a) { + /* we are now expecting data to come, unless size was zero! */ if(0 == ch->datasize) { ch->state = CHUNK_TRAILER; /* now check for trailers */ } - else + else { ch->state = CHUNK_DATA; + CURL_TRC_WRITE(data, "http_chunked, chunk start of %" + FMT_OFF_T " bytes", ch->datasize); + } } - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; break; case CHUNK_DATA: - /* We expect 'datasize' of data. We have 'length' right now, it can be + /* We expect 'datasize' of data. We have 'blen' right now, it can be more or less than 'datasize'. Get the smallest piece. */ - piece = curlx_sotouz((ch->datasize >= length)?length:ch->datasize); + piece = blen; + if(ch->datasize < (curl_off_t)blen) + piece = curlx_sotouz(ch->datasize); /* Write the data portion available */ - if(!data->set.http_te_skip && !k->ignorebody) { - if(!data->set.http_ce_skip && k->writer_stack) - result = Curl_unencode_write(data, k->writer_stack, datap, piece); + if(!data->set.http_te_skip && !ch->ignore_body) { + if(cw_next) + result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY, + buf, piece); else - result = Curl_client_write(data, CLIENTWRITE_BODY, datap, piece); - + result = Curl_client_write(data, CLIENTWRITE_BODY, + (char *)buf, piece); if(result) { - *extrap = result; - return CHUNKE_PASSTHRU_ERROR; + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_PASSTHRU_ERROR; + return result; } } - *wrote += piece; + *pconsumed += piece; ch->datasize -= piece; /* decrease amount left to expect */ - datap += piece; /* move read pointer forward */ - length -= piece; /* decrease space left in this round */ + buf += piece; /* move read pointer forward */ + blen -= piece; /* decrease space left in this round */ + CURL_TRC_WRITE(data, "http_chunked, write %zu body bytes, %" + FMT_OFF_T " bytes in chunk remain", + piece, ch->datasize); if(0 == ch->datasize) /* end of data this round, we now expect a trailing CRLF */ @@ -197,105 +235,140 @@ CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, break; case CHUNK_POSTLF: - if(*datap == 0x0a) { + if(*buf == 0x0a) { /* The last one before we go back to hex state and start all over. */ - Curl_httpchunk_init(data); /* sets state back to CHUNK_HEX */ + Curl_httpchunk_reset(data, ch, ch->ignore_body); + } + else if(*buf != 0x0d) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_BAD_CHUNK; + return CURLE_RECV_ERROR; } - else if(*datap != 0x0d) - return CHUNKE_BAD_CHUNK; - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; break; case CHUNK_TRAILER: - if((*datap == 0x0d) || (*datap == 0x0a)) { - char *tr = Curl_dyn_ptr(&conn->trailer); + if((*buf == 0x0d) || (*buf == 0x0a)) { + char *tr = Curl_dyn_ptr(&ch->trailer); /* this is the end of a trailer, but if the trailer was zero bytes there was no trailer and we move on */ if(tr) { size_t trlen; - result = Curl_dyn_addn(&conn->trailer, (char *)STRCONST("\x0d\x0a")); - if(result) - return CHUNKE_OUT_OF_MEMORY; - - tr = Curl_dyn_ptr(&conn->trailer); - trlen = Curl_dyn_len(&conn->trailer); + result = Curl_dyn_addn(&ch->trailer, (char *)STRCONST("\x0d\x0a")); + if(result) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_OUT_OF_MEMORY; + return result; + } + tr = Curl_dyn_ptr(&ch->trailer); + trlen = Curl_dyn_len(&ch->trailer); if(!data->set.http_te_skip) { - result = Curl_client_write(data, - CLIENTWRITE_HEADER|CLIENTWRITE_TRAILER, - tr, trlen); + if(cw_next) + result = Curl_cwriter_write(data, cw_next, + CLIENTWRITE_HEADER| + CLIENTWRITE_TRAILER, + tr, trlen); + else + result = Curl_client_write(data, + CLIENTWRITE_HEADER| + CLIENTWRITE_TRAILER, + tr, trlen); if(result) { - *extrap = result; - return CHUNKE_PASSTHRU_ERROR; + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_PASSTHRU_ERROR; + return result; } } - Curl_dyn_reset(&conn->trailer); + Curl_dyn_reset(&ch->trailer); ch->state = CHUNK_TRAILER_CR; - if(*datap == 0x0a) + if(*buf == 0x0a) /* already on the LF */ break; } else { - /* no trailer, we're on the final CRLF pair */ + /* no trailer, we are on the final CRLF pair */ ch->state = CHUNK_TRAILER_POSTCR; - break; /* don't advance the pointer */ + break; /* do not advance the pointer */ } } else { - result = Curl_dyn_addn(&conn->trailer, datap, 1); - if(result) - return CHUNKE_OUT_OF_MEMORY; + result = Curl_dyn_addn(&ch->trailer, buf, 1); + if(result) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_OUT_OF_MEMORY; + return result; + } } - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; break; case CHUNK_TRAILER_CR: - if(*datap == 0x0a) { + if(*buf == 0x0a) { ch->state = CHUNK_TRAILER_POSTCR; - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; + } + else { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_BAD_CHUNK; + return CURLE_RECV_ERROR; } - else - return CHUNKE_BAD_CHUNK; break; case CHUNK_TRAILER_POSTCR: /* We enter this state when a CR should arrive so we expect to have to first pass a CR before we wait for LF */ - if((*datap != 0x0d) && (*datap != 0x0a)) { + if((*buf != 0x0d) && (*buf != 0x0a)) { /* not a CR then it must be another header in the trailer */ ch->state = CHUNK_TRAILER; break; } - if(*datap == 0x0d) { + if(*buf == 0x0d) { /* skip if CR */ - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; } /* now wait for the final LF */ ch->state = CHUNK_STOP; break; case CHUNK_STOP: - if(*datap == 0x0a) { - length--; - + if(*buf == 0x0a) { + blen--; + (*pconsumed)++; /* Record the length of any data left in the end of the buffer - even if there's no more chunks to read */ - ch->datasize = curlx_sotouz(length); - - return CHUNKE_STOP; /* return stop */ + even if there is no more chunks to read */ + ch->datasize = blen; + ch->state = CHUNK_DONE; + CURL_TRC_WRITE(data, "http_chunk, response complete"); + return CURLE_OK; } - else - return CHUNKE_BAD_CHUNK; + else { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_BAD_CHUNK; + CURL_TRC_WRITE(data, "http_chunk error, expected 0x0a, seeing 0x%ux", + (unsigned int)*buf); + return CURLE_RECV_ERROR; + } + case CHUNK_DONE: + return CURLE_OK; + + case CHUNK_FAILED: + return CURLE_RECV_ERROR; } + } - return CHUNKE_OK; + return CURLE_OK; } -const char *Curl_chunked_strerror(CHUNKcode code) +static const char *Curl_chunked_strerror(CHUNKcode code) { switch(code) { default: @@ -307,8 +380,7 @@ const char *Curl_chunked_strerror(CHUNKcode code) case CHUNKE_BAD_CHUNK: return "Malformed encoding found"; case CHUNKE_PASSTHRU_ERROR: - DEBUGASSERT(0); /* never used */ - return ""; + return "Error writing data to client"; case CHUNKE_BAD_ENCODING: return "Bad content-encoding found"; case CHUNKE_OUT_OF_MEMORY: @@ -316,4 +388,295 @@ const char *Curl_chunked_strerror(CHUNKcode code) } } +CURLcode Curl_httpchunk_read(struct Curl_easy *data, + struct Curl_chunker *ch, + char *buf, size_t blen, + size_t *pconsumed) +{ + return httpchunk_readwrite(data, ch, NULL, buf, blen, pconsumed); +} + +struct chunked_writer { + struct Curl_cwriter super; + struct Curl_chunker ch; +}; + +static CURLcode cw_chunked_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct chunked_writer *ctx = writer->ctx; + + data->req.chunk = TRUE; /* chunks coming our way. */ + Curl_httpchunk_init(data, &ctx->ch, FALSE); + return CURLE_OK; +} + +static void cw_chunked_close(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct chunked_writer *ctx = writer->ctx; + Curl_httpchunk_free(data, &ctx->ch); +} + +static CURLcode cw_chunked_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) +{ + struct chunked_writer *ctx = writer->ctx; + CURLcode result; + size_t consumed; + + if(!(type & CLIENTWRITE_BODY)) + return Curl_cwriter_write(data, writer->next, type, buf, blen); + + consumed = 0; + result = httpchunk_readwrite(data, &ctx->ch, writer->next, buf, blen, + &consumed); + + if(result) { + if(CHUNKE_PASSTHRU_ERROR == ctx->ch.last_code) { + failf(data, "Failed reading the chunked-encoded stream"); + } + else { + failf(data, "%s in chunked-encoding", + Curl_chunked_strerror(ctx->ch.last_code)); + } + return result; + } + + blen -= consumed; + if(CHUNK_DONE == ctx->ch.state) { + /* chunks read successfully, download is complete */ + data->req.download_done = TRUE; + if(blen) { + infof(data, "Leftovers after chunking: %zu bytes", blen); + } + } + else if((type & CLIENTWRITE_EOS) && !data->req.no_body) { + failf(data, "transfer closed with outstanding read data remaining"); + return CURLE_PARTIAL_FILE; + } + + return CURLE_OK; +} + +/* HTTP chunked Transfer-Encoding decoder */ +const struct Curl_cwtype Curl_httpchunk_unencoder = { + "chunked", + NULL, + cw_chunked_init, + cw_chunked_write, + cw_chunked_close, + sizeof(struct chunked_writer) +}; + +/* max length of an HTTP chunk that we want to generate */ +#define CURL_CHUNKED_MINLEN (1024) +#define CURL_CHUNKED_MAXLEN (64 * 1024) + +struct chunked_reader { + struct Curl_creader super; + struct bufq chunkbuf; + BIT(read_eos); /* we read an EOS from the next reader */ + BIT(eos); /* we have returned an EOS */ +}; + +static CURLcode cr_chunked_init(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct chunked_reader *ctx = reader->ctx; + (void)data; + Curl_bufq_init2(&ctx->chunkbuf, CURL_CHUNKED_MAXLEN, 2, BUFQ_OPT_SOFT_LIMIT); + return CURLE_OK; +} + +static void cr_chunked_close(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct chunked_reader *ctx = reader->ctx; + (void)data; + Curl_bufq_free(&ctx->chunkbuf); +} + +static CURLcode add_last_chunk(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct chunked_reader *ctx = reader->ctx; + struct curl_slist *trailers = NULL, *tr; + CURLcode result; + size_t n; + int rc; + + if(!data->set.trailer_callback) { + CURL_TRC_READ(data, "http_chunk, added last, empty chunk"); + return Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("0\r\n\r\n"), &n); + } + + result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("0\r\n"), &n); + if(result) + goto out; + + Curl_set_in_callback(data, TRUE); + rc = data->set.trailer_callback(&trailers, data->set.trailer_data); + Curl_set_in_callback(data, FALSE); + + if(rc != CURL_TRAILERFUNC_OK) { + failf(data, "operation aborted by trailing headers callback"); + result = CURLE_ABORTED_BY_CALLBACK; + goto out; + } + + for(tr = trailers; tr; tr = tr->next) { + /* only add correctly formatted trailers */ + char *ptr = strchr(tr->data, ':'); + if(!ptr || *(ptr + 1) != ' ') { + infof(data, "Malformatted trailing header, skipping trailer"); + continue; + } + + result = Curl_bufq_cwrite(&ctx->chunkbuf, tr->data, + strlen(tr->data), &n); + if(!result) + result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("\r\n"), &n); + if(result) + goto out; + } + + result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("\r\n"), &n); + +out: + curl_slist_free_all(trailers); + CURL_TRC_READ(data, "http_chunk, added last chunk with trailers " + "from client -> %d", result); + return result; +} + +static CURLcode add_chunk(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen) +{ + struct chunked_reader *ctx = reader->ctx; + CURLcode result; + char tmp[CURL_CHUNKED_MINLEN]; + size_t nread; + bool eos; + + DEBUGASSERT(!ctx->read_eos); + blen = CURLMIN(blen, CURL_CHUNKED_MAXLEN); /* respect our buffer pref */ + if(blen < sizeof(tmp)) { + /* small read, make a chunk of decent size */ + buf = tmp; + blen = sizeof(tmp); + } + else { + /* larger read, make a chunk that will fit when read back */ + blen -= (8 + 2 + 2); /* deduct max overhead, 8 hex + 2*crlf */ + } + + result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos); + if(result) + return result; + if(eos) + ctx->read_eos = TRUE; + + if(nread) { + /* actually got bytes, wrap them into the chunkbuf */ + char hd[11] = ""; + int hdlen; + size_t n; + + hdlen = msnprintf(hd, sizeof(hd), "%zx\r\n", nread); + if(hdlen <= 0) + return CURLE_READ_ERROR; + /* On a soft-limited bufq, we do not need to check that all was written */ + result = Curl_bufq_cwrite(&ctx->chunkbuf, hd, hdlen, &n); + if(!result) + result = Curl_bufq_cwrite(&ctx->chunkbuf, buf, nread, &n); + if(!result) + result = Curl_bufq_cwrite(&ctx->chunkbuf, "\r\n", 2, &n); + CURL_TRC_READ(data, "http_chunk, made chunk of %zu bytes -> %d", + nread, result); + if(result) + return result; + } + + if(ctx->read_eos) + return add_last_chunk(data, reader); + return CURLE_OK; +} + +static CURLcode cr_chunked_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct chunked_reader *ctx = reader->ctx; + CURLcode result = CURLE_READ_ERROR; + + *pnread = 0; + *peos = ctx->eos; + + if(!ctx->eos) { + if(!ctx->read_eos && Curl_bufq_is_empty(&ctx->chunkbuf)) { + /* Still getting data form the next reader, buffer is empty */ + result = add_chunk(data, reader, buf, blen); + if(result) + return result; + } + + if(!Curl_bufq_is_empty(&ctx->chunkbuf)) { + result = Curl_bufq_cread(&ctx->chunkbuf, buf, blen, pnread); + if(!result && ctx->read_eos && Curl_bufq_is_empty(&ctx->chunkbuf)) { + /* no more data, read all, done. */ + ctx->eos = TRUE; + *peos = TRUE; + } + return result; + } + } + /* We may get here, because we are done or because callbacks paused */ + DEBUGASSERT(ctx->eos || !ctx->read_eos); + return CURLE_OK; +} + +static curl_off_t cr_chunked_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + /* this reader changes length depending on input */ + (void)data; + (void)reader; + return -1; +} + +/* HTTP chunked Transfer-Encoding encoder */ +const struct Curl_crtype Curl_httpchunk_encoder = { + "chunked", + cr_chunked_init, + cr_chunked_read, + cr_chunked_close, + Curl_creader_def_needs_rewind, + cr_chunked_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct chunked_reader) +}; + +CURLcode Curl_httpchunk_add_reader(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; + + result = Curl_creader_create(&reader, data, &Curl_httpchunk_encoder, + CURL_CR_TRANSFER_ENCODE); + if(!result) + result = Curl_creader_add(data, reader); + + if(result && reader) + Curl_creader_free(data, reader); + return result; +} + #endif /* CURL_DISABLE_HTTP */ diff --git a/deps/curl/lib/http_chunks.h b/deps/curl/lib/http_chunks.h index ed50713162c..34951ea0f41 100644 --- a/deps/curl/lib/http_chunks.h +++ b/deps/curl/lib/http_chunks.h @@ -24,17 +24,21 @@ * ***************************************************************************/ +#ifndef CURL_DISABLE_HTTP + +#include "dynbuf.h" + struct connectdata; /* * The longest possible hexadecimal number we support in a chunked transfer. * Neither RFC2616 nor the later HTTP specs define a maximum chunk size. - * For 64 bit curl_off_t we support 16 digits. For 32 bit, 8 digits. + * For 64-bit curl_off_t we support 16 digits. For 32-bit, 8 digits. */ #define CHUNK_MAXNUM_LEN (SIZEOF_CURL_OFF_T * 2) typedef enum { - /* await and buffer all hexadecimal digits until we get one that isn't a + /* await and buffer all hexadecimal digits until we get one that is not a hexadecimal digit. When done, we go CHUNK_LF */ CHUNK_HEX, @@ -50,9 +54,9 @@ typedef enum { big deal. */ CHUNK_POSTLF, - /* Used to mark that we're out of the game. NOTE: that there's a 'datasize' - field in the struct that will tell how many bytes that were not passed to - the client in the end of the last buffer! */ + /* Used to mark that we are out of the game. NOTE: that there is a + 'datasize' field in the struct that will tell how many bytes that were + not passed to the client in the end of the last buffer! */ CHUNK_STOP, /* At this point optional trailer headers can be found, unless the next line @@ -67,34 +71,75 @@ typedef enum { signalled If this is an empty trailer CHUNKE_STOP will be signalled. Otherwise the trailer will be broadcasted via Curl_client_write() and the next state will be CHUNK_TRAILER */ - CHUNK_TRAILER_POSTCR + CHUNK_TRAILER_POSTCR, + + /* Successfully de-chunked everything */ + CHUNK_DONE, + + /* Failed on seeing a bad or not correctly terminated chunk */ + CHUNK_FAILED } ChunkyState; typedef enum { - CHUNKE_STOP = -1, CHUNKE_OK = 0, CHUNKE_TOO_LONG_HEX = 1, CHUNKE_ILLEGAL_HEX, CHUNKE_BAD_CHUNK, CHUNKE_BAD_ENCODING, CHUNKE_OUT_OF_MEMORY, - CHUNKE_PASSTHRU_ERROR, /* Curl_httpchunk_read() returns a CURLcode to use */ - CHUNKE_LAST + CHUNKE_PASSTHRU_ERROR /* Curl_httpchunk_read() returns a CURLcode to use */ } CHUNKcode; -const char *Curl_chunked_strerror(CHUNKcode code); - struct Curl_chunker { curl_off_t datasize; ChunkyState state; + CHUNKcode last_code; + struct dynbuf trailer; /* for chunked-encoded trailer */ unsigned char hexindex; - char hexbuffer[ CHUNK_MAXNUM_LEN + 1]; /* +1 for null-terminator */ + char hexbuffer[CHUNK_MAXNUM_LEN + 1]; /* +1 for null-terminator */ + BIT(ignore_body); /* never write response body data */ }; /* The following functions are defined in http_chunks.c */ -void Curl_httpchunk_init(struct Curl_easy *data); -CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, char *datap, - ssize_t length, ssize_t *wrote, - CURLcode *passthru); +void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body); +void Curl_httpchunk_free(struct Curl_easy *data, struct Curl_chunker *ch); +void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body); + +/* + * Read BODY bytes in HTTP/1.1 chunked encoding from `buf` and return + * the amount of bytes consumed. The actual response bytes and trailer + * headers are written out to the client. + * On success, this will consume all bytes up to the end of the response, + * e.g. the last chunk, has been processed. + * @param data the transfer involved + * @param ch the chunker instance keeping state across calls + * @param buf the response data + * @param blen amount of bytes in `buf` + * @param pconsumed on successful return, the number of bytes in `buf` + * consumed + * + * This function always uses ASCII hex values to accommodate non-ASCII hosts. + * For example, 0x0d and 0x0a are used instead of '\r' and '\n'. + */ +CURLcode Curl_httpchunk_read(struct Curl_easy *data, struct Curl_chunker *ch, + char *buf, size_t blen, size_t *pconsumed); + +/** + * @return TRUE iff chunked decoded has finished successfully. + */ +bool Curl_httpchunk_is_done(struct Curl_easy *data, struct Curl_chunker *ch); + +extern const struct Curl_cwtype Curl_httpchunk_unencoder; + +extern const struct Curl_crtype Curl_httpchunk_encoder; + +/** + * Add a transfer-encoding "chunked" reader to the transfers reader stack + */ +CURLcode Curl_httpchunk_add_reader(struct Curl_easy *data); + +#endif /* !CURL_DISABLE_HTTP */ #endif /* HEADER_CURL_HTTP_CHUNKS_H */ diff --git a/deps/curl/lib/http_digest.c b/deps/curl/lib/http_digest.c index 2db3125a8e6..a3ba17a51fa 100644 --- a/deps/curl/lib/http_digest.c +++ b/deps/curl/lib/http_digest.c @@ -121,9 +121,9 @@ CURLcode Curl_output_digest(struct Curl_easy *data, passwdp = ""; #if defined(USE_WINDOWS_SSPI) - have_chlg = digest->input_token ? TRUE : FALSE; + have_chlg = !!digest->input_token; #else - have_chlg = digest->nonce ? TRUE : FALSE; + have_chlg = !!digest->nonce; #endif if(!have_chlg) { diff --git a/deps/curl/lib/http_negotiate.c b/deps/curl/lib/http_negotiate.c index 153e3d4ab81..5dda475057f 100644 --- a/deps/curl/lib/http_negotiate.c +++ b/deps/curl/lib/http_negotiate.c @@ -30,6 +30,7 @@ #include "sendf.h" #include "http_negotiate.h" #include "vauth/vauth.h" +#include "vtls/vtls.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -95,7 +96,7 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, Curl_http_auth_cleanup_negotiate(conn); } else if(state != GSS_AUTHNONE) { - /* The server rejected our authentication and hasn't supplied any more + /* The server rejected our authentication and has not supplied any more negotiation mechanisms */ Curl_http_auth_cleanup_negotiate(conn); return CURLE_LOGIN_DENIED; @@ -106,11 +107,27 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, #if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS) neg_ctx->sslContext = conn->sslContext; #endif + /* Check if the connection is using SSL and get the channel binding data */ +#if defined(USE_SSL) && defined(HAVE_GSSAPI) + if(conn->handler->flags & PROTOPT_SSL) { + Curl_dyn_init(&neg_ctx->channel_binding_data, SSL_CB_MAX_SIZE); + result = Curl_ssl_get_channel_binding( + data, FIRSTSOCKET, &neg_ctx->channel_binding_data); + if(result) { + Curl_http_auth_cleanup_negotiate(conn); + return result; + } + } +#endif /* Initialize the security context and decode our challenge */ result = Curl_auth_decode_spnego_message(data, userp, passwdp, service, host, header, neg_ctx); +#if defined(USE_SSL) && defined(HAVE_GSSAPI) + Curl_dyn_free(&neg_ctx->channel_binding_data); +#endif + if(result) Curl_http_auth_cleanup_negotiate(conn); @@ -120,16 +137,29 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, CURLcode Curl_output_negotiate(struct Curl_easy *data, struct connectdata *conn, bool proxy) { - struct negotiatedata *neg_ctx = proxy ? &conn->proxyneg : - &conn->negotiate; - struct auth *authp = proxy ? &data->state.authproxy : &data->state.authhost; - curlnegotiate *state = proxy ? &conn->proxy_negotiate_state : - &conn->http_negotiate_state; + struct negotiatedata *neg_ctx; + struct auth *authp; + curlnegotiate *state; char *base64 = NULL; size_t len = 0; char *userp; CURLcode result; + if(proxy) { +#ifndef CURL_DISABLE_PROXY + neg_ctx = &conn->proxyneg; + authp = &data->state.authproxy; + state = &conn->proxy_negotiate_state; +#else + return CURLE_NOT_BUILT_IN; +#endif + } + else { + neg_ctx = &conn->negotiate; + authp = &data->state.authhost; + state = &conn->http_negotiate_state; + } + authp->done = FALSE; if(*state == GSS_AUTHRECV) { @@ -171,8 +201,10 @@ CURLcode Curl_output_negotiate(struct Curl_easy *data, base64); if(proxy) { +#ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuserpwd); data->state.aptr.proxyuserpwd = userp; +#endif } else { Curl_safefree(data->state.aptr.userpwd); @@ -203,7 +235,7 @@ CURLcode Curl_output_negotiate(struct Curl_easy *data, if(*state == GSS_AUTHDONE || *state == GSS_AUTHSUCC) { /* connection is already authenticated, - * don't send a header in future requests */ + * do not send a header in future requests */ authp->done = TRUE; } diff --git a/deps/curl/lib/http_ntlm.c b/deps/curl/lib/http_ntlm.c index b845ddf37fe..49230bc1bdf 100644 --- a/deps/curl/lib/http_ntlm.c +++ b/deps/curl/lib/http_ntlm.c @@ -40,7 +40,6 @@ #include "strcase.h" #include "http_ntlm.h" #include "curl_ntlm_core.h" -#include "curl_ntlm_wb.h" #include "curl_base64.h" #include "vauth/vauth.h" #include "url.h" @@ -124,7 +123,7 @@ CURLcode Curl_input_ntlm(struct Curl_easy *data, } /* - * This is for creating ntlm header output + * This is for creating NTLM header output */ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) { @@ -188,10 +187,10 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) passwdp = ""; #ifdef USE_WINDOWS_SSPI - if(!s_hSecDll) { + if(!Curl_hSecDll) { /* not thread safe and leaks - use curl_global_init() to avoid */ CURLcode err = Curl_sspi_global_init(); - if(!s_hSecDll) + if(!Curl_hSecDll) return err; } #ifdef SECPKG_ATTR_ENDPOINT_BINDINGS @@ -201,7 +200,7 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) Curl_bufref_init(&ntlmmsg); - /* connection is already authenticated, don't send a header in future + /* connection is already authenticated, do not send a header in future * requests so go directly to NTLMSTATE_LAST */ if(*state == NTLMSTATE_TYPE3) *state = NTLMSTATE_LAST; @@ -266,10 +265,6 @@ void Curl_http_auth_cleanup_ntlm(struct connectdata *conn) { Curl_auth_cleanup_ntlm(&conn->ntlm); Curl_auth_cleanup_ntlm(&conn->proxyntlm); - -#if defined(NTLM_WB_ENABLED) - Curl_http_auth_cleanup_ntlm_wb(conn); -#endif } #endif /* !CURL_DISABLE_HTTP && USE_NTLM */ diff --git a/deps/curl/lib/http_ntlm.h b/deps/curl/lib/http_ntlm.h index f37572baecb..c1cf05701f4 100644 --- a/deps/curl/lib/http_ntlm.h +++ b/deps/curl/lib/http_ntlm.h @@ -28,11 +28,11 @@ #if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) -/* this is for ntlm header input */ +/* this is for NTLM header input */ CURLcode Curl_input_ntlm(struct Curl_easy *data, bool proxy, const char *header); -/* this is for creating ntlm header output */ +/* this is for creating NTLM header output */ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy); void Curl_http_auth_cleanup_ntlm(struct connectdata *conn); diff --git a/deps/curl/lib/http_proxy.c b/deps/curl/lib/http_proxy.c index 60bbfbe580b..1ec08d0d093 100644 --- a/deps/curl/lib/http_proxy.c +++ b/deps/curl/lib/http_proxy.c @@ -52,6 +52,113 @@ #include "memdebug.h" +CURLcode Curl_http_proxy_get_destination(struct Curl_cfilter *cf, + const char **phostname, + int *pport, bool *pipv6_ip) +{ + DEBUGASSERT(cf); + DEBUGASSERT(cf->conn); + + if(cf->conn->bits.conn_to_host) + *phostname = cf->conn->conn_to_host.name; + else if(cf->sockindex == SECONDARYSOCKET) + *phostname = cf->conn->secondaryhostname; + else + *phostname = cf->conn->host.name; + + if(cf->sockindex == SECONDARYSOCKET) + *pport = cf->conn->secondary_port; + else if(cf->conn->bits.conn_to_port) + *pport = cf->conn->conn_to_port; + else + *pport = cf->conn->remote_port; + + if(*phostname != cf->conn->host.name) + *pipv6_ip = (strchr(*phostname, ':') != NULL); + else + *pipv6_ip = cf->conn->bits.ipv6_ip; + + return CURLE_OK; +} + +CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq, + struct Curl_cfilter *cf, + struct Curl_easy *data, + int http_version_major) +{ + const char *hostname = NULL; + char *authority = NULL; + int port; + bool ipv6_ip; + CURLcode result; + struct httpreq *req = NULL; + + result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip); + if(result) + goto out; + + authority = aprintf("%s%s%s:%d", ipv6_ip ? "[" : "", hostname, + ipv6_ip ?"]" : "", port); + if(!authority) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT")-1, + NULL, 0, authority, strlen(authority), + NULL, 0); + if(result) + goto out; + + /* Setup the proxy-authorization header, if any */ + result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET, + req->authority, TRUE); + if(result) + goto out; + + /* If user is not overriding Host: header, we add for HTTP/1.x */ + if(http_version_major == 1 && + !Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) { + result = Curl_dynhds_cadd(&req->headers, "Host", authority); + if(result) + goto out; + } + + if(data->state.aptr.proxyuserpwd) { + result = Curl_dynhds_h1_cadd_line(&req->headers, + data->state.aptr.proxyuserpwd); + if(result) + goto out; + } + + if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) && + data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) { + result = Curl_dynhds_cadd(&req->headers, "User-Agent", + data->set.str[STRING_USERAGENT]); + if(result) + goto out; + } + + if(http_version_major == 1 && + !Curl_checkProxyheaders(data, cf->conn, STRCONST("Proxy-Connection"))) { + result = Curl_dynhds_cadd(&req->headers, "Proxy-Connection", "Keep-Alive"); + if(result) + goto out; + } + + result = Curl_dynhds_add_custom(data, TRUE, &req->headers); + +out: + if(result && req) { + Curl_http_req_free(req); + req = NULL; + } + free(authority); + *preq = req; + return result; +} + + struct cf_proxy_ctx { /* the protocol specific sub-filter we install during connect */ struct Curl_cfilter *cf_protocol; @@ -78,7 +185,7 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, *done = FALSE; if(!ctx->cf_protocol) { struct Curl_cfilter *cf_protocol = NULL; - int alpn = Curl_conn_cf_is_ssl(cf->next)? + int alpn = Curl_conn_cf_is_ssl(cf->next) ? cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1; /* First time call after the subchain connected */ @@ -88,7 +195,7 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, case CURL_HTTP_VERSION_1_1: CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.1"); infof(data, "CONNECT tunnel: HTTP/1.%d negotiated", - (alpn == CURL_HTTP_VERSION_1_0)? 0 : 1); + (alpn == CURL_HTTP_VERSION_1_0) ? 0 : 1); result = Curl_cf_h1_proxy_insert_after(cf, data); if(result) goto out; @@ -105,7 +212,6 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, break; #endif default: - CURL_TRC_CF(data, cf, "installing subfilter for default HTTP/1.1"); infof(data, "CONNECT tunnel: unsupported ALPN(%d) negotiated", alpn); result = CURLE_COULDNT_CONNECT; goto out; @@ -187,13 +293,14 @@ static void http_proxy_cf_close(struct Curl_cfilter *cf, struct Curl_cftype Curl_cft_http_proxy = { "HTTP-PROXY", - CF_TYPE_IP_CONNECT, + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, 0, http_proxy_cf_destroy, http_proxy_cf_connect, http_proxy_cf_close, + Curl_cf_def_shutdown, Curl_cf_http_proxy_get_host, - Curl_cf_def_get_select_socks, + Curl_cf_def_adjust_pollset, Curl_cf_def_data_pending, Curl_cf_def_send, Curl_cf_def_recv, diff --git a/deps/curl/lib/http_proxy.h b/deps/curl/lib/http_proxy.h index a1a03720bd0..2b5f7ae7064 100644 --- a/deps/curl/lib/http_proxy.h +++ b/deps/curl/lib/http_proxy.h @@ -30,6 +30,15 @@ #include "urldata.h" +CURLcode Curl_http_proxy_get_destination(struct Curl_cfilter *cf, + const char **phostname, + int *pport, bool *pipv6_ip); + +CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq, + struct Curl_cfilter *cf, + struct Curl_easy *data, + int http_version_major); + /* Default proxy timeout in milliseconds */ #define PROXY_TIMEOUT (3600*1000) diff --git a/deps/curl/lib/idn.c b/deps/curl/lib/idn.c index 1f31a9546c3..ed20cdc16b7 100644 --- a/deps/curl/lib/idn.c +++ b/deps/curl/lib/idn.c @@ -36,7 +36,7 @@ #ifdef USE_LIBIDN2 #include -#if defined(WIN32) && defined(UNICODE) +#if defined(_WIN32) && defined(UNICODE) #define IDN2_LOOKUP(name, host, flags) \ idn2_lookup_u8((const uint8_t *)name, (uint8_t **)host, flags) #else @@ -50,10 +50,108 @@ #include "curl_memory.h" #include "memdebug.h" +/* for macOS and iOS targets */ +#if defined(USE_APPLE_IDN) +#include +#include +#include + +#define MAX_HOST_LENGTH 512 + +static CURLcode iconv_to_utf8(const char *in, size_t inlen, + char **out, size_t *outlen) +{ + iconv_t cd = iconv_open("UTF-8", nl_langinfo(CODESET)); + if(cd != (iconv_t)-1) { + size_t iconv_outlen = *outlen; + char *iconv_in = (char *)in; + size_t iconv_inlen = inlen; + size_t iconv_result = iconv(cd, &iconv_in, &iconv_inlen, + out, &iconv_outlen); + *outlen -= iconv_outlen; + iconv_close(cd); + if(iconv_result == (size_t)-1) { + if(errno == ENOMEM) + return CURLE_OUT_OF_MEMORY; + else + return CURLE_URL_MALFORMAT; + } + + return CURLE_OK; + } + else { + if(errno == ENOMEM) + return CURLE_OUT_OF_MEMORY; + else + return CURLE_FAILED_INIT; + } +} + +static CURLcode mac_idn_to_ascii(const char *in, char **out) +{ + size_t inlen = strlen(in); + if(inlen < MAX_HOST_LENGTH) { + char iconv_buffer[MAX_HOST_LENGTH] = {0}; + char *iconv_outptr = iconv_buffer; + size_t iconv_outlen = sizeof(iconv_buffer); + CURLcode iconv_result = iconv_to_utf8(in, inlen, + &iconv_outptr, &iconv_outlen); + if(!iconv_result) { + UErrorCode err = U_ZERO_ERROR; + UIDNA* idna = uidna_openUTS46( + UIDNA_CHECK_BIDI|UIDNA_NONTRANSITIONAL_TO_ASCII, &err); + if(!U_FAILURE(err)) { + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + char buffer[MAX_HOST_LENGTH] = {0}; + (void)uidna_nameToASCII_UTF8(idna, iconv_buffer, (int)iconv_outlen, + buffer, sizeof(buffer) - 1, &info, &err); + uidna_close(idna); + if(!U_FAILURE(err) && !info.errors) { + *out = strdup(buffer); + if(*out) + return CURLE_OK; + else + return CURLE_OUT_OF_MEMORY; + } + } + } + else + return iconv_result; + } + return CURLE_URL_MALFORMAT; +} + +static CURLcode mac_ascii_to_idn(const char *in, char **out) +{ + size_t inlen = strlen(in); + if(inlen < MAX_HOST_LENGTH) { + UErrorCode err = U_ZERO_ERROR; + UIDNA* idna = uidna_openUTS46( + UIDNA_CHECK_BIDI|UIDNA_NONTRANSITIONAL_TO_UNICODE, &err); + if(!U_FAILURE(err)) { + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + char buffer[MAX_HOST_LENGTH] = {0}; + (void)uidna_nameToUnicodeUTF8(idna, in, -1, buffer, + sizeof(buffer) - 1, &info, &err); + uidna_close(idna); + if(!U_FAILURE(err)) { + *out = strdup(buffer); + if(*out) + return CURLE_OK; + else + return CURLE_OUT_OF_MEMORY; + } + } + } + return CURLE_URL_MALFORMAT; +} +#endif + #ifdef USE_WIN32_IDN /* using Windows kernel32 and normaliz libraries. */ -#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x600 +#if (!defined(_WIN32_WINNT) || _WIN32_WINNT < 0x600) && \ + (!defined(WINVER) || WINVER < 0x600) WINBASEAPI int WINAPI IdnToAscii(DWORD dwFlags, const WCHAR *lpUnicodeCharStr, int cchUnicodeChar, @@ -91,6 +189,8 @@ static CURLcode win32_idn_to_ascii(const char *in, char **out) else return CURLE_URL_MALFORMAT; } + else + return CURLE_URL_MALFORMAT; return CURLE_OK; } @@ -148,7 +248,7 @@ bool Curl_is_ASCII_name(const char *hostname) * Curl_idn_decode() returns an allocated IDN decoded string if it was * possible. NULL on error. * - * CURLE_URL_MALFORMAT - the host name could not be converted + * CURLE_URL_MALFORMAT - the hostname could not be converted * CURLE_OUT_OF_MEMORY - memory problem * */ @@ -174,8 +274,13 @@ static CURLcode idn_decode(const char *input, char **output) if(rc != IDN2_OK) result = CURLE_URL_MALFORMAT; } + else + /* a too old libidn2 version */ + result = CURLE_NOT_BUILT_IN; #elif defined(USE_WIN32_IDN) result = win32_idn_to_ascii(input, &decoded); +#elif defined(USE_APPLE_IDN) + result = mac_idn_to_ascii(input, &decoded); #endif if(!result) *output = decoded; @@ -193,6 +298,10 @@ static CURLcode idn_encode(const char *puny, char **output) CURLcode result = win32_ascii_to_idn(puny, &enc); if(result) return result; +#elif defined(USE_APPLE_IDN) + CURLcode result = mac_ascii_to_idn(puny, &enc); + if(result) + return result; #endif *output = enc; return CURLE_OK; @@ -241,11 +350,7 @@ CURLcode Curl_idn_encode(const char *puny, char **output) */ void Curl_free_idnconverted_hostname(struct hostname *host) { - if(host->encalloc) { - /* must be freed with idn2_free() if allocated by libidn */ - Curl_idn_free(host->encalloc); - host->encalloc = NULL; - } + Curl_safefree(host->encalloc); } #endif /* USE_IDN */ @@ -255,27 +360,18 @@ void Curl_free_idnconverted_hostname(struct hostname *host) */ CURLcode Curl_idnconvert_hostname(struct hostname *host) { - /* set the name we use to display the host name */ + /* set the name we use to display the hostname */ host->dispname = host->name; #ifdef USE_IDN /* Check name for non-ASCII and convert hostname if we can */ if(!Curl_is_ASCII_name(host->name)) { char *decoded; - CURLcode result = idn_decode(host->name, &decoded); - if(!result) { - if(!*decoded) { - /* zero length is a bad host name */ - Curl_idn_free(decoded); - return CURLE_URL_MALFORMAT; - } - /* successful */ - host->encalloc = decoded; - /* change the name pointer to point to the encoded hostname */ - host->name = host->encalloc; - } - else + CURLcode result = Curl_idn_decode(host->name, &decoded); + if(result) return result; + /* successful */ + host->name = host->encalloc = decoded; } #endif return CURLE_OK; diff --git a/deps/curl/lib/idn.h b/deps/curl/lib/idn.h index 74bbcaf4980..2bdce8927f9 100644 --- a/deps/curl/lib/idn.h +++ b/deps/curl/lib/idn.h @@ -26,16 +26,11 @@ bool Curl_is_ASCII_name(const char *hostname); CURLcode Curl_idnconvert_hostname(struct hostname *host); -#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) #define USE_IDN void Curl_free_idnconverted_hostname(struct hostname *host); CURLcode Curl_idn_decode(const char *input, char **output); CURLcode Curl_idn_encode(const char *input, char **output); -#ifdef USE_LIBIDN2 -#define Curl_idn_free(x) idn2_free(x) -#else -#define Curl_idn_free(x) free(x) -#endif #else #define Curl_free_idnconverted_hostname(x) diff --git a/deps/curl/lib/if2ip.c b/deps/curl/lib/if2ip.c index 5249f6cc7eb..55afd553d6a 100644 --- a/deps/curl/lib/if2ip.c +++ b/deps/curl/lib/if2ip.c @@ -62,7 +62,7 @@ /* ------------------------------------------------------------------ */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* Return the scope of the given address. */ unsigned int Curl_ipv6_scope(const struct sockaddr *sa) { @@ -97,17 +97,17 @@ unsigned int Curl_ipv6_scope(const struct sockaddr *sa) #if defined(HAVE_GETIFADDRS) if2ip_result_t Curl_if2ip(int af, -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int remote_scope, unsigned int local_scope_id, #endif const char *interf, - char *buf, int buf_size) + char *buf, size_t buf_size) { struct ifaddrs *iface, *head; if2ip_result_t res = IF2IP_NOT_FOUND; -#if defined(ENABLE_IPV6) && \ +#if defined(USE_IPV6) && \ !defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) (void) local_scope_id; #endif @@ -121,7 +121,7 @@ if2ip_result_t Curl_if2ip(int af, const char *ip; char scope[12] = ""; char ipstr[64]; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(af == AF_INET6) { #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID unsigned int scopeid = 0; @@ -182,12 +182,12 @@ if2ip_result_t Curl_if2ip(int af, #elif defined(HAVE_IOCTL_SIOCGIFADDR) if2ip_result_t Curl_if2ip(int af, -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int remote_scope, unsigned int local_scope_id, #endif const char *interf, - char *buf, int buf_size) + char *buf, size_t buf_size) { struct ifreq req; struct in_addr in; @@ -196,7 +196,7 @@ if2ip_result_t Curl_if2ip(int af, size_t len; const char *r; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 (void)remote_scope; (void)local_scope_id; #endif @@ -216,7 +216,15 @@ if2ip_result_t Curl_if2ip(int af, memcpy(req.ifr_name, interf, len + 1); req.ifr_addr.sa_family = AF_INET; +#if defined(__GNUC__) && defined(_AIX) +/* Suppress warning inside system headers */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshift-sign-overflow" +#endif if(ioctl(dummy, SIOCGIFADDR, &req) < 0) { +#if defined(__GNUC__) && defined(_AIX) +#pragma GCC diagnostic pop +#endif sclose(dummy); /* With SIOCGIFADDR, we cannot tell the difference between an interface that does not exist and an interface that has no address of the @@ -237,15 +245,15 @@ if2ip_result_t Curl_if2ip(int af, #else if2ip_result_t Curl_if2ip(int af, -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int remote_scope, unsigned int local_scope_id, #endif const char *interf, - char *buf, int buf_size) + char *buf, size_t buf_size) { (void) af; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 (void) remote_scope; (void) local_scope_id; #endif diff --git a/deps/curl/lib/if2ip.h b/deps/curl/lib/if2ip.h index 1f973505c08..f4b2f4c15d9 100644 --- a/deps/curl/lib/if2ip.h +++ b/deps/curl/lib/if2ip.h @@ -32,7 +32,7 @@ #define IPV6_SCOPE_UNIQUELOCAL 3 /* Unique local */ #define IPV6_SCOPE_NODELOCAL 4 /* Loopback. */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int Curl_ipv6_scope(const struct sockaddr *sa); #else #define Curl_ipv6_scope(x) 0 @@ -45,12 +45,12 @@ typedef enum { } if2ip_result_t; if2ip_result_t Curl_if2ip(int af, -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int remote_scope, unsigned int local_scope_id, #endif const char *interf, - char *buf, int buf_size); + char *buf, size_t buf_size); #ifdef __INTERIX diff --git a/deps/curl/lib/imap.c b/deps/curl/lib/imap.c index de64c2a7f26..e424cdb056a 100644 --- a/deps/curl/lib/imap.c +++ b/deps/curl/lib/imap.c @@ -97,7 +97,8 @@ static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done); static CURLcode imap_setup_connection(struct Curl_easy *data, struct connectdata *conn); static char *imap_atom(const char *str, bool escape_only); -static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...); +static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...) + CURL_PRINTF(2, 3); static CURLcode imap_parse_url_options(struct connectdata *conn); static CURLcode imap_parse_url_path(struct Curl_easy *data); static CURLcode imap_parse_custom_request(struct Curl_easy *data); @@ -116,7 +117,7 @@ static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out); */ const struct Curl_handler Curl_handler_imap = { - "IMAP", /* scheme */ + "imap", /* scheme */ imap_setup_connection, /* setup_connection */ imap_do, /* do_it */ imap_done, /* done */ @@ -129,7 +130,8 @@ const struct Curl_handler Curl_handler_imap = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ imap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_IMAP, /* defport */ @@ -145,7 +147,7 @@ const struct Curl_handler Curl_handler_imap = { */ const struct Curl_handler Curl_handler_imaps = { - "IMAPS", /* scheme */ + "imaps", /* scheme */ imap_setup_connection, /* setup_connection */ imap_do, /* do_it */ imap_done, /* done */ @@ -158,7 +160,8 @@ const struct Curl_handler Curl_handler_imaps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ imap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_IMAPS, /* defport */ @@ -354,8 +357,8 @@ static bool imap_endofresp(struct Curl_easy *data, struct connectdata *conn, */ static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out) { - char *message = data->state.buffer; - size_t len = strlen(message); + char *message = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); + size_t len = data->conn->proto.imapc.pp.nfinal; if(len > 2) { /* Find the start of the message */ @@ -509,7 +512,7 @@ static CURLcode imap_perform_login(struct Curl_easy *data, char *passwd; /* Check we have a username and password to authenticate with and end the - connect phase if we don't */ + connect phase if we do not */ if(!data->state.aptr.user) { imap_state(data, IMAP_STOP); @@ -517,8 +520,8 @@ static CURLcode imap_perform_login(struct Curl_easy *data, } /* Make sure the username and password are in the correct atom format */ - user = imap_atom(conn->user, false); - passwd = imap_atom(conn->passwd, false); + user = imap_atom(conn->user, FALSE); + passwd = imap_atom(conn->passwd, FALSE); /* Send the LOGIN command */ result = imap_sendf(data, "LOGIN %s %s", user ? user : "", @@ -609,7 +612,7 @@ static CURLcode imap_perform_authentication(struct Curl_easy *data, saslprogress progress; /* Check if already authenticated OR if there is enough data to authenticate - with and end the connect phase if we don't */ + with and end the connect phase if we do not */ if(imapc->preauth || !Curl_sasl_can_authenticate(&imapc->sasl, data)) { imap_state(data, IMAP_STOP); @@ -652,7 +655,7 @@ static CURLcode imap_perform_list(struct Curl_easy *data) imap->custom_params ? imap->custom_params : ""); else { /* Make sure the mailbox is in the correct atom format if necessary */ - char *mailbox = imap->mailbox ? imap_atom(imap->mailbox, true) + char *mailbox = imap->mailbox ? imap_atom(imap->mailbox, TRUE) : strdup(""); if(!mailbox) return CURLE_OUT_OF_MEMORY; @@ -694,7 +697,7 @@ static CURLcode imap_perform_select(struct Curl_easy *data) } /* Make sure the mailbox is in the correct atom format */ - mailbox = imap_atom(imap->mailbox, false); + mailbox = imap_atom(imap->mailbox, FALSE); if(!mailbox) return CURLE_OUT_OF_MEMORY; @@ -769,10 +772,11 @@ static CURLcode imap_perform_append(struct Curl_easy *data) return CURLE_URL_MALFORMAT; } +#ifndef CURL_DISABLE_MIME /* Prepare the mime data if some. */ if(data->set.mimepost.kind != MIMEKIND_NONE) { /* Use the whole structure as data. */ - data->set.mimepost.flags &= ~MIME_BODY_ONLY; + data->set.mimepost.flags &= ~(unsigned int)MIME_BODY_ONLY; /* Add external headers and mime version. */ curl_mime_headers(&data->set.mimepost, data->set.headers, 0); @@ -784,18 +788,18 @@ static CURLcode imap_perform_append(struct Curl_easy *data) result = Curl_mime_add_header(&data->set.mimepost.curlheaders, "Mime-Version: 1.0"); - /* Make sure we will read the entire mime structure. */ if(!result) - result = Curl_mime_rewind(&data->set.mimepost); - + result = Curl_creader_set_mime(data, &data->set.mimepost); + if(result) + return result; + data->state.infilesize = Curl_creader_client_length(data); + } + else +#endif + { + result = Curl_creader_set_fread(data, data->state.infilesize); if(result) return result; - - data->state.infilesize = Curl_mime_size(&data->set.mimepost); - - /* Read from mime structure. */ - data->state.fread_func = (curl_read_callback) Curl_mime_read; - data->state.in = (void *) &data->set.mimepost; } /* Check we know the size of the upload */ @@ -805,13 +809,12 @@ static CURLcode imap_perform_append(struct Curl_easy *data) } /* Make sure the mailbox is in the correct atom format */ - mailbox = imap_atom(imap->mailbox, false); + mailbox = imap_atom(imap->mailbox, FALSE); if(!mailbox) return CURLE_OUT_OF_MEMORY; /* Send the APPEND command */ - result = imap_sendf(data, - "APPEND %s (\\Seen) {%" CURL_FORMAT_CURL_OFF_T "}", + result = imap_sendf(data, "APPEND %s (\\Seen) {%" FMT_OFF_T "}", mailbox, data->state.infilesize); free(mailbox); @@ -895,7 +898,7 @@ static CURLcode imap_state_capability_resp(struct Curl_easy *data, CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; struct imap_conn *imapc = &conn->proto.imapc; - const char *line = data->state.buffer; + const char *line = Curl_dyn_ptr(&imapc->pp.recvbuf); (void)instate; /* no use for this yet */ @@ -981,7 +984,7 @@ static CURLcode imap_state_starttls_resp(struct Curl_easy *data, (void)instate; /* no use for this yet */ /* Pipelining in response is forbidden. */ - if(data->conn->proto.imapc.pp.cache_size) + if(data->conn->proto.imapc.pp.overflow) return CURLE_WEIRD_SERVER_REPLY; if(imapcode != IMAP_RESP_OK) { @@ -1057,17 +1060,13 @@ static CURLcode imap_state_listsearch_resp(struct Curl_easy *data, imapstate instate) { CURLcode result = CURLE_OK; - char *line = data->state.buffer; - size_t len = strlen(line); + char *line = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); + size_t len = data->conn->proto.imapc.pp.nfinal; (void)instate; /* No use for this yet */ - if(imapcode == '*') { - /* Temporarily add the LF character back and send as body to the client */ - line[len] = '\n'; - result = Curl_client_write(data, CLIENTWRITE_BODY, line, len + 1); - line[len] = '\0'; - } + if(imapcode == '*') + result = Curl_client_write(data, CLIENTWRITE_BODY, line, len); else if(imapcode != IMAP_RESP_OK) result = CURLE_QUOTE_ERROR; else @@ -1085,7 +1084,7 @@ static CURLcode imap_state_select_resp(struct Curl_easy *data, int imapcode, struct connectdata *conn = data->conn; struct IMAP *imap = data->req.p.imap; struct imap_conn *imapc = &conn->proto.imapc; - const char *line = data->state.buffer; + const char *line = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); (void)instate; /* no use for this yet */ @@ -1144,7 +1143,8 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, CURLcode result = CURLE_OK; struct imap_conn *imapc = &conn->proto.imapc; struct pingpong *pp = &imapc->pp; - const char *ptr = data->state.buffer; + const char *ptr = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); + size_t len = data->conn->proto.imapc.pp.nfinal; bool parsed = FALSE; curl_off_t size = 0; @@ -1158,76 +1158,72 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, /* Something like this is received "* 1 FETCH (BODY[TEXT] {2021}\r" so parse the continuation data contained within the curly brackets */ - while(*ptr && (*ptr != '{')) - ptr++; - - if(*ptr == '{') { + ptr = memchr(ptr, '{', len); + if(ptr) { char *endptr; - if(!curlx_strtoofft(ptr + 1, &endptr, 10, &size)) { - if(endptr - ptr > 1 && endptr[0] == '}' && - endptr[1] == '\r' && endptr[2] == '\0') - parsed = TRUE; - } + if(!curlx_strtoofft(ptr + 1, &endptr, 10, &size) && + (endptr - ptr > 1 && *endptr == '}')) + parsed = TRUE; } if(parsed) { - infof(data, "Found %" CURL_FORMAT_CURL_OFF_T " bytes to download", - size); + infof(data, "Found %" FMT_OFF_T " bytes to download", size); Curl_pgrsSetDownloadSize(data, size); - if(pp->cache) { - /* At this point there is a bunch of data in the header "cache" that is - actually body content, send it as body and then skip it. Do note - that there may even be additional "headers" after the body. */ - size_t chunk = pp->cache_size; + if(pp->overflow) { + /* At this point there is a data in the receive buffer that is body + content, send it as body and then skip it. Do note that there may + even be additional "headers" after the body. */ + size_t chunk = pp->overflow; + + /* keep only the overflow */ + Curl_dyn_tail(&pp->recvbuf, chunk); + pp->nfinal = 0; /* done */ if(chunk > (size_t)size) /* The conversion from curl_off_t to size_t is always fine here */ chunk = (size_t)size; if(!chunk) { - /* no size, we're done with the data */ + /* no size, we are done with the data */ imap_state(data, IMAP_STOP); return CURLE_OK; } - result = Curl_client_write(data, CLIENTWRITE_BODY, pp->cache, chunk); + result = Curl_client_write(data, CLIENTWRITE_BODY, + Curl_dyn_ptr(&pp->recvbuf), chunk); if(result) return result; - data->req.bytecount += chunk; - - infof(data, "Written %zu bytes, %" CURL_FORMAT_CURL_OFF_TU + infof(data, "Written %zu bytes, %" FMT_OFF_TU " bytes are left for transfer", chunk, size - chunk); - /* Have we used the entire cache or just part of it?*/ - if(pp->cache_size > chunk) { - /* Only part of it so shrink the cache to fit the trailing data */ - memmove(pp->cache, pp->cache + chunk, pp->cache_size - chunk); - pp->cache_size -= chunk; + /* Have we used the entire overflow or just part of it?*/ + if(pp->overflow > chunk) { + /* remember the remaining trailing overflow data */ + pp->overflow -= chunk; + Curl_dyn_tail(&pp->recvbuf, pp->overflow); } else { + pp->overflow = 0; /* handled */ /* Free the cache */ - Curl_safefree(pp->cache); - - /* Reset the cache size */ - pp->cache_size = 0; + Curl_dyn_reset(&pp->recvbuf); } } if(data->req.bytecount == size) /* The entire data is already transferred! */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); else { /* IMAP download */ data->req.maxdownload = size; /* force a recv/send check of this connection, as the data might've been read off the socket already */ - data->conn->cselect_bits = CURL_CSELECT_IN; - Curl_setup_transfer(data, FIRSTSOCKET, size, FALSE, -1); + data->state.select_bits = CURL_CSELECT_IN; + Curl_xfer_setup1(data, CURL_XFER_RECV, size, FALSE); } } else { - /* We don't know how to parse this line */ + /* We do not know how to parse this line */ failf(data, "Failed to parse FETCH response."); result = CURLE_WEIRD_SERVER_REPLY; } @@ -1271,7 +1267,7 @@ static CURLcode imap_state_append_resp(struct Curl_easy *data, int imapcode, Curl_pgrsSetUploadSize(data, data->state.infilesize); /* IMAP upload */ - Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); /* End of DO phase */ imap_state(data, IMAP_STOP); @@ -1302,7 +1298,6 @@ static CURLcode imap_statemachine(struct Curl_easy *data, struct connectdata *conn) { CURLcode result = CURLE_OK; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; int imapcode; struct imap_conn *imapc = &conn->proto.imapc; struct pingpong *pp = &imapc->pp; @@ -1319,7 +1314,7 @@ static CURLcode imap_statemachine(struct Curl_easy *data, do { /* Read the response from the server */ - result = Curl_pp_readresp(data, sock, pp, &imapcode, &nread); + result = Curl_pp_readresp(data, FIRSTSOCKET, pp, &imapcode, &nread); if(result) return result; @@ -1378,7 +1373,6 @@ static CURLcode imap_statemachine(struct Curl_easy *data, break; case IMAP_LOGOUT: - /* fallthrough, just stop! */ default: /* internal error */ imap_state(data, IMAP_STOP); @@ -1405,7 +1399,7 @@ static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done) } result = Curl_pp_statemach(data, &imapc->pp, FALSE, FALSE); - *done = (imapc->state == IMAP_STOP) ? TRUE : FALSE; + *done = (imapc->state == IMAP_STOP); return result; } @@ -1430,7 +1424,7 @@ static CURLcode imap_init(struct Curl_easy *data) CURLcode result = CURLE_OK; struct IMAP *imap; - imap = data->req.p.imap = calloc(sizeof(struct IMAP), 1); + imap = data->req.p.imap = calloc(1, sizeof(struct IMAP)); if(!imap) result = CURLE_OUT_OF_MEMORY; @@ -1474,9 +1468,7 @@ static CURLcode imap_connect(struct Curl_easy *data, bool *done) Curl_sasl_init(&imapc->sasl, data, &saslimap); Curl_dyn_init(&imapc->dyn, DYN_IMAP_CMD); - /* Initialise the pingpong layer */ - Curl_pp_setup(pp); - Curl_pp_init(data, pp); + Curl_pp_init(pp); /* Parse the URL options */ result = imap_parse_url_options(conn); @@ -1521,10 +1513,10 @@ static CURLcode imap_done(struct Curl_easy *data, CURLcode status, } else if(!data->set.connect_only && !imap->custom && (imap->uid || imap->mindex || data->state.upload || - data->set.mimepost.kind != MIMEKIND_NONE)) { + IS_MIME_POST(data))) { /* Handle responses after FETCH or APPEND transfer has finished */ - if(!data->state.upload && data->set.mimepost.kind == MIMEKIND_NONE) + if(!data->state.upload && !IS_MIME_POST(data)) imap_state(data, IMAP_FETCH_FINAL); else { /* End the APPEND command first by sending an empty line */ @@ -1590,7 +1582,7 @@ static CURLcode imap_perform(struct Curl_easy *data, bool *connected, selected = TRUE; /* Start the first command in the DO phase */ - if(data->state.upload || data->set.mimepost.kind != MIMEKIND_NONE) + if(data->state.upload || IS_MIME_POST(data)) /* APPEND can be executed directly */ result = imap_perform_append(data); else if(imap->custom && (selected || !imap->mailbox)) @@ -1700,7 +1692,7 @@ static CURLcode imap_dophase_done(struct Curl_easy *data, bool connected) if(imap->transfer != PPTRANSFER_BODY) /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); return CURLE_OK; } @@ -1797,7 +1789,14 @@ static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...) if(!result) { va_list ap; va_start(ap, fmt); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif result = Curl_pp_vsendf(data, &imapc->pp, Curl_dyn_ptr(&imapc->dyn), ap); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif va_end(ap); } return result; @@ -1860,7 +1859,7 @@ static bool imap_is_bchar(char ch) /* Performing the alnum check with this macro is faster because of ASCII arithmetic */ if(ISALNUM(ch)) - return true; + return TRUE; switch(ch) { /* bchar */ @@ -1874,10 +1873,10 @@ static bool imap_is_bchar(char ch) case '+': case ',': /* bchar -> achar -> uchar -> pct-encoded */ case '%': /* HEXDIG chars are already included above */ - return true; + return TRUE; default: - return false; + return FALSE; } } @@ -1892,7 +1891,7 @@ static CURLcode imap_parse_url_options(struct connectdata *conn) CURLcode result = CURLE_OK; struct imap_conn *imapc = &conn->proto.imapc; const char *ptr = conn->options; - bool prefer_login = false; + bool prefer_login = FALSE; while(!result && ptr && *ptr) { const char *key = ptr; @@ -1908,16 +1907,16 @@ static CURLcode imap_parse_url_options(struct connectdata *conn) if(strncasecompare(key, "AUTH=+LOGIN", 11)) { /* User prefers plaintext LOGIN over any SASL, including SASL LOGIN */ - prefer_login = true; + prefer_login = TRUE; imapc->sasl.prefmech = SASL_AUTH_NONE; } else if(strncasecompare(key, "AUTH=", 5)) { - prefer_login = false; + prefer_login = FALSE; result = Curl_sasl_parse_url_auth_option(&imapc->sasl, value, ptr - value); } else { - prefer_login = false; + prefer_login = FALSE; result = CURLE_URL_MALFORMAT; } diff --git a/deps/curl/lib/inet_ntop.c b/deps/curl/lib/inet_ntop.c index 135f4866c65..a2812cf8e24 100644 --- a/deps/curl/lib/inet_ntop.c +++ b/deps/curl/lib/inet_ntop.c @@ -42,11 +42,11 @@ #define INT16SZ 2 /* - * If ENABLE_IPV6 is disabled, we still want to parse IPv6 addresses, so make + * If USE_IPV6 is disabled, we still want to parse IPv6 addresses, so make * sure we have _some_ value for AF_INET6 without polluting our fake value * everywhere. */ -#if !defined(ENABLE_IPV6) && !defined(AF_INET6) +#if !defined(USE_IPV6) && !defined(AF_INET6) #define AF_INET6 (AF_INET + 1) #endif @@ -58,7 +58,7 @@ * - uses no statics * - takes a unsigned char* not an in_addr as input */ -static char *inet_ntop4 (const unsigned char *src, char *dst, size_t size) +static char *inet_ntop4(const unsigned char *src, char *dst, size_t size) { char tmp[sizeof("255.255.255.255")]; size_t len; @@ -84,22 +84,22 @@ static char *inet_ntop4 (const unsigned char *src, char *dst, size_t size) /* * Convert IPv6 binary address into presentation (printable) format. */ -static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) +static char *inet_ntop6(const unsigned char *src, char *dst, size_t size) { /* * Note that int32_t and int16_t need only be "at least" large enough - * to contain a value of the specified size. On some systems, like + * to contain a value of the specified size. On some systems, like * Crays, there is no such thing as an integer variable with 16 bits. * Keep this in mind if you think this function should have been coded - * to use pointer overlays. All the world's not a VAX. + * to use pointer overlays. All the world's not a VAX. */ char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; char *tp; struct { - long base; - long len; + int base; + int len; } best, cur; - unsigned long words[IN6ADDRSZ / INT16SZ]; + unsigned int words[IN6ADDRSZ / INT16SZ]; int i; /* Preprocess: @@ -108,7 +108,7 @@ static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) */ memset(words, '\0', sizeof(words)); for(i = 0; i < IN6ADDRSZ; i++) - words[i/2] |= (src[i] << ((1 - (i % 2)) << 3)); + words[i/2] |= ((unsigned int)src[i] << ((1 - (i % 2)) << 3)); best.base = -1; cur.base = -1; @@ -159,7 +159,7 @@ static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) tp += strlen(tp); break; } - tp += msnprintf(tp, 5, "%lx", words[i]); + tp += msnprintf(tp, 5, "%x", words[i]); } /* Was it a trailing run of 0x00's? @@ -168,7 +168,7 @@ static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) *tp++ = ':'; *tp++ = '\0'; - /* Check for overflow, copy, and we're done. + /* Check for overflow, copy, and we are done. */ if((size_t)(tp - tmp) > size) { errno = ENOSPC; @@ -185,10 +185,9 @@ static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) * Returns NULL on error and errno set with the specific * error, EAFNOSUPPORT or ENOSPC. * - * On Windows we store the error in the thread errno, not - * in the winsock error code. This is to avoid losing the - * actual last winsock error. So when this function returns - * NULL, check errno not SOCKERRNO. + * On Windows we store the error in the thread errno, not in the Winsock error + * code. This is to avoid losing the actual last Winsock error. When this + * function returns NULL, check errno not SOCKERRNO. */ char *Curl_inet_ntop(int af, const void *src, char *buf, size_t size) { diff --git a/deps/curl/lib/inet_ntop.h b/deps/curl/lib/inet_ntop.h index 7c3ead43418..f592f252517 100644 --- a/deps/curl/lib/inet_ntop.h +++ b/deps/curl/lib/inet_ntop.h @@ -32,8 +32,13 @@ char *Curl_inet_ntop(int af, const void *addr, char *buf, size_t size); #ifdef HAVE_ARPA_INET_H #include #endif +#ifdef _WIN32 #define Curl_inet_ntop(af,addr,buf,size) \ - inet_ntop(af, addr, buf, (curl_socklen_t)size) + inet_ntop(af, addr, buf, size) +#else +#define Curl_inet_ntop(af,addr,buf,size) \ + inet_ntop(af, addr, buf, (curl_socklen_t)(size)) +#endif #endif #endif /* HEADER_CURL_INET_NTOP_H */ diff --git a/deps/curl/lib/inet_pton.c b/deps/curl/lib/inet_pton.c index 7d3c6987957..97e6f80d79b 100644 --- a/deps/curl/lib/inet_pton.c +++ b/deps/curl/lib/inet_pton.c @@ -39,17 +39,17 @@ #define INT16SZ 2 /* - * If ENABLE_IPV6 is disabled, we still want to parse IPv6 addresses, so make + * If USE_IPV6 is disabled, we still want to parse IPv6 addresses, so make * sure we have _some_ value for AF_INET6 without polluting our fake value * everywhere. */ -#if !defined(ENABLE_IPV6) && !defined(AF_INET6) +#if !defined(USE_IPV6) && !defined(AF_INET6) #define AF_INET6 (AF_INET + 1) #endif /* - * WARNING: Don't even consider trying to compile this on a system where - * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + * WARNING: Do not even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. */ static int inet_pton4(const char *src, unsigned char *dst); @@ -61,12 +61,12 @@ static int inet_pton6(const char *src, unsigned char *dst); * to network format (which is usually some kind of binary format). * return: * 1 if the address was valid for the specified address family - * 0 if the address wasn't valid (`dst' is untouched in this case) + * 0 if the address was not valid (`dst' is untouched in this case) * -1 if some other error occurred (`dst' is untouched in this case, too) * notice: * On Windows we store the error in the thread errno, not - * in the winsock error code. This is to avoid losing the - * actual last winsock error. So when this function returns + * in the Winsock error code. This is to avoid losing the + * actual last Winsock error. When this function returns * -1, check errno not SOCKERRNO. * author: * Paul Vixie, 1996. @@ -92,7 +92,7 @@ Curl_inet_pton(int af, const char *src, void *dst) * return: * 1 if `src' is a valid dotted quad, else 0. * notice: - * does not touch `dst' unless it's returning 1. + * does not touch `dst' unless it is returning 1. * author: * Paul Vixie, 1996. */ @@ -112,7 +112,8 @@ inet_pton4(const char *src, unsigned char *dst) pch = strchr(digits, ch); if(pch) { - unsigned int val = *tp * 10 + (unsigned int)(pch - digits); + unsigned int val = (unsigned int)(*tp * 10) + + (unsigned int)(pch - digits); if(saw_digit && *tp == 0) return (0); @@ -146,7 +147,7 @@ inet_pton4(const char *src, unsigned char *dst) * return: * 1 if `src' is a valid [RFC1884 2.2] address, else 0. * notice: - * (1) does not touch `dst' unless it's returning 1. + * (1) does not touch `dst' unless it is returning 1. * (2) :: in a full address is silently ignored. * credit: * inspired by Mark Andrews. @@ -220,7 +221,7 @@ inet_pton6(const char *src, unsigned char *dst) if(colonp) { /* * Since some memmove()'s erroneously fail to handle - * overlapping regions, we'll do the shift by hand. + * overlapping regions, we will do the shift by hand. */ const ssize_t n = tp - colonp; ssize_t i; diff --git a/deps/curl/lib/inet_pton.h b/deps/curl/lib/inet_pton.h index 82fde7e2eb5..f8562fa8a7a 100644 --- a/deps/curl/lib/inet_pton.h +++ b/deps/curl/lib/inet_pton.h @@ -31,9 +31,6 @@ int Curl_inet_pton(int, const char *, void *); #ifdef HAVE_INET_PTON #ifdef HAVE_ARPA_INET_H #include -#elif defined(HAVE_WS2TCPIP_H) -/* inet_pton() exists in Vista or later */ -#include #endif #define Curl_inet_pton(x,y,z) inet_pton(x,y,z) #endif diff --git a/deps/curl/lib/krb5.c b/deps/curl/lib/krb5.c index 18e73debbfe..c953da6050a 100644 --- a/deps/curl/lib/krb5.c +++ b/deps/curl/lib/krb5.c @@ -25,7 +25,7 @@ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) @@ -52,6 +52,7 @@ #include "ftp.h" #include "curl_gssapi.h" #include "sendf.h" +#include "transfer.h" #include "curl_krb5.h" #include "warnless.h" #include "strcase.h" @@ -65,7 +66,7 @@ static CURLcode ftpsend(struct Curl_easy *data, struct connectdata *conn, const char *cmd) { - ssize_t bytes_written; + size_t bytes_written; #define SBUF_SIZE 1024 char s[SBUF_SIZE]; size_t write_len; @@ -75,8 +76,7 @@ static CURLcode ftpsend(struct Curl_easy *data, struct connectdata *conn, unsigned char data_sec = conn->data_prot; #endif - if(!cmd) - return CURLE_BAD_FUNCTION_ARGUMENT; + DEBUGASSERT(cmd); write_len = strlen(cmd); if(!write_len || write_len > (sizeof(s) -3)) @@ -91,8 +91,7 @@ static CURLcode ftpsend(struct Curl_easy *data, struct connectdata *conn, #ifdef HAVE_GSSAPI conn->data_prot = PROT_CMD; #endif - result = Curl_nwrite(data, FIRSTSOCKET, sptr, write_len, - &bytes_written); + result = Curl_xfer_send(data, sptr, write_len, FALSE, &bytes_written); #ifdef HAVE_GSSAPI DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST); conn->data_prot = data_sec; @@ -101,9 +100,9 @@ static CURLcode ftpsend(struct Curl_easy *data, struct connectdata *conn, if(result) break; - Curl_debug(data, CURLINFO_HEADER_OUT, sptr, (size_t)bytes_written); + Curl_debug(data, CURLINFO_HEADER_OUT, sptr, bytes_written); - if(bytes_written != (ssize_t)write_len) { + if(bytes_written != write_len) { write_len -= bytes_written; sptr += bytes_written; } @@ -170,7 +169,7 @@ krb5_encode(void *app_data, const void *from, int length, int level, void **to) * libraries modify the input buffer in gss_wrap() */ dec.value = (void *)from; - dec.length = length; + dec.length = (size_t)length; maj = gss_wrap(&min, *context, level == PROT_PRIVATE, GSS_C_QOP_DEFAULT, @@ -179,7 +178,7 @@ krb5_encode(void *app_data, const void *from, int length, int level, void **to) if(maj != GSS_S_COMPLETE) return -1; - /* malloc a new buffer, in case gss_release_buffer doesn't work as + /* malloc a new buffer, in case gss_release_buffer does not work as expected */ *to = malloc(enc.length); if(!*to) @@ -210,7 +209,7 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) struct gss_channel_bindings_struct chan; size_t base64_sz = 0; struct sockaddr_in *remote_addr = - (struct sockaddr_in *)(void *)&conn->remote_addr->sa_addr; + (struct sockaddr_in *)(void *)&conn->remote_addr->curl_sa_addr; char *stringp; if(getsockname(conn->sock[FIRSTSOCKET], @@ -228,7 +227,7 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) /* this loop will execute twice (once for service, once for host) */ for(;;) { - /* this really shouldn't be repeated here, but can't help it */ + /* this really should not be repeated here, but cannot help it */ if(service == srv_host) { result = ftpsend(data, conn, "AUTH GSSAPI"); if(result) @@ -236,9 +235,12 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) if(Curl_GetFTPResponse(data, &nread, NULL)) return -1; - - if(data->state.buffer[0] != '3') - return -1; + else { + struct pingpong *pp = &conn->proto.ftpc.pp; + char *line = Curl_dyn_ptr(&pp->recvbuf); + if(line[0] != '3') + return -1; + } } stringp = aprintf("%s@%s", service, host); @@ -322,25 +324,32 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) ret = -1; break; } - - if(data->state.buffer[0] != '2' && data->state.buffer[0] != '3') { - infof(data, "Server didn't accept auth data"); - ret = AUTH_ERROR; - break; + else { + struct pingpong *pp = &conn->proto.ftpc.pp; + size_t len = Curl_dyn_len(&pp->recvbuf); + p = Curl_dyn_ptr(&pp->recvbuf); + if((len < 4) || (p[0] != '2' && p[0] != '3')) { + infof(data, "Server did not accept auth data"); + ret = AUTH_ERROR; + break; + } } _gssresp.value = NULL; /* make sure it is initialized */ - p = data->state.buffer + 4; + _gssresp.length = 0; + p += 4; /* over '789 ' */ p = strstr(p, "ADAT="); if(p) { - result = Curl_base64_decode(p + 5, - (unsigned char **)&_gssresp.value, - &_gssresp.length); + unsigned char *outptr; + size_t outlen; + result = Curl_base64_decode(p + 5, &outptr, &outlen); if(result) { failf(data, "base64-decoding: %s", curl_easy_strerror(result)); ret = AUTH_CONTINUE; break; } + _gssresp.value = outptr; + _gssresp.length = outlen; } gssresp = &_gssresp; @@ -417,7 +426,6 @@ static char level_to_char(int level) case PROT_PRIVATE: return 'P'; case PROT_CMD: - /* Fall through */ default: /* Those 2 cases should not be reached! */ break; @@ -429,6 +437,9 @@ static char level_to_char(int level) /* Send an FTP command defined by |message| and the optional arguments. The function returns the ftp_code. If an error occurs, -1 is returned. */ +static int ftp_send_command(struct Curl_easy *data, const char *message, ...) + CURL_PRINTF(2, 3); + static int ftp_send_command(struct Curl_easy *data, const char *message, ...) { int ftp_code; @@ -462,7 +473,7 @@ socket_read(struct Curl_easy *data, int sockindex, void *to, size_t len) ssize_t nread = 0; while(len > 0) { - nread = Curl_conn_recv(data, sockindex, to_p, len, &result); + result = Curl_conn_recv(data, sockindex, to_p, len, &nread); if(nread > 0) { len -= nread; to_p += nread; @@ -486,11 +497,11 @@ socket_write(struct Curl_easy *data, int sockindex, const void *to, { const char *to_p = to; CURLcode result; - ssize_t written; + size_t written; while(len > 0) { - written = Curl_conn_send(data, sockindex, to_p, len, &result); - if(written > 0) { + result = Curl_conn_send(data, sockindex, to_p, len, FALSE, &written); + if(!result && written > 0) { len -= written; to_p += written; } @@ -516,24 +527,33 @@ static CURLcode read_data(struct Curl_easy *data, int sockindex, return result; if(len) { - /* only realloc if there was a length */ - len = ntohl(len); + len = (int)ntohl((uint32_t)len); if(len > CURL_MAX_INPUT_LENGTH) - len = 0; - else - buf->data = Curl_saferealloc(buf->data, len); + return CURLE_TOO_LARGE; + + Curl_dyn_reset(&buf->buf); } - if(!len || !buf->data) - return CURLE_OUT_OF_MEMORY; + else + return CURLE_RECV_ERROR; - result = socket_read(data, sockindex, buf->data, len); - if(result) - return result; - nread = conn->mech->decode(conn->app_data, buf->data, len, - conn->data_prot, conn); + do { + char buffer[1024]; + nread = CURLMIN(len, (int)sizeof(buffer)); + result = socket_read(data, sockindex, buffer, (size_t)nread); + if(result) + return result; + result = Curl_dyn_addn(&buf->buf, buffer, nread); + if(result) + return result; + len -= nread; + } while(len); + /* this decodes the dynbuf *in place* */ + nread = conn->mech->decode(conn->app_data, + Curl_dyn_ptr(&buf->buf), + len, conn->data_prot, conn); if(nread < 0) return CURLE_RECV_ERROR; - buf->size = (size_t)nread; + Curl_dyn_setlen(&buf->buf, nread); buf->index = 0; return CURLE_OK; } @@ -541,9 +561,10 @@ static CURLcode read_data(struct Curl_easy *data, int sockindex, static size_t buffer_read(struct krb5buffer *buf, void *data, size_t len) { - if(buf->size - buf->index < len) - len = buf->size - buf->index; - memcpy(data, (char *)buf->data + buf->index, len); + size_t size = Curl_dyn_len(&buf->buf); + if(size - buf->index < len) + len = size - buf->index; + memcpy(data, Curl_dyn_ptr(&buf->buf) + buf->index, len); buf->index += len; return len; } @@ -559,8 +580,11 @@ static ssize_t sec_recv(struct Curl_easy *data, int sockindex, *err = CURLE_OK; /* Handle clear text response. */ - if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR) - return Curl_conn_recv(data, sockindex, buffer, len, err); + if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR) { + ssize_t nread; + *err = Curl_conn_recv(data, sockindex, buffer, len, &nread); + return nread; + } if(conn->in_buffer.eof_flag) { conn->in_buffer.eof_flag = 0; @@ -575,7 +599,7 @@ static ssize_t sec_recv(struct Curl_easy *data, int sockindex, while(len > 0) { if(read_data(data, sockindex, &conn->in_buffer)) return -1; - if(conn->in_buffer.size == 0) { + if(Curl_dyn_len(&conn->in_buffer.buf) == 0) { if(bytes_read > 0) conn->in_buffer.eof_flag = 1; return bytes_read; @@ -599,7 +623,7 @@ static void do_sec_send(struct Curl_easy *data, struct connectdata *conn, size_t cmd_size = 0; CURLcode error; enum protection_level prot_level = conn->data_prot; - bool iscmd = (prot_level == PROT_CMD)?TRUE:FALSE; + bool iscmd = (prot_level == PROT_CMD); DEBUGASSERT(prot_level > PROT_NONE && prot_level < PROT_LAST); @@ -609,7 +633,7 @@ static void do_sec_send(struct Curl_easy *data, struct connectdata *conn, else prot_level = conn->command_prot; } - bytes = conn->mech->encode(conn->app_data, from, length, prot_level, + bytes = conn->mech->encode(conn->app_data, from, length, (int)prot_level, (void **)&buffer); if(!buffer || bytes <= 0) return; /* error */ @@ -631,13 +655,13 @@ static void do_sec_send(struct Curl_easy *data, struct connectdata *conn, socket_write(data, fd, cmd_buffer, cmd_size); socket_write(data, fd, "\r\n", 2); - infof(data, "Send: %s%s", prot_level == PROT_PRIVATE?enc:mic, + infof(data, "Send: %s%s", prot_level == PROT_PRIVATE ? enc : mic, cmd_buffer); free(cmd_buffer); } } else { - htonl_bytes = htonl(bytes); + htonl_bytes = (int)htonl((OM_uint32)bytes); socket_write(data, fd, &htonl_bytes, sizeof(htonl_bytes)); socket_write(data, fd, buffer, curlx_sitouz(bytes)); } @@ -665,10 +689,12 @@ static ssize_t sec_write(struct Curl_easy *data, struct connectdata *conn, /* Matches Curl_send signature */ static ssize_t sec_send(struct Curl_easy *data, int sockindex, - const void *buffer, size_t len, CURLcode *err) + const void *buffer, size_t len, bool eos, + CURLcode *err) { struct connectdata *conn = data->conn; curl_socket_t fd = conn->sock[sockindex]; + (void)eos; /* unused */ *err = CURLE_OK; return sec_write(data, conn, fd, buffer, len); } @@ -703,7 +729,7 @@ int Curl_sec_read_msg(struct Curl_easy *data, struct connectdata *conn, decoded_len = curlx_uztosi(decoded_sz); decoded_len = conn->mech->decode(conn->app_data, buf, decoded_len, - level, conn); + (int)level, conn); if(decoded_len <= 0) { free(buf); return -1; @@ -750,6 +776,8 @@ static int sec_set_protection_level(struct Curl_easy *data) if(level) { char *pbsz; unsigned int buffer_size = 1 << 20; /* 1048576 */ + struct pingpong *pp = &conn->proto.ftpc.pp; + char *line; code = ftp_send_command(data, "PBSZ %u", buffer_size); if(code < 0) @@ -761,11 +789,12 @@ static int sec_set_protection_level(struct Curl_easy *data) } conn->buffer_size = buffer_size; - pbsz = strstr(data->state.buffer, "PBSZ="); + line = Curl_dyn_ptr(&pp->recvbuf); + pbsz = strstr(line, "PBSZ="); if(pbsz) { /* stick to default value if the check fails */ - if(!strncmp(pbsz, "PBSZ=", 5) && ISDIGIT(pbsz[5])) - buffer_size = atoi(&pbsz[5]); + if(ISDIGIT(pbsz[5])) + buffer_size = (unsigned int)atoi(&pbsz[5]); if(buffer_size < conn->buffer_size) conn->buffer_size = buffer_size; } @@ -821,6 +850,7 @@ static CURLcode choose_mech(struct Curl_easy *data, struct connectdata *conn) mech->name); return CURLE_FAILED_INIT; } + Curl_dyn_init(&conn->in_buffer.buf, CURL_MAX_INPUT_LENGTH); } infof(data, "Trying mechanism %s...", mech->name); @@ -853,7 +883,7 @@ static CURLcode choose_mech(struct Curl_easy *data, struct connectdata *conn) if(ret != AUTH_CONTINUE) { if(ret != AUTH_OK) { - /* Mechanism has dumped the error to stderr, don't error here. */ + /* Mechanism has dumped the error to stderr, do not error here. */ return CURLE_USE_SSL_FAILED; } DEBUGASSERT(ret == AUTH_OK); @@ -885,15 +915,10 @@ Curl_sec_end(struct connectdata *conn) { if(conn->mech && conn->mech->end) conn->mech->end(conn->app_data); - free(conn->app_data); - conn->app_data = NULL; - if(conn->in_buffer.data) { - free(conn->in_buffer.data); - conn->in_buffer.data = NULL; - conn->in_buffer.size = 0; - conn->in_buffer.index = 0; - conn->in_buffer.eof_flag = 0; - } + Curl_safefree(conn->app_data); + Curl_dyn_free(&conn->in_buffer.buf); + conn->in_buffer.index = 0; + conn->in_buffer.eof_flag = 0; conn->sec_complete = 0; conn->data_prot = PROT_CLEAR; conn->mech = NULL; diff --git a/deps/curl/lib/ldap.c b/deps/curl/lib/ldap.c index 33a4dea0a8b..01429ba79e8 100644 --- a/deps/curl/lib/ldap.c +++ b/deps/curl/lib/ldap.c @@ -137,13 +137,13 @@ static void _ldap_free_urldesc(LDAPURLDesc *ludp); _ldap_trace x; \ } while(0) - static void _ldap_trace(const char *fmt, ...); + static void _ldap_trace(const char *fmt, ...) CURL_PRINTF(1, 2); #else #define LDAP_TRACE(x) Curl_nop_stmt #endif #if defined(USE_WIN32_LDAP) && defined(ldap_err2string) -/* Use ansi error strings in UNICODE builds */ +/* Use ANSI error strings in Unicode builds */ #undef ldap_err2string #define ldap_err2string ldap_err2stringA #endif @@ -164,7 +164,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done); */ const struct Curl_handler Curl_handler_ldap = { - "LDAP", /* scheme */ + "ldap", /* scheme */ ZERO_NULL, /* setup_connection */ ldap_do, /* do_it */ ZERO_NULL, /* done */ @@ -177,7 +177,8 @@ const struct Curl_handler Curl_handler_ldap = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAP, /* defport */ @@ -192,7 +193,7 @@ const struct Curl_handler Curl_handler_ldap = { */ const struct Curl_handler Curl_handler_ldaps = { - "LDAPS", /* scheme */ + "ldaps", /* scheme */ ZERO_NULL, /* setup_connection */ ldap_do, /* do_it */ ZERO_NULL, /* done */ @@ -205,7 +206,8 @@ const struct Curl_handler Curl_handler_ldaps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAPS, /* defport */ @@ -250,16 +252,17 @@ static int ldap_win_bind_auth(LDAP *server, const char *user, } if(method && user && passwd) { - rc = Curl_create_sspi_identity(user, passwd, &cred); + CURLcode res = Curl_create_sspi_identity(user, passwd, &cred); + rc = (int)res; if(!rc) { - rc = ldap_bind_s(server, NULL, (TCHAR *)&cred, method); + rc = (int)ldap_bind_s(server, NULL, (TCHAR *)&cred, method); Curl_sspi_free_identity(&cred); } } else { /* proceed with current user credentials */ method = LDAP_AUTH_NEGOTIATE; - rc = ldap_bind_s(server, NULL, NULL, method); + rc = (int)ldap_bind_s(server, NULL, NULL, method); } return rc; } @@ -277,14 +280,14 @@ static int ldap_win_bind(struct Curl_easy *data, LDAP *server, inuser = curlx_convert_UTF8_to_tchar((char *) user); inpass = curlx_convert_UTF8_to_tchar((char *) passwd); - rc = ldap_simple_bind_s(server, inuser, inpass); + rc = (int)ldap_simple_bind_s(server, inuser, inpass); curlx_unicodefree(inuser); curlx_unicodefree(inpass); } #if defined(USE_WINDOWS_SSPI) else { - rc = ldap_win_bind_auth(server, user, passwd, data->set.httpauth); + rc = (int)ldap_win_bind_auth(server, user, passwd, data->set.httpauth); } #endif @@ -294,8 +297,10 @@ static int ldap_win_bind(struct Curl_easy *data, LDAP *server, #if defined(USE_WIN32_LDAP) #define FREE_ON_WINLDAP(x) curlx_unicodefree(x) +#define curl_ldap_num_t ULONG #else #define FREE_ON_WINLDAP(x) +#define curl_ldap_num_t int #endif @@ -313,7 +318,6 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) int ldap_ssl = 0; char *val_b64 = NULL; size_t val_b64_sz = 0; - curl_off_t dlsize = 0; #ifdef LDAP_OPT_NETWORK_TIMEOUT struct timeval ldap_timeout = {10, 0}; /* 10 sec connection/search timeout */ #endif @@ -327,7 +331,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) *done = TRUE; /* unconditionally */ infof(data, "LDAP local: LDAP Vendor = %s ; LDAP Version = %d", - LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION); + LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION); infof(data, "LDAP local: %s", data->state.url); #ifdef HAVE_LDAP_URL_PARSE @@ -336,7 +340,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) rc = _ldap_url_parse(data, conn, &ludp); #endif if(rc) { - failf(data, "Bad LDAP URL: %s", ldap_err2string(rc)); + failf(data, "Bad LDAP URL: %s", ldap_err2string((curl_ldap_num_t)rc)); result = CURLE_URL_MALFORMAT; goto quit; } @@ -345,7 +349,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) if(conn->given->flags & PROTOPT_SSL) ldap_ssl = 1; infof(data, "LDAP local: trying to establish %s connection", - ldap_ssl ? "encrypted" : "cleartext"); + ldap_ssl ? "encrypted" : "cleartext"); #if defined(USE_WIN32_LDAP) host = curlx_convert_UTF8_to_tchar(conn->host.name); @@ -371,8 +375,8 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) if(ldap_ssl) { #ifdef HAVE_LDAP_SSL #ifdef USE_WIN32_LDAP - /* Win32 LDAP SDK doesn't support insecure mode without CA! */ - server = ldap_sslinit(host, conn->port, 1); + /* Win32 LDAP SDK does not support insecure mode without CA! */ + server = ldap_sslinit(host, (curl_ldap_num_t)conn->primary.remote_port, 1); ldap_set_option(server, LDAP_OPT_SSL, LDAP_OPT_ON); #else int ldap_option; @@ -418,10 +422,10 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) result = CURLE_SSL_CERTPROBLEM; goto quit; } - server = ldapssl_init(host, conn->port, 1); + server = ldapssl_init(host, conn->primary.remote_port, 1); if(!server) { failf(data, "LDAP local: Cannot connect to %s:%u", - conn->host.dispname, conn->port); + conn->host.dispname, conn->primary.remote_port); result = CURLE_COULDNT_CONNECT; goto quit; } @@ -459,10 +463,10 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) result = CURLE_SSL_CERTPROBLEM; goto quit; } - server = ldap_init(host, conn->port); + server = ldap_init(host, conn->primary.remote_port); if(!server) { failf(data, "LDAP local: Cannot connect to %s:%u", - conn->host.dispname, conn->port); + conn->host.dispname, conn->primary.remote_port); result = CURLE_COULDNT_CONNECT; goto quit; } @@ -484,6 +488,8 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) } */ #else + (void)ldap_option; + (void)ldap_ca; /* we should probably never come up to here since configure should check in first place if we can support LDAP SSL/TLS */ failf(data, "LDAP local: SSL/TLS not supported with this version " @@ -500,10 +506,10 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } else { - server = ldap_init(host, conn->port); + server = ldap_init(host, (curl_ldap_num_t)conn->primary.remote_port); if(!server) { failf(data, "LDAP local: Cannot connect to %s:%u", - conn->host.dispname, conn->port); + conn->host.dispname, conn->primary.remote_port); result = CURLE_COULDNT_CONNECT; goto quit; } @@ -526,7 +532,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) if(rc) { #ifdef USE_WIN32_LDAP failf(data, "LDAP local: bind via ldap_win_bind %s", - ldap_err2string(rc)); + ldap_err2string((ULONG)rc)); #else failf(data, "LDAP local: bind via ldap_simple_bind_s %s", ldap_err2string(rc)); @@ -535,16 +541,19 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - rc = ldap_search_s(server, ludp->lud_dn, ludp->lud_scope, - ludp->lud_filter, ludp->lud_attrs, 0, &ldapmsg); + Curl_pgrsSetDownloadCounter(data, 0); + rc = (int)ldap_search_s(server, ludp->lud_dn, + (curl_ldap_num_t)ludp->lud_scope, + ludp->lud_filter, ludp->lud_attrs, 0, &ldapmsg); if(rc && rc != LDAP_SIZELIMIT_EXCEEDED) { - failf(data, "LDAP remote: %s", ldap_err2string(rc)); + failf(data, "LDAP remote: %s", ldap_err2string((curl_ldap_num_t)rc)); result = CURLE_LDAP_SEARCH_FAILED; goto quit; } - for(num = 0, entryIterator = ldap_first_entry(server, ldapmsg); + num = 0; + for(entryIterator = ldap_first_entry(server, ldapmsg); entryIterator; entryIterator = ldap_next_entry(server, entryIterator), num++) { BerElement *ber = NULL; @@ -596,8 +605,6 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - dlsize += name_len + 5; - FREE_ON_WINLDAP(name); ldap_memfree(dn); } @@ -659,8 +666,6 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - dlsize += attr_len + 3; - if((attr_len > 7) && (strcmp(";binary", attr + (attr_len - 7)) == 0)) { /* Binary attribute, encode to base64. */ @@ -689,8 +694,6 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - - dlsize += val_b64_sz; } } else { @@ -705,8 +708,6 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - - dlsize += vals[i]->bv_len; } result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1); @@ -719,8 +720,6 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - - dlsize++; } /* Free memory used to store values */ @@ -734,8 +733,6 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1); if(result) goto quit; - dlsize++; - Curl_pgrsSetDownloadCounter(data, dlsize); } if(ber) @@ -761,7 +758,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) FREE_ON_WINLDAP(host); /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); connclose(conn, "LDAP connection always disable reuse"); return result; diff --git a/deps/curl/lib/libcurl.def b/deps/curl/lib/libcurl.def new file mode 100644 index 00000000000..9bf9fcc958f --- /dev/null +++ b/deps/curl/lib/libcurl.def @@ -0,0 +1,95 @@ +EXPORTS +curl_easy_cleanup +curl_easy_duphandle +curl_easy_escape +curl_easy_getinfo +curl_easy_header +curl_easy_init +curl_easy_nextheader +curl_easy_option_by_id +curl_easy_option_by_name +curl_easy_option_next +curl_easy_pause +curl_easy_perform +curl_easy_recv +curl_easy_reset +curl_easy_send +curl_easy_setopt +curl_easy_strerror +curl_easy_unescape +curl_easy_upkeep +curl_escape +curl_formadd +curl_formfree +curl_formget +curl_free +curl_getdate +curl_getenv +curl_global_cleanup +curl_global_init +curl_global_init_mem +curl_global_sslset +curl_global_trace +curl_maprintf +curl_mfprintf +curl_mime_addpart +curl_mime_data +curl_mime_data_cb +curl_mime_encoder +curl_mime_filedata +curl_mime_filename +curl_mime_free +curl_mime_headers +curl_mime_init +curl_mime_name +curl_mime_subparts +curl_mime_type +curl_mprintf +curl_msnprintf +curl_msprintf +curl_multi_add_handle +curl_multi_assign +curl_multi_cleanup +curl_multi_fdset +curl_multi_get_handles +curl_multi_info_read +curl_multi_init +curl_multi_perform +curl_multi_poll +curl_multi_remove_handle +curl_multi_setopt +curl_multi_socket +curl_multi_socket_action +curl_multi_socket_all +curl_multi_strerror +curl_multi_timeout +curl_multi_wait +curl_multi_waitfds +curl_multi_wakeup +curl_mvaprintf +curl_mvfprintf +curl_mvprintf +curl_mvsnprintf +curl_mvsprintf +curl_pushheader_byname +curl_pushheader_bynum +curl_share_cleanup +curl_share_init +curl_share_setopt +curl_share_strerror +curl_slist_append +curl_slist_free_all +curl_strequal +curl_strnequal +curl_unescape +curl_url +curl_url_cleanup +curl_url_dup +curl_url_get +curl_url_set +curl_url_strerror +curl_version +curl_version_info +curl_ws_meta +curl_ws_recv +curl_ws_send diff --git a/deps/curl/lib/libcurl.plist b/deps/curl/lib/libcurl.plist deleted file mode 100644 index 8522260dc13..00000000000 --- a/deps/curl/lib/libcurl.plist +++ /dev/null @@ -1,35 +0,0 @@ - - - - - CFBundleInfoDictionaryVersion - 6.0 - - CFBundleDevelopmentRegion - English - - CFBundleExecutable - curl - - CFBundleIdentifier - se.curl.libcurl - - CFBundleVersion - 8.3.0 - - CFBundleName - libcurl - - CFBundlePackageType - FMWK - - CFBundleSignature - ???? - - CFBundleShortVersionString - libcurl 8.3.0 - - CFBundleGetInfoString - libcurl.plist 8.3.0 - - diff --git a/deps/curl/lib/libcurl.plist.in b/deps/curl/lib/libcurl.plist.in deleted file mode 100644 index d2e6492f694..00000000000 --- a/deps/curl/lib/libcurl.plist.in +++ /dev/null @@ -1,35 +0,0 @@ - - - - - CFBundleInfoDictionaryVersion - 6.0 - - CFBundleDevelopmentRegion - English - - CFBundleExecutable - curl - - CFBundleIdentifier - se.curl.libcurl - - CFBundleVersion - @CURL_PLIST_VERSION@ - - CFBundleName - libcurl - - CFBundlePackageType - FMWK - - CFBundleSignature - ???? - - CFBundleShortVersionString - libcurl @CURL_PLIST_VERSION@ - - CFBundleGetInfoString - libcurl.plist @CURL_PLIST_VERSION@ - - diff --git a/deps/curl/lib/libcurl.rc b/deps/curl/lib/libcurl.rc index daa2d62d8a5..1ceb4691fb8 100644 --- a/deps/curl/lib/libcurl.rc +++ b/deps/curl/lib/libcurl.rc @@ -32,7 +32,7 @@ VS_VERSION_INFO VERSIONINFO FILEVERSION RC_VERSION PRODUCTVERSION RC_VERSION FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#if defined(DEBUGBUILD) || defined(_DEBUG) +#if defined(DEBUGBUILD) || defined(UNITTESTS) || defined(CURLDEBUG) || defined(_DEBUG) FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0L diff --git a/deps/curl/lib/libcurl.vers.in b/deps/curl/lib/libcurl.vers.in index ae978a485d3..82196699935 100644 --- a/deps/curl/lib/libcurl.vers.in +++ b/deps/curl/lib/libcurl.vers.in @@ -1,12 +1,4 @@ -HIDDEN -{ - local: - __*; - _rest*; - _save*; -}; - -CURL_@CURL_LT_SHLIB_VERSIONED_FLAVOUR@4 +CURL_@CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX@@CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME@ { global: curl_*; local: *; diff --git a/deps/curl/lib/llist.c b/deps/curl/lib/llist.c index 5b6b0336da2..e5c65fb90e4 100644 --- a/deps/curl/lib/llist.c +++ b/deps/curl/lib/llist.c @@ -32,16 +32,34 @@ /* this must be the last include file */ #include "memdebug.h" +#define LLISTINIT 0x100cc001 /* random pattern */ +#define NODEINIT 0x12344321 /* random pattern */ +#define NODEREM 0x54321012 /* random pattern */ + + +#ifdef DEBUGBUILD +#define VERIFYNODE(x) verifynode(x) +static struct Curl_llist_node *verifynode(struct Curl_llist_node *n) +{ + DEBUGASSERT(!n || (n->_init == NODEINIT)); + return n; +} +#else +#define VERIFYNODE(x) x +#endif /* * @unittest: 1300 */ void Curl_llist_init(struct Curl_llist *l, Curl_llist_dtor dtor) { - l->size = 0; - l->dtor = dtor; - l->head = NULL; - l->tail = NULL; + l->_size = 0; + l->_dtor = dtor; + l->_head = NULL; + l->_tail = NULL; +#ifdef DEBUGBUILD + l->_init = LLISTINIT; +#endif } /* @@ -56,91 +74,193 @@ Curl_llist_init(struct Curl_llist *l, Curl_llist_dtor dtor) * @unittest: 1300 */ void -Curl_llist_insert_next(struct Curl_llist *list, struct Curl_llist_element *e, +Curl_llist_insert_next(struct Curl_llist *list, + struct Curl_llist_node *e, /* may be NULL */ const void *p, - struct Curl_llist_element *ne) + struct Curl_llist_node *ne) { - ne->ptr = (void *) p; - if(list->size == 0) { - list->head = ne; - list->head->prev = NULL; - list->head->next = NULL; - list->tail = ne; + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + DEBUGASSERT(ne); + +#ifdef DEBUGBUILD + ne->_init = NODEINIT; +#endif + ne->_ptr = (void *) p; + ne->_list = list; + if(list->_size == 0) { + list->_head = ne; + list->_head->_prev = NULL; + list->_head->_next = NULL; + list->_tail = ne; } else { /* if 'e' is NULL here, we insert the new element first in the list */ - ne->next = e?e->next:list->head; - ne->prev = e; + ne->_next = e ? e->_next : list->_head; + ne->_prev = e; if(!e) { - list->head->prev = ne; - list->head = ne; + list->_head->_prev = ne; + list->_head = ne; } - else if(e->next) { - e->next->prev = ne; + else if(e->_next) { + e->_next->_prev = ne; } else { - list->tail = ne; + list->_tail = ne; } if(e) - e->next = ne; + e->_next = ne; } - ++list->size; + ++list->_size; +} + +/* + * Curl_llist_append() + * + * Adds a new list element to the end of the list. + * + * The 'ne' argument should be a pointer into the object to store. + * + * @unittest: 1300 + */ +void +Curl_llist_append(struct Curl_llist *list, const void *p, + struct Curl_llist_node *ne) +{ + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + DEBUGASSERT(ne); + Curl_llist_insert_next(list, list->_tail, p, ne); } /* * @unittest: 1300 */ void -Curl_llist_remove(struct Curl_llist *list, struct Curl_llist_element *e, - void *user) +Curl_node_uremove(struct Curl_llist_node *e, void *user) { void *ptr; - if(!e || list->size == 0) + struct Curl_llist *list; + if(!e) return; - if(e == list->head) { - list->head = e->next; + list = e->_list; + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + DEBUGASSERT(list->_size); + DEBUGASSERT(e->_init == NODEINIT); + if(e == list->_head) { + list->_head = e->_next; - if(!list->head) - list->tail = NULL; + if(!list->_head) + list->_tail = NULL; else - e->next->prev = NULL; + e->_next->_prev = NULL; } else { - if(e->prev) - e->prev->next = e->next; + if(e->_prev) + e->_prev->_next = e->_next; - if(!e->next) - list->tail = e->prev; + if(!e->_next) + list->_tail = e->_prev; else - e->next->prev = e->prev; + e->_next->_prev = e->_prev; } - ptr = e->ptr; + ptr = e->_ptr; - e->ptr = NULL; - e->prev = NULL; - e->next = NULL; + e->_list = NULL; + e->_ptr = NULL; + e->_prev = NULL; + e->_next = NULL; +#ifdef DEBUGBUILD + e->_init = NODEREM; /* specific pattern on remove - not zero */ +#endif - --list->size; + --list->_size; /* call the dtor() last for when it actually frees the 'e' memory itself */ - if(list->dtor) - list->dtor(user, ptr); + if(list->_dtor) + list->_dtor(user, ptr); +} + +void Curl_node_remove(struct Curl_llist_node *e) +{ + Curl_node_uremove(e, NULL); } void Curl_llist_destroy(struct Curl_llist *list, void *user) { if(list) { - while(list->size > 0) - Curl_llist_remove(list, list->tail, user); + DEBUGASSERT(list->_init == LLISTINIT); + while(list->_size > 0) + Curl_node_uremove(list->_tail, user); } } -size_t -Curl_llist_count(struct Curl_llist *list) +/* Curl_llist_head() returns the first 'struct Curl_llist_node *', which + might be NULL */ +struct Curl_llist_node *Curl_llist_head(struct Curl_llist *list) +{ + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + return VERIFYNODE(list->_head); +} + +#ifdef UNITTESTS +/* Curl_llist_tail() returns the last 'struct Curl_llist_node *', which + might be NULL */ +struct Curl_llist_node *Curl_llist_tail(struct Curl_llist *list) +{ + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + return VERIFYNODE(list->_tail); +} +#endif + +/* Curl_llist_count() returns a size_t the number of nodes in the list */ +size_t Curl_llist_count(struct Curl_llist *list) +{ + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + return list->_size; +} + +/* Curl_node_elem() returns the custom data from a Curl_llist_node */ +void *Curl_node_elem(struct Curl_llist_node *n) +{ + DEBUGASSERT(n); + DEBUGASSERT(n->_init == NODEINIT); + return n->_ptr; +} + +/* Curl_node_next() returns the next element in a list from a given + Curl_llist_node */ +struct Curl_llist_node *Curl_node_next(struct Curl_llist_node *n) +{ + DEBUGASSERT(n); + DEBUGASSERT(n->_init == NODEINIT); + return VERIFYNODE(n->_next); +} + +#ifdef UNITTESTS + +/* Curl_node_prev() returns the previous element in a list from a given + Curl_llist_node */ +struct Curl_llist_node *Curl_node_prev(struct Curl_llist_node *n) +{ + DEBUGASSERT(n); + DEBUGASSERT(n->_init == NODEINIT); + return VERIFYNODE(n->_prev); +} + +#endif + +struct Curl_llist *Curl_node_llist(struct Curl_llist_node *n) { - return list->size; + DEBUGASSERT(n); + DEBUGASSERT(!n->_list || n->_init == NODEINIT); + return n->_list; } diff --git a/deps/curl/lib/llist.h b/deps/curl/lib/llist.h index 320580e33cc..26581869a3f 100644 --- a/deps/curl/lib/llist.h +++ b/deps/curl/lib/llist.h @@ -27,26 +27,63 @@ #include "curl_setup.h" #include -typedef void (*Curl_llist_dtor)(void *, void *); +typedef void (*Curl_llist_dtor)(void *user, void *elem); -struct Curl_llist_element { - void *ptr; - struct Curl_llist_element *prev; - struct Curl_llist_element *next; -}; +/* none of these struct members should be referenced directly, use the + dedicated functions */ struct Curl_llist { - struct Curl_llist_element *head; - struct Curl_llist_element *tail; - Curl_llist_dtor dtor; - size_t size; + struct Curl_llist_node *_head; + struct Curl_llist_node *_tail; + Curl_llist_dtor _dtor; + size_t _size; +#ifdef DEBUGBUILD + int _init; /* detect API usage mistakes */ +#endif +}; + +struct Curl_llist_node { + struct Curl_llist *_list; /* the list where this belongs */ + void *_ptr; + struct Curl_llist_node *_prev; + struct Curl_llist_node *_next; +#ifdef DEBUGBUILD + int _init; /* detect API usage mistakes */ +#endif }; void Curl_llist_init(struct Curl_llist *, Curl_llist_dtor); -void Curl_llist_insert_next(struct Curl_llist *, struct Curl_llist_element *, - const void *, struct Curl_llist_element *node); -void Curl_llist_remove(struct Curl_llist *, struct Curl_llist_element *, - void *); -size_t Curl_llist_count(struct Curl_llist *); +void Curl_llist_insert_next(struct Curl_llist *, struct Curl_llist_node *, + const void *, struct Curl_llist_node *node); +void Curl_llist_append(struct Curl_llist *, + const void *, struct Curl_llist_node *node); +void Curl_node_uremove(struct Curl_llist_node *, void *); +void Curl_node_remove(struct Curl_llist_node *); void Curl_llist_destroy(struct Curl_llist *, void *); + +/* Curl_llist_head() returns the first 'struct Curl_llist_node *', which + might be NULL */ +struct Curl_llist_node *Curl_llist_head(struct Curl_llist *list); + +/* Curl_llist_tail() returns the last 'struct Curl_llist_node *', which + might be NULL */ +struct Curl_llist_node *Curl_llist_tail(struct Curl_llist *list); + +/* Curl_llist_count() returns a size_t the number of nodes in the list */ +size_t Curl_llist_count(struct Curl_llist *list); + +/* Curl_node_elem() returns the custom data from a Curl_llist_node */ +void *Curl_node_elem(struct Curl_llist_node *n); + +/* Curl_node_next() returns the next element in a list from a given + Curl_llist_node */ +struct Curl_llist_node *Curl_node_next(struct Curl_llist_node *n); + +/* Curl_node_prev() returns the previous element in a list from a given + Curl_llist_node */ +struct Curl_llist_node *Curl_node_prev(struct Curl_llist_node *n); + +/* Curl_node_llist() return the list the node is in or NULL. */ +struct Curl_llist *Curl_node_llist(struct Curl_llist_node *n); + #endif /* HEADER_CURL_LLIST_H */ diff --git a/deps/curl/lib/macos.c b/deps/curl/lib/macos.c index 9e8e76e867d..e4662be1d36 100644 --- a/deps/curl/lib/macos.c +++ b/deps/curl/lib/macos.c @@ -34,21 +34,19 @@ CURLcode Curl_macos_init(void) { - { - /* - * The automagic conversion from IPv4 literals to IPv6 literals only - * works if the SCDynamicStoreCopyProxies system function gets called - * first. As Curl currently doesn't support system-wide HTTP proxies, we - * therefore don't use any value this function might return. - * - * This function is only available on macOS and is not needed for - * IPv4-only builds, hence the conditions for defining - * CURL_MACOS_CALL_COPYPROXIES in curl_setup.h. - */ - CFDictionaryRef dict = SCDynamicStoreCopyProxies(NULL); - if(dict) - CFRelease(dict); - } + /* + * The automagic conversion from IPv4 literals to IPv6 literals only + * works if the SCDynamicStoreCopyProxies system function gets called + * first. As Curl currently does not support system-wide HTTP proxies, we + * therefore do not use any value this function might return. + * + * This function is only available on macOS and is not needed for + * IPv4-only builds, hence the conditions for defining + * CURL_MACOS_CALL_COPYPROXIES in curl_setup.h. + */ + CFDictionaryRef dict = SCDynamicStoreCopyProxies(NULL); + if(dict) + CFRelease(dict); return CURLE_OK; } diff --git a/deps/curl/lib/md4.c b/deps/curl/lib/md4.c index 30ab62e6020..f006bdcf052 100644 --- a/deps/curl/lib/md4.c +++ b/deps/curl/lib/md4.c @@ -28,15 +28,18 @@ #include +#include "strdup.h" #include "curl_md4.h" #include "warnless.h" #ifdef USE_OPENSSL -#include -#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) && \ - !defined(USE_AMISSL) +#include +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) && !defined(USE_AMISSL) /* OpenSSL 3.0.0 marks the MD4 functions as deprecated */ #define OPENSSL_NO_MD4 +#else +/* Cover also OPENSSL_NO_MD4 configured in openssl */ +#include #endif #endif /* USE_OPENSSL */ @@ -55,7 +58,8 @@ #else #include #endif -#if(MBEDTLS_VERSION_NUMBER >= 0x02070000) +#if(MBEDTLS_VERSION_NUMBER >= 0x02070000) && \ + (MBEDTLS_VERSION_NUMBER < 0x03000000) #define HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS #endif #endif /* USE_MBEDTLS */ @@ -195,11 +199,9 @@ static int MD4_Init(MD4_CTX *ctx) static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) { if(!ctx->data) { - ctx->data = malloc(size); - if(ctx->data) { - memcpy(ctx->data, data, size); + ctx->data = Curl_memdup(data, size); + if(ctx->data) ctx->size = size; - } } } @@ -218,7 +220,7 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) } #else -/* When no other crypto library is available, or the crypto library doesn't +/* When no other crypto library is available, or the crypto library does not * support MD4, we use this code segment this implementation of it * * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. @@ -230,8 +232,8 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) * Author: * Alexander Peslyak, better known as Solar Designer * - * This software was written by Alexander Peslyak in 2001. No copyright is - * claimed, and the software is hereby placed in the public domain. In case + * This software was written by Alexander Peslyak in 2001. No copyright is + * claimed, and the software is hereby placed in the public domain. In case * this attempt to disclaim copyright and place the software in the public * domain is deemed null and void, then the software is Copyright (c) 2001 * Alexander Peslyak and it is hereby released to the general public under the @@ -240,19 +242,19 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) * Redistribution and use in source and binary forms, with or without * modification, are permitted. * - * There's ABSOLUTELY NO WARRANTY, express or implied. + * There is ABSOLUTELY NO WARRANTY, express or implied. * * (This is a heavily cut-down "BSD license".) * * This differs from Colin Plumb's older public domain implementation in that * no exactly 32-bit integer data type is required (any 32-bit or wider - * unsigned integer data type will do), there's no compile-time endianness - * configuration, and the function prototypes match OpenSSL's. No code from + * unsigned integer data type will do), there is no compile-time endianness + * configuration, and the function prototypes match OpenSSL's. No code from * Colin Plumb's implementation has been reused; this comment merely compares * the properties of the two independent implementations. * * The primary goals of this implementation are portability and ease of use. - * It is meant to be fast, but not as fast as possible. Some known + * It is meant to be fast, but not as fast as possible. Some known * optimizations are not included to reduce source code size and avoid * compile-time configuration. */ @@ -278,14 +280,14 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx); * F and G are optimized compared to their RFC 1320 definitions, with the * optimization for F borrowed from Colin Plumb's MD5 implementation. */ -#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) -#define G(x, y, z) (((x) & ((y) | (z))) | ((y) & (z))) -#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define MD4_F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define MD4_G(x, y, z) (((x) & ((y) | (z))) | ((y) & (z))) +#define MD4_H(x, y, z) ((x) ^ (y) ^ (z)) /* * The MD4 transformation for all three rounds. */ -#define STEP(f, a, b, c, d, x, s) \ +#define MD4_STEP(f, a, b, c, d, x, s) \ (a) += f((b), (c), (d)) + (x); \ (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); @@ -294,30 +296,31 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx); * in a properly aligned word in host byte order. * * The check for little-endian architectures that tolerate unaligned - * memory accesses is just an optimization. Nothing will break if it - * doesn't work. + * memory accesses is just an optimization. Nothing will break if it + * does not work. */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) -#define SET(n) \ +#define MD4_SET(n) \ (*(MD4_u32plus *)(void *)&ptr[(n) * 4]) -#define GET(n) \ - SET(n) +#define MD4_GET(n) \ + MD4_SET(n) #else -#define SET(n) \ +#define MD4_SET(n) \ (ctx->block[(n)] = \ (MD4_u32plus)ptr[(n) * 4] | \ ((MD4_u32plus)ptr[(n) * 4 + 1] << 8) | \ ((MD4_u32plus)ptr[(n) * 4 + 2] << 16) | \ ((MD4_u32plus)ptr[(n) * 4 + 3] << 24)) -#define GET(n) \ +#define MD4_GET(n) \ (ctx->block[(n)]) #endif /* * This processes one or more 64-byte data blocks, but does NOT update - * the bit counters. There are no alignment requirements. + * the bit counters. There are no alignment requirements. */ -static const void *body(MD4_CTX *ctx, const void *data, unsigned long size) +static const void *my_md4_body(MD4_CTX *ctx, + const void *data, unsigned long size) { const unsigned char *ptr; MD4_u32plus a, b, c, d; @@ -338,58 +341,58 @@ static const void *body(MD4_CTX *ctx, const void *data, unsigned long size) saved_d = d; /* Round 1 */ - STEP(F, a, b, c, d, SET(0), 3) - STEP(F, d, a, b, c, SET(1), 7) - STEP(F, c, d, a, b, SET(2), 11) - STEP(F, b, c, d, a, SET(3), 19) - STEP(F, a, b, c, d, SET(4), 3) - STEP(F, d, a, b, c, SET(5), 7) - STEP(F, c, d, a, b, SET(6), 11) - STEP(F, b, c, d, a, SET(7), 19) - STEP(F, a, b, c, d, SET(8), 3) - STEP(F, d, a, b, c, SET(9), 7) - STEP(F, c, d, a, b, SET(10), 11) - STEP(F, b, c, d, a, SET(11), 19) - STEP(F, a, b, c, d, SET(12), 3) - STEP(F, d, a, b, c, SET(13), 7) - STEP(F, c, d, a, b, SET(14), 11) - STEP(F, b, c, d, a, SET(15), 19) + MD4_STEP(MD4_F, a, b, c, d, MD4_SET(0), 3) + MD4_STEP(MD4_F, d, a, b, c, MD4_SET(1), 7) + MD4_STEP(MD4_F, c, d, a, b, MD4_SET(2), 11) + MD4_STEP(MD4_F, b, c, d, a, MD4_SET(3), 19) + MD4_STEP(MD4_F, a, b, c, d, MD4_SET(4), 3) + MD4_STEP(MD4_F, d, a, b, c, MD4_SET(5), 7) + MD4_STEP(MD4_F, c, d, a, b, MD4_SET(6), 11) + MD4_STEP(MD4_F, b, c, d, a, MD4_SET(7), 19) + MD4_STEP(MD4_F, a, b, c, d, MD4_SET(8), 3) + MD4_STEP(MD4_F, d, a, b, c, MD4_SET(9), 7) + MD4_STEP(MD4_F, c, d, a, b, MD4_SET(10), 11) + MD4_STEP(MD4_F, b, c, d, a, MD4_SET(11), 19) + MD4_STEP(MD4_F, a, b, c, d, MD4_SET(12), 3) + MD4_STEP(MD4_F, d, a, b, c, MD4_SET(13), 7) + MD4_STEP(MD4_F, c, d, a, b, MD4_SET(14), 11) + MD4_STEP(MD4_F, b, c, d, a, MD4_SET(15), 19) /* Round 2 */ - STEP(G, a, b, c, d, GET(0) + 0x5a827999, 3) - STEP(G, d, a, b, c, GET(4) + 0x5a827999, 5) - STEP(G, c, d, a, b, GET(8) + 0x5a827999, 9) - STEP(G, b, c, d, a, GET(12) + 0x5a827999, 13) - STEP(G, a, b, c, d, GET(1) + 0x5a827999, 3) - STEP(G, d, a, b, c, GET(5) + 0x5a827999, 5) - STEP(G, c, d, a, b, GET(9) + 0x5a827999, 9) - STEP(G, b, c, d, a, GET(13) + 0x5a827999, 13) - STEP(G, a, b, c, d, GET(2) + 0x5a827999, 3) - STEP(G, d, a, b, c, GET(6) + 0x5a827999, 5) - STEP(G, c, d, a, b, GET(10) + 0x5a827999, 9) - STEP(G, b, c, d, a, GET(14) + 0x5a827999, 13) - STEP(G, a, b, c, d, GET(3) + 0x5a827999, 3) - STEP(G, d, a, b, c, GET(7) + 0x5a827999, 5) - STEP(G, c, d, a, b, GET(11) + 0x5a827999, 9) - STEP(G, b, c, d, a, GET(15) + 0x5a827999, 13) + MD4_STEP(MD4_G, a, b, c, d, MD4_GET(0) + 0x5a827999, 3) + MD4_STEP(MD4_G, d, a, b, c, MD4_GET(4) + 0x5a827999, 5) + MD4_STEP(MD4_G, c, d, a, b, MD4_GET(8) + 0x5a827999, 9) + MD4_STEP(MD4_G, b, c, d, a, MD4_GET(12) + 0x5a827999, 13) + MD4_STEP(MD4_G, a, b, c, d, MD4_GET(1) + 0x5a827999, 3) + MD4_STEP(MD4_G, d, a, b, c, MD4_GET(5) + 0x5a827999, 5) + MD4_STEP(MD4_G, c, d, a, b, MD4_GET(9) + 0x5a827999, 9) + MD4_STEP(MD4_G, b, c, d, a, MD4_GET(13) + 0x5a827999, 13) + MD4_STEP(MD4_G, a, b, c, d, MD4_GET(2) + 0x5a827999, 3) + MD4_STEP(MD4_G, d, a, b, c, MD4_GET(6) + 0x5a827999, 5) + MD4_STEP(MD4_G, c, d, a, b, MD4_GET(10) + 0x5a827999, 9) + MD4_STEP(MD4_G, b, c, d, a, MD4_GET(14) + 0x5a827999, 13) + MD4_STEP(MD4_G, a, b, c, d, MD4_GET(3) + 0x5a827999, 3) + MD4_STEP(MD4_G, d, a, b, c, MD4_GET(7) + 0x5a827999, 5) + MD4_STEP(MD4_G, c, d, a, b, MD4_GET(11) + 0x5a827999, 9) + MD4_STEP(MD4_G, b, c, d, a, MD4_GET(15) + 0x5a827999, 13) /* Round 3 */ - STEP(H, a, b, c, d, GET(0) + 0x6ed9eba1, 3) - STEP(H, d, a, b, c, GET(8) + 0x6ed9eba1, 9) - STEP(H, c, d, a, b, GET(4) + 0x6ed9eba1, 11) - STEP(H, b, c, d, a, GET(12) + 0x6ed9eba1, 15) - STEP(H, a, b, c, d, GET(2) + 0x6ed9eba1, 3) - STEP(H, d, a, b, c, GET(10) + 0x6ed9eba1, 9) - STEP(H, c, d, a, b, GET(6) + 0x6ed9eba1, 11) - STEP(H, b, c, d, a, GET(14) + 0x6ed9eba1, 15) - STEP(H, a, b, c, d, GET(1) + 0x6ed9eba1, 3) - STEP(H, d, a, b, c, GET(9) + 0x6ed9eba1, 9) - STEP(H, c, d, a, b, GET(5) + 0x6ed9eba1, 11) - STEP(H, b, c, d, a, GET(13) + 0x6ed9eba1, 15) - STEP(H, a, b, c, d, GET(3) + 0x6ed9eba1, 3) - STEP(H, d, a, b, c, GET(11) + 0x6ed9eba1, 9) - STEP(H, c, d, a, b, GET(7) + 0x6ed9eba1, 11) - STEP(H, b, c, d, a, GET(15) + 0x6ed9eba1, 15) + MD4_STEP(MD4_H, a, b, c, d, MD4_GET(0) + 0x6ed9eba1, 3) + MD4_STEP(MD4_H, d, a, b, c, MD4_GET(8) + 0x6ed9eba1, 9) + MD4_STEP(MD4_H, c, d, a, b, MD4_GET(4) + 0x6ed9eba1, 11) + MD4_STEP(MD4_H, b, c, d, a, MD4_GET(12) + 0x6ed9eba1, 15) + MD4_STEP(MD4_H, a, b, c, d, MD4_GET(2) + 0x6ed9eba1, 3) + MD4_STEP(MD4_H, d, a, b, c, MD4_GET(10) + 0x6ed9eba1, 9) + MD4_STEP(MD4_H, c, d, a, b, MD4_GET(6) + 0x6ed9eba1, 11) + MD4_STEP(MD4_H, b, c, d, a, MD4_GET(14) + 0x6ed9eba1, 15) + MD4_STEP(MD4_H, a, b, c, d, MD4_GET(1) + 0x6ed9eba1, 3) + MD4_STEP(MD4_H, d, a, b, c, MD4_GET(9) + 0x6ed9eba1, 9) + MD4_STEP(MD4_H, c, d, a, b, MD4_GET(5) + 0x6ed9eba1, 11) + MD4_STEP(MD4_H, b, c, d, a, MD4_GET(13) + 0x6ed9eba1, 15) + MD4_STEP(MD4_H, a, b, c, d, MD4_GET(3) + 0x6ed9eba1, 3) + MD4_STEP(MD4_H, d, a, b, c, MD4_GET(11) + 0x6ed9eba1, 9) + MD4_STEP(MD4_H, c, d, a, b, MD4_GET(7) + 0x6ed9eba1, 11) + MD4_STEP(MD4_H, b, c, d, a, MD4_GET(15) + 0x6ed9eba1, 15) a += saved_a; b += saved_b; @@ -443,11 +446,11 @@ static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) memcpy(&ctx->buffer[used], data, available); data = (const unsigned char *)data + available; size -= available; - body(ctx, ctx->buffer, 64); + my_md4_body(ctx, ctx->buffer, 64); } if(size >= 64) { - data = body(ctx, data, size & ~(unsigned long)0x3f); + data = my_md4_body(ctx, data, size & ~(unsigned long)0x3f); size &= 0x3f; } @@ -466,7 +469,7 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) if(available < 8) { memset(&ctx->buffer[used], 0, available); - body(ctx, ctx->buffer, 64); + my_md4_body(ctx, ctx->buffer, 64); used = 0; available = 64; } @@ -483,7 +486,7 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) ctx->buffer[62] = curlx_ultouc((ctx->hi >> 16)&0xff); ctx->buffer[63] = curlx_ultouc(ctx->hi >> 24); - body(ctx, ctx->buffer, 64); + my_md4_body(ctx, ctx->buffer, 64); result[0] = curlx_ultouc((ctx->a)&0xff); result[1] = curlx_ultouc((ctx->a >> 8)&0xff); diff --git a/deps/curl/lib/md5.c b/deps/curl/lib/md5.c index 01415af911f..73e04e37c10 100644 --- a/deps/curl/lib/md5.c +++ b/deps/curl/lib/md5.c @@ -88,20 +88,20 @@ typedef struct md5_ctx my_md5_ctx; -static CURLcode my_md5_init(my_md5_ctx *ctx) +static CURLcode my_md5_init(void *ctx) { md5_init(ctx); return CURLE_OK; } -static void my_md5_update(my_md5_ctx *ctx, +static void my_md5_update(void *ctx, const unsigned char *input, unsigned int inputLen) { md5_update(ctx, inputLen, input); } -static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +static void my_md5_final(unsigned char *digest, void *ctx) { md5_digest(ctx, 16, digest); } @@ -110,7 +110,7 @@ static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) typedef MD5_CTX my_md5_ctx; -static CURLcode my_md5_init(my_md5_ctx *ctx) +static CURLcode my_md5_init(void *ctx) { if(!MD5_Init(ctx)) return CURLE_OUT_OF_MEMORY; @@ -118,14 +118,14 @@ static CURLcode my_md5_init(my_md5_ctx *ctx) return CURLE_OK; } -static void my_md5_update(my_md5_ctx *ctx, +static void my_md5_update(void *ctx, const unsigned char *input, unsigned int len) { (void)MD5_Update(ctx, input, len); } -static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +static void my_md5_final(unsigned char *digest, void *ctx) { (void)MD5_Final(digest, ctx); } @@ -134,7 +134,7 @@ static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) typedef mbedtls_md5_context my_md5_ctx; -static CURLcode my_md5_init(my_md5_ctx *ctx) +static CURLcode my_md5_init(void *ctx) { #if (MBEDTLS_VERSION_NUMBER >= 0x03000000) if(mbedtls_md5_starts(ctx)) @@ -148,7 +148,7 @@ static CURLcode my_md5_init(my_md5_ctx *ctx) return CURLE_OK; } -static void my_md5_update(my_md5_ctx *ctx, +static void my_md5_update(void *ctx, const unsigned char *data, unsigned int length) { @@ -159,7 +159,7 @@ static void my_md5_update(my_md5_ctx *ctx, #endif } -static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +static void my_md5_final(unsigned char *digest, void *ctx) { #if !defined(HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS) (void) mbedtls_md5_finish(ctx, digest); @@ -172,13 +172,13 @@ static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) /* For Apple operating systems: CommonCrypto has the functions we need. These functions are available on Tiger and later, as well as iOS 2.0 - and later. If you're building for an older cat, well, sorry. + and later. If you are building for an older cat, well, sorry. Declaring the functions as static like this seems to be a bit more reliable than defining COMMON_DIGEST_FOR_OPENSSL on older cats. */ # define my_md5_ctx CC_MD5_CTX -static CURLcode my_md5_init(my_md5_ctx *ctx) +static CURLcode my_md5_init(void *ctx) { if(!CC_MD5_Init(ctx)) return CURLE_OUT_OF_MEMORY; @@ -186,14 +186,14 @@ static CURLcode my_md5_init(my_md5_ctx *ctx) return CURLE_OK; } -static void my_md5_update(my_md5_ctx *ctx, +static void my_md5_update(void *ctx, const unsigned char *input, unsigned int inputLen) { CC_MD5_Update(ctx, input, inputLen); } -static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +static void my_md5_final(unsigned char *digest, void *ctx) { CC_MD5_Final(digest, ctx); } @@ -206,8 +206,9 @@ struct md5_ctx { }; typedef struct md5_ctx my_md5_ctx; -static CURLcode my_md5_init(my_md5_ctx *ctx) +static CURLcode my_md5_init(void *in) { + my_md5_ctx *ctx = (my_md5_ctx *)in; if(!CryptAcquireContext(&ctx->hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) return CURLE_OUT_OF_MEMORY; @@ -221,15 +222,17 @@ static CURLcode my_md5_init(my_md5_ctx *ctx) return CURLE_OK; } -static void my_md5_update(my_md5_ctx *ctx, +static void my_md5_update(void *in, const unsigned char *input, unsigned int inputLen) { + my_md5_ctx *ctx = in; CryptHashData(ctx->hHash, (unsigned char *)input, inputLen, 0); } -static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) +static void my_md5_final(unsigned char *digest, void *in) { + my_md5_ctx *ctx = (my_md5_ctx *)in; unsigned long length = 0; CryptGetHashParam(ctx->hHash, HP_HASHVAL, NULL, &length, 0); if(length == 16) @@ -254,7 +257,7 @@ static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) * Author: * Alexander Peslyak, better known as Solar Designer * - * This software was written by Alexander Peslyak in 2001. No copyright is + * This software was written by Alexander Peslyak in 2001. No copyright is * claimed, and the software is hereby placed in the public domain. * In case this attempt to disclaim copyright and place the software in the * public domain is deemed null and void, then the software is @@ -264,19 +267,19 @@ static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) * Redistribution and use in source and binary forms, with or without * modification, are permitted. * - * There's ABSOLUTELY NO WARRANTY, express or implied. + * There is ABSOLUTELY NO WARRANTY, express or implied. * * (This is a heavily cut-down "BSD license".) * * This differs from Colin Plumb's older public domain implementation in that * no exactly 32-bit integer data type is required (any 32-bit or wider - * unsigned integer data type will do), there's no compile-time endianness - * configuration, and the function prototypes match OpenSSL's. No code from + * unsigned integer data type will do), there is no compile-time endianness + * configuration, and the function prototypes match OpenSSL's. No code from * Colin Plumb's implementation has been reused; this comment merely compares * the properties of the two independent implementations. * * The primary goals of this implementation are portability and ease of use. - * It is meant to be fast, but not as fast as possible. Some known + * It is meant to be fast, but not as fast as possible. Some known * optimizations are not included to reduce source code size and avoid * compile-time configuration. */ @@ -292,10 +295,10 @@ struct md5_ctx { }; typedef struct md5_ctx my_md5_ctx; -static CURLcode my_md5_init(my_md5_ctx *ctx); -static void my_md5_update(my_md5_ctx *ctx, const void *data, - unsigned long size); -static void my_md5_final(unsigned char *result, my_md5_ctx *ctx); +static CURLcode my_md5_init(void *ctx); +static void my_md5_update(void *ctx, const unsigned char *data, + unsigned int size); +static void my_md5_final(unsigned char *result, void *ctx); /* * The basic MD5 functions. @@ -304,16 +307,16 @@ static void my_md5_final(unsigned char *result, my_md5_ctx *ctx); * architectures that lack an AND-NOT instruction, just like in Colin Plumb's * implementation. */ -#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) -#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) -#define H(x, y, z) (((x) ^ (y)) ^ (z)) -#define H2(x, y, z) ((x) ^ ((y) ^ (z))) -#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define MD5_F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define MD5_G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) +#define MD5_H(x, y, z) (((x) ^ (y)) ^ (z)) +#define MD5_H2(x, y, z) ((x) ^ ((y) ^ (z))) +#define MD5_I(x, y, z) ((y) ^ ((x) | ~(z))) /* * The MD5 transformation for all four rounds. */ -#define STEP(f, a, b, c, d, x, t, s) \ +#define MD5_STEP(f, a, b, c, d, x, t, s) \ (a) += f((b), (c), (d)) + (x) + (t); \ (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ (a) += (b); @@ -323,30 +326,31 @@ static void my_md5_final(unsigned char *result, my_md5_ctx *ctx); * in a properly aligned word in host byte order. * * The check for little-endian architectures that tolerate unaligned - * memory accesses is just an optimization. Nothing will break if it - * doesn't work. + * memory accesses is just an optimization. Nothing will break if it + * does not work. */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) -#define SET(n) \ +#define MD5_SET(n) \ (*(MD5_u32plus *)(void *)&ptr[(n) * 4]) -#define GET(n) \ - SET(n) +#define MD5_GET(n) \ + MD5_SET(n) #else -#define SET(n) \ +#define MD5_SET(n) \ (ctx->block[(n)] = \ (MD5_u32plus)ptr[(n) * 4] | \ ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) -#define GET(n) \ +#define MD5_GET(n) \ (ctx->block[(n)]) #endif /* * This processes one or more 64-byte data blocks, but does NOT update - * the bit counters. There are no alignment requirements. + * the bit counters. There are no alignment requirements. */ -static const void *body(my_md5_ctx *ctx, const void *data, unsigned long size) +static const void *my_md5_body(my_md5_ctx *ctx, + const void *data, unsigned long size) { const unsigned char *ptr; MD5_u32plus a, b, c, d; @@ -367,76 +371,76 @@ static const void *body(my_md5_ctx *ctx, const void *data, unsigned long size) saved_d = d; /* Round 1 */ - STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) - STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) - STEP(F, c, d, a, b, SET(2), 0x242070db, 17) - STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) - STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) - STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) - STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) - STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) - STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) - STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) - STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) - STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) - STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) - STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) - STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) - STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) + MD5_STEP(MD5_F, a, b, c, d, MD5_SET(0), 0xd76aa478, 7) + MD5_STEP(MD5_F, d, a, b, c, MD5_SET(1), 0xe8c7b756, 12) + MD5_STEP(MD5_F, c, d, a, b, MD5_SET(2), 0x242070db, 17) + MD5_STEP(MD5_F, b, c, d, a, MD5_SET(3), 0xc1bdceee, 22) + MD5_STEP(MD5_F, a, b, c, d, MD5_SET(4), 0xf57c0faf, 7) + MD5_STEP(MD5_F, d, a, b, c, MD5_SET(5), 0x4787c62a, 12) + MD5_STEP(MD5_F, c, d, a, b, MD5_SET(6), 0xa8304613, 17) + MD5_STEP(MD5_F, b, c, d, a, MD5_SET(7), 0xfd469501, 22) + MD5_STEP(MD5_F, a, b, c, d, MD5_SET(8), 0x698098d8, 7) + MD5_STEP(MD5_F, d, a, b, c, MD5_SET(9), 0x8b44f7af, 12) + MD5_STEP(MD5_F, c, d, a, b, MD5_SET(10), 0xffff5bb1, 17) + MD5_STEP(MD5_F, b, c, d, a, MD5_SET(11), 0x895cd7be, 22) + MD5_STEP(MD5_F, a, b, c, d, MD5_SET(12), 0x6b901122, 7) + MD5_STEP(MD5_F, d, a, b, c, MD5_SET(13), 0xfd987193, 12) + MD5_STEP(MD5_F, c, d, a, b, MD5_SET(14), 0xa679438e, 17) + MD5_STEP(MD5_F, b, c, d, a, MD5_SET(15), 0x49b40821, 22) /* Round 2 */ - STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) - STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) - STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) - STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) - STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) - STEP(G, d, a, b, c, GET(10), 0x02441453, 9) - STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) - STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) - STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) - STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) - STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) - STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) - STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) - STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) - STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) - STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) + MD5_STEP(MD5_G, a, b, c, d, MD5_GET(1), 0xf61e2562, 5) + MD5_STEP(MD5_G, d, a, b, c, MD5_GET(6), 0xc040b340, 9) + MD5_STEP(MD5_G, c, d, a, b, MD5_GET(11), 0x265e5a51, 14) + MD5_STEP(MD5_G, b, c, d, a, MD5_GET(0), 0xe9b6c7aa, 20) + MD5_STEP(MD5_G, a, b, c, d, MD5_GET(5), 0xd62f105d, 5) + MD5_STEP(MD5_G, d, a, b, c, MD5_GET(10), 0x02441453, 9) + MD5_STEP(MD5_G, c, d, a, b, MD5_GET(15), 0xd8a1e681, 14) + MD5_STEP(MD5_G, b, c, d, a, MD5_GET(4), 0xe7d3fbc8, 20) + MD5_STEP(MD5_G, a, b, c, d, MD5_GET(9), 0x21e1cde6, 5) + MD5_STEP(MD5_G, d, a, b, c, MD5_GET(14), 0xc33707d6, 9) + MD5_STEP(MD5_G, c, d, a, b, MD5_GET(3), 0xf4d50d87, 14) + MD5_STEP(MD5_G, b, c, d, a, MD5_GET(8), 0x455a14ed, 20) + MD5_STEP(MD5_G, a, b, c, d, MD5_GET(13), 0xa9e3e905, 5) + MD5_STEP(MD5_G, d, a, b, c, MD5_GET(2), 0xfcefa3f8, 9) + MD5_STEP(MD5_G, c, d, a, b, MD5_GET(7), 0x676f02d9, 14) + MD5_STEP(MD5_G, b, c, d, a, MD5_GET(12), 0x8d2a4c8a, 20) /* Round 3 */ - STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) - STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11) - STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) - STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23) - STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) - STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11) - STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) - STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23) - STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) - STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11) - STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) - STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23) - STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) - STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11) - STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) - STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23) + MD5_STEP(MD5_H, a, b, c, d, MD5_GET(5), 0xfffa3942, 4) + MD5_STEP(MD5_H2, d, a, b, c, MD5_GET(8), 0x8771f681, 11) + MD5_STEP(MD5_H, c, d, a, b, MD5_GET(11), 0x6d9d6122, 16) + MD5_STEP(MD5_H2, b, c, d, a, MD5_GET(14), 0xfde5380c, 23) + MD5_STEP(MD5_H, a, b, c, d, MD5_GET(1), 0xa4beea44, 4) + MD5_STEP(MD5_H2, d, a, b, c, MD5_GET(4), 0x4bdecfa9, 11) + MD5_STEP(MD5_H, c, d, a, b, MD5_GET(7), 0xf6bb4b60, 16) + MD5_STEP(MD5_H2, b, c, d, a, MD5_GET(10), 0xbebfbc70, 23) + MD5_STEP(MD5_H, a, b, c, d, MD5_GET(13), 0x289b7ec6, 4) + MD5_STEP(MD5_H2, d, a, b, c, MD5_GET(0), 0xeaa127fa, 11) + MD5_STEP(MD5_H, c, d, a, b, MD5_GET(3), 0xd4ef3085, 16) + MD5_STEP(MD5_H2, b, c, d, a, MD5_GET(6), 0x04881d05, 23) + MD5_STEP(MD5_H, a, b, c, d, MD5_GET(9), 0xd9d4d039, 4) + MD5_STEP(MD5_H2, d, a, b, c, MD5_GET(12), 0xe6db99e5, 11) + MD5_STEP(MD5_H, c, d, a, b, MD5_GET(15), 0x1fa27cf8, 16) + MD5_STEP(MD5_H2, b, c, d, a, MD5_GET(2), 0xc4ac5665, 23) /* Round 4 */ - STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) - STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) - STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) - STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) - STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) - STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) - STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) - STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) - STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) - STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) - STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) - STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) - STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) - STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) - STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) - STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) + MD5_STEP(MD5_I, a, b, c, d, MD5_GET(0), 0xf4292244, 6) + MD5_STEP(MD5_I, d, a, b, c, MD5_GET(7), 0x432aff97, 10) + MD5_STEP(MD5_I, c, d, a, b, MD5_GET(14), 0xab9423a7, 15) + MD5_STEP(MD5_I, b, c, d, a, MD5_GET(5), 0xfc93a039, 21) + MD5_STEP(MD5_I, a, b, c, d, MD5_GET(12), 0x655b59c3, 6) + MD5_STEP(MD5_I, d, a, b, c, MD5_GET(3), 0x8f0ccc92, 10) + MD5_STEP(MD5_I, c, d, a, b, MD5_GET(10), 0xffeff47d, 15) + MD5_STEP(MD5_I, b, c, d, a, MD5_GET(1), 0x85845dd1, 21) + MD5_STEP(MD5_I, a, b, c, d, MD5_GET(8), 0x6fa87e4f, 6) + MD5_STEP(MD5_I, d, a, b, c, MD5_GET(15), 0xfe2ce6e0, 10) + MD5_STEP(MD5_I, c, d, a, b, MD5_GET(6), 0xa3014314, 15) + MD5_STEP(MD5_I, b, c, d, a, MD5_GET(13), 0x4e0811a1, 21) + MD5_STEP(MD5_I, a, b, c, d, MD5_GET(4), 0xf7537e82, 6) + MD5_STEP(MD5_I, d, a, b, c, MD5_GET(11), 0xbd3af235, 10) + MD5_STEP(MD5_I, c, d, a, b, MD5_GET(2), 0x2ad7d2bb, 15) + MD5_STEP(MD5_I, b, c, d, a, MD5_GET(9), 0xeb86d391, 21) a += saved_a; b += saved_b; @@ -454,8 +458,9 @@ static const void *body(my_md5_ctx *ctx, const void *data, unsigned long size) return ptr; } -static CURLcode my_md5_init(my_md5_ctx *ctx) +static CURLcode my_md5_init(void *in) { + my_md5_ctx *ctx = (my_md5_ctx *)in; ctx->a = 0x67452301; ctx->b = 0xefcdab89; ctx->c = 0x98badcfe; @@ -467,11 +472,12 @@ static CURLcode my_md5_init(my_md5_ctx *ctx) return CURLE_OK; } -static void my_md5_update(my_md5_ctx *ctx, const void *data, - unsigned long size) +static void my_md5_update(void *in, const unsigned char *data, + unsigned int size) { MD5_u32plus saved_lo; - unsigned long used; + unsigned int used; + my_md5_ctx *ctx = (my_md5_ctx *)in; saved_lo = ctx->lo; ctx->lo = (saved_lo + size) & 0x1fffffff; @@ -482,7 +488,7 @@ static void my_md5_update(my_md5_ctx *ctx, const void *data, used = saved_lo & 0x3f; if(used) { - unsigned long available = 64 - used; + unsigned int available = 64 - used; if(size < available) { memcpy(&ctx->buffer[used], data, size); @@ -492,20 +498,21 @@ static void my_md5_update(my_md5_ctx *ctx, const void *data, memcpy(&ctx->buffer[used], data, available); data = (const unsigned char *)data + available; size -= available; - body(ctx, ctx->buffer, 64); + my_md5_body(ctx, ctx->buffer, 64); } if(size >= 64) { - data = body(ctx, data, size & ~(unsigned long)0x3f); + data = my_md5_body(ctx, data, size & ~(unsigned long)0x3f); size &= 0x3f; } memcpy(ctx->buffer, data, size); } -static void my_md5_final(unsigned char *result, my_md5_ctx *ctx) +static void my_md5_final(unsigned char *result, void *in) { - unsigned long used, available; + unsigned int used, available; + my_md5_ctx *ctx = (my_md5_ctx *)in; used = ctx->lo & 0x3f; @@ -515,7 +522,7 @@ static void my_md5_final(unsigned char *result, my_md5_ctx *ctx) if(available < 8) { memset(&ctx->buffer[used], 0, available); - body(ctx, ctx->buffer, 64); + my_md5_body(ctx, ctx->buffer, 64); used = 0; available = 64; } @@ -532,7 +539,7 @@ static void my_md5_final(unsigned char *result, my_md5_ctx *ctx) ctx->buffer[62] = curlx_ultouc((ctx->hi >> 16)&0xff); ctx->buffer[63] = curlx_ultouc(ctx->hi >> 24); - body(ctx, ctx->buffer, 64); + my_md5_body(ctx, ctx->buffer, 64); result[0] = curlx_ultouc((ctx->a)&0xff); result[1] = curlx_ultouc((ctx->a >> 8)&0xff); @@ -556,36 +563,21 @@ static void my_md5_final(unsigned char *result, my_md5_ctx *ctx) #endif /* CRYPTO LIBS */ -const struct HMAC_params Curl_HMAC_MD5[] = { - { - /* Hash initialization function. */ - CURLX_FUNCTION_CAST(HMAC_hinit_func, my_md5_init), - /* Hash update function. */ - CURLX_FUNCTION_CAST(HMAC_hupdate_func, my_md5_update), - /* Hash computation end function. */ - CURLX_FUNCTION_CAST(HMAC_hfinal_func, my_md5_final), - /* Size of hash context structure. */ - sizeof(my_md5_ctx), - /* Maximum key length. */ - 64, - /* Result size. */ - 16 - } +const struct HMAC_params Curl_HMAC_MD5 = { + my_md5_init, /* Hash initialization function. */ + my_md5_update, /* Hash update function. */ + my_md5_final, /* Hash computation end function. */ + sizeof(my_md5_ctx), /* Size of hash context structure. */ + 64, /* Maximum key length. */ + 16 /* Result size. */ }; -const struct MD5_params Curl_DIGEST_MD5[] = { - { - /* Digest initialization function */ - CURLX_FUNCTION_CAST(Curl_MD5_init_func, my_md5_init), - /* Digest update function */ - CURLX_FUNCTION_CAST(Curl_MD5_update_func, my_md5_update), - /* Digest computation end function */ - CURLX_FUNCTION_CAST(Curl_MD5_final_func, my_md5_final), - /* Size of digest context struct */ - sizeof(my_md5_ctx), - /* Result size */ - 16 - } +const struct MD5_params Curl_DIGEST_MD5 = { + my_md5_init, /* Digest initialization function */ + my_md5_update, /* Digest update function */ + my_md5_final, /* Digest computation end function */ + sizeof(my_md5_ctx), /* Size of digest context struct */ + 16 /* Result size */ }; /* diff --git a/deps/curl/lib/memdebug.c b/deps/curl/lib/memdebug.c index d6952a07a19..02612c2a23f 100644 --- a/deps/curl/lib/memdebug.c +++ b/deps/curl/lib/memdebug.c @@ -30,7 +30,7 @@ #include "urldata.h" -#define MEMDEBUG_NODEFINES /* don't redefine the standard functions */ +#define MEMDEBUG_NODEFINES /* do not redefine the standard functions */ /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -44,16 +44,16 @@ struct memdebug { double d; void *p; } mem[1]; - /* I'm hoping this is the thing with the strictest alignment - * requirements. That also means we waste some space :-( */ + /* I am hoping this is the thing with the strictest alignment + * requirements. That also means we waste some space :-( */ }; /* - * Note that these debug functions are very simple and they are meant to - * remain so. For advanced analysis, record a log file and write perl scripts - * to analyze them! + * Note that these debug functions are simple and they are meant to remain so. + * For advanced analysis, record a log file and write perl scripts to analyze + * them! * - * Don't use these with multithreaded test programs! + * Do not use these with multithreaded test programs! */ FILE *curl_dbg_logfile = NULL; @@ -75,7 +75,7 @@ static void curl_dbg_cleanup(void) curl_dbg_logfile = NULL; } -/* this sets the log file name */ +/* this sets the log filename */ void curl_dbg_memdebug(const char *logname) { if(!curl_dbg_logfile) { @@ -84,7 +84,7 @@ void curl_dbg_memdebug(const char *logname) else curl_dbg_logfile = stderr; #ifdef MEMDEBUG_LOG_SYNC - /* Flush the log file after every line so the log isn't lost in a crash */ + /* Flush the log file after every line so the log is not lost in a crash */ if(curl_dbg_logfile) setbuf(curl_dbg_logfile, (char *)NULL); #endif @@ -103,7 +103,7 @@ void curl_dbg_memlimit(long limit) } } -/* returns TRUE if this isn't allowed! */ +/* returns TRUE if this is not allowed! */ static bool countcheck(const char *func, int line, const char *source) { /* if source is NULL, then the call is made internally and this check @@ -208,7 +208,7 @@ ALLOC_FUNC char *curl_dbg_strdup(const char *str, return mem; } -#if defined(WIN32) && defined(UNICODE) +#if defined(_WIN32) && defined(UNICODE) ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, int line, const char *source) { @@ -304,12 +304,6 @@ void curl_dbg_free(void *ptr, int line, const char *source) curl_socket_t curl_dbg_socket(int domain, int type, int protocol, int line, const char *source) { - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d socket() = %d\n" : - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d socket() = %ld\n" : - "FD %s:%d socket() = %zd\n"; - curl_socket_t sockfd; if(countcheck("socket", line, source)) @@ -318,7 +312,8 @@ curl_socket_t curl_dbg_socket(int domain, int type, int protocol, sockfd = socket(domain, type, protocol); if(source && (sockfd != CURL_SOCKET_BAD)) - curl_dbg_log(fmt, source, line, sockfd); + curl_dbg_log("FD %s:%d socket() = %" FMT_SOCKET_T "\n", + source, line, sockfd); return sockfd; } @@ -357,16 +352,12 @@ int curl_dbg_socketpair(int domain, int type, int protocol, curl_socket_t socket_vector[2], int line, const char *source) { - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d socketpair() = %d %d\n" : - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d socketpair() = %ld %ld\n" : - "FD %s:%d socketpair() = %zd %zd\n"; - int res = socketpair(domain, type, protocol, socket_vector); if(source && (0 == res)) - curl_dbg_log(fmt, source, line, socket_vector[0], socket_vector[1]); + curl_dbg_log("FD %s:%d socketpair() = " + "%" FMT_SOCKET_T " %" FMT_SOCKET_T "\n", + source, line, socket_vector[0], socket_vector[1]); return res; } @@ -375,19 +366,14 @@ int curl_dbg_socketpair(int domain, int type, int protocol, curl_socket_t curl_dbg_accept(curl_socket_t s, void *saddr, void *saddrlen, int line, const char *source) { - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d accept() = %d\n" : - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d accept() = %ld\n" : - "FD %s:%d accept() = %zd\n"; - struct sockaddr *addr = (struct sockaddr *)saddr; curl_socklen_t *addrlen = (curl_socklen_t *)saddrlen; curl_socket_t sockfd = accept(s, addr, addrlen); if(source && (sockfd != CURL_SOCKET_BAD)) - curl_dbg_log(fmt, source, line, sockfd); + curl_dbg_log("FD %s:%d accept() = %" FMT_SOCKET_T "\n", + source, line, sockfd); return sockfd; } @@ -395,14 +381,9 @@ curl_socket_t curl_dbg_accept(curl_socket_t s, void *saddr, void *saddrlen, /* separate function to allow libcurl to mark a "faked" close */ void curl_dbg_mark_sclose(curl_socket_t sockfd, int line, const char *source) { - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d sclose(%d)\n": - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d sclose(%ld)\n": - "FD %s:%d sclose(%zd)\n"; - if(source) - curl_dbg_log(fmt, source, line, sockfd); + curl_dbg_log("FD %s:%d sclose(%" FMT_SOCKET_T ")\n", + source, line, sockfd); } /* this is our own defined way to close sockets on *ALL* platforms */ diff --git a/deps/curl/lib/memdebug.h b/deps/curl/lib/memdebug.h index c9eb5dc37c0..80f3374e52a 100644 --- a/deps/curl/lib/memdebug.h +++ b/deps/curl/lib/memdebug.h @@ -34,9 +34,9 @@ #include "functypes.h" #if defined(__GNUC__) && __GNUC__ >= 3 -# define ALLOC_FUNC __attribute__((malloc)) -# define ALLOC_SIZE(s) __attribute__((alloc_size(s))) -# define ALLOC_SIZE2(n, s) __attribute__((alloc_size(n, s))) +# define ALLOC_FUNC __attribute__((__malloc__)) +# define ALLOC_SIZE(s) __attribute__((__alloc_size__(s))) +# define ALLOC_SIZE2(n, s) __attribute__((__alloc_size__(n, s))) #elif defined(_MSC_VER) # define ALLOC_FUNC __declspec(restrict) # define ALLOC_SIZE(s) @@ -64,7 +64,7 @@ CURL_EXTERN ALLOC_SIZE(2) void *curl_dbg_realloc(void *ptr, CURL_EXTERN void curl_dbg_free(void *ptr, int line, const char *source); CURL_EXTERN ALLOC_FUNC char *curl_dbg_strdup(const char *str, int line, const char *src); -#if defined(WIN32) && defined(UNICODE) +#if defined(_WIN32) && defined(UNICODE) CURL_EXTERN ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, int line, const char *source); @@ -72,7 +72,7 @@ CURL_EXTERN ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, CURL_EXTERN void curl_dbg_memdebug(const char *logname); CURL_EXTERN void curl_dbg_memlimit(long limit); -CURL_EXTERN void curl_dbg_log(const char *format, ...); +CURL_EXTERN void curl_dbg_log(const char *format, ...) CURL_PRINTF(1, 2); /* file descriptor manipulators */ CURL_EXTERN curl_socket_t curl_dbg_socket(int domain, int type, int protocol, @@ -114,14 +114,20 @@ CURL_EXTERN int curl_dbg_fclose(FILE *file, int line, const char *source); /* Set this symbol on the command-line, recompile all lib-sources */ #undef strdup #define strdup(ptr) curl_dbg_strdup(ptr, __LINE__, __FILE__) +#undef malloc #define malloc(size) curl_dbg_malloc(size, __LINE__, __FILE__) +#undef calloc #define calloc(nbelem,size) curl_dbg_calloc(nbelem, size, __LINE__, __FILE__) +#undef realloc #define realloc(ptr,size) curl_dbg_realloc(ptr, size, __LINE__, __FILE__) +#undef free #define free(ptr) curl_dbg_free(ptr, __LINE__, __FILE__) +#undef send #define send(a,b,c,d) curl_dbg_send(a,b,c,d, __LINE__, __FILE__) +#undef recv #define recv(a,b,c,d) curl_dbg_recv(a,b,c,d, __LINE__, __FILE__) -#ifdef WIN32 +#ifdef _WIN32 # ifdef UNICODE # undef wcsdup # define wcsdup(ptr) curl_dbg_wcsdup(ptr, __LINE__, __FILE__) @@ -137,15 +143,17 @@ CURL_EXTERN int curl_dbg_fclose(FILE *file, int line, const char *source); #undef socket #define socket(domain,type,protocol)\ - curl_dbg_socket(domain, type, protocol, __LINE__, __FILE__) + curl_dbg_socket((int)domain, type, protocol, __LINE__, __FILE__) #undef accept /* for those with accept as a macro */ #define accept(sock,addr,len)\ curl_dbg_accept(sock, addr, len, __LINE__, __FILE__) #ifdef HAVE_SOCKETPAIR #define socketpair(domain,type,protocol,socket_vector)\ - curl_dbg_socketpair(domain, type, protocol, socket_vector, __LINE__, __FILE__) + curl_dbg_socketpair((int)domain, type, protocol, socket_vector, \ + __LINE__, __FILE__) #endif +#ifndef CURL_NO_GETADDRINFO_OVERRIDE #ifdef HAVE_GETADDRINFO #if defined(getaddrinfo) && defined(__osf__) /* OSF/1 and Tru64 have getaddrinfo as a define already, so we cannot define @@ -165,6 +173,7 @@ CURL_EXTERN int curl_dbg_fclose(FILE *file, int line, const char *source); #define freeaddrinfo(data) \ curl_dbg_freeaddrinfo(data, __LINE__, __FILE__) #endif /* HAVE_FREEADDRINFO */ +#endif /* !CURL_NO_GETADDRINFO_OVERRIDE */ /* sclose is probably already defined, redefine it! */ #undef sclose diff --git a/deps/curl/lib/mime.c b/deps/curl/lib/mime.c index 842b2da7e60..70286069ce0 100644 --- a/deps/curl/lib/mime.c +++ b/deps/curl/lib/mime.c @@ -26,10 +26,13 @@ #include +struct Curl_easy; + #include "mime.h" #include "warnless.h" #include "urldata.h" #include "sendf.h" +#include "strdup.h" #if !defined(CURL_DISABLE_MIME) && (!defined(CURL_DISABLE_HTTP) || \ !defined(CURL_DISABLE_SMTP) || \ @@ -48,7 +51,7 @@ #include "curl_memory.h" #include "memdebug.h" -#ifdef WIN32 +#ifdef _WIN32 # ifndef R_OK # define R_OK 4 # endif @@ -73,6 +76,7 @@ static curl_off_t encoder_base64_size(curl_mimepart *part); static size_t encoder_qp_read(char *buffer, size_t size, bool ateof, curl_mimepart *part); static curl_off_t encoder_qp_size(curl_mimepart *part); +static curl_off_t mime_size(curl_mimepart *part); static const struct mime_encoder encoders[] = { {"binary", encoder_nop_read, encoder_nop_size}, @@ -90,7 +94,7 @@ static const char base64enc[] = /* Quoted-printable character class table. * * We cannot rely on ctype functions since quoted-printable input data - * is assumed to be ascii-compatible, even on non-ascii platforms. */ + * is assumed to be ASCII-compatible, even on non-ASCII platforms. */ #define QP_OK 1 /* Can be represented by itself. */ #define QP_SP 2 /* Space or tab. */ #define QP_CR 3 /* Carriage return. */ @@ -257,7 +261,7 @@ static char *Curl_basename(char *path) s2 = strrchr(path, '\\'); if(s1 && s2) { - path = (s1 > s2? s1 : s2) + 1; + path = (s1 > s2 ? s1 : s2) + 1; } else if(s1) path = s1 + 1; @@ -311,8 +315,7 @@ static char *escape_string(struct Curl_easy *data, table = formtable; /* data can be NULL when this function is called indirectly from curl_formget(). */ - if(strategy == MIMESTRATEGY_MAIL || - (data && (data->set.mime_options & CURLMIMEOPT_FORMESCAPE))) + if(strategy == MIMESTRATEGY_MAIL || (data && (data->set.mime_formescape))) table = mimetable; Curl_dyn_init(&db, CURL_MAX_INPUT_LENGTH); @@ -423,7 +426,7 @@ static size_t encoder_7bit_read(char *buffer, size_t size, bool ateof, for(cursize = 0; cursize < size; cursize++) { *buffer = st->buf[st->bufbeg]; if(*buffer++ & 0x80) - return cursize? cursize: READ_ERROR; + return cursize ? cursize : READ_ERROR; st->bufbeg++; } @@ -537,7 +540,7 @@ static int qp_lookahead_eol(struct mime_encoder_state *st, int ateof, size_t n) if(n >= st->bufend && ateof) return 1; if(n + 2 > st->bufend) - return ateof? 0: -1; + return ateof ? 0 : -1; if(qp_class[st->buf[n] & 0xFF] == QP_CR && qp_class[st->buf[n + 1] & 0xFF] == QP_LF) return 1; @@ -556,7 +559,7 @@ static size_t encoder_qp_read(char *buffer, size_t size, bool ateof, /* On all platforms, input is supposed to be ASCII compatible: for this reason, we use hexadecimal ASCII codes in this function rather than - character constants that can be interpreted as non-ascii on some + character constants that can be interpreted as non-ASCII on some platforms. Preserve ASCII encoding on output too. */ while(st->bufbeg < st->bufend) { size_t len = 1; @@ -650,7 +653,7 @@ static curl_off_t encoder_qp_size(curl_mimepart *part) { /* Determining the size can only be done by reading the data: unless the data size is 0, we return it as unknown (-1). */ - return part->datasize? -1: 0; + return part->datasize ? -1 : 0; } @@ -710,7 +713,7 @@ static int mime_open_file(curl_mimepart *part) if(part->fp) return 0; part->fp = fopen_read(part->data, "rb"); - return part->fp? 0: -1; + return part->fp ? 0 : -1; } static size_t mime_file_read(char *buffer, size_t size, size_t nitems, @@ -737,8 +740,8 @@ static int mime_file_seek(void *instream, curl_off_t offset, int whence) if(mime_open_file(part)) return CURL_SEEKFUNC_FAIL; - return fseek(part->fp, (long) offset, whence)? - CURL_SEEKFUNC_CANTSEEK: CURL_SEEKFUNC_OK; + return fseek(part->fp, (long) offset, whence) ? + CURL_SEEKFUNC_CANTSEEK : CURL_SEEKFUNC_OK; } static void mime_file_free(void *ptr) @@ -818,7 +821,7 @@ static size_t read_part_content(curl_mimepart *part, case MIMEKIND_FILE: if(part->fp && feof(part->fp)) break; /* At EOF. */ - /* FALLTHROUGH */ + FALLTHROUGH(); default: if(part->readfunc) { if(!(part->flags & MIME_FAST_READ)) { @@ -870,7 +873,7 @@ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer, break; case READ_ERROR: case STOP_FILLING: - return cursize? cursize: sz; + return cursize ? cursize : sz; default: cursize += sz; buffer += sz; @@ -889,7 +892,7 @@ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer, st->bufend = len; } if(st->bufend >= sizeof(st->buf)) - return cursize? cursize: READ_ERROR; /* Buffer full. */ + return cursize ? cursize : READ_ERROR; /* Buffer full. */ sz = read_part_content(part, st->buf + st->bufend, sizeof(st->buf) - st->bufend, hasread); switch(sz) { @@ -900,7 +903,7 @@ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer, case CURL_READFUNC_PAUSE: case READ_ERROR: case STOP_FILLING: - return cursize? cursize: sz; + return cursize ? cursize : sz; default: st->bufend += sz; break; @@ -924,8 +927,8 @@ static size_t readback_part(curl_mimepart *part, switch(part->state.state) { case MIMESTATE_BEGIN: mimesetstate(&part->state, - (part->flags & MIME_BODY_ONLY)? - MIMESTATE_BODY: MIMESTATE_CURLHEADERS, + (part->flags & MIME_BODY_ONLY) ? + MIMESTATE_BODY : MIMESTATE_CURLHEADERS, part->curlheaders); break; case MIMESTATE_USERHEADERS: @@ -937,7 +940,7 @@ static size_t readback_part(curl_mimepart *part, mimesetstate(&part->state, MIMESTATE_USERHEADERS, hdr->next); break; } - /* FALLTHROUGH */ + FALLTHROUGH(); case MIMESTATE_CURLHEADERS: if(!hdr) mimesetstate(&part->state, MIMESTATE_USERHEADERS, part->userheaders); @@ -971,12 +974,12 @@ static size_t readback_part(curl_mimepart *part, fclose(part->fp); part->fp = NULL; } - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_READFUNC_ABORT: case CURL_READFUNC_PAUSE: case READ_ERROR: case STOP_FILLING: - return cursize? cursize: sz; + return cursize ? cursize : sz; } break; case MIMESTATE_END: @@ -1042,7 +1045,7 @@ static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems, case CURL_READFUNC_PAUSE: case READ_ERROR: case STOP_FILLING: - return cursize? cursize: sz; + return cursize ? cursize : sz; case 0: mimesetstate(&mime->state, MIMESTATE_BOUNDARY1, part->nextpart); break; @@ -1136,7 +1139,7 @@ static void cleanup_part_content(curl_mimepart *part) part->datasize = (curl_off_t) 0; /* No size yet. */ cleanup_encoder_state(&part->encstate); part->kind = MIMEKIND_NONE; - part->flags &= ~MIME_FAST_READ; + part->flags &= ~(unsigned int)MIME_FAST_READ; part->lastreadstatus = 1; /* Successful read status. */ part->state.state = MIMESTATE_BEGIN; } @@ -1146,7 +1149,7 @@ static void mime_subparts_free(void *ptr) curl_mime *mime = (curl_mime *) ptr; if(mime && mime->parent) { - mime->parent->freefunc = NULL; /* Be sure we won't be called again. */ + mime->parent->freefunc = NULL; /* Be sure we will not be called again. */ cleanup_part_content(mime->parent); /* Avoid dangling pointer in part. */ } curl_mime_free(mime); @@ -1158,7 +1161,7 @@ static void mime_subparts_unbind(void *ptr) curl_mime *mime = (curl_mime *) ptr; if(mime && mime->parent) { - mime->parent->freefunc = NULL; /* Be sure we won't be called again. */ + mime->parent->freefunc = NULL; /* Be sure we will not be called again. */ cleanup_part_content(mime->parent); /* Avoid dangling pointer in part. */ mime->parent = NULL; } @@ -1185,7 +1188,7 @@ void curl_mime_free(curl_mime *mime) curl_mimepart *part; if(mime) { - mime_subparts_unbind(mime); /* Be sure it's not referenced anymore. */ + mime_subparts_unbind(mime); /* Be sure it is not referenced anymore. */ while(mime->firstpart) { part = mime->firstpart; mime->firstpart = part->nextpart; @@ -1227,15 +1230,16 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data, /* No one knows about the cloned subparts, thus always attach ownership to the part. */ mime = curl_mime_init(data); - res = mime? curl_mime_subparts(dst, mime): CURLE_OUT_OF_MEMORY; + res = mime ? curl_mime_subparts(dst, mime) : CURLE_OUT_OF_MEMORY; /* Duplicate subparts. */ for(s = ((curl_mime *) src->arg)->firstpart; !res && s; s = s->nextpart) { d = curl_mime_addpart(mime); - res = d? Curl_mime_duppart(data, d, s): CURLE_OUT_OF_MEMORY; + res = d ? Curl_mime_duppart(data, d, s) : CURLE_OUT_OF_MEMORY; } break; default: /* Invalid kind: should not occur. */ + DEBUGF(infof(data, "invalid MIMEKIND* attempt")); res = CURLE_BAD_FUNCTION_ARGUMENT; /* Internal error? */ break; } @@ -1277,7 +1281,7 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data, */ /* Create a mime handle. */ -curl_mime *curl_mime_init(struct Curl_easy *easy) +curl_mime *curl_mime_init(void *easy) { curl_mime *mime; @@ -1289,9 +1293,9 @@ curl_mime *curl_mime_init(struct Curl_easy *easy) mime->lastpart = NULL; memset(mime->boundary, '-', MIME_BOUNDARY_DASHES); - if(Curl_rand_hex(easy, - (unsigned char *) &mime->boundary[MIME_BOUNDARY_DASHES], - MIME_RAND_BOUNDARY_CHARS + 1)) { + if(Curl_rand_alnum(easy, + (unsigned char *) &mime->boundary[MIME_BOUNDARY_DASHES], + MIME_RAND_BOUNDARY_CHARS + 1)) { /* failed to get random separator, bail out */ free(mime); return NULL; @@ -1352,7 +1356,7 @@ CURLcode curl_mime_name(curl_mimepart *part, const char *name) return CURLE_OK; } -/* Set mime part remote file name. */ +/* Set mime part remote filename. */ CURLcode curl_mime_filename(curl_mimepart *part, const char *filename) { if(!part) @@ -1371,27 +1375,22 @@ CURLcode curl_mime_filename(curl_mimepart *part, const char *filename) /* Set mime part content from memory data. */ CURLcode curl_mime_data(curl_mimepart *part, - const char *data, size_t datasize) + const char *ptr, size_t datasize) { if(!part) return CURLE_BAD_FUNCTION_ARGUMENT; cleanup_part_content(part); - if(data) { + if(ptr) { if(datasize == CURL_ZERO_TERMINATED) - datasize = strlen(data); + datasize = strlen(ptr); - part->data = malloc(datasize + 1); + part->data = Curl_memdup0(ptr, datasize); if(!part->data) return CURLE_OUT_OF_MEMORY; part->datasize = datasize; - - if(datasize) - memcpy(part->data, data, datasize); - part->data[datasize] = '\0'; /* Set a null terminator as sentinel. */ - part->readfunc = mime_mem_read; part->seekfunc = mime_mem_seek; part->freefunc = mime_mem_free; @@ -1416,36 +1415,35 @@ CURLcode curl_mime_filedata(curl_mimepart *part, const char *filename) char *base; struct_stat sbuf; - if(stat(filename, &sbuf) || access(filename, R_OK)) + if(stat(filename, &sbuf)) result = CURLE_READ_ERROR; - - part->data = strdup(filename); - if(!part->data) - result = CURLE_OUT_OF_MEMORY; - - part->datasize = -1; - if(!result && S_ISREG(sbuf.st_mode)) { - part->datasize = filesize(filename, sbuf); - part->seekfunc = mime_file_seek; - } - - part->readfunc = mime_file_read; - part->freefunc = mime_file_free; - part->kind = MIMEKIND_FILE; - - /* As a side effect, set the filename to the current file's base name. - It is possible to withdraw this by explicitly calling - curl_mime_filename() with a NULL filename argument after the current - call. */ - base = strippath(filename); - if(!base) - result = CURLE_OUT_OF_MEMORY; else { - CURLcode res = curl_mime_filename(part, base); + part->data = strdup(filename); + if(!part->data) + result = CURLE_OUT_OF_MEMORY; + else { + part->datasize = -1; + if(S_ISREG(sbuf.st_mode)) { + part->datasize = filesize(filename, sbuf); + part->seekfunc = mime_file_seek; + } - if(res) - result = res; - free(base); + part->readfunc = mime_file_read; + part->freefunc = mime_file_free; + part->kind = MIMEKIND_FILE; + + /* As a side effect, set the filename to the current file's base name. + It is possible to withdraw this by explicitly calling + curl_mime_filename() with a NULL filename argument after the current + call. */ + base = strippath(filename); + if(!base) + result = CURLE_OUT_OF_MEMORY; + else { + result = curl_mime_filename(part, base); + free(base); + } + } } } return result; @@ -1501,7 +1499,7 @@ CURLcode curl_mime_headers(curl_mimepart *part, if(part->flags & MIME_USERHEADERS_OWNER) { if(part->userheaders != headers) /* Allow setting twice the same list. */ curl_slist_free_all(part->userheaders); - part->flags &= ~MIME_USERHEADERS_OWNER; + part->flags &= ~(unsigned int)MIME_USERHEADERS_OWNER; } part->userheaders = headers; if(headers && take_ownership) @@ -1558,7 +1556,7 @@ CURLcode Curl_mime_set_subparts(curl_mimepart *part, while(root->parent && root->parent->parent) root = root->parent->parent; if(subparts == root) { - /* Can't add as a subpart of itself. */ + /* cannot add as a subpart of itself. */ return CURLE_BAD_FUNCTION_ARGUMENT; } } @@ -1566,7 +1564,8 @@ CURLcode Curl_mime_set_subparts(curl_mimepart *part, subparts->parent = part; /* Subparts are processed internally: no read callback. */ part->seekfunc = mime_subparts_seek; - part->freefunc = take_ownership? mime_subparts_free: mime_subparts_unbind; + part->freefunc = take_ownership ? mime_subparts_free : + mime_subparts_unbind; part->arg = subparts; part->datasize = -1; part->kind = MIMEKIND_MULTIPART; @@ -1591,6 +1590,8 @@ size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, void *instream) (void) size; /* Always 1. */ + /* TODO: this loop is broken. If `nitems` is <= 4, some encoders will + * return STOP_FILLING without adding any data and this loops infinitely. */ do { hasread = FALSE; ret = readback_part(part, buffer, nitems, &hasread); @@ -1606,10 +1607,10 @@ size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, void *instream) } /* Rewind mime stream. */ -CURLcode Curl_mime_rewind(curl_mimepart *part) +static CURLcode mime_rewind(curl_mimepart *part) { - return mime_part_rewind(part) == CURL_SEEKFUNC_OK? - CURLE_OK: CURLE_SEND_FAIL_REWIND; + return mime_part_rewind(part) == CURL_SEEKFUNC_OK ? + CURLE_OK : CURLE_SEND_FAIL_REWIND; } /* Compute header list size. */ @@ -1638,7 +1639,7 @@ static curl_off_t multipart_size(curl_mime *mime) size = boundarysize; /* Final boundary - CRLF after headers. */ for(part = mime->firstpart; part; part = part->nextpart) { - curl_off_t sz = Curl_mime_size(part); + curl_off_t sz = mime_size(part); if(sz < 0) size = sz; @@ -1651,7 +1652,7 @@ static curl_off_t multipart_size(curl_mime *mime) } /* Get/compute mime size. */ -curl_off_t Curl_mime_size(curl_mimepart *part) +static curl_off_t mime_size(curl_mimepart *part) { curl_off_t size; @@ -1666,7 +1667,8 @@ curl_off_t Curl_mime_size(curl_mimepart *part) if(size >= 0 && !(part->flags & MIME_BODY_ONLY)) { /* Compute total part size. */ size += slist_size(part->curlheaders, 2, NULL, 0); - size += slist_size(part->userheaders, 2, STRCONST("Content-Type")); + size += slist_size(part->userheaders, 2, + STRCONST("Content-Type")); size += 2; /* CRLF after headers. */ } return size; @@ -1681,7 +1683,7 @@ CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...) va_list ap; va_start(ap, fmt); - s = curl_mvaprintf(fmt, ap); + s = vaprintf(fmt, ap); va_end(ap); if(s) { @@ -1692,7 +1694,7 @@ CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...) free(s); } - return hdr? CURLE_OK: CURLE_OUT_OF_MEMORY; + return hdr ? CURLE_OK : CURLE_OUT_OF_MEMORY; } /* Add a content type header. */ @@ -1700,8 +1702,8 @@ static CURLcode add_content_type(struct curl_slist **slp, const char *type, const char *boundary) { return Curl_mime_add_header(slp, "Content-Type: %s%s%s", type, - boundary? "; boundary=": "", - boundary? boundary: ""); + boundary ? "; boundary=" : "", + boundary ? boundary : ""); } const char *Curl_mime_contenttype(const char *filename) @@ -1774,7 +1776,7 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, curl_slist_free_all(part->curlheaders); part->curlheaders = NULL; - /* Be sure we won't access old headers later. */ + /* Be sure we will not access old headers later. */ if(part->state.state == MIMESTATE_CURLHEADERS) mimesetstate(&part->state, MIMESTATE_CURLHEADERS, NULL); @@ -1841,12 +1843,12 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, ret = Curl_mime_add_header(&part->curlheaders, "Content-Disposition: %s%s%s%s%s%s%s", disposition, - name? "; name=\"": "", - name? name: "", - name? "\"": "", - filename? "; filename=\"": "", - filename? filename: "", - filename? "\"": ""); + name ? "; name=\"" : "", + name ? name : "", + name ? "\"" : "", + filename ? "; filename=\"" : "", + filename ? filename : "", + filename ? "\"" : ""); Curl_safefree(name); Curl_safefree(filename); if(ret) @@ -1900,7 +1902,7 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, } /* Recursively reset paused status in the given part. */ -void Curl_mime_unpause(curl_mimepart *part) +static void mime_unpause(curl_mimepart *part) { if(part) { if(part->lastreadstatus == CURL_READFUNC_PAUSE) @@ -1912,12 +1914,264 @@ void Curl_mime_unpause(curl_mimepart *part) curl_mimepart *subpart; for(subpart = mime->firstpart; subpart; subpart = subpart->nextpart) - Curl_mime_unpause(subpart); + mime_unpause(subpart); + } + } + } +} + +struct cr_mime_ctx { + struct Curl_creader super; + curl_mimepart *part; + curl_off_t total_len; + curl_off_t read_len; + CURLcode error_result; + BIT(seen_eos); + BIT(errored); +}; + +static CURLcode cr_mime_init(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + ctx->total_len = -1; + ctx->read_len = 0; + return CURLE_OK; +} + +/* Real client reader to installed client callbacks. */ +static CURLcode cr_mime_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_mime_ctx *ctx = reader->ctx; + size_t nread; + + + /* Once we have errored, we will return the same error forever */ + if(ctx->errored) { + CURL_TRC_READ(data, "cr_mime_read(len=%zu) is errored -> %d, eos=0", + blen, ctx->error_result); + *pnread = 0; + *peos = FALSE; + return ctx->error_result; + } + if(ctx->seen_eos) { + CURL_TRC_READ(data, "cr_mime_read(len=%zu) seen eos -> 0, eos=1", blen); + *pnread = 0; + *peos = TRUE; + return CURLE_OK; + } + /* respect length limitations */ + if(ctx->total_len >= 0) { + curl_off_t remain = ctx->total_len - ctx->read_len; + if(remain <= 0) + blen = 0; + else if(remain < (curl_off_t)blen) + blen = (size_t)remain; + } + + if(blen <= 4) { + /* TODO: Curl_mime_read() may go into an infinite loop when reading + * such small lengths. Returning 0 bytes read is a fix that only works + * as request upload buffers will get flushed eventually and larger + * reads will happen again. */ + CURL_TRC_READ(data, "cr_mime_read(len=%zu), too small, return", blen); + *pnread = 0; + *peos = FALSE; + goto out; + } + + nread = Curl_mime_read(buf, 1, blen, ctx->part); + CURL_TRC_READ(data, "cr_mime_read(len=%zu), mime_read() -> %zd", + blen, nread); + + switch(nread) { + case 0: + if((ctx->total_len >= 0) && (ctx->read_len < ctx->total_len)) { + failf(data, "client mime read EOF fail, " + "only %"FMT_OFF_T"/%"FMT_OFF_T + " of needed bytes read", ctx->read_len, ctx->total_len); + return CURLE_READ_ERROR; + } + *pnread = 0; + *peos = TRUE; + ctx->seen_eos = TRUE; + break; + + case CURL_READFUNC_ABORT: + failf(data, "operation aborted by callback"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_ABORTED_BY_CALLBACK; + return CURLE_ABORTED_BY_CALLBACK; + + case CURL_READFUNC_PAUSE: + /* CURL_READFUNC_PAUSE pauses read callbacks that feed socket writes */ + CURL_TRC_READ(data, "cr_mime_read(len=%zu), paused by callback", blen); + data->req.keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */ + *pnread = 0; + *peos = FALSE; + break; /* nothing was read */ + + case STOP_FILLING: + case READ_ERROR: + failf(data, "read error getting mime data"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_READ_ERROR; + return CURLE_READ_ERROR; + + default: + if(nread > blen) { + /* the read function returned a too large value */ + failf(data, "read function returned funny value"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_READ_ERROR; + return CURLE_READ_ERROR; + } + ctx->read_len += nread; + if(ctx->total_len >= 0) + ctx->seen_eos = (ctx->read_len >= ctx->total_len); + *pnread = nread; + *peos = ctx->seen_eos; + break; + } + +out: + CURL_TRC_READ(data, "cr_mime_read(len=%zu, total=%" FMT_OFF_T + ", read=%"FMT_OFF_T") -> %d, %zu, %d", + blen, ctx->total_len, ctx->read_len, CURLE_OK, *pnread, *peos); + return CURLE_OK; +} + +static bool cr_mime_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + return ctx->read_len > 0; +} + +static curl_off_t cr_mime_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + return ctx->total_len; +} + +static CURLcode cr_mime_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset) +{ + struct cr_mime_ctx *ctx = reader->ctx; + + if(offset > 0) { + curl_off_t passed = 0; + + do { + char scratch[4*1024]; + size_t readthisamountnow = + (offset - passed > (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : + curlx_sotouz(offset - passed); + size_t nread; + + nread = Curl_mime_read(scratch, 1, readthisamountnow, ctx->part); + passed += (curl_off_t)nread; + if((nread == 0) || (nread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Could only read %" FMT_OFF_T + " bytes from the mime post", passed); + return CURLE_READ_ERROR; + } + } while(passed < offset); + + /* now, decrease the size of the read */ + if(ctx->total_len > 0) { + ctx->total_len -= offset; + + if(ctx->total_len <= 0) { + failf(data, "Mime post already completely uploaded"); + return CURLE_PARTIAL_FILE; } } + /* we have passed, proceed as normal */ } + return CURLE_OK; +} + +static CURLcode cr_mime_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + CURLcode result = mime_rewind(ctx->part); + if(result) + failf(data, "Cannot rewind mime/post data"); + return result; } +static CURLcode cr_mime_unpause(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + mime_unpause(ctx->part); + return CURLE_OK; +} + +static bool cr_mime_is_paused(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + return (ctx->part && ctx->part->lastreadstatus == CURL_READFUNC_PAUSE); +} + +static const struct Curl_crtype cr_mime = { + "cr-mime", + cr_mime_init, + cr_mime_read, + Curl_creader_def_close, + cr_mime_needs_rewind, + cr_mime_total_length, + cr_mime_resume_from, + cr_mime_rewind, + cr_mime_unpause, + cr_mime_is_paused, + Curl_creader_def_done, + sizeof(struct cr_mime_ctx) +}; + +CURLcode Curl_creader_set_mime(struct Curl_easy *data, curl_mimepart *part) +{ + struct Curl_creader *r; + struct cr_mime_ctx *ctx; + CURLcode result; + + result = Curl_creader_create(&r, data, &cr_mime, CURL_CR_CLIENT); + if(result) + return result; + ctx = r->ctx; + ctx->part = part; + /* Make sure we will read the entire mime structure. */ + result = mime_rewind(ctx->part); + if(result) { + Curl_creader_free(data, r); + return result; + } + ctx->total_len = mime_size(ctx->part); + + return Curl_creader_set(data, r); +} #else /* !CURL_DISABLE_MIME && (!CURL_DISABLE_HTTP || !CURL_DISABLE_SMTP || !CURL_DISABLE_IMAP) */ diff --git a/deps/curl/lib/mime.h b/deps/curl/lib/mime.h index 04adf2d2476..5073a38f709 100644 --- a/deps/curl/lib/mime.h +++ b/deps/curl/lib/mime.h @@ -27,7 +27,7 @@ #include "curl_setup.h" #define MIME_BOUNDARY_DASHES 24 /* leading boundary dashes */ -#define MIME_RAND_BOUNDARY_CHARS 16 /* Nb. of random boundary chars. */ +#define MIME_RAND_BOUNDARY_CHARS 22 /* Nb. of random boundary chars. */ #define MAX_ENCODED_LINE_LENGTH 76 /* Maximum encoded line length. */ #define ENCODING_BUFFER_SIZE 256 /* Encoding temp buffers size. */ @@ -112,7 +112,7 @@ struct curl_mimepart { curl_mimepart *nextpart; /* Forward linked list. */ enum mimekind kind; /* The part kind. */ unsigned int flags; /* Flags. */ - char *data; /* Memory data or file name. */ + char *data; /* Memory data or filename. */ curl_read_callback readfunc; /* Read function. */ curl_seek_callback seekfunc; /* Seek function. */ curl_free_callback freefunc; /* Argument free function. */ @@ -121,7 +121,7 @@ struct curl_mimepart { struct curl_slist *curlheaders; /* Part headers. */ struct curl_slist *userheaders; /* Part headers. */ char *mimetype; /* Part mime type. */ - char *filename; /* Remote file name. */ + char *filename; /* Remote filename. */ char *name; /* Data name. */ curl_off_t datasize; /* Expected data size. */ struct mime_state state; /* Current readback state. */ @@ -130,7 +130,8 @@ struct curl_mimepart { size_t lastreadstatus; /* Last read callback returned status. */ }; -CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...); +CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...) + CURL_PRINTF(2, 3); #if !defined(CURL_DISABLE_MIME) && (!defined(CURL_DISABLE_HTTP) || \ !defined(CURL_DISABLE_SMTP) || \ @@ -150,12 +151,15 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, const char *contenttype, const char *disposition, enum mimestrategy strategy); -curl_off_t Curl_mime_size(struct curl_mimepart *part); size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, void *instream); -CURLcode Curl_mime_rewind(struct curl_mimepart *part); const char *Curl_mime_contenttype(const char *filename); -void Curl_mime_unpause(struct curl_mimepart *part); + +/** + * Install a client reader as upload source that reads the given + * mime part. + */ +CURLcode Curl_creader_set_mime(struct Curl_easy *data, curl_mimepart *part); #else /* if disabled */ @@ -164,10 +168,8 @@ void Curl_mime_unpause(struct curl_mimepart *part); #define Curl_mime_duppart(x,y,z) CURLE_OK /* Nothing to duplicate. Succeed */ #define Curl_mime_set_subparts(a,b,c) CURLE_NOT_BUILT_IN #define Curl_mime_prepare_headers(a,b,c,d,e) CURLE_NOT_BUILT_IN -#define Curl_mime_size(x) (curl_off_t) -1 #define Curl_mime_read NULL -#define Curl_mime_rewind(x) ((void)x, CURLE_NOT_BUILT_IN) -#define Curl_mime_unpause(x) +#define Curl_creader_set_mime(x,y) ((void)x, CURLE_NOT_BUILT_IN) #endif diff --git a/deps/curl/lib/mprintf.c b/deps/curl/lib/mprintf.c index af5d753d6af..6576f3ebf9a 100644 --- a/deps/curl/lib/mprintf.c +++ b/deps/curl/lib/mprintf.c @@ -20,26 +20,11 @@ * * SPDX-License-Identifier: curl * - * - * Purpose: - * A merge of Bjorn Reese's format() function and Daniel's dsprintf() - * 1.0. A full blooded printf() clone with full support for $ - * everywhere (parameters, widths and precisions) including variabled - * sized parameters (like doubles, long longs, long doubles and even - * void * in 64-bit architectures). - * - * Current restrictions: - * - Max 128 parameters - * - No 'long double' support. - * - * If you ever want truly portable and good *printf() clones, the project that - * took on from here is named 'Trio' and you find more details on the trio web - * page at https://daniel.haxx.se/projects/trio/ */ #include "curl_setup.h" #include "dynbuf.h" -#include +#include "curl_printf.h" #include "curl_memory.h" /* The last #include file should be: */ @@ -62,18 +47,6 @@ # endif #endif -/* - * Non-ANSI integer extensions - */ - -#if (defined(__BORLANDC__) && (__BORLANDC__ >= 0x520)) || \ - (defined(__POCC__) && defined(_MSC_VER)) || \ - (defined(_WIN32_WCE)) || \ - (defined(__MINGW32__)) || \ - (defined(_MSC_VER) && (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64)) -# define MP_HAVE_INT_EXTENSIONS -#endif - /* * Max integer data types that mprintf.c is capable */ @@ -88,7 +61,8 @@ #define BUFFSIZE 326 /* buffer for long-to-str and float-to-str calcs, should fit negative DBL_MAX (317 letters) */ -#define MAX_PARAMETERS 128 /* lame static limit */ +#define MAX_PARAMETERS 128 /* number of input arguments */ +#define MAX_SEGMENTS 128 /* number of output segments */ #ifdef __AMIGA__ # undef FORMAT_INT @@ -100,67 +74,86 @@ static const char lower_digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; /* Upper-case digits. */ static const char upper_digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; -#define OUTCHAR(x) \ - do { \ - if(stream((unsigned char)(x), (FILE *)data) != -1) \ - done++; \ - else \ - return done; /* return immediately on failure */ \ +#define OUTCHAR(x) \ + do { \ + if(!stream((unsigned char)x, userp)) \ + done++; \ + else \ + return done; /* return on failure */ \ } while(0) /* Data type to read from the arglist */ typedef enum { - FORMAT_UNKNOWN = 0, FORMAT_STRING, FORMAT_PTR, - FORMAT_INT, FORMAT_INTPTR, + FORMAT_INT, FORMAT_LONG, FORMAT_LONGLONG, + FORMAT_INTU, + FORMAT_LONGU, + FORMAT_LONGLONGU, FORMAT_DOUBLE, FORMAT_LONGDOUBLE, - FORMAT_WIDTH /* For internal use */ + FORMAT_WIDTH, + FORMAT_PRECISION } FormatType; /* conversion and display flags */ enum { - FLAGS_NEW = 0, - FLAGS_SPACE = 1<<0, - FLAGS_SHOWSIGN = 1<<1, - FLAGS_LEFT = 1<<2, - FLAGS_ALT = 1<<3, - FLAGS_SHORT = 1<<4, - FLAGS_LONG = 1<<5, - FLAGS_LONGLONG = 1<<6, - FLAGS_LONGDOUBLE = 1<<7, - FLAGS_PAD_NIL = 1<<8, - FLAGS_UNSIGNED = 1<<9, - FLAGS_OCTAL = 1<<10, - FLAGS_HEX = 1<<11, - FLAGS_UPPER = 1<<12, - FLAGS_WIDTH = 1<<13, /* '*' or '*$' used */ - FLAGS_WIDTHPARAM = 1<<14, /* width PARAMETER was specified */ - FLAGS_PREC = 1<<15, /* precision was specified */ - FLAGS_PRECPARAM = 1<<16, /* precision PARAMETER was specified */ - FLAGS_CHAR = 1<<17, /* %c story */ - FLAGS_FLOATE = 1<<18, /* %e or %E */ - FLAGS_FLOATG = 1<<19 /* %g or %G */ + FLAGS_SPACE = 1 << 0, + FLAGS_SHOWSIGN = 1 << 1, + FLAGS_LEFT = 1 << 2, + FLAGS_ALT = 1 << 3, + FLAGS_SHORT = 1 << 4, + FLAGS_LONG = 1 << 5, + FLAGS_LONGLONG = 1 << 6, + FLAGS_LONGDOUBLE = 1 << 7, + FLAGS_PAD_NIL = 1 << 8, + FLAGS_UNSIGNED = 1 << 9, + FLAGS_OCTAL = 1 << 10, + FLAGS_HEX = 1 << 11, + FLAGS_UPPER = 1 << 12, + FLAGS_WIDTH = 1 << 13, /* '*' or '*$' used */ + FLAGS_WIDTHPARAM = 1 << 14, /* width PARAMETER was specified */ + FLAGS_PREC = 1 << 15, /* precision was specified */ + FLAGS_PRECPARAM = 1 << 16, /* precision PARAMETER was specified */ + FLAGS_CHAR = 1 << 17, /* %c story */ + FLAGS_FLOATE = 1 << 18, /* %e or %E */ + FLAGS_FLOATG = 1 << 19, /* %g or %G */ + FLAGS_SUBSTR = 1 << 20 /* no input, only substring */ }; -struct va_stack { - FormatType type; - int flags; - long width; /* width OR width parameter number */ - long precision; /* precision OR precision parameter number */ +enum { + DOLLAR_UNKNOWN, + DOLLAR_NOPE, + DOLLAR_USE +}; + +/* + * Describes an input va_arg type and hold its value. + */ +struct va_input { + FormatType type; /* FormatType */ union { char *str; void *ptr; - union { - mp_intmax_t as_signed; - mp_uintmax_t as_unsigned; - } num; + mp_intmax_t nums; /* signed */ + mp_uintmax_t numu; /* unsigned */ double dnum; - } data; + } val; +}; + +/* + * Describes an output segment. + */ +struct outsegment { + int width; /* width OR width parameter number */ + int precision; /* precision OR precision parameter number */ + unsigned int flags; + unsigned int input; /* input argument array index */ + char *start; /* format string start to output */ + size_t outlen; /* number of bytes from the format string to output */ }; struct nsprintf { @@ -171,118 +164,124 @@ struct nsprintf { struct asprintf { struct dynbuf *b; - bool fail; /* if an alloc has failed and thus the output is not the complete - data */ + char merr; }; -static long dprintf_DollarString(char *input, char **end) -{ - int number = 0; - while(ISDIGIT(*input)) { - if(number < MAX_PARAMETERS) { - number *= 10; - number += *input - '0'; - } - input++; - } - if(number <= MAX_PARAMETERS && ('$' == *input)) { - *end = ++input; - return number; - } - return 0; -} +/* the provided input number is 1-based but this returns the number 0-based. -static bool dprintf_IsQualifierNoDollar(const char *fmt) + returns -1 if no valid number was provided. +*/ +static int dollarstring(char *input, char **end) { -#if defined(MP_HAVE_INT_EXTENSIONS) - if(!strncmp(fmt, "I32", 3) || !strncmp(fmt, "I64", 3)) { - return TRUE; - } -#endif - - switch(*fmt) { - case '-': case '+': case ' ': case '#': case '.': - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case 'h': case 'l': case 'L': case 'z': case 'q': - case '*': case 'O': -#if defined(MP_HAVE_INT_EXTENSIONS) - case 'I': -#endif - return TRUE; + if(ISDIGIT(*input)) { + int number = 0; + do { + if(number < MAX_PARAMETERS) { + number *= 10; + number += *input - '0'; + } + input++; + } while(ISDIGIT(*input)); - default: - return FALSE; + if(number && (number <= MAX_PARAMETERS) && ('$' == *input)) { + *end = ++input; + return number - 1; + } } + return -1; } -/****************************************************************** +/* + * Parse the format string. * - * Pass 1: - * Create an index with the type of each parameter entry and its - * value (may vary in size) + * Create two arrays. One describes the inputs, one describes the outputs. * * Returns zero on success. - * - ******************************************************************/ + */ -static int dprintf_Pass1(const char *format, struct va_stack *vto, - char **endpos, va_list arglist) +#define PFMT_OK 0 +#define PFMT_DOLLAR 1 /* bad dollar for main param */ +#define PFMT_DOLLARWIDTH 2 /* bad dollar use for width */ +#define PFMT_DOLLARPREC 3 /* bad dollar use for precision */ +#define PFMT_MANYARGS 4 /* too many input arguments used */ +#define PFMT_PREC 5 /* precision overflow */ +#define PFMT_PRECMIX 6 /* bad mix of precision specifiers */ +#define PFMT_WIDTH 7 /* width overflow */ +#define PFMT_INPUTGAP 8 /* gap in arguments */ +#define PFMT_WIDTHARG 9 /* attempted to use same arg twice, for width */ +#define PFMT_PRECARG 10 /* attempted to use same arg twice, for prec */ +#define PFMT_MANYSEGS 11 /* maxed out output segments */ + +static int parsefmt(const char *format, + struct outsegment *out, + struct va_input *in, + int *opieces, + int *ipieces, va_list arglist) { char *fmt = (char *)format; int param_num = 0; - long this_param; - long width; - long precision; - int flags; - long max_param = 0; - long i; + int param; + int width; + int precision; + unsigned int flags; + FormatType type; + int max_param = -1; + int i; + int ocount = 0; + unsigned char usedinput[MAX_PARAMETERS/8]; + size_t outlen = 0; + struct outsegment *optr; + int use_dollar = DOLLAR_UNKNOWN; + char *start = fmt; + + /* clear, set a bit for each used input */ + memset(usedinput, 0, sizeof(usedinput)); while(*fmt) { - if(*fmt++ == '%') { + if(*fmt == '%') { + struct va_input *iptr; + bool loopit = TRUE; + fmt++; + outlen = (size_t)(fmt - start - 1); if(*fmt == '%') { + /* this means a %% that should be output only as %. Create an output + segment. */ + if(outlen) { + optr = &out[ocount++]; + if(ocount > MAX_SEGMENTS) + return PFMT_MANYSEGS; + optr->input = 0; + optr->flags = FLAGS_SUBSTR; + optr->start = start; + optr->outlen = outlen; + } + start = fmt; fmt++; continue; /* while */ } - flags = FLAGS_NEW; - - /* Handle the positional case (N$) */ - - param_num++; + flags = 0; + width = precision = 0; - this_param = dprintf_DollarString(fmt, &fmt); - if(0 == this_param) - /* we got no positional, get the next counter */ - this_param = param_num; + if(use_dollar != DOLLAR_NOPE) { + param = dollarstring(fmt, &fmt); + if(param < 0) { + if(use_dollar == DOLLAR_USE) + /* illegal combo */ + return PFMT_DOLLAR; - if(this_param > max_param) - max_param = this_param; - - /* - * The parameter with number 'i' should be used. Next, we need - * to get SIZE and TYPE of the parameter. Add the information - * to our array. - */ - - width = 0; - precision = 0; - - /* Handle the flags */ - - while(dprintf_IsQualifierNoDollar(fmt)) { -#if defined(MP_HAVE_INT_EXTENSIONS) - if(!strncmp(fmt, "I32", 3)) { - flags |= FLAGS_LONG; - fmt += 3; - } - else if(!strncmp(fmt, "I64", 3)) { - flags |= FLAGS_LONGLONG; - fmt += 3; + /* we got no positional, just get the next arg */ + param = -1; + use_dollar = DOLLAR_NOPE; } else -#endif + use_dollar = DOLLAR_USE; + } + else + param = -1; + /* Handle the flags */ + while(loopit) { switch(*fmt++) { case ' ': flags |= FLAGS_SPACE; @@ -292,7 +291,7 @@ static int dprintf_Pass1(const char *format, struct va_stack *vto, break; case '-': flags |= FLAGS_LEFT; - flags &= ~FLAGS_PAD_NIL; + flags &= ~(unsigned int)FLAGS_PAD_NIL; break; case '#': flags |= FLAGS_ALT; @@ -300,42 +299,66 @@ static int dprintf_Pass1(const char *format, struct va_stack *vto, case '.': if('*' == *fmt) { /* The precision is picked from a specified parameter */ - flags |= FLAGS_PRECPARAM; fmt++; - param_num++; - i = dprintf_DollarString(fmt, &fmt); - if(i) - precision = i; + if(use_dollar == DOLLAR_USE) { + precision = dollarstring(fmt, &fmt); + if(precision < 0) + /* illegal combo */ + return PFMT_DOLLARPREC; + } else - precision = param_num; - - if(precision > max_param) - max_param = precision; + /* get it from the next argument */ + precision = -1; } else { + bool is_neg = FALSE; flags |= FLAGS_PREC; - precision = strtol(fmt, &fmt, 10); + precision = 0; + if('-' == *fmt) { + is_neg = TRUE; + fmt++; + } + while(ISDIGIT(*fmt)) { + if(precision > INT_MAX/10) + return PFMT_PREC; + precision *= 10; + precision += *fmt - '0'; + fmt++; + } + if(is_neg) + precision = -precision; } if((flags & (FLAGS_PREC | FLAGS_PRECPARAM)) == (FLAGS_PREC | FLAGS_PRECPARAM)) /* it is not permitted to use both kinds of precision for the same argument */ - return 1; + return PFMT_PRECMIX; break; case 'h': flags |= FLAGS_SHORT; break; -#if defined(MP_HAVE_INT_EXTENSIONS) +#if defined(_WIN32) || defined(_WIN32_WCE) case 'I': + /* Non-ANSI integer extensions I32 I64 */ + if((fmt[0] == '3') && (fmt[1] == '2')) { + flags |= FLAGS_LONG; + fmt += 2; + } + else if((fmt[0] == '6') && (fmt[1] == '4')) { + flags |= FLAGS_LONGLONG; + fmt += 2; + } + else { #if (SIZEOF_CURL_OFF_T > SIZEOF_LONG) - flags |= FLAGS_LONGLONG; + flags |= FLAGS_LONGLONG; #else - flags |= FLAGS_LONG; + flags |= FLAGS_LONG; #endif + } break; -#endif +#endif /* _WIN32 || _WIN32_WCE */ case 'l': if(flags & FLAGS_LONG) flags |= FLAGS_LONGLONG; @@ -367,401 +390,436 @@ static int dprintf_Pass1(const char *format, struct va_stack *vto, case '0': if(!(flags & FLAGS_LEFT)) flags |= FLAGS_PAD_NIL; - /* FALLTHROUGH */ + FALLTHROUGH(); case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': flags |= FLAGS_WIDTH; - width = strtol(fmt-1, &fmt, 10); + width = 0; + fmt--; + do { + if(width > INT_MAX/10) + return PFMT_WIDTH; + width *= 10; + width += *fmt - '0'; + fmt++; + } while(ISDIGIT(*fmt)); break; - case '*': /* Special case */ + case '*': /* read width from argument list */ flags |= FLAGS_WIDTHPARAM; - param_num++; - - i = dprintf_DollarString(fmt, &fmt); - if(i) - width = i; + if(use_dollar == DOLLAR_USE) { + width = dollarstring(fmt, &fmt); + if(width < 0) + /* illegal combo */ + return PFMT_DOLLARWIDTH; + } else - width = param_num; - if(width > max_param) - max_param = width; + /* pick from the next argument */ + width = -1; break; - case '\0': - fmt--; default: + loopit = FALSE; + fmt--; break; - } - } /* switch */ - - /* Handle the specifier */ - - i = this_param - 1; - - if((i < 0) || (i >= MAX_PARAMETERS)) - /* out of allowed range */ - return 1; + } /* switch */ + } /* while */ switch(*fmt) { case 'S': flags |= FLAGS_ALT; - /* FALLTHROUGH */ + FALLTHROUGH(); case 's': - vto[i].type = FORMAT_STRING; + type = FORMAT_STRING; break; case 'n': - vto[i].type = FORMAT_INTPTR; + type = FORMAT_INTPTR; break; case 'p': - vto[i].type = FORMAT_PTR; + type = FORMAT_PTR; break; - case 'd': case 'i': - vto[i].type = FORMAT_INT; + case 'd': + case 'i': + if(flags & FLAGS_LONGLONG) + type = FORMAT_LONGLONG; + else if(flags & FLAGS_LONG) + type = FORMAT_LONG; + else + type = FORMAT_INT; break; case 'u': - vto[i].type = FORMAT_INT; + if(flags & FLAGS_LONGLONG) + type = FORMAT_LONGLONGU; + else if(flags & FLAGS_LONG) + type = FORMAT_LONGU; + else + type = FORMAT_INTU; flags |= FLAGS_UNSIGNED; break; case 'o': - vto[i].type = FORMAT_INT; - flags |= FLAGS_OCTAL; + if(flags & FLAGS_LONGLONG) + type = FORMAT_LONGLONGU; + else if(flags & FLAGS_LONG) + type = FORMAT_LONGU; + else + type = FORMAT_INTU; + flags |= FLAGS_OCTAL|FLAGS_UNSIGNED; break; case 'x': - vto[i].type = FORMAT_INT; + if(flags & FLAGS_LONGLONG) + type = FORMAT_LONGLONGU; + else if(flags & FLAGS_LONG) + type = FORMAT_LONGU; + else + type = FORMAT_INTU; flags |= FLAGS_HEX|FLAGS_UNSIGNED; break; case 'X': - vto[i].type = FORMAT_INT; + if(flags & FLAGS_LONGLONG) + type = FORMAT_LONGLONGU; + else if(flags & FLAGS_LONG) + type = FORMAT_LONGU; + else + type = FORMAT_INTU; flags |= FLAGS_HEX|FLAGS_UPPER|FLAGS_UNSIGNED; break; case 'c': - vto[i].type = FORMAT_INT; + type = FORMAT_INT; flags |= FLAGS_CHAR; break; case 'f': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; break; case 'e': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATE; break; case 'E': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATE|FLAGS_UPPER; break; case 'g': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATG; break; case 'G': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATG|FLAGS_UPPER; break; default: - vto[i].type = FORMAT_UNKNOWN; - break; + /* invalid instruction, disregard and continue */ + continue; } /* switch */ - vto[i].flags = flags; - vto[i].width = width; - vto[i].precision = precision; - if(flags & FLAGS_WIDTHPARAM) { - /* we have the width specified from a parameter, so we make that - parameter's info setup properly */ - long k = width - 1; - if((k < 0) || (k >= MAX_PARAMETERS)) - /* out of allowed range */ - return 1; - vto[i].width = k; - vto[k].type = FORMAT_WIDTH; - vto[k].flags = FLAGS_NEW; - /* can't use width or precision of width! */ - vto[k].width = 0; - vto[k].precision = 0; + if(width < 0) + width = param_num++; + else { + /* if this identifies a parameter already used, this + is illegal */ + if(usedinput[width/8] & (1 << (width&7))) + return PFMT_WIDTHARG; + } + if(width >= MAX_PARAMETERS) + return PFMT_MANYARGS; + if(width >= max_param) + max_param = width; + + in[width].type = FORMAT_WIDTH; + /* mark as used */ + usedinput[width/8] |= (unsigned char)(1 << (width&7)); } + if(flags & FLAGS_PRECPARAM) { - /* we have the precision specified from a parameter, so we make that - parameter's info setup properly */ - long k = precision - 1; - if((k < 0) || (k >= MAX_PARAMETERS)) - /* out of allowed range */ - return 1; - vto[i].precision = k; - vto[k].type = FORMAT_WIDTH; - vto[k].flags = FLAGS_NEW; - /* can't use width or precision of width! */ - vto[k].width = 0; - vto[k].precision = 0; + if(precision < 0) + precision = param_num++; + else { + /* if this identifies a parameter already used, this + is illegal */ + if(usedinput[precision/8] & (1 << (precision&7))) + return PFMT_PRECARG; + } + if(precision >= MAX_PARAMETERS) + return PFMT_MANYARGS; + if(precision >= max_param) + max_param = precision; + + in[precision].type = FORMAT_PRECISION; + usedinput[precision/8] |= (unsigned char)(1 << (precision&7)); } - *endpos++ = fmt + ((*fmt == '\0') ? 0 : 1); /* end of this sequence */ + + /* Handle the specifier */ + if(param < 0) + param = param_num++; + if(param >= MAX_PARAMETERS) + return PFMT_MANYARGS; + if(param >= max_param) + max_param = param; + + iptr = &in[param]; + iptr->type = type; + + /* mark this input as used */ + usedinput[param/8] |= (unsigned char)(1 << (param&7)); + + fmt++; + optr = &out[ocount++]; + if(ocount > MAX_SEGMENTS) + return PFMT_MANYSEGS; + optr->input = (unsigned int)param; + optr->flags = flags; + optr->width = width; + optr->precision = precision; + optr->start = start; + optr->outlen = outlen; + start = fmt; } + else + fmt++; } - /* Read the arg list parameters into our data list */ - for(i = 0; i MAX_SEGMENTS) + return PFMT_MANYSEGS; + optr->input = 0; + optr->flags = FLAGS_SUBSTR; + optr->start = start; + optr->outlen = outlen; + } - switch(vto[i].type) { + /* Read the arg list parameters into our data list */ + for(i = 0; i < max_param + 1; i++) { + struct va_input *iptr = &in[i]; + if(!(usedinput[i/8] & (1 << (i&7)))) + /* bad input */ + return PFMT_INPUTGAP; + + /* based on the type, read the correct argument */ + switch(iptr->type) { case FORMAT_STRING: - vto[i].data.str = va_arg(arglist, char *); + iptr->val.str = va_arg(arglist, char *); break; case FORMAT_INTPTR: - case FORMAT_UNKNOWN: case FORMAT_PTR: - vto[i].data.ptr = va_arg(arglist, void *); + iptr->val.ptr = va_arg(arglist, void *); break; - case FORMAT_INT: -#ifdef HAVE_LONG_LONG_TYPE - if((vto[i].flags & FLAGS_LONGLONG) && (vto[i].flags & FLAGS_UNSIGNED)) - vto[i].data.num.as_unsigned = - (mp_uintmax_t)va_arg(arglist, mp_uintmax_t); - else if(vto[i].flags & FLAGS_LONGLONG) - vto[i].data.num.as_signed = - (mp_intmax_t)va_arg(arglist, mp_intmax_t); - else -#endif - { - if((vto[i].flags & FLAGS_LONG) && (vto[i].flags & FLAGS_UNSIGNED)) - vto[i].data.num.as_unsigned = - (mp_uintmax_t)va_arg(arglist, unsigned long); - else if(vto[i].flags & FLAGS_LONG) - vto[i].data.num.as_signed = - (mp_intmax_t)va_arg(arglist, long); - else if(vto[i].flags & FLAGS_UNSIGNED) - vto[i].data.num.as_unsigned = - (mp_uintmax_t)va_arg(arglist, unsigned int); - else - vto[i].data.num.as_signed = - (mp_intmax_t)va_arg(arglist, int); - } + case FORMAT_LONGLONGU: + iptr->val.numu = (mp_uintmax_t)va_arg(arglist, mp_uintmax_t); break; - case FORMAT_DOUBLE: - vto[i].data.dnum = va_arg(arglist, double); + case FORMAT_LONGLONG: + iptr->val.nums = (mp_intmax_t)va_arg(arglist, mp_intmax_t); break; + case FORMAT_LONGU: + iptr->val.numu = (mp_uintmax_t)va_arg(arglist, unsigned long); + break; + + case FORMAT_LONG: + iptr->val.nums = (mp_intmax_t)va_arg(arglist, long); + break; + + case FORMAT_INTU: + iptr->val.numu = (mp_uintmax_t)va_arg(arglist, unsigned int); + break; + + case FORMAT_INT: case FORMAT_WIDTH: - /* Argument has been read. Silently convert it into an integer - * for later use - */ - vto[i].type = FORMAT_INT; + case FORMAT_PRECISION: + iptr->val.nums = (mp_intmax_t)va_arg(arglist, int); + break; + + case FORMAT_DOUBLE: + iptr->val.dnum = va_arg(arglist, double); break; default: + DEBUGASSERT(NULL); /* unexpected */ break; } } + *ipieces = max_param + 1; + *opieces = ocount; - return 0; - + return PFMT_OK; } -static int dprintf_formatf( - void *data, /* untouched by format(), just sent to the stream() function in - the second argument */ +/* + * formatf() - the general printf function. + * + * It calls parsefmt() to parse the format string. It populates two arrays; + * one that describes the input arguments and one that describes a number of + * output segments. + * + * On success, the input array describes the type of all arguments and their + * values. + * + * The function then iterates over the output segments and outputs them one + * by one until done. Using the appropriate input arguments (if any). + * + * All output is sent to the 'stream()' callback, one byte at a time. + */ + +static int formatf( + void *userp, /* untouched by format(), just sent to the stream() function in + the second argument */ /* function pointer called for each output character */ - int (*stream)(int, FILE *), + int (*stream)(unsigned char, void *), const char *format, /* %-formatted string */ va_list ap_save) /* list of parameters */ { - /* Base-36 digits for numbers. */ - const char *digits = lower_digits; - - /* Pointer into the format string. */ - char *f; - - /* Number of characters written. */ - int done = 0; - - long param; /* current parameter to read */ - long param_num = 0; /* parameter counter */ - - struct va_stack vto[MAX_PARAMETERS]; - char *endpos[MAX_PARAMETERS]; - char **end; + static const char nilstr[] = "(nil)"; + const char *digits = lower_digits; /* Base-36 digits for numbers. */ + int done = 0; /* number of characters written */ + int i; + int ocount = 0; /* number of output segments */ + int icount = 0; /* number of input arguments */ + + struct outsegment output[MAX_SEGMENTS]; + struct va_input input[MAX_PARAMETERS]; char work[BUFFSIZE]; - struct va_stack *p; /* 'workend' points to the final buffer byte position, but with an extra - byte as margin to avoid the (false?) warning Coverity gives us + byte as margin to avoid the (FALSE?) warning Coverity gives us otherwise */ char *workend = &work[sizeof(work) - 2]; - /* Do the actual %-code parsing */ - if(dprintf_Pass1(format, vto, endpos, ap_save)) + /* Parse the format string */ + if(parsefmt(format, output, input, &ocount, &icount, ap_save)) return 0; - end = &endpos[0]; /* the initial end-position from the list dprintf_Pass1() - created for us */ - - f = (char *)format; - while(*f != '\0') { - /* Format spec modifiers. */ - int is_alt; - - /* Width of a field. */ - long width; - - /* Precision of a field. */ - long prec; - - /* Decimal integer is negative. */ - int is_neg; - - /* Base of a number to be written. */ - unsigned long base; - - /* Integral values to be written. */ - mp_uintmax_t num; - - /* Used to convert negative in positive. */ - mp_intmax_t signed_num; - + for(i = 0; i < ocount; i++) { + struct outsegment *optr = &output[i]; + struct va_input *iptr; + bool is_alt; /* Format spec modifiers. */ + int width; /* Width of a field. */ + int prec; /* Precision of a field. */ + bool is_neg; /* Decimal integer is negative. */ + unsigned long base; /* Base of a number to be written. */ + mp_uintmax_t num; /* Integral values to be written. */ + mp_intmax_t signed_num; /* Used to convert negative in positive. */ char *w; - - if(*f != '%') { - /* This isn't a format spec, so write everything out until the next one - OR end of string is reached. */ - do { - OUTCHAR(*f); - } while(*++f && ('%' != *f)); - continue; + size_t outlen = optr->outlen; + unsigned int flags = optr->flags; + + if(outlen) { + char *str = optr->start; + for(; outlen && *str; outlen--) + OUTCHAR(*str++); + if(optr->flags & FLAGS_SUBSTR) + /* this is just a substring */ + continue; } - ++f; - - /* Check for "%%". Note that although the ANSI standard lists - '%' as a conversion specifier, it says "The complete format - specification shall be `%%'," so we can avoid all the width - and precision processing. */ - if(*f == '%') { - ++f; - OUTCHAR('%'); - continue; - } - - /* If this is a positional parameter, the position must follow immediately - after the %, thus create a %$ sequence */ - param = dprintf_DollarString(f, &f); - - if(!param) - param = param_num; - else - --param; - - param_num++; /* increase this always to allow "%2$s %1$s %s" and then the - third %s will pick the 3rd argument */ - - p = &vto[param]; - /* pick up the specified width */ - if(p->flags & FLAGS_WIDTHPARAM) { - width = (long)vto[p->width].data.num.as_signed; - param_num++; /* since the width is extracted from a parameter, we - must skip that to get to the next one properly */ + if(flags & FLAGS_WIDTHPARAM) { + width = (int)input[optr->width].val.nums; if(width < 0) { /* "A negative field width is taken as a '-' flag followed by a positive field width." */ - width = -width; - p->flags |= FLAGS_LEFT; - p->flags &= ~FLAGS_PAD_NIL; + if(width == INT_MIN) + width = INT_MAX; + else + width = -width; + flags |= FLAGS_LEFT; + flags &= ~(unsigned int)FLAGS_PAD_NIL; } } else - width = p->width; + width = optr->width; /* pick up the specified precision */ - if(p->flags & FLAGS_PRECPARAM) { - prec = (long)vto[p->precision].data.num.as_signed; - param_num++; /* since the precision is extracted from a parameter, we - must skip that to get to the next one properly */ + if(flags & FLAGS_PRECPARAM) { + prec = (int)input[optr->precision].val.nums; if(prec < 0) /* "A negative precision is taken as if the precision were omitted." */ prec = -1; } - else if(p->flags & FLAGS_PREC) - prec = p->precision; + else if(flags & FLAGS_PREC) + prec = optr->precision; else prec = -1; - is_alt = (p->flags & FLAGS_ALT) ? 1 : 0; + is_alt = (flags & FLAGS_ALT) ? 1 : 0; + iptr = &input[optr->input]; - switch(p->type) { + switch(iptr->type) { + case FORMAT_INTU: + case FORMAT_LONGU: + case FORMAT_LONGLONGU: + flags |= FLAGS_UNSIGNED; + FALLTHROUGH(); case FORMAT_INT: - num = p->data.num.as_unsigned; - if(p->flags & FLAGS_CHAR) { + case FORMAT_LONG: + case FORMAT_LONGLONG: + num = iptr->val.numu; + if(flags & FLAGS_CHAR) { /* Character. */ - if(!(p->flags & FLAGS_LEFT)) + if(!(flags & FLAGS_LEFT)) while(--width > 0) OUTCHAR(' '); OUTCHAR((char) num); - if(p->flags & FLAGS_LEFT) + if(flags & FLAGS_LEFT) while(--width > 0) OUTCHAR(' '); break; } - if(p->flags & FLAGS_OCTAL) { - /* Octal unsigned integer. */ + if(flags & FLAGS_OCTAL) { + /* Octal unsigned integer */ base = 8; - goto unsigned_number; + is_neg = FALSE; } - else if(p->flags & FLAGS_HEX) { - /* Hexadecimal unsigned integer. */ - - digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits; + else if(flags & FLAGS_HEX) { + /* Hexadecimal unsigned integer */ + digits = (flags & FLAGS_UPPER) ? upper_digits : lower_digits; base = 16; - goto unsigned_number; + is_neg = FALSE; } - else if(p->flags & FLAGS_UNSIGNED) { - /* Decimal unsigned integer. */ + else if(flags & FLAGS_UNSIGNED) { + /* Decimal unsigned integer */ base = 10; - goto unsigned_number; + is_neg = FALSE; } + else { + /* Decimal integer. */ + base = 10; - /* Decimal integer. */ - base = 10; - - is_neg = (p->data.num.as_signed < (mp_intmax_t)0) ? 1 : 0; - if(is_neg) { - /* signed_num might fail to hold absolute negative minimum by 1 */ - signed_num = p->data.num.as_signed + (mp_intmax_t)1; - signed_num = -signed_num; - num = (mp_uintmax_t)signed_num; - num += (mp_uintmax_t)1; + is_neg = (iptr->val.nums < (mp_intmax_t)0); + if(is_neg) { + /* signed_num might fail to hold absolute negative minimum by 1 */ + signed_num = iptr->val.nums + (mp_intmax_t)1; + signed_num = -signed_num; + num = (mp_uintmax_t)signed_num; + num += (mp_uintmax_t)1; + } } - - goto number; - -unsigned_number: - /* Unsigned number of base BASE. */ - is_neg = 0; - number: - /* Number of base BASE. */ - /* Supply a default precision if none was given. */ if(prec == -1) prec = 1; /* Put the number in WORK. */ w = workend; - while(num > 0) { - *w-- = digits[num % base]; - num /= base; + switch(base) { + case 10: + while(num > 0) { + *w-- = (char)('0' + (num % 10)); + num /= 10; + } + break; + default: + while(num > 0) { + *w-- = digits[num % base]; + num /= base; + } + break; } - width -= (long)(workend - w); - prec -= (long)(workend - w); + width -= (int)(workend - w); + prec -= (int)(workend - w); if(is_alt && base == 8 && prec <= 0) { *w-- = '0'; @@ -777,29 +835,29 @@ static int dprintf_formatf( if(is_alt && base == 16) width -= 2; - if(is_neg || (p->flags & FLAGS_SHOWSIGN) || (p->flags & FLAGS_SPACE)) + if(is_neg || (flags & FLAGS_SHOWSIGN) || (flags & FLAGS_SPACE)) --width; - if(!(p->flags & FLAGS_LEFT) && !(p->flags & FLAGS_PAD_NIL)) + if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_PAD_NIL)) while(width-- > 0) OUTCHAR(' '); if(is_neg) OUTCHAR('-'); - else if(p->flags & FLAGS_SHOWSIGN) + else if(flags & FLAGS_SHOWSIGN) OUTCHAR('+'); - else if(p->flags & FLAGS_SPACE) + else if(flags & FLAGS_SPACE) OUTCHAR(' '); if(is_alt && base == 16) { OUTCHAR('0'); - if(p->flags & FLAGS_UPPER) + if(flags & FLAGS_UPPER) OUTCHAR('X'); else OUTCHAR('x'); } - if(!(p->flags & FLAGS_LEFT) && (p->flags & FLAGS_PAD_NIL)) + if(!(flags & FLAGS_LEFT) && (flags & FLAGS_PAD_NIL)) while(width-- > 0) OUTCHAR('0'); @@ -808,219 +866,200 @@ static int dprintf_formatf( OUTCHAR(*w); } - if(p->flags & FLAGS_LEFT) + if(flags & FLAGS_LEFT) while(width-- > 0) OUTCHAR(' '); break; - case FORMAT_STRING: - /* String. */ - { - static const char null[] = "(nil)"; - const char *str; - size_t len; - - str = (char *) p->data.str; - if(!str) { - /* Write null[] if there's space. */ - if(prec == -1 || prec >= (long) sizeof(null) - 1) { - str = null; - len = sizeof(null) - 1; - /* Disable quotes around (nil) */ - p->flags &= (~FLAGS_ALT); - } - else { - str = ""; - len = 0; - } + case FORMAT_STRING: { + const char *str; + size_t len; + + str = (char *)iptr->val.str; + if(!str) { + /* Write null string if there is space. */ + if(prec == -1 || prec >= (int) sizeof(nilstr) - 1) { + str = nilstr; + len = sizeof(nilstr) - 1; + /* Disable quotes around (nil) */ + flags &= ~(unsigned int)FLAGS_ALT; } - else if(prec != -1) - len = (size_t)prec; - else if(*str == '\0') + else { + str = ""; len = 0; - else - len = strlen(str); + } + } + else if(prec != -1) + len = (size_t)prec; + else if(*str == '\0') + len = 0; + else + len = strlen(str); - width -= (len > LONG_MAX) ? LONG_MAX : (long)len; + width -= (len > INT_MAX) ? INT_MAX : (int)len; - if(p->flags & FLAGS_ALT) - OUTCHAR('"'); + if(flags & FLAGS_ALT) + OUTCHAR('"'); - if(!(p->flags&FLAGS_LEFT)) - while(width-- > 0) - OUTCHAR(' '); + if(!(flags & FLAGS_LEFT)) + while(width-- > 0) + OUTCHAR(' '); - for(; len && *str; len--) - OUTCHAR(*str++); - if(p->flags&FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); + for(; len && *str; len--) + OUTCHAR(*str++); + if(flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); - if(p->flags & FLAGS_ALT) - OUTCHAR('"'); - } + if(flags & FLAGS_ALT) + OUTCHAR('"'); break; + } case FORMAT_PTR: /* Generic pointer. */ - { - void *ptr; - ptr = (void *) p->data.ptr; - if(ptr) { - /* If the pointer is not NULL, write it as a %#x spec. */ - base = 16; - digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits; - is_alt = 1; - num = (size_t) ptr; - is_neg = 0; - goto number; - } - else { - /* Write "(nil)" for a nil pointer. */ - static const char strnil[] = "(nil)"; - const char *point; - - width -= (long)(sizeof(strnil) - 1); - if(p->flags & FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); - for(point = strnil; *point != '\0'; ++point) - OUTCHAR(*point); - if(!(p->flags & FLAGS_LEFT)) - while(width-- > 0) - OUTCHAR(' '); - } + if(iptr->val.ptr) { + /* If the pointer is not NULL, write it as a %#x spec. */ + base = 16; + digits = (flags & FLAGS_UPPER) ? upper_digits : lower_digits; + is_alt = TRUE; + num = (size_t) iptr->val.ptr; + is_neg = FALSE; + goto number; } - break; + else { + /* Write "(nil)" for a nil pointer. */ + const char *point; - case FORMAT_DOUBLE: - { - char formatbuf[32]="%"; - char *fptr = &formatbuf[1]; - size_t left = sizeof(formatbuf)-strlen(formatbuf); - int len; - - width = -1; - if(p->flags & FLAGS_WIDTH) - width = p->width; - else if(p->flags & FLAGS_WIDTHPARAM) - width = (long)vto[p->width].data.num.as_signed; + width -= (int)(sizeof(nilstr) - 1); + if(flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); + for(point = nilstr; *point != '\0'; ++point) + OUTCHAR(*point); + if(!(flags & FLAGS_LEFT)) + while(width-- > 0) + OUTCHAR(' '); + } + break; - prec = -1; - if(p->flags & FLAGS_PREC) - prec = p->precision; - else if(p->flags & FLAGS_PRECPARAM) - prec = (long)vto[p->precision].data.num.as_signed; - - if(p->flags & FLAGS_LEFT) - *fptr++ = '-'; - if(p->flags & FLAGS_SHOWSIGN) - *fptr++ = '+'; - if(p->flags & FLAGS_SPACE) - *fptr++ = ' '; - if(p->flags & FLAGS_ALT) - *fptr++ = '#'; - - *fptr = 0; - - if(width >= 0) { - if(width >= (long)sizeof(work)) - width = sizeof(work)-1; - /* RECURSIVE USAGE */ - len = curl_msnprintf(fptr, left, "%ld", width); - fptr += len; - left -= len; + case FORMAT_DOUBLE: { + char formatbuf[32]="%"; + char *fptr = &formatbuf[1]; + size_t left = sizeof(formatbuf)-strlen(formatbuf); + int len; + + if(flags & FLAGS_WIDTH) + width = optr->width; + + if(flags & FLAGS_PREC) + prec = optr->precision; + + if(flags & FLAGS_LEFT) + *fptr++ = '-'; + if(flags & FLAGS_SHOWSIGN) + *fptr++ = '+'; + if(flags & FLAGS_SPACE) + *fptr++ = ' '; + if(flags & FLAGS_ALT) + *fptr++ = '#'; + + *fptr = 0; + + if(width >= 0) { + size_t dlen; + if(width >= (int)sizeof(work)) + width = sizeof(work)-1; + /* RECURSIVE USAGE */ + dlen = (size_t)curl_msnprintf(fptr, left, "%d", width); + fptr += dlen; + left -= dlen; + } + if(prec >= 0) { + /* for each digit in the integer part, we can have one less + precision */ + size_t maxprec = sizeof(work) - 2; + double val = iptr->val.dnum; + if(width > 0 && prec <= width) + maxprec -= (size_t)width; + while(val >= 10.0) { + val /= 10; + maxprec--; } - if(prec >= 0) { - /* for each digit in the integer part, we can have one less - precision */ - size_t maxprec = sizeof(work) - 2; - double val = p->data.dnum; - if(width > 0 && prec <= width) - maxprec -= width; - while(val >= 10.0) { - val /= 10; - maxprec--; - } - if(prec > (long)maxprec) - prec = (long)maxprec-1; - if(prec < 0) - prec = 0; - /* RECURSIVE USAGE */ - len = curl_msnprintf(fptr, left, ".%ld", prec); - fptr += len; - } - if(p->flags & FLAGS_LONG) - *fptr++ = 'l'; + if(prec > (int)maxprec) + prec = (int)maxprec-1; + if(prec < 0) + prec = 0; + /* RECURSIVE USAGE */ + len = curl_msnprintf(fptr, left, ".%d", prec); + fptr += len; + } + if(flags & FLAGS_LONG) + *fptr++ = 'l'; - if(p->flags & FLAGS_FLOATE) - *fptr++ = (char)((p->flags & FLAGS_UPPER) ? 'E':'e'); - else if(p->flags & FLAGS_FLOATG) - *fptr++ = (char)((p->flags & FLAGS_UPPER) ? 'G' : 'g'); - else - *fptr++ = 'f'; + if(flags & FLAGS_FLOATE) + *fptr++ = (char)((flags & FLAGS_UPPER) ? 'E' : 'e'); + else if(flags & FLAGS_FLOATG) + *fptr++ = (char)((flags & FLAGS_UPPER) ? 'G' : 'g'); + else + *fptr++ = 'f'; - *fptr = 0; /* and a final null-termination */ + *fptr = 0; /* and a final null-termination */ #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" #endif - /* NOTE NOTE NOTE!! Not all sprintf implementations return number of - output characters */ + /* NOTE NOTE NOTE!! Not all sprintf implementations return number of + output characters */ #ifdef HAVE_SNPRINTF - (snprintf)(work, sizeof(work), formatbuf, p->data.dnum); + (snprintf)(work, sizeof(work), formatbuf, iptr->val.dnum); #else - (sprintf)(work, formatbuf, p->data.dnum); + (sprintf)(work, formatbuf, iptr->val.dnum); #endif #ifdef __clang__ #pragma clang diagnostic pop #endif - DEBUGASSERT(strlen(work) <= sizeof(work)); - for(fptr = work; *fptr; fptr++) - OUTCHAR(*fptr); - } + DEBUGASSERT(strlen(work) <= sizeof(work)); + for(fptr = work; *fptr; fptr++) + OUTCHAR(*fptr); break; + } case FORMAT_INTPTR: /* Answer the count of characters written. */ #ifdef HAVE_LONG_LONG_TYPE - if(p->flags & FLAGS_LONGLONG) - *(LONG_LONG_TYPE *) p->data.ptr = (LONG_LONG_TYPE)done; + if(flags & FLAGS_LONGLONG) + *(LONG_LONG_TYPE *) iptr->val.ptr = (LONG_LONG_TYPE)done; else #endif - if(p->flags & FLAGS_LONG) - *(long *) p->data.ptr = (long)done; - else if(!(p->flags & FLAGS_SHORT)) - *(int *) p->data.ptr = (int)done; + if(flags & FLAGS_LONG) + *(long *) iptr->val.ptr = (long)done; + else if(!(flags & FLAGS_SHORT)) + *(int *) iptr->val.ptr = (int)done; else - *(short *) p->data.ptr = (short)done; + *(short *) iptr->val.ptr = (short)done; break; default: break; } - f = *end++; /* goto end of %-code */ - } return done; } /* fputc() look-alike */ -static int addbyter(int output, FILE *data) +static int addbyter(unsigned char outc, void *f) { - struct nsprintf *infop = (struct nsprintf *)data; - unsigned char outc = (unsigned char)output; - + struct nsprintf *infop = f; if(infop->length < infop->max) { - /* only do this if we haven't reached max length yet */ - infop->buffer[0] = outc; /* store */ - infop->buffer++; /* increase pointer */ + /* only do this if we have not reached max length yet */ + *infop->buffer++ = (char)outc; /* store */ infop->length++; /* we are now one byte larger */ - return outc; /* fputc() returns like this on success */ + return 0; /* fputc() returns like this on success */ } - return -1; + return 1; } int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, @@ -1033,14 +1072,14 @@ int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, info.length = 0; info.max = maxlength; - retcode = dprintf_formatf(&info, addbyter, format, ap_save); + retcode = formatf(&info, addbyter, format, ap_save); if(info.max) { /* we terminate this with a zero byte */ if(info.max == info.length) { - /* we're at maximum, scrap the last letter */ + /* we are at maximum, scrap the last letter */ info.buffer[-1] = 0; DEBUGASSERT(retcode); - retcode--; /* don't count the nul byte */ + retcode--; /* do not count the nul byte */ } else info.buffer[0] = 0; @@ -1059,32 +1098,28 @@ int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...) } /* fputc() look-alike */ -static int alloc_addbyter(int output, FILE *data) +static int alloc_addbyter(unsigned char outc, void *f) { - struct asprintf *infop = (struct asprintf *)data; - unsigned char outc = (unsigned char)output; - - if(Curl_dyn_addn(infop->b, &outc, 1)) { - infop->fail = 1; - return -1; /* fail */ + struct asprintf *infop = f; + CURLcode result = Curl_dyn_addn(infop->b, &outc, 1); + if(result) { + infop->merr = result == CURLE_TOO_LARGE ? MERR_TOO_LARGE : MERR_MEM; + return 1 ; /* fail */ } - return outc; /* fputc() returns like this on success */ + return 0; } -extern int Curl_dyn_vprintf(struct dynbuf *dyn, - const char *format, va_list ap_save); - -/* appends the formatted string, returns 0 on success, 1 on error */ +/* appends the formatted string, returns MERR error code */ int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save) { struct asprintf info; info.b = dyn; - info.fail = 0; + info.merr = MERR_OK; - (void)dprintf_formatf(&info, alloc_addbyter, format, ap_save); - if(info.fail) { + (void)formatf(&info, alloc_addbyter, format, ap_save); + if(info.merr) { Curl_dyn_free(info.b); - return 1; + return info.merr; } return 0; } @@ -1095,10 +1130,10 @@ char *curl_mvaprintf(const char *format, va_list ap_save) struct dynbuf dyn; info.b = &dyn; Curl_dyn_init(info.b, DYN_APRINTF); - info.fail = 0; + info.merr = MERR_OK; - (void)dprintf_formatf(&info, alloc_addbyter, format, ap_save); - if(info.fail) { + (void)formatf(&info, alloc_addbyter, format, ap_save); + if(info.merr) { Curl_dyn_free(info.b); return NULL; } @@ -1117,13 +1152,12 @@ char *curl_maprintf(const char *format, ...) return s; } -static int storebuffer(int output, FILE *data) +static int storebuffer(unsigned char outc, void *f) { - char **buffer = (char **)data; - unsigned char outc = (unsigned char)output; - **buffer = outc; + char **buffer = f; + **buffer = (char)outc; (*buffer)++; - return outc; /* act like fputc() ! */ + return 0; } int curl_msprintf(char *buffer, const char *format, ...) @@ -1131,19 +1165,27 @@ int curl_msprintf(char *buffer, const char *format, ...) va_list ap_save; /* argument pointer */ int retcode; va_start(ap_save, format); - retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save); + retcode = formatf(&buffer, storebuffer, format, ap_save); va_end(ap_save); *buffer = 0; /* we terminate this with a zero byte */ return retcode; } +static int fputc_wrapper(unsigned char outc, void *f) +{ + int out = outc; + FILE *s = f; + int rc = fputc(out, s); + return rc == EOF; +} + int curl_mprintf(const char *format, ...) { int retcode; va_list ap_save; /* argument pointer */ va_start(ap_save, format); - retcode = dprintf_formatf(stdout, fputc, format, ap_save); + retcode = formatf(stdout, fputc_wrapper, format, ap_save); va_end(ap_save); return retcode; } @@ -1153,25 +1195,24 @@ int curl_mfprintf(FILE *whereto, const char *format, ...) int retcode; va_list ap_save; /* argument pointer */ va_start(ap_save, format); - retcode = dprintf_formatf(whereto, fputc, format, ap_save); + retcode = formatf(whereto, fputc_wrapper, format, ap_save); va_end(ap_save); return retcode; } int curl_mvsprintf(char *buffer, const char *format, va_list ap_save) { - int retcode; - retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save); + int retcode = formatf(&buffer, storebuffer, format, ap_save); *buffer = 0; /* we terminate this with a zero byte */ return retcode; } int curl_mvprintf(const char *format, va_list ap_save) { - return dprintf_formatf(stdout, fputc, format, ap_save); + return formatf(stdout, fputc_wrapper, format, ap_save); } int curl_mvfprintf(FILE *whereto, const char *format, va_list ap_save) { - return dprintf_formatf(whereto, fputc, format, ap_save); + return formatf(whereto, fputc_wrapper, format, ap_save); } diff --git a/deps/curl/lib/mqtt.c b/deps/curl/lib/mqtt.c index 30edf3dd8a4..69eaf344aea 100644 --- a/deps/curl/lib/mqtt.c +++ b/deps/curl/lib/mqtt.c @@ -75,7 +75,7 @@ static CURLcode mqtt_setup_conn(struct Curl_easy *data, */ const struct Curl_handler Curl_handler_mqtt = { - "MQTT", /* scheme */ + "mqtt", /* scheme */ mqtt_setup_conn, /* setup_connection */ mqtt_do, /* do_it */ mqtt_done, /* done */ @@ -88,7 +88,8 @@ const struct Curl_handler Curl_handler_mqtt = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_MQTT, /* defport */ @@ -109,6 +110,7 @@ static CURLcode mqtt_setup_conn(struct Curl_easy *data, mq = calloc(1, sizeof(struct MQTT)); if(!mq) return CURLE_OUT_OF_MEMORY; + Curl_dyn_init(&mq->recvbuf, DYN_MQTT_RECV); data->req.p.mqtt = mq; return CURLE_OK; } @@ -118,12 +120,12 @@ static CURLcode mqtt_send(struct Curl_easy *data, { CURLcode result = CURLE_OK; struct MQTT *mq = data->req.p.mqtt; - ssize_t n; - result = Curl_nwrite(data, FIRSTSOCKET, buf, len, &n); + size_t n; + result = Curl_xfer_send(data, buf, len, FALSE, &n); if(result) return result; Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)n); - if(len != (size_t)n) { + if(len != n) { size_t nsend = len - n; char *sendleftovers = Curl_memdup(&buf[n], nsend); if(!sendleftovers) @@ -152,15 +154,15 @@ static int mqtt_getsock(struct Curl_easy *data, static int mqtt_encode_len(char *buf, size_t len) { - unsigned char encoded; int i; - for(i = 0; (len > 0) && (i<4); i++) { + for(i = 0; (len > 0) && (i < 4); i++) { + unsigned char encoded; encoded = len % 0x80; len /= 0x80; if(len) encoded |= 0x80; - buf[i] = encoded; + buf[i] = (char)encoded; } return i; @@ -295,12 +297,12 @@ static CURLcode mqtt_connect(struct Curl_easy *data) /* set initial values for the CONNECT packet */ pos = init_connpack(packet, remain, remain_pos); - result = Curl_rand_hex(data, (unsigned char *)&client_id[clen], - MQTT_CLIENTID_LEN - clen + 1); + result = Curl_rand_alnum(data, (unsigned char *)&client_id[clen], + MQTT_CLIENTID_LEN - clen + 1); /* add client id */ rc = add_client_id(client_id, strlen(client_id), packet, pos + 1); if(rc) { - failf(data, "Client ID length mismatched: [%lu]", strlen(client_id)); + failf(data, "Client ID length mismatched: [%zu]", strlen(client_id)); result = CURLE_WEIRD_SERVER_REPLY; goto end; } @@ -310,14 +312,14 @@ static CURLcode mqtt_connect(struct Curl_easy *data) start_user = pos + 3 + MQTT_CLIENTID_LEN; /* position where starts the password payload */ start_pwd = start_user + ulen; - /* if user name was provided, add it to the packet */ + /* if username was provided, add it to the packet */ if(ulen) { start_pwd += 2; rc = add_user(username, ulen, (unsigned char *)packet, start_user, remain_pos); if(rc) { - failf(data, "Username is too large: [%lu]", ulen); + failf(data, "Username is too large: [%zu]", ulen); result = CURLE_WEIRD_SERVER_REPLY; goto end; } @@ -327,7 +329,7 @@ static CURLcode mqtt_connect(struct Curl_easy *data) if(plen) { rc = add_passwd(passwd, plen, packet, start_pwd, remain_pos); if(rc) { - failf(data, "Password is too large: [%lu]", plen); + failf(data, "Password is too large: [%zu]", plen); result = CURLE_WEIRD_SERVER_REPLY; goto end; } @@ -350,36 +352,65 @@ static CURLcode mqtt_disconnect(struct Curl_easy *data) struct MQTT *mq = data->req.p.mqtt; result = mqtt_send(data, (char *)"\xe0\x00", 2); Curl_safefree(mq->sendleftovers); + Curl_dyn_free(&mq->recvbuf); return result; } -static CURLcode mqtt_verify_connack(struct Curl_easy *data) +static CURLcode mqtt_recv_atleast(struct Curl_easy *data, size_t nbytes) { + struct MQTT *mq = data->req.p.mqtt; + size_t rlen = Curl_dyn_len(&mq->recvbuf); CURLcode result; - struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - unsigned char readbuf[MQTT_CONNACK_LEN]; - ssize_t nread; - result = Curl_read(data, sockfd, (char *)readbuf, MQTT_CONNACK_LEN, &nread); - if(result) - goto fail; + if(rlen < nbytes) { + unsigned char readbuf[1024]; + ssize_t nread; + + DEBUGASSERT(nbytes - rlen < sizeof(readbuf)); + result = Curl_xfer_recv(data, (char *)readbuf, nbytes - rlen, &nread); + if(result) + return result; + DEBUGASSERT(nread >= 0); + if(Curl_dyn_addn(&mq->recvbuf, readbuf, (size_t)nread)) + return CURLE_OUT_OF_MEMORY; + rlen = Curl_dyn_len(&mq->recvbuf); + } + return (rlen >= nbytes) ? CURLE_OK : CURLE_AGAIN; +} - Curl_debug(data, CURLINFO_HEADER_IN, (char *)readbuf, (size_t)nread); +static void mqtt_recv_consume(struct Curl_easy *data, size_t nbytes) +{ + struct MQTT *mq = data->req.p.mqtt; + size_t rlen = Curl_dyn_len(&mq->recvbuf); + if(rlen <= nbytes) + Curl_dyn_reset(&mq->recvbuf); + else + Curl_dyn_tail(&mq->recvbuf, rlen - nbytes); +} - /* fixme */ - if(nread < MQTT_CONNACK_LEN) { - result = CURLE_WEIRD_SERVER_REPLY; +static CURLcode mqtt_verify_connack(struct Curl_easy *data) +{ + struct MQTT *mq = data->req.p.mqtt; + CURLcode result; + char *ptr; + + result = mqtt_recv_atleast(data, MQTT_CONNACK_LEN); + if(result) goto fail; - } /* verify CONNACK */ - if(readbuf[0] != 0x00 || readbuf[1] != 0x00) { + DEBUGASSERT(Curl_dyn_len(&mq->recvbuf) >= MQTT_CONNACK_LEN); + ptr = Curl_dyn_ptr(&mq->recvbuf); + Curl_debug(data, CURLINFO_HEADER_IN, ptr, MQTT_CONNACK_LEN); + + if(ptr[0] != 0x00 || ptr[1] != 0x00) { failf(data, "Expected %02x%02x but got %02x%02x", - 0x00, 0x00, readbuf[0], readbuf[1]); + 0x00, 0x00, ptr[0], ptr[1]); + Curl_dyn_reset(&mq->recvbuf); result = CURLE_WEIRD_SERVER_REPLY; + goto fail; } - + mqtt_recv_consume(data, MQTT_CONNACK_LEN); fail: return result; } @@ -452,31 +483,29 @@ static CURLcode mqtt_subscribe(struct Curl_easy *data) */ static CURLcode mqtt_verify_suback(struct Curl_easy *data) { - CURLcode result; + struct MQTT *mq = data->req.p.mqtt; struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - unsigned char readbuf[MQTT_SUBACK_LEN]; - ssize_t nread; struct mqtt_conn *mqtt = &conn->proto.mqtt; + CURLcode result; + char *ptr; - result = Curl_read(data, sockfd, (char *)readbuf, MQTT_SUBACK_LEN, &nread); + result = mqtt_recv_atleast(data, MQTT_SUBACK_LEN); if(result) goto fail; - Curl_debug(data, CURLINFO_HEADER_IN, (char *)readbuf, (size_t)nread); - - /* fixme */ - if(nread < MQTT_SUBACK_LEN) { + /* verify SUBACK */ + DEBUGASSERT(Curl_dyn_len(&mq->recvbuf) >= MQTT_SUBACK_LEN); + ptr = Curl_dyn_ptr(&mq->recvbuf); + Curl_debug(data, CURLINFO_HEADER_IN, ptr, MQTT_SUBACK_LEN); + + if(((unsigned char)ptr[0]) != ((mqtt->packetid >> 8) & 0xff) || + ((unsigned char)ptr[1]) != (mqtt->packetid & 0xff) || + ptr[2] != 0x00) { + Curl_dyn_reset(&mq->recvbuf); result = CURLE_WEIRD_SERVER_REPLY; goto fail; } - - /* verify SUBACK */ - if(readbuf[0] != ((mqtt->packetid >> 8) & 0xff) || - readbuf[1] != (mqtt->packetid & 0xff) || - readbuf[2] != 0x00) - result = CURLE_WEIRD_SERVER_REPLY; - + mqtt_recv_consume(data, MQTT_SUBACK_LEN); fail: return result; } @@ -495,8 +524,10 @@ static CURLcode mqtt_publish(struct Curl_easy *data) char encodedbytes[4]; curl_off_t postfieldsize = data->set.postfieldsize; - if(!payload) + if(!payload) { + DEBUGF(infof(data, "mqtt_publish without payload, return bad arg")); return CURLE_BAD_FUNCTION_ARGUMENT; + } if(postfieldsize < 0) payloadlen = strlen(payload); else @@ -554,7 +585,7 @@ static size_t mqtt_decode_len(unsigned char *buf, return len; } -#ifdef CURLDEBUG +#ifdef DEBUGBUILD static const char *statenames[]={ "MQTT_FIRST", "MQTT_REMAINING_LENGTH", @@ -575,11 +606,11 @@ static void mqstate(struct Curl_easy *data, { struct connectdata *conn = data->conn; struct mqtt_conn *mqtt = &conn->proto.mqtt; -#ifdef CURLDEBUG +#ifdef DEBUGBUILD infof(data, "%s (from %s) (next is %s)", statenames[state], statenames[mqtt->state], - (state == MQTT_FIRST)? statenames[nextstate] : ""); + (state == MQTT_FIRST) ? statenames[nextstate] : ""); #endif mqtt->state = state; if(state == MQTT_FIRST) @@ -587,16 +618,11 @@ static void mqstate(struct Curl_easy *data, } -/* for the publish packet */ -#define MQTT_HEADER_LEN 5 /* max 5 bytes */ - static CURLcode mqtt_read_publish(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; ssize_t nread; - unsigned char *pkt = (unsigned char *)data->state.buffer; size_t remlen; struct mqtt_conn *mqtt = &conn->proto.mqtt; struct MQTT *mq = data->req.p.mqtt; @@ -645,14 +671,14 @@ static CURLcode mqtt_read_publish(struct Curl_easy *data, bool *done) data->req.bytecount = 0; data->req.size = remlen; mq->npacket = remlen; /* get this many bytes */ - /* FALLTHROUGH */ + FALLTHROUGH(); case MQTT_PUB_REMAIN: { /* read rest of packet, but no more. Cap to buffer size */ - struct SingleRequest *k = &data->req; + char buffer[4*1024]; size_t rest = mq->npacket; - if(rest > (size_t)data->set.buffer_size) - rest = (size_t)data->set.buffer_size; - result = Curl_read(data, sockfd, (char *)pkt, rest, &nread); + if(rest > sizeof(buffer)) + rest = sizeof(buffer); + result = Curl_xfer_recv(data, buffer, rest, &nread); if(result) { if(CURLE_AGAIN == result) { infof(data, "EEEE AAAAGAIN"); @@ -664,18 +690,13 @@ static CURLcode mqtt_read_publish(struct Curl_easy *data, bool *done) result = CURLE_PARTIAL_FILE; goto end; } - Curl_debug(data, CURLINFO_DATA_IN, (char *)pkt, (size_t)nread); - - mq->npacket -= nread; - k->bytecount += nread; - Curl_pgrsSetDownloadCounter(data, k->bytecount); /* if QoS is set, message contains packet id */ - - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)pkt, nread); + result = Curl_client_write(data, CLIENTWRITE_BODY, buffer, nread); if(result) goto end; + mq->npacket -= nread; if(!mq->npacket) /* no more PUBLISH payload, back to subscribe wait state */ mqstate(data, MQTT_FIRST, MQTT_PUBWAIT); @@ -711,6 +732,7 @@ static CURLcode mqtt_done(struct Curl_easy *data, (void)status; (void)premature; Curl_safefree(mq->sendleftovers); + Curl_dyn_free(&mq->recvbuf); return CURLE_OK; } @@ -721,9 +743,7 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) struct mqtt_conn *mqtt = &conn->proto.mqtt; struct MQTT *mq = data->req.p.mqtt; ssize_t nread; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - unsigned char *pkt = (unsigned char *)data->state.buffer; - unsigned char byte; + unsigned char recvbyte; *done = FALSE; @@ -740,7 +760,7 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) switch(mqtt->state) { case MQTT_FIRST: /* Read the initial byte only */ - result = Curl_read(data, sockfd, (char *)&mq->firstbyte, 1, &nread); + result = Curl_xfer_recv(data, (char *)&mq->firstbyte, 1, &nread); if(result) break; else if(!nread) { @@ -753,22 +773,22 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) /* remember the first byte */ mq->npacket = 0; mqstate(data, MQTT_REMAINING_LENGTH, MQTT_NOSTATE); - /* FALLTHROUGH */ + FALLTHROUGH(); case MQTT_REMAINING_LENGTH: do { - result = Curl_read(data, sockfd, (char *)&byte, 1, &nread); - if(!nread) + result = Curl_xfer_recv(data, (char *)&recvbyte, 1, &nread); + if(result || !nread) break; - Curl_debug(data, CURLINFO_HEADER_IN, (char *)&byte, 1); - pkt[mq->npacket++] = byte; - } while((byte & 0x80) && (mq->npacket < 4)); - if(nread && (byte & 0x80)) + Curl_debug(data, CURLINFO_HEADER_IN, (char *)&recvbyte, 1); + mq->pkt_hd[mq->npacket++] = recvbyte; + } while((recvbyte & 0x80) && (mq->npacket < 4)); + if(!result && nread && (recvbyte & 0x80)) /* MQTT supports up to 127 * 128^0 + 127 * 128^1 + 127 * 128^2 + 127 * 128^3 bytes. server tried to send more */ result = CURLE_WEIRD_SERVER_REPLY; if(result) break; - mq->remaining_length = mqtt_decode_len(&pkt[0], mq->npacket, NULL); + mq->remaining_length = mqtt_decode_len(mq->pkt_hd, mq->npacket, NULL); mq->npacket = 0; if(mq->remaining_length) { mqstate(data, mqtt->nextstate, MQTT_NOSTATE); diff --git a/deps/curl/lib/mqtt.h b/deps/curl/lib/mqtt.h index 63961366fcb..99ab12a98a4 100644 --- a/deps/curl/lib/mqtt.h +++ b/deps/curl/lib/mqtt.h @@ -56,6 +56,8 @@ struct MQTT { size_t npacket; /* byte counter */ unsigned char firstbyte; size_t remaining_length; + struct dynbuf recvbuf; + unsigned char pkt_hd[4]; /* for decoding the arriving packet length */ }; #endif /* HEADER_CURL_MQTT_H */ diff --git a/deps/curl/lib/multi.c b/deps/curl/lib/multi.c index bb57dbceee2..263b396d3df 100644 --- a/deps/curl/lib/multi.c +++ b/deps/curl/lib/multi.c @@ -50,30 +50,15 @@ #include "http2.h" #include "socketpair.h" #include "socks.h" +#include "urlapi-int.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -#ifdef __APPLE__ - -#define wakeup_write write -#define wakeup_read read -#define wakeup_close close -#define wakeup_create pipe - -#else /* __APPLE__ */ - -#define wakeup_write swrite -#define wakeup_read sread -#define wakeup_close sclose -#define wakeup_create(p) Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, p) - -#endif /* __APPLE__ */ - /* CURL_SOCKET_HASH_TABLE_SIZE should be a prime number. Increasing it from 97 - to 911 takes on a 32-bit machine 4 x 804 = 3211 more bytes. Still, every + to 911 takes on a 32-bit machine 4 x 804 = 3211 more bytes. Still, every CURL handle takes 45-50 K memory, therefore this 3K are not significant. */ #ifndef CURL_SOCKET_HASH_TABLE_SIZE @@ -95,26 +80,33 @@ * are not NULL, but no longer have the MAGIC touch. This gives * us early warning on things only discovered by valgrind otherwise. */ #define GOOD_MULTI_HANDLE(x) \ - (((x) && (x)->magic == CURL_MULTI_HANDLE)? TRUE: \ + (((x) && (x)->magic == CURL_MULTI_HANDLE)? TRUE: \ (DEBUGASSERT(!(x)), FALSE)) #else #define GOOD_MULTI_HANDLE(x) \ ((x) && (x)->magic == CURL_MULTI_HANDLE) #endif +static void move_pending_to_connect(struct Curl_multi *multi, + struct Curl_easy *data); static CURLMcode singlesocket(struct Curl_multi *multi, struct Curl_easy *data); static CURLMcode add_next_timeout(struct curltime now, struct Curl_multi *multi, struct Curl_easy *d); static CURLMcode multi_timeout(struct Curl_multi *multi, + struct curltime *expire_time, long *timeout_ms); static void process_pending_handles(struct Curl_multi *multi); +static void multi_xfer_bufs_free(struct Curl_multi *multi); +static void expire_ex(struct Curl_easy *data, const struct curltime *nowp, + timediff_t milli, expire_id id); -#ifdef DEBUGBUILD +#if defined( DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) static const char * const multi_statename[]={ "INIT", "PENDING", + "SETUP", "CONNECT", "RESOLVING", "CONNECTING", @@ -147,7 +139,7 @@ static void init_completed(struct Curl_easy *data) { /* this is a completed transfer */ - /* Important: reset the conn pointer so that we don't point to memory + /* Important: reset the conn pointer so that we do not point to memory that could be freed anytime */ Curl_detach_connection(data); Curl_expire_clear(data); /* stop all timers */ @@ -164,6 +156,7 @@ static void mstate(struct Curl_easy *data, CURLMstate state static const init_multistate_func finit[MSTATE_LAST] = { NULL, /* INIT */ NULL, /* PENDING */ + NULL, /* SETUP */ Curl_init_CONNECT, /* CONNECT */ NULL, /* RESOLVING */ NULL, /* CONNECTING */ @@ -186,7 +179,7 @@ static void mstate(struct Curl_easy *data, CURLMstate state #endif if(oldstate == state) - /* don't bother when the new state is the same as the old state */ + /* do not bother when the new state is the same as the old state */ return; data->mstate = state; @@ -202,9 +195,13 @@ static void mstate(struct Curl_easy *data, CURLMstate state #endif if(state == MSTATE_COMPLETED) { - /* changing to COMPLETED means there's one less easy handle 'alive' */ + /* changing to COMPLETED means there is one less easy handle 'alive' */ DEBUGASSERT(data->multi->num_alive > 0); data->multi->num_alive--; + if(!data->multi->num_alive) { + /* free the transfer buffer when we have no more active transfers */ + multi_xfer_bufs_free(data->multi); + } } /* if this state has an init-function, run it */ @@ -231,10 +228,6 @@ struct Curl_sh_entry { unsigned int readers; /* this many transfers want to read */ unsigned int writers; /* this many transfers want to write */ }; -/* bits for 'action' having no bits means this socket is not expecting any - action */ -#define SH_READ 1 -#define SH_WRITE 2 /* look up a given socket in the socket hash, skip invalid sockets */ static struct Curl_sh_entry *sh_getentry(struct Curl_hash *sh, @@ -248,20 +241,19 @@ static struct Curl_sh_entry *sh_getentry(struct Curl_hash *sh, } #define TRHASH_SIZE 13 + +/* the given key here is a struct Curl_easy pointer */ static size_t trhash(void *key, size_t key_length, size_t slots_num) { - size_t keyval = (size_t)*(struct Curl_easy **)key; - (void) key_length; - - return (keyval % slots_num); + unsigned char bytes = ((unsigned char *)key)[key_length - 1] ^ + ((unsigned char *)key)[0]; + return (bytes % slots_num); } static size_t trhash_compare(void *k1, size_t k1_len, void *k2, size_t k2_len) { - (void)k1_len; (void)k2_len; - - return *(struct Curl_easy **)k1 == *(struct Curl_easy **)k2; + return !memcmp(k1, k2, k1_len); } static void trhash_dtor(void *nada) @@ -354,7 +346,7 @@ static size_t hash_fd(void *key, size_t key_length, size_t slots_num) curl_socket_t fd = *((curl_socket_t *) key); (void) key_length; - return (fd % slots_num); + return (fd % (curl_socket_t)slots_num); } /* @@ -365,22 +357,33 @@ static size_t hash_fd(void *key, size_t key_length, size_t slots_num) * "Some tests at 7000 and 9000 connections showed that the socket hash lookup * is somewhat of a bottle neck. Its current implementation may be a bit too * limiting. It simply has a fixed-size array, and on each entry in the array - * it has a linked list with entries. So the hash only checks which list to - * scan through. The code I had used so for used a list with merely 7 slots - * (as that is what the DNS hash uses) but with 7000 connections that would - * make an average of 1000 nodes in each list to run through. I upped that to - * 97 slots (I believe a prime is suitable) and noticed a significant speed - * increase. I need to reconsider the hash implementation or use a rather + * it has a linked list with entries. The hash only checks which list to scan + * through. The code I had used so for used a list with merely 7 slots (as + * that is what the DNS hash uses) but with 7000 connections that would make + * an average of 1000 nodes in each list to run through. I upped that to 97 + * slots (I believe a prime is suitable) and noticed a significant speed + * increase. I need to reconsider the hash implementation or use a rather * large default value like this. At 9000 connections I was still below 10us * per call." * */ -static void sh_init(struct Curl_hash *hash, int hashsize) +static void sh_init(struct Curl_hash *hash, size_t hashsize) { Curl_hash_init(hash, hashsize, hash_fd, fd_key_compare, sh_freeentry); } +/* multi->proto_hash destructor. Should never be called as elements + * MUST be added with their own destructor */ +static void ph_freeentry(void *p) +{ + (void)p; + /* Will always be FALSE. Cannot use a 0 assert here since compilers + * are not in agreement if they then want a NORETURN attribute or + * not. *sigh* */ + DEBUGASSERT(p == NULL); +} + /* * multi_addmsg() * @@ -389,13 +392,12 @@ static void sh_init(struct Curl_hash *hash, int hashsize) */ static void multi_addmsg(struct Curl_multi *multi, struct Curl_message *msg) { - Curl_llist_insert_next(&multi->msglist, multi->msglist.tail, msg, - &msg->list); + Curl_llist_append(&multi->msglist, msg, &msg->list); } -struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ - int chashsize, /* connection hash */ - int dnssize) /* dns hash */ +struct Curl_multi *Curl_multi_handle(size_t hashsize, /* socket hash */ + size_t chashsize, /* connection hash */ + size_t dnssize) /* dns hash */ { struct Curl_multi *multi = calloc(1, sizeof(struct Curl_multi)); @@ -408,18 +410,21 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ sh_init(&multi->sockhash, hashsize); - if(Curl_conncache_init(&multi->conn_cache, chashsize)) + Curl_hash_init(&multi->proto_hash, 23, + Curl_hash_str, Curl_str_key_compare, ph_freeentry); + + if(Curl_cpool_init(&multi->cpool, Curl_on_disconnect, + multi, NULL, chashsize)) goto error; Curl_llist_init(&multi->msglist, NULL); + Curl_llist_init(&multi->process, NULL); Curl_llist_init(&multi->pending, NULL); Curl_llist_init(&multi->msgsent, NULL); multi->multiplexing = TRUE; - - /* -1 means it not set by user, use the default value */ - multi->maxconnects = -1; multi->max_concurrent_streams = 100; + multi->last_timeout_ms = -1; #ifdef USE_WINSOCK multi->wsa_event = WSACreateEvent(); @@ -427,14 +432,7 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ goto error; #else #ifdef ENABLE_WAKEUP - if(wakeup_create(multi->wakeup_pair) < 0) { - multi->wakeup_pair[0] = CURL_SOCKET_BAD; - multi->wakeup_pair[1] = CURL_SOCKET_BAD; - } - else if(curlx_nonblock(multi->wakeup_pair[0], TRUE) < 0 || - curlx_nonblock(multi->wakeup_pair[1], TRUE) < 0) { - wakeup_close(multi->wakeup_pair[0]); - wakeup_close(multi->wakeup_pair[1]); + if(wakeup_create(multi->wakeup_pair, TRUE) < 0) { multi->wakeup_pair[0] = CURL_SOCKET_BAD; multi->wakeup_pair[1] = CURL_SOCKET_BAD; } @@ -446,13 +444,14 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ error: sockhash_destroy(&multi->sockhash); + Curl_hash_destroy(&multi->proto_hash); Curl_hash_destroy(&multi->hostcache); - Curl_conncache_destroy(&multi->conn_cache); + Curl_cpool_destroy(&multi->cpool); free(multi); return NULL; } -struct Curl_multi *curl_multi_init(void) +CURLM *curl_multi_init(void) { return Curl_multi_handle(CURL_SOCKET_HASH_TABLE_SIZE, CURL_CONNECTION_HASH_SIZE, @@ -466,63 +465,18 @@ static void multi_warn_debug(struct Curl_multi *multi, struct Curl_easy *data) infof(data, "!!! WARNING !!!"); infof(data, "This is a debug build of libcurl, " "do not use in production."); - multi->warned = true; + multi->warned = TRUE; } } #else #define multi_warn_debug(x,y) Curl_nop_stmt #endif -/* returns TRUE if the easy handle is supposed to be present in the main link - list */ -static bool in_main_list(struct Curl_easy *data) -{ - return ((data->mstate != MSTATE_PENDING) && - (data->mstate != MSTATE_MSGSENT)); -} - -static void link_easy(struct Curl_multi *multi, - struct Curl_easy *data) -{ - /* We add the new easy entry last in the list. */ - data->next = NULL; /* end of the line */ - if(multi->easyp) { - struct Curl_easy *last = multi->easylp; - last->next = data; - data->prev = last; - multi->easylp = data; /* the new last node */ - } - else { - /* first node, make prev NULL! */ - data->prev = NULL; - multi->easylp = multi->easyp = data; /* both first and last */ - } -} - -/* unlink the given easy handle from the linked list of easy handles */ -static void unlink_easy(struct Curl_multi *multi, - struct Curl_easy *data) -{ - /* make the previous node point to our next */ - if(data->prev) - data->prev->next = data->next; - else - multi->easyp = data->next; /* point to first node */ - - /* make our next point to our previous node */ - if(data->next) - data->next->prev = data->prev; - else - multi->easylp = data->prev; /* point to last node */ - - data->prev = data->next = NULL; -} - - -CURLMcode curl_multi_add_handle(struct Curl_multi *multi, - struct Curl_easy *data) +CURLMcode curl_multi_add_handle(CURLM *m, CURL *d) { CURLMcode rc; + struct Curl_multi *multi = m; + struct Curl_easy *data = d; /* First, make some basic checks that the CURLM handle is a good handle */ if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; @@ -548,18 +502,27 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, multi->dead = FALSE; } + if(data->multi_easy) { + /* if this easy handle was previously used for curl_easy_perform(), there + is a private multi handle here that we can kill */ + curl_multi_cleanup(data->multi_easy); + data->multi_easy = NULL; + } + /* Initialize timeout list for this handle */ Curl_llist_init(&data->state.timeoutlist, NULL); /* - * No failure allowed in this function beyond this point. And no - * modification of easy nor multi handle allowed before this except for - * potential multi's connection cache growing which won't be undone in this - * function no matter what. + * No failure allowed in this function beyond this point. No modification of + * easy nor multi handle allowed before this except for potential multi's + * connection pool growing which will not be undone in this function no + * matter what. */ if(data->set.errorbuffer) data->set.errorbuffer[0] = 0; + data->state.os_errno = 0; + /* make the Curl_easy refer back to this multi handle - before Curl_expire() is called. */ data->multi = multi; @@ -572,21 +535,11 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, happen. */ Curl_expire(data, 0, EXPIRE_RUN_NOW); - /* A somewhat crude work-around for a little glitch in Curl_update_timer() - that happens if the lastcall time is set to the same time when the handle - is removed as when the next handle is added, as then the check in - Curl_update_timer() that prevents calling the application multiple times - with the same timer info will not trigger and then the new handle's - timeout will not be notified to the app. - - The work-around is thus simply to clear the 'lastcall' variable to force - Curl_update_timer() to always trigger a callback to the app when a new - easy handle is added */ - memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall)); - rc = Curl_update_timer(multi); - if(rc) + if(rc) { + data->multi = NULL; /* not anymore */ return rc; + } /* set the easy handle */ multistate(data, MSTATE_INIT); @@ -599,13 +552,6 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, data->dns.hostcachetype = HCACHE_MULTI; } - /* Point to the shared or multi handle connection cache */ - if(data->share && (data->share->specifier & (1<< CURL_LOCK_DATA_CONNECT))) - data->state.conn_cache = &data->share->conn_cache; - else - data->state.conn_cache = &multi->conn_cache; - data->state.lastconnect_id = -1; - #ifdef USE_LIBPSL /* Do the same for PSL. */ if(data->share && (data->share->specifier & (1 << CURL_LOCK_DATA_PSL))) @@ -614,7 +560,8 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, data->psl = &multi->psl; #endif - link_easy(multi, data); + /* add the easy handle to the process list */ + Curl_llist_append(&multi->process, data, &data->multi_queue); /* increase the node-counter */ multi->num_easy++; @@ -622,21 +569,12 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, /* increase the alive-counter */ multi->num_alive++; - CONNCACHE_LOCK(data); - /* The closure handle only ever has default timeouts set. To improve the - state somewhat we clone the timeouts from each added handle so that the - closure handle always has the same timeouts as the most recently added - easy handle. */ - data->state.conn_cache->closure_handle->set.timeout = data->set.timeout; - data->state.conn_cache->closure_handle->set.server_response_timeout = - data->set.server_response_timeout; - data->state.conn_cache->closure_handle->set.no_signal = - data->set.no_signal; - data->id = data->state.conn_cache->next_easy_id++; - if(data->state.conn_cache->next_easy_id <= 0) - data->state.conn_cache->next_easy_id = 0; - CONNCACHE_UNLOCK(data); + /* the identifier inside the multi instance */ + data->mid = multi->next_easy_mid++; + if(multi->next_easy_mid <= 0) + multi->next_easy_mid = 0; + Curl_cpool_xfer_init(data); multi_warn_debug(multi, data); return CURLM_OK; @@ -658,14 +596,101 @@ static void debug_print_sock_hash(void *p) } #endif +struct multi_done_ctx { + BIT(premature); +}; + +static void multi_done_locked(struct connectdata *conn, + struct Curl_easy *data, + void *userdata) +{ + struct multi_done_ctx *mdctx = userdata; + + Curl_detach_connection(data); + + if(CONN_INUSE(conn)) { + /* Stop if still used. */ + DEBUGF(infof(data, "Connection still in use %zu, " + "no more multi_done now!", + Curl_llist_count(&conn->easyq))); + return; + } + + data->state.done = TRUE; /* called just now! */ + data->state.recent_conn_id = conn->connection_id; + + if(conn->dns_entry) + Curl_resolv_unlink(data, &conn->dns_entry); /* done with this */ + Curl_hostcache_prune(data); + + /* if data->set.reuse_forbid is TRUE, it means the libcurl client has + forced us to close this connection. This is ignored for requests taking + place in a NTLM/NEGOTIATE authentication handshake + + if conn->bits.close is TRUE, it means that the connection should be + closed in spite of all our efforts to be nice, due to protocol + restrictions in our or the server's end + + if premature is TRUE, it means this connection was said to be DONE before + the entire request operation is complete and thus we cannot know in what + state it is for reusing, so we are forced to close it. In a perfect world + we can add code that keep track of if we really must close it here or not, + but currently we have no such detail knowledge. + */ + + if((data->set.reuse_forbid +#if defined(USE_NTLM) + && !(conn->http_ntlm_state == NTLMSTATE_TYPE2 || + conn->proxy_ntlm_state == NTLMSTATE_TYPE2) +#endif +#if defined(USE_SPNEGO) + && !(conn->http_negotiate_state == GSS_AUTHRECV || + conn->proxy_negotiate_state == GSS_AUTHRECV) +#endif + ) || conn->bits.close + || (mdctx->premature && !Curl_conn_is_multiplex(conn, FIRSTSOCKET))) { + DEBUGF(infof(data, "multi_done, not reusing connection=%" + FMT_OFF_T ", forbid=%d" + ", close=%d, premature=%d, conn_multiplex=%d", + conn->connection_id, data->set.reuse_forbid, + conn->bits.close, mdctx->premature, + Curl_conn_is_multiplex(conn, FIRSTSOCKET))); + connclose(conn, "disconnecting"); + Curl_cpool_disconnect(data, conn, mdctx->premature); + } + else { + /* the connection is no longer in use by any transfer */ + if(Curl_cpool_conn_now_idle(data, conn)) { + /* connection kept in the cpool */ + const char *host = +#ifndef CURL_DISABLE_PROXY + conn->bits.socksproxy ? + conn->socks_proxy.host.dispname : + conn->bits.httpproxy ? conn->http_proxy.host.dispname : +#endif + conn->bits.conn_to_host ? conn->conn_to_host.dispname : + conn->host.dispname; + data->state.lastconnect_id = conn->connection_id; + infof(data, "Connection #%" FMT_OFF_T " to host %s left intact", + conn->connection_id, host); + } + else { + /* connection was removed from the cpool and destroyed. */ + data->state.lastconnect_id = -1; + } + } +} + static CURLcode multi_done(struct Curl_easy *data, CURLcode status, /* an error if this is called after an error was detected */ bool premature) { - CURLcode result; + CURLcode result, r2; struct connectdata *conn = data->conn; - unsigned int i; + struct multi_done_ctx mdctx; + + memset(&mdctx, 0, sizeof(mdctx)); #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) DEBUGF(infof(data, "multi_done[%s]: status: %d prem: %d done: %d", @@ -691,11 +716,12 @@ static CURLcode multi_done(struct Curl_easy *data, case CURLE_ABORTED_BY_CALLBACK: case CURLE_READ_ERROR: case CURLE_WRITE_ERROR: - /* When we're aborted due to a callback return code it basically have to - be counted as premature as there is trouble ahead if we don't. We have + /* When we are aborted due to a callback return code it basically have to + be counted as premature as there is trouble ahead if we do not. We have many callbacks and protocols work differently, we could potentially do this more fine-grained in the future. */ premature = TRUE; + FALLTHROUGH(); default: break; } @@ -714,137 +740,54 @@ static CURLcode multi_done(struct Curl_easy *data, result = CURLE_ABORTED_BY_CALLBACK; } + /* Make sure that transfer client writes are really done now. */ + r2 = Curl_xfer_write_done(data, premature); + if(r2 && !result) + result = r2; + /* Inform connection filters that this transfer is done */ Curl_conn_ev_data_done(data, premature); process_pending_handles(data->multi); /* connection / multiplex */ - Curl_safefree(data->state.ulbuf); - - /* if the transfer was completed in a paused state there can be buffered - data left to free */ - for(i = 0; i < data->state.tempcount; i++) { - Curl_dyn_free(&data->state.tempwrite[i].b); - } - data->state.tempcount = 0; - - CONNCACHE_LOCK(data); - Curl_detach_connection(data); - if(CONN_INUSE(conn)) { - /* Stop if still used. */ - CONNCACHE_UNLOCK(data); - DEBUGF(infof(data, "Connection still in use %zu, " - "no more multi_done now!", - conn->easyq.size)); - return CURLE_OK; - } - - data->state.done = TRUE; /* called just now! */ - - if(conn->dns_entry) { - Curl_resolv_unlock(data, conn->dns_entry); /* done with this */ - conn->dns_entry = NULL; - } - Curl_hostcache_prune(data); - - /* if data->set.reuse_forbid is TRUE, it means the libcurl client has - forced us to close this connection. This is ignored for requests taking - place in a NTLM/NEGOTIATE authentication handshake - - if conn->bits.close is TRUE, it means that the connection should be - closed in spite of all our efforts to be nice, due to protocol - restrictions in our or the server's end - - if premature is TRUE, it means this connection was said to be DONE before - the entire request operation is complete and thus we can't know in what - state it is for reusing, so we're forced to close it. In a perfect world - we can add code that keep track of if we really must close it here or not, - but currently we have no such detail knowledge. - */ + if(!result) + result = Curl_req_done(&data->req, data, premature); - data->state.recent_conn_id = conn->connection_id; - if((data->set.reuse_forbid -#if defined(USE_NTLM) - && !(conn->http_ntlm_state == NTLMSTATE_TYPE2 || - conn->proxy_ntlm_state == NTLMSTATE_TYPE2) -#endif -#if defined(USE_SPNEGO) - && !(conn->http_negotiate_state == GSS_AUTHRECV || - conn->proxy_negotiate_state == GSS_AUTHRECV) -#endif - ) || conn->bits.close - || (premature && !Curl_conn_is_multiplex(conn, FIRSTSOCKET))) { - DEBUGF(infof(data, "multi_done, not reusing connection=%" - CURL_FORMAT_CURL_OFF_T ", forbid=%d" - ", close=%d, premature=%d, conn_multiplex=%d", - conn->connection_id, - data->set.reuse_forbid, conn->bits.close, premature, - Curl_conn_is_multiplex(conn, FIRSTSOCKET))); - connclose(conn, "disconnecting"); - Curl_conncache_remove_conn(data, conn, FALSE); - CONNCACHE_UNLOCK(data); - Curl_disconnect(data, conn, premature); - } - else { - char buffer[256]; - const char *host = -#ifndef CURL_DISABLE_PROXY - conn->bits.socksproxy ? - conn->socks_proxy.host.dispname : - conn->bits.httpproxy ? conn->http_proxy.host.dispname : -#endif - conn->bits.conn_to_host ? conn->conn_to_host.dispname : - conn->host.dispname; - /* create string before returning the connection */ - curl_off_t connection_id = conn->connection_id; - msnprintf(buffer, sizeof(buffer), - "Connection #%" CURL_FORMAT_CURL_OFF_T " to host %s left intact", - connection_id, host); - /* the connection is no longer in use by this transfer */ - CONNCACHE_UNLOCK(data); - if(Curl_conncache_return_conn(data, conn)) { - /* remember the most recently used connection */ - data->state.lastconnect_id = connection_id; - data->state.recent_conn_id = connection_id; - infof(data, "%s", buffer); - } - else - data->state.lastconnect_id = -1; - } + /* Under the potential connection pool's share lock, decide what to + * do with the transfer's connection. */ + mdctx.premature = premature; + Curl_cpool_do_locked(data, data->conn, multi_done_locked, &mdctx); - Curl_safefree(data->state.buffer); + /* flush the netrc cache */ + Curl_netrc_cleanup(&data->state.netrc); return result; } -static int close_connect_only(struct Curl_easy *data, - struct connectdata *conn, void *param) +static void close_connect_only(struct connectdata *conn, + struct Curl_easy *data, + void *userdata) { - (void)param; - if(data->state.lastconnect_id != conn->connection_id) - return 0; - - if(!conn->connect_only) - return 1; - - connclose(conn, "Removing connect-only easy handle"); - - return 1; + (void)userdata; + (void)data; + if(conn->connect_only) + connclose(conn, "Removing connect-only easy handle"); } -CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, - struct Curl_easy *data) +CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d) { - struct Curl_easy *easy = data; + struct Curl_multi *multi = m; + struct Curl_easy *data = d; bool premature; - struct Curl_llist_element *e; + struct Curl_llist_node *e; CURLMcode rc; + bool removed_timer = FALSE; /* First, make some basic checks that the CURLM handle is a good handle */ if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; /* Verify that we got a somewhat good easy handle too */ - if(!GOOD_EASY_HANDLE(data)) + if(!GOOD_EASY_HANDLE(data) || !multi->num_easy) return CURLM_BAD_EASY_HANDLE; /* Prevent users from trying to remove same easy handle more than once */ @@ -858,7 +801,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - premature = (data->mstate < MSTATE_COMPLETED) ? TRUE : FALSE; + premature = (data->mstate < MSTATE_COMPLETED); /* If the 'state' is not INIT or COMPLETED, we might need to do something nice to put the easy_handle in a good known state when this returns. */ @@ -871,7 +814,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, if(data->conn && data->mstate > MSTATE_DO && data->mstate < MSTATE_COMPLETED) { - /* Set connection owner so that the DONE function closes it. We can + /* Set connection owner so that the DONE function closes it. We can safely do this here since connection is killed. */ streamclose(data->conn, "Removed with partial response"); } @@ -880,7 +823,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, /* multi_done() clears the association between the easy handle and the connection. - Note that this ignores the return code simply because there's + Note that this ignores the return code simply because there is nothing really useful to do with it anyway! */ (void)multi_done(data, data->result, premature); } @@ -888,18 +831,10 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, /* The timer must be shut down before data->multi is set to NULL, else the timenode will remain in the splay tree after curl_easy_cleanup is called. Do it after multi_done() in case that sets another time! */ - Curl_expire_clear(data); + removed_timer = Curl_expire_clear(data); - if(data->connect_queue.ptr) { - /* the handle is in the pending or msgsent lists, so go ahead and remove - it */ - if(data->mstate == MSTATE_PENDING) - Curl_llist_remove(&multi->pending, &data->connect_queue, NULL); - else - Curl_llist_remove(&multi->msgsent, &data->connect_queue, NULL); - } - if(in_main_list(data)) - unlink_easy(multi, data); + /* the handle is in a list, remove it from whichever it is */ + Curl_node_remove(&data->multi_queue); if(data->dns.hostcachetype == HCACHE_MULTI) { /* stop using the multi handle's DNS cache, *after* the possible @@ -914,9 +849,9 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, what we want */ data->mstate = MSTATE_COMPLETED; - /* This ignores the return code even in case of problems because there's + /* This ignores the return code even in case of problems because there is nothing more to do about that, here */ - (void)singlesocket(multi, easy); /* to let the application know what sockets + (void)singlesocket(multi, data); /* to let the application know what sockets that vanish with this handle */ /* Remove the association between the connection and the handle */ @@ -926,7 +861,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, /* This removes a handle that was part the multi interface that used CONNECT_ONLY, that connection is now left alive but since this handle has bits.close set nothing can use that transfer anymore and it is - forbidden from reuse. And this easy handle cannot find the connection + forbidden from reuse. This easy handle cannot find the connection anymore once removed from the multi handle Better close the connection here, at once. @@ -935,15 +870,14 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, curl_socket_t s; s = Curl_getconnectinfo(data, &c); if((s != CURL_SOCKET_BAD) && c) { - Curl_conncache_remove_conn(data, c, TRUE); - Curl_disconnect(data, c, TRUE); + Curl_cpool_disconnect(data, c, TRUE); } } if(data->state.lastconnect_id != -1) { /* Mark any connect-only connection for closure */ - Curl_conncache_foreach(data, data->state.conn_cache, - NULL, close_connect_only); + Curl_cpool_do_by_id(data, data->state.lastconnect_id, + close_connect_only, NULL); } #ifdef USE_LIBPSL @@ -952,33 +886,31 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, data->psl = NULL; #endif - /* as this was using a shared connection cache we clear the pointer to that - since we're not part of that multi handle anymore */ - data->state.conn_cache = NULL; - - data->multi = NULL; /* clear the association to this multi handle */ - - /* make sure there's no pending message in the queue sent from this easy + /* make sure there is no pending message in the queue sent from this easy handle */ - for(e = multi->msglist.head; e; e = e->next) { - struct Curl_message *msg = e->ptr; + for(e = Curl_llist_head(&multi->msglist); e; e = Curl_node_next(e)) { + struct Curl_message *msg = Curl_node_elem(e); - if(msg->extmsg.easy_handle == easy) { - Curl_llist_remove(&multi->msglist, e, NULL); + if(msg->extmsg.easy_handle == data) { + Curl_node_remove(e); /* there can only be one from this specific handle */ break; } } + data->multi = NULL; /* clear the association to this multi handle */ + data->mid = -1; + /* NOTE NOTE NOTE We do not touch the easy handle here! */ multi->num_easy--; /* one less to care about now */ - process_pending_handles(multi); - rc = Curl_update_timer(multi); - if(rc) - return rc; + if(removed_timer) { + rc = Curl_update_timer(multi); + if(rc) + return rc; + } return CURLM_OK; } @@ -999,7 +931,7 @@ void Curl_detach_connection(struct Curl_easy *data) struct connectdata *conn = data->conn; if(conn) { Curl_conn_ev_data_detach(conn, data); - Curl_llist_remove(&conn->easyq, &data->conn_queue, NULL); + Curl_node_remove(&data->conn_queue); } data->conn = NULL; } @@ -1010,98 +942,216 @@ void Curl_detach_connection(struct Curl_easy *data) * This is the only function that should assign data->conn */ void Curl_attach_connection(struct Curl_easy *data, - struct connectdata *conn) + struct connectdata *conn) { + DEBUGASSERT(data); DEBUGASSERT(!data->conn); DEBUGASSERT(conn); data->conn = conn; - Curl_llist_insert_next(&conn->easyq, conn->easyq.tail, data, - &data->conn_queue); + Curl_llist_append(&conn->easyq, data, &data->conn_queue); if(conn->handler && conn->handler->attach) conn->handler->attach(data, conn); Curl_conn_ev_data_attach(conn, data); } -static int domore_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) +static int connecting_getsock(struct Curl_easy *data, curl_socket_t *socks) +{ + struct connectdata *conn = data->conn; + curl_socket_t sockfd; + + if(!conn) + return GETSOCK_BLANK; + sockfd = Curl_conn_get_socket(data, FIRSTSOCKET); + if(sockfd != CURL_SOCKET_BAD) { + /* Default is to wait to something from the server */ + socks[0] = sockfd; + return GETSOCK_READSOCK(0); + } + return GETSOCK_BLANK; +} + +static int protocol_getsock(struct Curl_easy *data, curl_socket_t *socks) +{ + struct connectdata *conn = data->conn; + curl_socket_t sockfd; + + if(!conn) + return GETSOCK_BLANK; + if(conn->handler->proto_getsock) + return conn->handler->proto_getsock(data, conn, socks); + sockfd = Curl_conn_get_socket(data, FIRSTSOCKET); + if(sockfd != CURL_SOCKET_BAD) { + /* Default is to wait to something from the server */ + socks[0] = sockfd; + return GETSOCK_READSOCK(0); + } + return GETSOCK_BLANK; +} + +static int domore_getsock(struct Curl_easy *data, curl_socket_t *socks) { - if(conn && conn->handler->domore_getsock) + struct connectdata *conn = data->conn; + if(!conn) + return GETSOCK_BLANK; + if(conn->handler->domore_getsock) return conn->handler->domore_getsock(data, conn, socks); + else if(conn->sockfd != CURL_SOCKET_BAD) { + /* Default is that we want to send something to the server */ + socks[0] = conn->sockfd; + return GETSOCK_WRITESOCK(0); + } return GETSOCK_BLANK; } -static int doing_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) +static int doing_getsock(struct Curl_easy *data, curl_socket_t *socks) { - if(conn && conn->handler->doing_getsock) + struct connectdata *conn = data->conn; + if(!conn) + return GETSOCK_BLANK; + if(conn->handler->doing_getsock) return conn->handler->doing_getsock(data, conn, socks); + else if(conn->sockfd != CURL_SOCKET_BAD) { + /* Default is that we want to send something to the server */ + socks[0] = conn->sockfd; + return GETSOCK_WRITESOCK(0); + } return GETSOCK_BLANK; } -static int protocol_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) +static int perform_getsock(struct Curl_easy *data, curl_socket_t *sock) { - if(conn->handler->proto_getsock) - return conn->handler->proto_getsock(data, conn, socks); - return Curl_conn_get_select_socks(data, FIRSTSOCKET, socks); + struct connectdata *conn = data->conn; + if(!conn) + return GETSOCK_BLANK; + else if(conn->handler->perform_getsock) + return conn->handler->perform_getsock(data, conn, sock); + else { + /* Default is to obey the data->req.keepon flags for send/recv */ + int bitmap = GETSOCK_BLANK; + unsigned sockindex = 0; + if(CURL_WANT_RECV(data)) { + DEBUGASSERT(conn->sockfd != CURL_SOCKET_BAD); + bitmap |= GETSOCK_READSOCK(sockindex); + sock[sockindex] = conn->sockfd; + } + + if(Curl_req_want_send(data)) { + if((conn->sockfd != conn->writesockfd) || + bitmap == GETSOCK_BLANK) { + /* only if they are not the same socket and we have a readable + one, we increase index */ + if(bitmap != GETSOCK_BLANK) + sockindex++; /* increase index if we need two entries */ + + DEBUGASSERT(conn->writesockfd != CURL_SOCKET_BAD); + sock[sockindex] = conn->writesockfd; + } + bitmap |= GETSOCK_WRITESOCK(sockindex); + } + return bitmap; + } } -/* returns bitmapped flags for this handle and its sockets. The 'socks[]' - array contains MAX_SOCKSPEREASYHANDLE entries. */ -static int multi_getsock(struct Curl_easy *data, - curl_socket_t *socks) +/* Initializes `poll_set` with the current socket poll actions needed + * for transfer `data`. */ +static void multi_getsock(struct Curl_easy *data, + struct easy_pollset *ps) { - struct connectdata *conn = data->conn; + bool expect_sockets = TRUE; /* The no connection case can happen when this is called from curl_multi_remove_handle() => singlesocket() => multi_getsock(). */ - if(!conn) - return 0; + Curl_pollset_reset(data, ps); + if(!data->conn) + return; switch(data->mstate) { - default: - return 0; + case MSTATE_INIT: + case MSTATE_PENDING: + case MSTATE_SETUP: + case MSTATE_CONNECT: + /* nothing to poll for yet */ + expect_sockets = FALSE; + break; case MSTATE_RESOLVING: - return Curl_resolv_getsock(data, socks); + Curl_pollset_add_socks(data, ps, Curl_resolv_getsock); + /* connection filters are not involved in this phase. It's ok if we get no + * sockets to wait for. Resolving can wake up from other sources. */ + expect_sockets = FALSE; + break; + + case MSTATE_CONNECTING: + case MSTATE_TUNNELING: + Curl_pollset_add_socks(data, ps, connecting_getsock); + Curl_conn_adjust_pollset(data, ps); + break; - case MSTATE_PROTOCONNECTING: case MSTATE_PROTOCONNECT: - return protocol_getsock(data, conn, socks); + case MSTATE_PROTOCONNECTING: + Curl_pollset_add_socks(data, ps, protocol_getsock); + Curl_conn_adjust_pollset(data, ps); + break; case MSTATE_DO: case MSTATE_DOING: - return doing_getsock(data, conn, socks); - - case MSTATE_TUNNELING: - case MSTATE_CONNECTING: - return Curl_conn_get_select_socks(data, FIRSTSOCKET, socks); + Curl_pollset_add_socks(data, ps, doing_getsock); + Curl_conn_adjust_pollset(data, ps); + break; case MSTATE_DOING_MORE: - return domore_getsock(data, conn, socks); + Curl_pollset_add_socks(data, ps, domore_getsock); + Curl_conn_adjust_pollset(data, ps); + break; - case MSTATE_DID: /* since is set after DO is completed, we switch to - waiting for the same as the PERFORMING state */ + case MSTATE_DID: /* same as PERFORMING in regard to polling */ case MSTATE_PERFORMING: - return Curl_single_getsock(data, conn, socks); + Curl_pollset_add_socks(data, ps, perform_getsock); + Curl_conn_adjust_pollset(data, ps); + break; + + case MSTATE_RATELIMITING: + /* we need to let time pass, ignore socket(s) */ + expect_sockets = FALSE; + break; + + case MSTATE_DONE: + case MSTATE_COMPLETED: + case MSTATE_MSGSENT: + /* nothing more to poll for */ + expect_sockets = FALSE; + break; + + default: + failf(data, "multi_getsock: unexpected multi state %d", data->mstate); + DEBUGASSERT(0); + expect_sockets = FALSE; + break; } + if(expect_sockets && !ps->num && + !Curl_llist_count(&data->state.timeoutlist) && + !Curl_cwriter_is_paused(data) && !Curl_creader_is_paused(data) && + Curl_conn_is_ip_connected(data, FIRSTSOCKET)) { + /* We expected sockets for POLL monitoring, but none are set. + * We are not waiting on any timer. + * None of the READ/WRITE directions are paused. + * We are connected to the server on IP level, at least. */ + infof(data, "WARNING: no socket in pollset or timer, transfer may stall!"); + DEBUGASSERT(0); + } } -CURLMcode curl_multi_fdset(struct Curl_multi *multi, +CURLMcode curl_multi_fdset(CURLM *m, fd_set *read_fd_set, fd_set *write_fd_set, fd_set *exc_fd_set, int *max_fd) { /* Scan through all the easy handles to get the file descriptors set. Some easy handles may not have connected to the remote host yet, and then we must make sure that is done. */ - struct Curl_easy *data; int this_max_fd = -1; - curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE]; - int i; + struct Curl_llist_node *e; + struct Curl_multi *multi = m; (void)exc_fd_set; /* not used */ if(!GOOD_MULTI_HANDLE(multi)) @@ -1110,29 +1160,22 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi, if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - for(data = multi->easyp; data; data = data->next) { - int bitmap; -#ifdef __clang_analyzer_ - /* to prevent "The left operand of '>=' is a garbage value" warnings */ - memset(sockbunch, 0, sizeof(sockbunch)); -#endif - bitmap = multi_getsock(data, sockbunch); - - for(i = 0; i< MAX_SOCKSPEREASYHANDLE; i++) { - if((bitmap & GETSOCK_MASK_RW(i)) && VALID_SOCK((sockbunch[i]))) { - if(!FDSET_SOCK(sockbunch[i])) - /* pretend it doesn't exist */ - continue; - if(bitmap & GETSOCK_READSOCK(i)) - FD_SET(sockbunch[i], read_fd_set); - if(bitmap & GETSOCK_WRITESOCK(i)) - FD_SET(sockbunch[i], write_fd_set); - if((int)sockbunch[i] > this_max_fd) - this_max_fd = (int)sockbunch[i]; - } - else { - break; - } + for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *data = Curl_node_elem(e); + unsigned int i; + + multi_getsock(data, &data->last_poll); + + for(i = 0; i < data->last_poll.num; i++) { + if(!FDSET_SOCK(data->last_poll.sockets[i])) + /* pretend it does not exist */ + continue; + if(data->last_poll.actions[i] & CURL_POLL_IN) + FD_SET(data->last_poll.sockets[i], read_fd_set); + if(data->last_poll.actions[i] & CURL_POLL_OUT) + FD_SET(data->last_poll.sockets[i], write_fd_set); + if((int)data->last_poll.sockets[i] > this_max_fd) + this_max_fd = (int)data->last_poll.sockets[i]; } } @@ -1141,21 +1184,61 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi, return CURLM_OK; } -#ifdef USE_WINSOCK -/* Reset FD_WRITE for TCP sockets. Nothing is actually sent. UDP sockets can't - * be reset this way because an empty datagram would be sent. #9203 - * - * "On Windows the internal state of FD_WRITE as returned from - * WSAEnumNetworkEvents is only reset after successful send()." - */ -static void reset_socket_fdwrite(curl_socket_t s) +CURLMcode curl_multi_waitfds(CURLM *m, + struct curl_waitfd *ufds, + unsigned int size, + unsigned int *fd_count) { - int t; - int l = (int)sizeof(t); - if(!getsockopt(s, SOL_SOCKET, SO_TYPE, (char *)&t, &l) && t == SOCK_STREAM) - send(s, NULL, 0, 0); -} -#endif + struct curl_waitfds cwfds; + CURLMcode result = CURLM_OK; + struct Curl_llist_node *e; + struct Curl_multi *multi = m; + + if(!ufds) + return CURLM_BAD_FUNCTION_ARGUMENT; + + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + + Curl_waitfds_init(&cwfds, ufds, size); + for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *data = Curl_node_elem(e); + multi_getsock(data, &data->last_poll); + if(Curl_waitfds_add_ps(&cwfds, &data->last_poll)) { + result = CURLM_OUT_OF_MEMORY; + goto out; + } + } + + if(Curl_cpool_add_waitfds(&multi->cpool, &cwfds)) { + result = CURLM_OUT_OF_MEMORY; + goto out; + } + +out: + if(fd_count) + *fd_count = cwfds.n; + return result; +} + +#ifdef USE_WINSOCK +/* Reset FD_WRITE for TCP sockets. Nothing is actually sent. UDP sockets cannot + * be reset this way because an empty datagram would be sent. #9203 + * + * "On Windows the internal state of FD_WRITE as returned from + * WSAEnumNetworkEvents is only reset after successful send()." + */ +static void reset_socket_fdwrite(curl_socket_t s) +{ + int t; + int l = (int)sizeof(t); + if(!getsockopt(s, SOL_SOCKET, SO_TYPE, (char *)&t, &l) && t == SOCK_STREAM) + send(s, NULL, 0, 0); +} +#endif #define NUM_POLLS_ON_STACK 10 @@ -1167,17 +1250,16 @@ static CURLMcode multi_wait(struct Curl_multi *multi, bool extrawait, /* when no socket, wait */ bool use_wakeup) { - struct Curl_easy *data; - curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE]; - int bitmap; - unsigned int i; - unsigned int nfds = 0; - unsigned int curlfds; + size_t i; + struct curltime expire_time; long timeout_internal; int retcode = 0; struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; - struct pollfd *ufds = &a_few_on_stack[0]; - bool ufds_malloc = FALSE; + struct curl_pollfds cpfds; + unsigned int curl_nfds = 0; /* how many pfds are for curl transfers */ + CURLMcode result = CURLM_OK; + struct Curl_llist_node *e; + #ifdef USE_WINSOCK WSANETWORKEVENTS wsa_events; DEBUGASSERT(multi->wsa_event != WSA_INVALID_EVENT); @@ -1195,160 +1277,108 @@ static CURLMcode multi_wait(struct Curl_multi *multi, if(timeout_ms < 0) return CURLM_BAD_FUNCTION_ARGUMENT; - /* Count up how many fds we have from the multi handle */ - for(data = multi->easyp; data; data = data->next) { - bitmap = multi_getsock(data, sockbunch); + Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK); - for(i = 0; i < MAX_SOCKSPEREASYHANDLE; i++) { - if((bitmap & GETSOCK_MASK_RW(i)) && VALID_SOCK((sockbunch[i]))) { - ++nfds; - } - else { - break; - } + /* Add the curl handles to our pollfds first */ + for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *data = Curl_node_elem(e); + + multi_getsock(data, &data->last_poll); + if(Curl_pollfds_add_ps(&cpfds, &data->last_poll)) { + result = CURLM_OUT_OF_MEMORY; + goto out; } } - /* If the internally desired timeout is actually shorter than requested from - the outside, then use the shorter time! But only if the internal timer - is actually larger than -1! */ - (void)multi_timeout(multi, &timeout_internal); - if((timeout_internal >= 0) && (timeout_internal < (long)timeout_ms)) - timeout_ms = (int)timeout_internal; - - curlfds = nfds; /* number of internal file descriptors */ - nfds += extra_nfds; /* add the externally provided ones */ - -#ifdef ENABLE_WAKEUP -#ifdef USE_WINSOCK - if(use_wakeup) { -#else - if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) { -#endif - ++nfds; + if(Curl_cpool_add_pollfds(&multi->cpool, &cpfds)) { + result = CURLM_OUT_OF_MEMORY; + goto out; } -#endif - if(nfds > NUM_POLLS_ON_STACK) { - /* 'nfds' is a 32 bit value and 'struct pollfd' is typically 8 bytes - big, so at 2^29 sockets this value might wrap. When a process gets - the capability to actually handle over 500 million sockets this - calculation needs a integer overflow check. */ - ufds = malloc(nfds * sizeof(struct pollfd)); - if(!ufds) - return CURLM_OUT_OF_MEMORY; - ufds_malloc = TRUE; - } - nfds = 0; - - /* only do the second loop if we found descriptors in the first stage run - above */ - - if(curlfds) { - /* Add the curl handles to our pollfds first */ - for(data = multi->easyp; data; data = data->next) { - bitmap = multi_getsock(data, sockbunch); - - for(i = 0; i < MAX_SOCKSPEREASYHANDLE; i++) { - if((bitmap & GETSOCK_MASK_RW(i)) && VALID_SOCK((sockbunch[i]))) { - struct pollfd *ufd = &ufds[nfds++]; -#ifdef USE_WINSOCK - long mask = 0; -#endif - ufd->fd = sockbunch[i]; - ufd->events = 0; - if(bitmap & GETSOCK_READSOCK(i)) { -#ifdef USE_WINSOCK - mask |= FD_READ|FD_ACCEPT|FD_CLOSE; -#endif - ufd->events |= POLLIN; - } - if(bitmap & GETSOCK_WRITESOCK(i)) { -#ifdef USE_WINSOCK - mask |= FD_WRITE|FD_CONNECT|FD_CLOSE; - reset_socket_fdwrite(sockbunch[i]); -#endif - ufd->events |= POLLOUT; - } -#ifdef USE_WINSOCK - if(WSAEventSelect(sockbunch[i], multi->wsa_event, mask) != 0) { - if(ufds_malloc) - free(ufds); - return CURLM_INTERNAL_ERROR; - } -#endif - } - else { - break; - } - } + curl_nfds = cpfds.n; /* what curl internally uses in cpfds */ + /* Add external file descriptions from poll-like struct curl_waitfd */ + for(i = 0; i < extra_nfds; i++) { + unsigned short events = 0; + if(extra_fds[i].events & CURL_WAIT_POLLIN) + events |= POLLIN; + if(extra_fds[i].events & CURL_WAIT_POLLPRI) + events |= POLLPRI; + if(extra_fds[i].events & CURL_WAIT_POLLOUT) + events |= POLLOUT; + if(Curl_pollfds_add_sock(&cpfds, extra_fds[i].fd, events)) { + result = CURLM_OUT_OF_MEMORY; + goto out; } } - /* Add external file descriptions from poll-like struct curl_waitfd */ - for(i = 0; i < extra_nfds; i++) { #ifdef USE_WINSOCK + /* Set the WSA events based on the collected pollds */ + for(i = 0; i < cpfds.n; i++) { long mask = 0; - if(extra_fds[i].events & CURL_WAIT_POLLIN) + if(cpfds.pfds[i].events & POLLIN) mask |= FD_READ|FD_ACCEPT|FD_CLOSE; - if(extra_fds[i].events & CURL_WAIT_POLLPRI) + if(cpfds.pfds[i].events & POLLPRI) mask |= FD_OOB; - if(extra_fds[i].events & CURL_WAIT_POLLOUT) { + if(cpfds.pfds[i].events & POLLOUT) { mask |= FD_WRITE|FD_CONNECT|FD_CLOSE; - reset_socket_fdwrite(extra_fds[i].fd); + reset_socket_fdwrite(cpfds.pfds[i].fd); } - if(WSAEventSelect(extra_fds[i].fd, multi->wsa_event, mask) != 0) { - if(ufds_malloc) - free(ufds); - return CURLM_INTERNAL_ERROR; + if(mask) { + if(WSAEventSelect(cpfds.pfds[i].fd, multi->wsa_event, mask) != 0) { + result = CURLM_OUT_OF_MEMORY; + goto out; + } } -#endif - ufds[nfds].fd = extra_fds[i].fd; - ufds[nfds].events = 0; - if(extra_fds[i].events & CURL_WAIT_POLLIN) - ufds[nfds].events |= POLLIN; - if(extra_fds[i].events & CURL_WAIT_POLLPRI) - ufds[nfds].events |= POLLPRI; - if(extra_fds[i].events & CURL_WAIT_POLLOUT) - ufds[nfds].events |= POLLOUT; - ++nfds; } +#endif #ifdef ENABLE_WAKEUP #ifndef USE_WINSOCK if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) { - ufds[nfds].fd = multi->wakeup_pair[0]; - ufds[nfds].events = POLLIN; - ++nfds; + if(Curl_pollfds_add_sock(&cpfds, multi->wakeup_pair[0], POLLIN)) { + result = CURLM_OUT_OF_MEMORY; + goto out; + } } #endif #endif + /* We check the internal timeout *AFTER* we collected all sockets to + * poll. Collecting the sockets may install new timers by protocols + * and connection filters. + * Use the shorter one of the internal and the caller requested timeout. */ + (void)multi_timeout(multi, &expire_time, &timeout_internal); + if((timeout_internal >= 0) && (timeout_internal < (long)timeout_ms)) + timeout_ms = (int)timeout_internal; + #if defined(ENABLE_WAKEUP) && defined(USE_WINSOCK) - if(nfds || use_wakeup) { + if(cpfds.n || use_wakeup) { #else - if(nfds) { + if(cpfds.n) { #endif int pollrc; #ifdef USE_WINSOCK - if(nfds) - pollrc = Curl_poll(ufds, nfds, 0); /* just pre-check with WinSock */ + if(cpfds.n) /* just pre-check with Winsock */ + pollrc = Curl_poll(cpfds.pfds, cpfds.n, 0); else pollrc = 0; #else - pollrc = Curl_poll(ufds, nfds, timeout_ms); /* wait... */ + pollrc = Curl_poll(cpfds.pfds, cpfds.n, timeout_ms); /* wait... */ #endif - if(pollrc < 0) - return CURLM_UNRECOVERABLE_POLL; + if(pollrc < 0) { + result = CURLM_UNRECOVERABLE_POLL; + goto out; + } if(pollrc > 0) { retcode = pollrc; #ifdef USE_WINSOCK } else { /* now wait... if not ready during the pre-check (pollrc == 0) */ - WSAWaitForMultipleEvents(1, &multi->wsa_event, FALSE, timeout_ms, FALSE); + WSAWaitForMultipleEvents(1, &multi->wsa_event, FALSE, (DWORD)timeout_ms, + FALSE); } - /* With WinSock, we have to run the following section unconditionally + /* With Winsock, we have to run the following section unconditionally to call WSAEventSelect(fd, event, 0) on all the sockets */ { #endif @@ -1356,7 +1386,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, struct, the bit values of the actual underlying poll() implementation may not be the same as the ones in the public libcurl API! */ for(i = 0; i < extra_nfds; i++) { - unsigned r = ufds[curlfds + i].revents; + unsigned r = (unsigned)cpfds.pfds[curl_nfds + i].revents; unsigned short mask = 0; #ifdef USE_WINSOCK curl_socket_t s = extra_fds[i].fd; @@ -1373,7 +1403,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, } WSAEventSelect(s, multi->wsa_event, 0); if(!pollrc) { - extra_fds[i].revents = mask; + extra_fds[i].revents = (short)mask; continue; } #endif @@ -1383,30 +1413,25 @@ static CURLMcode multi_wait(struct Curl_multi *multi, mask |= CURL_WAIT_POLLOUT; if(r & POLLPRI) mask |= CURL_WAIT_POLLPRI; - extra_fds[i].revents = mask; + extra_fds[i].revents = (short)mask; } #ifdef USE_WINSOCK /* Count up all our own sockets that had activity, and remove them from the event. */ - if(curlfds) { - - for(data = multi->easyp; data; data = data->next) { - bitmap = multi_getsock(data, sockbunch); - - for(i = 0; i < MAX_SOCKSPEREASYHANDLE; i++) { - if(bitmap & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i))) { - wsa_events.lNetworkEvents = 0; - if(WSAEnumNetworkEvents(sockbunch[i], NULL, &wsa_events) == 0) { - if(ret && !pollrc && wsa_events.lNetworkEvents) - retcode++; - } - WSAEventSelect(sockbunch[i], multi->wsa_event, 0); - } - else { - /* break on entry not checked for being readable or writable */ - break; + if(curl_nfds) { + for(e = Curl_llist_head(&multi->process); e && !result; + e = Curl_node_next(e)) { + struct Curl_easy *data = Curl_node_elem(e); + + for(i = 0; i < data->last_poll.num; i++) { + wsa_events.lNetworkEvents = 0; + if(WSAEnumNetworkEvents(data->last_poll.sockets[i], NULL, + &wsa_events) == 0) { + if(ret && !pollrc && wsa_events.lNetworkEvents) + retcode++; } + WSAEventSelect(data->last_poll.sockets[i], multi->wsa_event, 0); } } } @@ -1415,7 +1440,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, #else #ifdef ENABLE_WAKEUP if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) { - if(ufds[curlfds + extra_nfds].revents & POLLIN) { + if(cpfds.pfds[curl_nfds + extra_nfds].revents & POLLIN) { char buf[64]; ssize_t nread; while(1) { @@ -1439,18 +1464,16 @@ static CURLMcode multi_wait(struct Curl_multi *multi, } } - if(ufds_malloc) - free(ufds); if(ret) *ret = retcode; #if defined(ENABLE_WAKEUP) && defined(USE_WINSOCK) - if(extrawait && !nfds && !use_wakeup) { + if(extrawait && !cpfds.n && !use_wakeup) { #else - if(extrawait && !nfds) { + if(extrawait && !cpfds.n) { #endif long sleep_ms = 0; - /* Avoid busy-looping when there's nothing particular to wait for */ + /* Avoid busy-looping when there is nothing particular to wait for */ if(!curl_multi_timeout(multi, &sleep_ms) && sleep_ms) { if(sleep_ms > timeout_ms) sleep_ms = timeout_ms; @@ -1462,10 +1485,12 @@ static CURLMcode multi_wait(struct Curl_multi *multi, } } - return CURLM_OK; +out: + Curl_pollfds_cleanup(&cpfds); + return result; } -CURLMcode curl_multi_wait(struct Curl_multi *multi, +CURLMcode curl_multi_wait(CURLM *multi, struct curl_waitfd extra_fds[], unsigned int extra_nfds, int timeout_ms, @@ -1475,7 +1500,7 @@ CURLMcode curl_multi_wait(struct Curl_multi *multi, FALSE); } -CURLMcode curl_multi_poll(struct Curl_multi *multi, +CURLMcode curl_multi_poll(CURLM *multi, struct curl_waitfd extra_fds[], unsigned int extra_nfds, int timeout_ms, @@ -1485,11 +1510,21 @@ CURLMcode curl_multi_poll(struct Curl_multi *multi, TRUE); } -CURLMcode curl_multi_wakeup(struct Curl_multi *multi) +CURLMcode curl_multi_wakeup(CURLM *m) { /* this function is usually called from another thread, it has to be careful only to access parts of the Curl_multi struct that are constant */ + struct Curl_multi *multi = m; + +#if defined(ENABLE_WAKEUP) && !defined(USE_WINSOCK) +#ifdef USE_EVENTFD + const void *buf; + const uint64_t val = 1; +#else + char buf[1]; +#endif +#endif /* GOOD_MULTI_HANDLE can be safely called */ if(!GOOD_MULTI_HANDLE(multi)) @@ -1504,8 +1539,11 @@ CURLMcode curl_multi_wakeup(struct Curl_multi *multi) making it safe to access from another thread after the init part and before cleanup */ if(multi->wakeup_pair[1] != CURL_SOCKET_BAD) { - char buf[1]; +#ifdef USE_EVENTFD + buf = &val; +#else buf[0] = 1; +#endif while(1) { /* swrite() is not thread-safe in general, because concurrent calls can have their messages interleaved, but in this case the content @@ -1514,7 +1552,7 @@ CURLMcode curl_multi_wakeup(struct Curl_multi *multi) The write socket is set to non-blocking, this way this function cannot block, making it safe to call even from the same thread that will call curl_multi_wait(). If swrite() returns that it - would block, it's considered successful because it means that + would block, it is considered successful because it means that previous calls to this function will wake up the poll(). */ if(wakeup_write(multi->wakeup_pair[1], buf, sizeof(buf)) < 0) { int err = SOCKERRNO; @@ -1578,7 +1616,7 @@ CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, if(!rc) { struct SingleRequest *k = &data->req; - /* pass in NULL for 'conn' here since we don't want to init the + /* pass in NULL for 'conn' here since we do not want to init the connection, only this transfer */ Curl_init_do(data, NULL); @@ -1610,7 +1648,7 @@ static CURLcode multi_do(struct Curl_easy *data, bool *done) * second connection. * * 'complete' can return 0 for incomplete, 1 for done and -1 for go back to - * DOING state there's more work to do! + * DOING state there is more work to do! */ static CURLcode multi_do_more(struct Curl_easy *data, int *complete) @@ -1632,50 +1670,50 @@ static CURLcode multi_do_more(struct Curl_easy *data, int *complete) static bool multi_handle_timeout(struct Curl_easy *data, struct curltime *now, bool *stream_error, - CURLcode *result, - bool connect_timeout) + CURLcode *result) { - timediff_t timeout_ms; - timeout_ms = Curl_timeleft(data, now, connect_timeout); - + bool connect_timeout = data->mstate < MSTATE_DO; + timediff_t timeout_ms = Curl_timeleft(data, now, connect_timeout); if(timeout_ms < 0) { /* Handle timed out */ + struct curltime since; + if(connect_timeout) + since = data->progress.t_startsingle; + else + since = data->progress.t_startop; if(data->mstate == MSTATE_RESOLVING) - failf(data, "Resolving timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds", - Curl_timediff(*now, data->progress.t_startsingle)); + failf(data, "Resolving timed out after %" FMT_TIMEDIFF_T + " milliseconds", Curl_timediff(*now, since)); else if(data->mstate == MSTATE_CONNECTING) - failf(data, "Connection timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds", - Curl_timediff(*now, data->progress.t_startsingle)); + failf(data, "Connection timed out after %" FMT_TIMEDIFF_T + " milliseconds", Curl_timediff(*now, since)); else { struct SingleRequest *k = &data->req; if(k->size != -1) { - failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds with %" CURL_FORMAT_CURL_OFF_T " out of %" - CURL_FORMAT_CURL_OFF_T " bytes received", - Curl_timediff(*now, data->progress.t_startsingle), - k->bytecount, k->size); + failf(data, "Operation timed out after %" FMT_TIMEDIFF_T + " milliseconds with %" FMT_OFF_T " out of %" + FMT_OFF_T " bytes received", + Curl_timediff(*now, since), k->bytecount, k->size); } else { - failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds with %" CURL_FORMAT_CURL_OFF_T - " bytes received", - Curl_timediff(*now, data->progress.t_startsingle), - k->bytecount); + failf(data, "Operation timed out after %" FMT_TIMEDIFF_T + " milliseconds with %" FMT_OFF_T " bytes received", + Curl_timediff(*now, since), k->bytecount); } } - - /* Force connection closed if the connection has indeed been used */ - if(data->mstate > MSTATE_DO) { - streamclose(data->conn, "Disconnected with pending data"); - *stream_error = TRUE; - } *result = CURLE_OPERATION_TIMEDOUT; - (void)multi_done(data, *result, TRUE); + if(data->conn) { + /* Force connection closed if the connection has indeed been used */ + if(data->mstate > MSTATE_DO) { + streamclose(data->conn, "Disconnect due to timeout"); + *stream_error = TRUE; + } + (void)multi_done(data, *result, TRUE); + } + return TRUE; } - return (timeout_ms < 0); + return FALSE; } /* @@ -1738,10 +1776,10 @@ static CURLcode protocol_connect(struct Curl_easy *data, && conn->bits.protoconnstart) { /* We already are connected, get back. This may happen when the connect worked fine in the first call, like when we connect to a local server - or proxy. Note that we don't know if the protocol is actually done. + or proxy. Note that we do not know if the protocol is actually done. - Unless this protocol doesn't have any protocol-connect callback, as - then we know we're done. */ + Unless this protocol does not have any protocol-connect callback, as + then we know we are done. */ if(!conn->handler->connecting) *protocol_done = TRUE; @@ -1758,7 +1796,7 @@ static CURLcode protocol_connect(struct Curl_easy *data, else *protocol_done = TRUE; - /* it has started, possibly even completed but that knowledge isn't stored + /* it has started, possibly even completed but that knowledge is not stored in this bit! */ if(!result) conn->bits.protoconnstart = TRUE; @@ -1767,108 +1805,788 @@ static CURLcode protocol_connect(struct Curl_easy *data, return result; /* pass back status */ } +static void set_in_callback(struct Curl_multi *multi, bool value) +{ + multi->in_callback = value; +} + /* - * readrewind() rewinds the read stream. This is typically used for HTTP - * POST/PUT with multi-pass authentication when a sending was denied and a - * resend is necessary. + * posttransfer() is called immediately after a transfer ends */ -static CURLcode readrewind(struct Curl_easy *data) +static void multi_posttransfer(struct Curl_easy *data) { - curl_mimepart *mimepart = &data->set.mimepost; - DEBUGASSERT(data->conn); +#if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) + /* restore the signal handler for SIGPIPE before we get back */ + if(!data->set.no_signal) + signal(SIGPIPE, data->state.prev_signal); +#else + (void)data; /* unused parameter */ +#endif +} - data->state.rewindbeforesend = FALSE; /* we rewind now */ +/* + * multi_follow() handles the URL redirect magic. Pass in the 'newurl' string + * as given by the remote server and set up the new URL to request. + * + * This function DOES NOT FREE the given url. + */ +static CURLcode multi_follow(struct Curl_easy *data, + char *newurl, /* the Location: string */ + followtype type) /* see transfer.h */ +{ +#ifdef CURL_DISABLE_HTTP + (void)data; + (void)newurl; + (void)type; + /* Location: following will not happen when HTTP is disabled */ + return CURLE_TOO_MANY_REDIRECTS; +#else - /* explicitly switch off sending data on this connection now since we are - about to restart a new transfer and thus we want to avoid inadvertently - sending more data on the existing connection until the next transfer - starts */ - data->req.keepon &= ~KEEP_SEND; + /* Location: redirect */ + bool disallowport = FALSE; + bool reachedmax = FALSE; + CURLUcode uc; + + DEBUGASSERT(type != FOLLOW_NONE); + + if(type != FOLLOW_FAKE) + data->state.requests++; /* count all real follows */ + if(type == FOLLOW_REDIR) { + if((data->set.maxredirs != -1) && + (data->state.followlocation >= data->set.maxredirs)) { + reachedmax = TRUE; + type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected + to URL */ + } + else { + data->state.followlocation++; /* count redirect-followings, including + auth reloads */ - /* We have sent away data. If not using CURLOPT_POSTFIELDS or - CURLOPT_HTTPPOST, call app to rewind - */ -#ifndef CURL_DISABLE_HTTP - if(data->conn->handler->protocol & PROTO_FAMILY_HTTP) { - if(data->state.mimepost) - mimepart = data->state.mimepost; + if(data->set.http_auto_referer) { + CURLU *u; + char *referer = NULL; + + /* We are asked to automatically set the previous URL as the referer + when we get the next URL. We pick the ->url field, which may or may + not be 100% correct */ + + if(data->state.referer_alloc) { + Curl_safefree(data->state.referer); + data->state.referer_alloc = FALSE; + } + + /* Make a copy of the URL without credentials and fragment */ + u = curl_url(); + if(!u) + return CURLE_OUT_OF_MEMORY; + + uc = curl_url_set(u, CURLUPART_URL, data->state.url, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_USER, NULL, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0); + if(!uc) + uc = curl_url_get(u, CURLUPART_URL, &referer, 0); + + curl_url_cleanup(u); + + if(uc || !referer) + return CURLE_OUT_OF_MEMORY; + + data->state.referer = referer; + data->state.referer_alloc = TRUE; /* yes, free this later */ + } + } } -#endif - if(data->set.postfields || - (data->state.httpreq == HTTPREQ_GET) || - (data->state.httpreq == HTTPREQ_HEAD)) - ; /* no need to rewind */ - else if(data->state.httpreq == HTTPREQ_POST_MIME || - data->state.httpreq == HTTPREQ_POST_FORM) { - CURLcode result = Curl_mime_rewind(mimepart); - if(result) { - failf(data, "Cannot rewind mime/post data"); - return result; + + if((type != FOLLOW_RETRY) && + (data->req.httpcode != 401) && (data->req.httpcode != 407) && + Curl_is_absolute_url(newurl, NULL, 0, FALSE)) { + /* If this is not redirect due to a 401 or 407 response and an absolute + URL: do not allow a custom port number */ + disallowport = TRUE; + } + + DEBUGASSERT(data->state.uh); + uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl, (unsigned int) + ((type == FOLLOW_FAKE) ? CURLU_NON_SUPPORT_SCHEME : + ((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) | + CURLU_ALLOW_SPACE | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0))); + if(uc) { + if(type != FOLLOW_FAKE) { + failf(data, "The redirect target URL could not be parsed: %s", + curl_url_strerror(uc)); + return Curl_uc_to_curlcode(uc); } + + /* the URL could not be parsed for some reason, but since this is FAKE + mode, just duplicate the field as-is */ + newurl = strdup(newurl); + if(!newurl) + return CURLE_OUT_OF_MEMORY; } else { - if(data->set.seek_func) { - int err; - - Curl_set_in_callback(data, true); - err = (data->set.seek_func)(data->set.seek_client, 0, SEEK_SET); - Curl_set_in_callback(data, false); - if(err) { - failf(data, "seek callback returned error %d", (int)err); - return CURLE_SEND_FAIL_REWIND; + uc = curl_url_get(data->state.uh, CURLUPART_URL, &newurl, 0); + if(uc) + return Curl_uc_to_curlcode(uc); + + /* Clear auth if this redirects to a different port number or protocol, + unless permitted */ + if(!data->set.allow_auth_to_other_hosts && (type != FOLLOW_FAKE)) { + char *portnum; + int port; + bool clear = FALSE; + + if(data->set.use_port && data->state.allow_port) + /* a custom port is used */ + port = (int)data->set.use_port; + else { + uc = curl_url_get(data->state.uh, CURLUPART_PORT, &portnum, + CURLU_DEFAULT_PORT); + if(uc) { + free(newurl); + return Curl_uc_to_curlcode(uc); + } + port = atoi(portnum); + free(portnum); } + if(port != data->info.conn_remote_port) { + infof(data, "Clear auth, redirects to port from %u to %u", + data->info.conn_remote_port, port); + clear = TRUE; + } + else { + char *scheme; + const struct Curl_handler *p; + uc = curl_url_get(data->state.uh, CURLUPART_SCHEME, &scheme, 0); + if(uc) { + free(newurl); + return Curl_uc_to_curlcode(uc); + } + + p = Curl_get_scheme_handler(scheme); + if(p && (p->protocol != data->info.conn_protocol)) { + infof(data, "Clear auth, redirects scheme from %s to %s", + data->info.conn_scheme, scheme); + clear = TRUE; + } + free(scheme); + } + if(clear) { + Curl_safefree(data->state.aptr.user); + Curl_safefree(data->state.aptr.passwd); + } + } + } + + if(type == FOLLOW_FAKE) { + /* we are only figuring out the new URL if we would have followed locations + but now we are done so we can get out! */ + data->info.wouldredirect = newurl; + + if(reachedmax) { + failf(data, "Maximum (%ld) redirects followed", data->set.maxredirs); + return CURLE_TOO_MANY_REDIRECTS; + } + return CURLE_OK; + } + + if(disallowport) + data->state.allow_port = FALSE; + + if(data->state.url_alloc) + Curl_safefree(data->state.url); + + data->state.url = newurl; + data->state.url_alloc = TRUE; + Curl_req_soft_reset(&data->req, data); + infof(data, "Issue another request to this URL: '%s'", data->state.url); + + /* + * We get here when the HTTP code is 300-399 (and 401). We need to perform + * differently based on exactly what return code there was. + * + * News from 7.10.6: we can also get here on a 401 or 407, in case we act on + * an HTTP (proxy-) authentication scheme other than Basic. + */ + switch(data->info.httpcode) { + /* 401 - Act on a WWW-Authenticate, we keep on moving and do the + Authorization: XXXX header in the HTTP request code snippet */ + /* 407 - Act on a Proxy-Authenticate, we keep on moving and do the + Proxy-Authorization: XXXX header in the HTTP request code snippet */ + /* 300 - Multiple Choices */ + /* 306 - Not used */ + /* 307 - Temporary Redirect */ + default: /* for all above (and the unknown ones) */ + /* Some codes are explicitly mentioned since I have checked RFC2616 and + * they seem to be OK to POST to. + */ + break; + case 301: /* Moved Permanently */ + /* (quote from RFC7231, section 6.4.2) + * + * Note: For historical reasons, a user agent MAY change the request + * method from POST to GET for the subsequent request. If this + * behavior is undesired, the 307 (Temporary Redirect) status code + * can be used instead. + * + * ---- + * + * Many webservers expect this, so these servers often answers to a POST + * request with an error page. To be sure that libcurl gets the page that + * most user agents would get, libcurl has to force GET. + * + * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and + * can be overridden with CURLOPT_POSTREDIR. + */ + if((data->state.httpreq == HTTPREQ_POST + || data->state.httpreq == HTTPREQ_POST_FORM + || data->state.httpreq == HTTPREQ_POST_MIME) + && !(data->set.keep_post & CURL_REDIR_POST_301)) { + infof(data, "Switch from POST to GET"); + data->state.httpreq = HTTPREQ_GET; + Curl_creader_set_rewind(data, FALSE); } - else if(data->set.ioctl_func) { - curlioerr err; + break; + case 302: /* Found */ + /* (quote from RFC7231, section 6.4.3) + * + * Note: For historical reasons, a user agent MAY change the request + * method from POST to GET for the subsequent request. If this + * behavior is undesired, the 307 (Temporary Redirect) status code + * can be used instead. + * + * ---- + * + * Many webservers expect this, so these servers often answers to a POST + * request with an error page. To be sure that libcurl gets the page that + * most user agents would get, libcurl has to force GET. + * + * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and + * can be overridden with CURLOPT_POSTREDIR. + */ + if((data->state.httpreq == HTTPREQ_POST + || data->state.httpreq == HTTPREQ_POST_FORM + || data->state.httpreq == HTTPREQ_POST_MIME) + && !(data->set.keep_post & CURL_REDIR_POST_302)) { + infof(data, "Switch from POST to GET"); + data->state.httpreq = HTTPREQ_GET; + Curl_creader_set_rewind(data, FALSE); + } + break; - Curl_set_in_callback(data, true); - err = (data->set.ioctl_func)(data, CURLIOCMD_RESTARTREAD, - data->set.ioctl_client); - Curl_set_in_callback(data, false); - infof(data, "the ioctl callback returned %d", (int)err); + case 303: /* See Other */ + /* 'See Other' location is not the resource but a substitute for the + * resource. In this case we switch the method to GET/HEAD, unless the + * method is POST and the user specified to keep it as POST. + * https://github.com/curl/curl/issues/5237#issuecomment-614641049 + */ + if(data->state.httpreq != HTTPREQ_GET && + ((data->state.httpreq != HTTPREQ_POST && + data->state.httpreq != HTTPREQ_POST_FORM && + data->state.httpreq != HTTPREQ_POST_MIME) || + !(data->set.keep_post & CURL_REDIR_POST_303))) { + data->state.httpreq = HTTPREQ_GET; + infof(data, "Switch to %s", + data->req.no_body ? "HEAD" : "GET"); + } + break; + case 304: /* Not Modified */ + /* 304 means we did a conditional request and it was "Not modified". + * We should not get any Location: header in this response! + */ + break; + case 305: /* Use Proxy */ + /* (quote from RFC2616, section 10.3.6): + * "The requested resource MUST be accessed through the proxy given + * by the Location field. The Location field gives the URI of the + * proxy. The recipient is expected to repeat this single request + * via the proxy. 305 responses MUST only be generated by origin + * servers." + */ + break; + } + Curl_pgrsTime(data, TIMER_REDIRECT); + Curl_pgrsResetTransferSizes(data); - if(err) { - failf(data, "ioctl callback returned error %d", (int)err); - return CURLE_SEND_FAIL_REWIND; + return CURLE_OK; +#endif /* CURL_DISABLE_HTTP */ +} + +static CURLMcode state_performing(struct Curl_easy *data, + struct curltime *nowp, + bool *stream_errorp, + CURLcode *resultp) +{ + char *newurl = NULL; + bool retry = FALSE; + timediff_t recv_timeout_ms = 0; + timediff_t send_timeout_ms = 0; + CURLMcode rc = CURLM_OK; + CURLcode result = *resultp = CURLE_OK; + *stream_errorp = FALSE; + + /* check if over send speed */ + if(data->set.max_send_speed) + send_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.ul, + data->set.max_send_speed, + *nowp); + + /* check if over recv speed */ + if(data->set.max_recv_speed) + recv_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.dl, + data->set.max_recv_speed, + *nowp); + + if(send_timeout_ms || recv_timeout_ms) { + Curl_ratelimit(data, *nowp); + multistate(data, MSTATE_RATELIMITING); + if(send_timeout_ms >= recv_timeout_ms) + Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST); + else + Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST); + return CURLM_OK; + } + + /* read/write data if it is ready to do so */ + result = Curl_sendrecv(data, nowp); + + if(data->req.done || (result == CURLE_RECV_ERROR)) { + /* If CURLE_RECV_ERROR happens early enough, we assume it was a race + * condition and the server closed the reused connection exactly when we + * wanted to use it, so figure out if that is indeed the case. + */ + CURLcode ret = Curl_retry_request(data, &newurl); + if(!ret) + retry = !!newurl; + else if(!result) + result = ret; + + if(retry) { + /* if we are to retry, set the result to OK and consider the + request as done */ + result = CURLE_OK; + data->req.done = TRUE; + } + } + else if((CURLE_HTTP2_STREAM == result) && + Curl_h2_http_1_1_error(data)) { + CURLcode ret = Curl_retry_request(data, &newurl); + + if(!ret) { + infof(data, "Downgrades to HTTP/1.1"); + streamclose(data->conn, "Disconnect HTTP/2 for HTTP/1"); + data->state.httpwant = CURL_HTTP_VERSION_1_1; + /* clear the error message bit too as we ignore the one we got */ + data->state.errorbuf = FALSE; + if(!newurl) + /* typically for HTTP_1_1_REQUIRED error on first flight */ + newurl = strdup(data->state.url); + /* if we are to retry, set the result to OK and consider the request + as done */ + retry = TRUE; + result = CURLE_OK; + data->req.done = TRUE; + } + else + result = ret; + } + + if(result) { + /* + * The transfer phase returned error, we mark the connection to get closed + * to prevent being reused. This is because we cannot possibly know if the + * connection is in a good shape or not now. Unless it is a protocol which + * uses two "channels" like FTP, as then the error happened in the data + * connection. + */ + + if(!(data->conn->handler->flags & PROTOPT_DUAL) && + result != CURLE_HTTP2_STREAM) + streamclose(data->conn, "Transfer returned error"); + + multi_posttransfer(data); + multi_done(data, result, TRUE); + } + else if(data->req.done && !Curl_cwriter_is_paused(data)) { + + /* call this even if the readwrite function returned error */ + multi_posttransfer(data); + + /* When we follow redirects or is set to retry the connection, we must to + go back to the CONNECT state */ + if(data->req.newurl || retry) { + followtype follow = FOLLOW_NONE; + if(!retry) { + /* if the URL is a follow-location and not just a retried request then + figure out the URL here */ + free(newurl); + newurl = data->req.newurl; + data->req.newurl = NULL; + follow = FOLLOW_REDIR; + } + else + follow = FOLLOW_RETRY; + (void)multi_done(data, CURLE_OK, FALSE); + /* multi_done() might return CURLE_GOT_NOTHING */ + result = multi_follow(data, newurl, follow); + if(!result) { + multistate(data, MSTATE_SETUP); + rc = CURLM_CALL_MULTI_PERFORM; } } else { - /* If no CURLOPT_READFUNCTION is used, we know that we operate on a - given FILE * stream and we can actually attempt to rewind that - ourselves with fseek() */ - if(data->state.fread_func == (curl_read_callback)fread) { - if(-1 != fseek(data->state.in, 0, SEEK_SET)) - /* successful rewind */ - return CURLE_OK; + /* after the transfer is done, go DONE */ + + /* but first check to see if we got a location info even though we are + not following redirects */ + if(data->req.location) { + free(newurl); + newurl = data->req.location; + data->req.location = NULL; + result = multi_follow(data, newurl, FOLLOW_FAKE); + if(result) { + *stream_errorp = TRUE; + result = multi_done(data, result, TRUE); + } } - /* no callback set or failure above, makes us fail at once */ - failf(data, "necessary data rewind wasn't possible"); - return CURLE_SEND_FAIL_REWIND; + if(!result) { + multistate(data, MSTATE_DONE); + rc = CURLM_CALL_MULTI_PERFORM; + } } } - return CURLE_OK; + else if(data->state.select_bits && !Curl_xfer_is_blocked(data)) { + /* This avoids CURLM_CALL_MULTI_PERFORM so that a very fast transfer does + not get stuck on this transfer at the expense of other concurrent + transfers */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + free(newurl); + *resultp = result; + return rc; } -/* - * Curl_preconnect() is called immediately before a connect starts. When a - * redirect is followed, this is then called multiple times during a single - * transfer. - */ -CURLcode Curl_preconnect(struct Curl_easy *data) +static CURLMcode state_do(struct Curl_easy *data, + bool *stream_errorp, + CURLcode *resultp) { - if(!data->state.buffer) { - data->state.buffer = malloc(data->set.buffer_size + 1); - if(!data->state.buffer) - return CURLE_OUT_OF_MEMORY; + CURLMcode rc = CURLM_OK; + CURLcode result = CURLE_OK; + if(data->set.fprereq) { + int prereq_rc; + + /* call the prerequest callback function */ + Curl_set_in_callback(data, TRUE); + prereq_rc = data->set.fprereq(data->set.prereq_userp, + data->info.primary.remote_ip, + data->info.primary.local_ip, + data->info.primary.remote_port, + data->info.primary.local_port); + Curl_set_in_callback(data, FALSE); + if(prereq_rc != CURL_PREREQFUNC_OK) { + failf(data, "operation aborted by pre-request callback"); + /* failure in pre-request callback - do not do any other processing */ + result = CURLE_ABORTED_BY_CALLBACK; + multi_posttransfer(data); + multi_done(data, result, FALSE); + *stream_errorp = TRUE; + goto end; + } } - return CURLE_OK; + if(data->set.connect_only == 1) { + /* keep connection open for application to use the socket */ + connkeep(data->conn, "CONNECT_ONLY"); + multistate(data, MSTATE_DONE); + rc = CURLM_CALL_MULTI_PERFORM; + } + else { + bool dophase_done = FALSE; + /* Perform the protocol's DO action */ + result = multi_do(data, &dophase_done); + + /* When multi_do() returns failure, data->conn might be NULL! */ + + if(!result) { + if(!dophase_done) { +#ifndef CURL_DISABLE_FTP + /* some steps needed for wildcard matching */ + if(data->state.wildcardmatch) { + struct WildcardData *wc = data->wildcard; + if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) { + /* skip some states if it is important */ + multi_done(data, CURLE_OK, FALSE); + + /* if there is no connection left, skip the DONE state */ + multistate(data, data->conn ? + MSTATE_DONE : MSTATE_COMPLETED); + rc = CURLM_CALL_MULTI_PERFORM; + goto end; + } + } +#endif + /* DO was not completed in one function call, we must continue + DOING... */ + multistate(data, MSTATE_DOING); + rc = CURLM_CALL_MULTI_PERFORM; + } + + /* after DO, go DO_DONE... or DO_MORE */ + else if(data->conn->bits.do_more) { + /* we are supposed to do more, but we need to sit down, relax and wait + a little while first */ + multistate(data, MSTATE_DOING_MORE); + rc = CURLM_CALL_MULTI_PERFORM; + } + else { + /* we are done with the DO, now DID */ + multistate(data, MSTATE_DID); + rc = CURLM_CALL_MULTI_PERFORM; + } + } + else if((CURLE_SEND_ERROR == result) && + data->conn->bits.reuse) { + /* + * In this situation, a connection that we were trying to use may have + * unexpectedly died. If possible, send the connection back to the + * CONNECT phase so we can try again. + */ + char *newurl = NULL; + followtype follow = FOLLOW_NONE; + CURLcode drc; + + drc = Curl_retry_request(data, &newurl); + if(drc) { + /* a failure here pretty much implies an out of memory */ + result = drc; + *stream_errorp = TRUE; + } + + multi_posttransfer(data); + drc = multi_done(data, result, FALSE); + + /* When set to retry the connection, we must go back to the CONNECT + * state */ + if(newurl) { + if(!drc || (drc == CURLE_SEND_ERROR)) { + follow = FOLLOW_RETRY; + drc = multi_follow(data, newurl, follow); + if(!drc) { + multistate(data, MSTATE_SETUP); + rc = CURLM_CALL_MULTI_PERFORM; + result = CURLE_OK; + } + else { + /* Follow failed */ + result = drc; + } + } + else { + /* done did not return OK or SEND_ERROR */ + result = drc; + } + } + else { + /* Have error handler disconnect conn if we cannot retry */ + *stream_errorp = TRUE; + } + free(newurl); + } + else { + /* failure detected */ + multi_posttransfer(data); + if(data->conn) + multi_done(data, result, FALSE); + *stream_errorp = TRUE; + } + } +end: + *resultp = result; + return rc; } -static void set_in_callback(struct Curl_multi *multi, bool value) +static CURLMcode state_ratelimiting(struct Curl_easy *data, + struct curltime *nowp, + CURLcode *resultp) { - multi->in_callback = value; + CURLcode result = CURLE_OK; + CURLMcode rc = CURLM_OK; + DEBUGASSERT(data->conn); + /* if both rates are within spec, resume transfer */ + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + else + result = Curl_speedcheck(data, *nowp); + + if(result) { + if(!(data->conn->handler->flags & PROTOPT_DUAL) && + result != CURLE_HTTP2_STREAM) + streamclose(data->conn, "Transfer returned error"); + + multi_posttransfer(data); + multi_done(data, result, TRUE); + } + else { + timediff_t recv_timeout_ms = 0; + timediff_t send_timeout_ms = 0; + if(data->set.max_send_speed) + send_timeout_ms = + Curl_pgrsLimitWaitTime(&data->progress.ul, + data->set.max_send_speed, + *nowp); + + if(data->set.max_recv_speed) + recv_timeout_ms = + Curl_pgrsLimitWaitTime(&data->progress.dl, + data->set.max_recv_speed, + *nowp); + + if(!send_timeout_ms && !recv_timeout_ms) { + multistate(data, MSTATE_PERFORMING); + Curl_ratelimit(data, *nowp); + /* start performing again right away */ + rc = CURLM_CALL_MULTI_PERFORM; + } + else if(send_timeout_ms >= recv_timeout_ms) + Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST); + else + Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST); + } + *resultp = result; + return rc; +} + +static CURLMcode state_resolving(struct Curl_multi *multi, + struct Curl_easy *data, + bool *stream_errorp, + CURLcode *resultp) +{ + struct Curl_dns_entry *dns = NULL; + struct connectdata *conn = data->conn; + const char *hostname; + CURLcode result = CURLE_OK; + CURLMcode rc = CURLM_OK; + + DEBUGASSERT(conn); +#ifndef CURL_DISABLE_PROXY + if(conn->bits.httpproxy) + hostname = conn->http_proxy.host.name; + else +#endif + if(conn->bits.conn_to_host) + hostname = conn->conn_to_host.name; + else + hostname = conn->host.name; + + /* check if we have the name resolved by now */ + dns = Curl_fetch_addr(data, hostname, conn->primary.remote_port); + + if(dns) { +#ifdef CURLRES_ASYNCH + data->state.async.dns = dns; + data->state.async.done = TRUE; +#endif + result = CURLE_OK; + infof(data, "Hostname '%s' was found in DNS cache", hostname); + } + + if(!dns) + result = Curl_resolv_check(data, &dns); + + /* Update sockets here, because the socket(s) may have been closed and the + application thus needs to be told, even if it is likely that the same + socket(s) will again be used further down. If the name has not yet been + resolved, it is likely that new sockets have been opened in an attempt to + contact another resolver. */ + rc = singlesocket(multi, data); + if(rc) + return rc; + + if(dns) { + bool connected; + /* Perform the next step in the connection phase, and then move on to the + WAITCONNECT state */ + result = Curl_once_resolved(data, &connected); + + if(result) + /* if Curl_once_resolved() returns failure, the connection struct is + already freed and gone */ + data->conn = NULL; /* no more connection */ + else { + /* call again please so that we get the next socket setup */ + rc = CURLM_CALL_MULTI_PERFORM; + if(connected) + multistate(data, MSTATE_PROTOCONNECT); + else { + multistate(data, MSTATE_CONNECTING); + } + } + } + + if(result) + /* failure detected */ + *stream_errorp = TRUE; + + *resultp = result; + return rc; +} + +static CURLMcode state_connect(struct Curl_multi *multi, + struct Curl_easy *data, + struct curltime *nowp, + CURLcode *resultp) +{ + /* Connect. We want to get a connection identifier filled in. This state can + be entered from SETUP and from PENDING. */ + bool connected; + bool async; + CURLMcode rc = CURLM_OK; + CURLcode result = Curl_connect(data, &async, &connected); + if(CURLE_NO_CONNECTION_AVAILABLE == result) { + /* There was no connection available. We will go to the pending state and + wait for an available connection. */ + multistate(data, MSTATE_PENDING); + /* unlink from process list */ + Curl_node_remove(&data->multi_queue); + /* add handle to pending list */ + Curl_llist_append(&multi->pending, data, &data->multi_queue); + *resultp = CURLE_OK; + return rc; + } + else + process_pending_handles(data->multi); + + if(!result) { + *nowp = Curl_pgrsTime(data, TIMER_POSTQUEUE); + if(async) + /* We are now waiting for an asynchronous name lookup */ + multistate(data, MSTATE_RESOLVING); + else { + /* after the connect has been sent off, go WAITCONNECT unless the + protocol connect is already done and we can go directly to WAITDO or + DO! */ + rc = CURLM_CALL_MULTI_PERFORM; + + if(connected) { + if(!data->conn->bits.reuse && + Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) { + /* new connection, can multiplex, wake pending handles */ + process_pending_handles(data->multi); + } + multistate(data, MSTATE_PROTOCONNECT); + } + else { + multistate(data, MSTATE_CONNECTING); + } + } + } + *resultp = result; + return rc; } static CURLMcode multi_runsingle(struct Curl_multi *multi, @@ -1877,14 +2595,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, { struct Curl_message *msg = NULL; bool connected; - bool async; bool protocol_connected = FALSE; bool dophase_done = FALSE; - bool done = FALSE; CURLMcode rc; CURLcode result = CURLE_OK; - timediff_t recv_timeout_ms; - timediff_t send_timeout_ms; int control; if(!GOOD_EASY_HANDLE(data)) @@ -1894,7 +2608,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* a multi-level callback returned error before, meaning every individual transfer now has failed */ result = CURLE_ABORTED_BY_CALLBACK; - Curl_posttransfer(data); + multi_posttransfer(data); multi_done(data, result, FALSE); multistate(data, MSTATE_COMPLETED); } @@ -1920,182 +2634,61 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, return CURLM_INTERNAL_ERROR; } - if(data->conn && - (data->mstate >= MSTATE_CONNECT) && - (data->mstate < MSTATE_COMPLETED)) { - /* Check for overall operation timeout here but defer handling the - * connection timeout to later, to allow for a connection to be set up - * in the window since we last checked timeout. This prevents us - * tearing down a completed connection in the case where we were slow - * to check the timeout (e.g. process descheduled during this loop). - * We set connect_timeout=FALSE to do this. */ - - /* we need to wait for the connect state as only then is the start time - stored, but we must not check already completed handles */ - if(multi_handle_timeout(data, nowp, &stream_error, &result, FALSE)) { - /* Skip the statemachine and go directly to error handling section. */ - goto statemachine_end; - } - } + /* Wait for the connect state as only then is the start time stored, but + we must not check already completed handles */ + if((data->mstate >= MSTATE_CONNECT) && (data->mstate < MSTATE_COMPLETED) && + multi_handle_timeout(data, nowp, &stream_error, &result)) + /* Skip the statemachine and go directly to error handling section. */ + goto statemachine_end; switch(data->mstate) { case MSTATE_INIT: - /* init this transfer. */ + /* Transitional state. init this transfer. A handle never comes back to + this state. */ result = Curl_pretransfer(data); - - if(!result) { - /* after init, go CONNECT */ - multistate(data, MSTATE_CONNECT); - *nowp = Curl_pgrsTime(data, TIMER_STARTOP); - rc = CURLM_CALL_MULTI_PERFORM; - } - break; - - case MSTATE_CONNECT: - /* Connect. We want to get a connection identifier filled in. */ - /* init this transfer. */ - result = Curl_preconnect(data); if(result) break; + /* after init, go SETUP */ + multistate(data, MSTATE_SETUP); + (void)Curl_pgrsTime(data, TIMER_STARTOP); + FALLTHROUGH(); + + case MSTATE_SETUP: + /* Transitional state. Setup things for a new transfer. The handle + can come back to this state on a redirect. */ *nowp = Curl_pgrsTime(data, TIMER_STARTSINGLE); if(data->set.timeout) Curl_expire(data, data->set.timeout, EXPIRE_TIMEOUT); - if(data->set.connecttimeout) + /* Since a connection might go to pending and back to CONNECT several + times before it actually takes off, we need to set the timeout once + in SETUP before we enter CONNECT the first time. */ Curl_expire(data, data->set.connecttimeout, EXPIRE_CONNECTTIMEOUT); - result = Curl_connect(data, &async, &connected); - if(CURLE_NO_CONNECTION_AVAILABLE == result) { - /* There was no connection available. We will go to the pending - state and wait for an available connection. */ - multistate(data, MSTATE_PENDING); - - /* add this handle to the list of connect-pending handles */ - Curl_llist_insert_next(&multi->pending, multi->pending.tail, data, - &data->connect_queue); - /* unlink from the main list */ - unlink_easy(multi, data); - result = CURLE_OK; - break; - } - else if(data->state.previouslypending) { - /* this transfer comes from the pending queue so try move another */ - infof(data, "Transfer was pending, now try another"); - process_pending_handles(data->multi); - } - - if(!result) { - if(async) - /* We're now waiting for an asynchronous name lookup */ - multistate(data, MSTATE_RESOLVING); - else { - /* after the connect has been sent off, go WAITCONNECT unless the - protocol connect is already done and we can go directly to - WAITDO or DO! */ - rc = CURLM_CALL_MULTI_PERFORM; + multistate(data, MSTATE_CONNECT); + FALLTHROUGH(); - if(connected) - multistate(data, MSTATE_PROTOCONNECT); - else { - multistate(data, MSTATE_CONNECTING); - } - } - } + case MSTATE_CONNECT: + rc = state_connect(multi, data, nowp, &result); break; case MSTATE_RESOLVING: /* awaiting an asynch name resolve to complete */ - { - struct Curl_dns_entry *dns = NULL; - struct connectdata *conn = data->conn; - const char *hostname; - - DEBUGASSERT(conn); -#ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy) - hostname = conn->http_proxy.host.name; - else -#endif - if(conn->bits.conn_to_host) - hostname = conn->conn_to_host.name; - else - hostname = conn->host.name; - - /* check if we have the name resolved by now */ - dns = Curl_fetch_addr(data, hostname, (int)conn->port); - - if(dns) { -#ifdef CURLRES_ASYNCH - data->state.async.dns = dns; - data->state.async.done = TRUE; -#endif - result = CURLE_OK; - infof(data, "Hostname '%s' was found in DNS cache", hostname); - } - - if(!dns) - result = Curl_resolv_check(data, &dns); - - /* Update sockets here, because the socket(s) may have been - closed and the application thus needs to be told, even if it - is likely that the same socket(s) will again be used further - down. If the name has not yet been resolved, it is likely - that new sockets have been opened in an attempt to contact - another resolver. */ - rc = singlesocket(multi, data); - if(rc) - return rc; - - if(dns) { - /* Perform the next step in the connection phase, and then move on - to the WAITCONNECT state */ - result = Curl_once_resolved(data, &connected); - - if(result) - /* if Curl_once_resolved() returns failure, the connection struct - is already freed and gone */ - data->conn = NULL; /* no more connection */ - else { - /* call again please so that we get the next socket setup */ - rc = CURLM_CALL_MULTI_PERFORM; - if(connected) - multistate(data, MSTATE_PROTOCONNECT); - else { - multistate(data, MSTATE_CONNECTING); - } - } - } - - if(result) { - /* failure detected */ - stream_error = TRUE; - break; - } - } - break; + rc = state_resolving(multi, data, &stream_error, &result); + break; #ifndef CURL_DISABLE_HTTP case MSTATE_TUNNELING: /* this is HTTP-specific, but sending CONNECT to a proxy is HTTP... */ DEBUGASSERT(data->conn); result = Curl_http_connect(data, &protocol_connected); -#ifndef CURL_DISABLE_PROXY - if(data->conn->bits.proxy_connect_closed) { + if(!result) { rc = CURLM_CALL_MULTI_PERFORM; - /* connect back to proxy again */ - result = CURLE_OK; - multi_done(data, CURLE_OK, FALSE); - multistate(data, MSTATE_CONNECT); + /* initiate protocol connect phase */ + multistate(data, MSTATE_PROTOCONNECT); } else -#endif - if(!result) { - rc = CURLM_CALL_MULTI_PERFORM; - /* initiate protocol connect phase */ - multistate(data, MSTATE_PROTOCONNECT); - } - else stream_error = TRUE; break; #endif @@ -2105,12 +2698,17 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, DEBUGASSERT(data->conn); result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &connected); if(connected && !result) { + if(!data->conn->bits.reuse && + Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) { + /* new connection, can multiplex, wake pending handles */ + process_pending_handles(data->multi); + } rc = CURLM_CALL_MULTI_PERFORM; multistate(data, MSTATE_PROTOCONNECT); } else if(result) { /* failure detected */ - Curl_posttransfer(data); + multi_posttransfer(data); multi_done(data, result, TRUE); stream_error = TRUE; break; @@ -2118,22 +2716,22 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break; case MSTATE_PROTOCONNECT: - if(data->state.rewindbeforesend) - result = readrewind(data); - if(!result && data->conn->bits.reuse) { - /* ftp seems to hang when protoconnect on reused connection - * since we handle PROTOCONNECT in general inside the filers, it - * seems wrong to restart this on a reused connection. */ + /* ftp seems to hang when protoconnect on reused connection since we + * handle PROTOCONNECT in general inside the filers, it seems wrong to + * restart this on a reused connection. + */ multistate(data, MSTATE_DO); rc = CURLM_CALL_MULTI_PERFORM; break; } if(!result) result = protocol_connect(data, &protocol_connected); - if(!result && !protocol_connected) + if(!result && !protocol_connected) { /* switch to waiting state */ multistate(data, MSTATE_PROTOCONNECTING); + rc = CURLM_CALL_MULTI_PERFORM; + } else if(!result) { /* protocol connect has completed, go WAITDO or DO */ multistate(data, MSTATE_DO); @@ -2141,7 +2739,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } else { /* failure detected */ - Curl_posttransfer(data); + multi_posttransfer(data); multi_done(data, result, TRUE); stream_error = TRUE; } @@ -2157,139 +2755,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } else if(result) { /* failure detected */ - Curl_posttransfer(data); + multi_posttransfer(data); multi_done(data, result, TRUE); stream_error = TRUE; } break; case MSTATE_DO: - if(data->set.fprereq) { - int prereq_rc; - - /* call the prerequest callback function */ - Curl_set_in_callback(data, true); - prereq_rc = data->set.fprereq(data->set.prereq_userp, - data->info.conn_primary_ip, - data->info.conn_local_ip, - data->info.conn_primary_port, - data->info.conn_local_port); - Curl_set_in_callback(data, false); - if(prereq_rc != CURL_PREREQFUNC_OK) { - failf(data, "operation aborted by pre-request callback"); - /* failure in pre-request callback - don't do any other processing */ - result = CURLE_ABORTED_BY_CALLBACK; - Curl_posttransfer(data); - multi_done(data, result, FALSE); - stream_error = TRUE; - break; - } - } - - if(data->set.connect_only == 1) { - /* keep connection open for application to use the socket */ - connkeep(data->conn, "CONNECT_ONLY"); - multistate(data, MSTATE_DONE); - result = CURLE_OK; - rc = CURLM_CALL_MULTI_PERFORM; - } - else { - /* Perform the protocol's DO action */ - result = multi_do(data, &dophase_done); - - /* When multi_do() returns failure, data->conn might be NULL! */ - - if(!result) { - if(!dophase_done) { -#ifndef CURL_DISABLE_FTP - /* some steps needed for wildcard matching */ - if(data->state.wildcardmatch) { - struct WildcardData *wc = data->wildcard; - if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) { - /* skip some states if it is important */ - multi_done(data, CURLE_OK, FALSE); - - /* if there's no connection left, skip the DONE state */ - multistate(data, data->conn ? - MSTATE_DONE : MSTATE_COMPLETED); - rc = CURLM_CALL_MULTI_PERFORM; - break; - } - } -#endif - /* DO was not completed in one function call, we must continue - DOING... */ - multistate(data, MSTATE_DOING); - } - - /* after DO, go DO_DONE... or DO_MORE */ - else if(data->conn->bits.do_more) { - /* we're supposed to do more, but we need to sit down, relax - and wait a little while first */ - multistate(data, MSTATE_DOING_MORE); - } - else { - /* we're done with the DO, now DID */ - multistate(data, MSTATE_DID); - rc = CURLM_CALL_MULTI_PERFORM; - } - } - else if((CURLE_SEND_ERROR == result) && - data->conn->bits.reuse) { - /* - * In this situation, a connection that we were trying to use - * may have unexpectedly died. If possible, send the connection - * back to the CONNECT phase so we can try again. - */ - char *newurl = NULL; - followtype follow = FOLLOW_NONE; - CURLcode drc; - - drc = Curl_retry_request(data, &newurl); - if(drc) { - /* a failure here pretty much implies an out of memory */ - result = drc; - stream_error = TRUE; - } - - Curl_posttransfer(data); - drc = multi_done(data, result, FALSE); - - /* When set to retry the connection, we must go back to the CONNECT - * state */ - if(newurl) { - if(!drc || (drc == CURLE_SEND_ERROR)) { - follow = FOLLOW_RETRY; - drc = Curl_follow(data, newurl, follow); - if(!drc) { - multistate(data, MSTATE_CONNECT); - rc = CURLM_CALL_MULTI_PERFORM; - result = CURLE_OK; - } - else { - /* Follow failed */ - result = drc; - } - } - else { - /* done didn't return OK or SEND_ERROR */ - result = drc; - } - } - else { - /* Have error handler disconnect conn if we can't retry */ - stream_error = TRUE; - } - free(newurl); - } - else { - /* failure detected */ - Curl_posttransfer(data); - if(data->conn) - multi_done(data, result, FALSE); - stream_error = TRUE; - } - } + rc = state_do(data, &stream_error, &result); break; case MSTATE_DOING: @@ -2299,14 +2772,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(!result) { if(dophase_done) { /* after DO, go DO_DONE or DO_MORE */ - multistate(data, data->conn->bits.do_more? + multistate(data, data->conn->bits.do_more ? MSTATE_DOING_MORE : MSTATE_DID); rc = CURLM_CALL_MULTI_PERFORM; } /* dophase_done */ } else { /* failure detected */ - Curl_posttransfer(data); + multi_posttransfer(data); multi_done(data, result, FALSE); stream_error = TRUE; } @@ -2323,7 +2796,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(control) { /* if positive, advance to DO_DONE if negative, go back to DOING */ - multistate(data, control == 1? + multistate(data, control == 1 ? MSTATE_DID : MSTATE_DOING); rc = CURLM_CALL_MULTI_PERFORM; } @@ -2332,7 +2805,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } else { /* failure detected */ - Curl_posttransfer(data); + multi_posttransfer(data); multi_done(data, result, FALSE); stream_error = TRUE; } @@ -2344,7 +2817,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* Check if we can move pending requests to send pipe */ process_pending_handles(multi); /* multiplexed */ - /* Only perform the transfer if there's a good socket to work with. + /* Only perform the transfer if there is a good socket to work with. Having both BAD is a signal to skip immediately to DONE */ if((data->conn->sockfd != CURL_SOCKET_BAD) || (data->conn->writesockfd != CURL_SOCKET_BAD)) @@ -2362,204 +2835,12 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break; case MSTATE_RATELIMITING: /* limit-rate exceeded in either direction */ - DEBUGASSERT(data->conn); - /* if both rates are within spec, resume transfer */ - if(Curl_pgrsUpdate(data)) - result = CURLE_ABORTED_BY_CALLBACK; - else - result = Curl_speedcheck(data, *nowp); - - if(result) { - if(!(data->conn->handler->flags & PROTOPT_DUAL) && - result != CURLE_HTTP2_STREAM) - streamclose(data->conn, "Transfer returned error"); - - Curl_posttransfer(data); - multi_done(data, result, TRUE); - } - else { - send_timeout_ms = 0; - if(data->set.max_send_speed) - send_timeout_ms = - Curl_pgrsLimitWaitTime(data->progress.uploaded, - data->progress.ul_limit_size, - data->set.max_send_speed, - data->progress.ul_limit_start, - *nowp); - - recv_timeout_ms = 0; - if(data->set.max_recv_speed) - recv_timeout_ms = - Curl_pgrsLimitWaitTime(data->progress.downloaded, - data->progress.dl_limit_size, - data->set.max_recv_speed, - data->progress.dl_limit_start, - *nowp); - - if(!send_timeout_ms && !recv_timeout_ms) { - multistate(data, MSTATE_PERFORMING); - Curl_ratelimit(data, *nowp); - } - else if(send_timeout_ms >= recv_timeout_ms) - Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST); - else - Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST); - } + rc = state_ratelimiting(data, nowp, &result); break; case MSTATE_PERFORMING: - { - char *newurl = NULL; - bool retry = FALSE; - bool comeback = FALSE; - DEBUGASSERT(data->state.buffer); - /* check if over send speed */ - send_timeout_ms = 0; - if(data->set.max_send_speed) - send_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.uploaded, - data->progress.ul_limit_size, - data->set.max_send_speed, - data->progress.ul_limit_start, - *nowp); - - /* check if over recv speed */ - recv_timeout_ms = 0; - if(data->set.max_recv_speed) - recv_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.downloaded, - data->progress.dl_limit_size, - data->set.max_recv_speed, - data->progress.dl_limit_start, - *nowp); - - if(send_timeout_ms || recv_timeout_ms) { - Curl_ratelimit(data, *nowp); - multistate(data, MSTATE_RATELIMITING); - if(send_timeout_ms >= recv_timeout_ms) - Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST); - else - Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST); - break; - } - - /* read/write data if it is ready to do so */ - result = Curl_readwrite(data->conn, data, &done, &comeback); - - if(done || (result == CURLE_RECV_ERROR)) { - /* If CURLE_RECV_ERROR happens early enough, we assume it was a race - * condition and the server closed the reused connection exactly when - * we wanted to use it, so figure out if that is indeed the case. - */ - CURLcode ret = Curl_retry_request(data, &newurl); - if(!ret) - retry = (newurl)?TRUE:FALSE; - else if(!result) - result = ret; - - if(retry) { - /* if we are to retry, set the result to OK and consider the - request as done */ - result = CURLE_OK; - done = TRUE; - } - } - else if((CURLE_HTTP2_STREAM == result) && - Curl_h2_http_1_1_error(data)) { - CURLcode ret = Curl_retry_request(data, &newurl); - - if(!ret) { - infof(data, "Downgrades to HTTP/1.1"); - streamclose(data->conn, "Disconnect HTTP/2 for HTTP/1"); - data->state.httpwant = CURL_HTTP_VERSION_1_1; - /* clear the error message bit too as we ignore the one we got */ - data->state.errorbuf = FALSE; - if(!newurl) - /* typically for HTTP_1_1_REQUIRED error on first flight */ - newurl = strdup(data->state.url); - /* if we are to retry, set the result to OK and consider the request - as done */ - retry = TRUE; - result = CURLE_OK; - done = TRUE; - } - else - result = ret; - } - - if(result) { - /* - * The transfer phase returned error, we mark the connection to get - * closed to prevent being reused. This is because we can't possibly - * know if the connection is in a good shape or not now. Unless it is - * a protocol which uses two "channels" like FTP, as then the error - * happened in the data connection. - */ - - if(!(data->conn->handler->flags & PROTOPT_DUAL) && - result != CURLE_HTTP2_STREAM) - streamclose(data->conn, "Transfer returned error"); - - Curl_posttransfer(data); - multi_done(data, result, TRUE); - } - else if(done) { - - /* call this even if the readwrite function returned error */ - Curl_posttransfer(data); - - /* When we follow redirects or is set to retry the connection, we must - to go back to the CONNECT state */ - if(data->req.newurl || retry) { - followtype follow = FOLLOW_NONE; - if(!retry) { - /* if the URL is a follow-location and not just a retried request - then figure out the URL here */ - free(newurl); - newurl = data->req.newurl; - data->req.newurl = NULL; - follow = FOLLOW_REDIR; - } - else - follow = FOLLOW_RETRY; - (void)multi_done(data, CURLE_OK, FALSE); - /* multi_done() might return CURLE_GOT_NOTHING */ - result = Curl_follow(data, newurl, follow); - if(!result) { - multistate(data, MSTATE_CONNECT); - rc = CURLM_CALL_MULTI_PERFORM; - } - free(newurl); - } - else { - /* after the transfer is done, go DONE */ - - /* but first check to see if we got a location info even though we're - not following redirects */ - if(data->req.location) { - free(newurl); - newurl = data->req.location; - data->req.location = NULL; - result = Curl_follow(data, newurl, FOLLOW_FAKE); - free(newurl); - if(result) { - stream_error = TRUE; - result = multi_done(data, result, TRUE); - } - } - - if(!result) { - multistate(data, MSTATE_DONE); - rc = CURLM_CALL_MULTI_PERFORM; - } - } - } - else if(comeback) { - /* This avoids CURLM_CALL_MULTI_PERFORM so that a very fast transfer - won't get stuck on this transfer at the expense of other concurrent - transfers */ - Curl_expire(data, 0, EXPIRE_RUN_NOW); - } + rc = state_performing(data, nowp, &stream_error, &result); break; - } case MSTATE_DONE: /* this state is highly transient, so run another loop after this */ @@ -2568,10 +2849,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(data->conn) { CURLcode res; - if(data->conn->bits.multiplex) - /* Check if we can move pending requests to connection */ - process_pending_handles(multi); /* multiplexing */ - /* post-transfer command */ res = multi_done(data, result, FALSE); @@ -2590,8 +2867,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } } #endif - /* after we have DONE what we're supposed to do, go COMPLETED, and - it doesn't matter what the multi_done() returned! */ + /* after we have DONE what we are supposed to do, go COMPLETED, and + it does not matter what the multi_done() returned! */ multistate(data, MSTATE_COMPLETED); break; @@ -2608,18 +2885,17 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, return CURLM_INTERNAL_ERROR; } - if(data->conn && - data->mstate >= MSTATE_CONNECT && + if(data->mstate >= MSTATE_CONNECT && data->mstate < MSTATE_DO && rc != CURLM_CALL_MULTI_PERFORM && - !multi_ischanged(multi, false)) { + !multi_ischanged(multi, FALSE)) { /* We now handle stream timeouts if and only if this will be the last * loop iteration. We only check this on the last iteration to ensure * that if we know we have additional work to do immediately * (i.e. CURLM_CALL_MULTI_PERFORM == TRUE) then we should do that before * declaring the connection timed out as we may almost have a completed * connection. */ - multi_handle_timeout(data, nowp, &stream_error, &result, TRUE); + multi_handle_timeout(data, nowp, &stream_error, &result); } statemachine_end: @@ -2627,7 +2903,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(data->mstate < MSTATE_COMPLETED) { if(result) { /* - * If an error was returned, and we aren't in completed state now, + * If an error was returned, and we are not in completed state now, * then we go to completed and consider this transfer aborted. */ @@ -2639,31 +2915,27 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(data->conn) { if(stream_error) { - /* Don't attempt to send data over a connection that timed out */ + /* Do not attempt to send data over a connection that timed out */ bool dead_connection = result == CURLE_OPERATION_TIMEDOUT; struct connectdata *conn = data->conn; /* This is where we make sure that the conn pointer is reset. - We don't have to do this in every case block above where a + We do not have to do this in every case block above where a failure is detected */ Curl_detach_connection(data); - - /* remove connection from cache */ - Curl_conncache_remove_conn(data, conn, TRUE); - - /* disconnect properly */ - Curl_disconnect(data, conn, dead_connection); + Curl_cpool_disconnect(data, conn, dead_connection); } } else if(data->mstate == MSTATE_CONNECT) { /* Curl_connect() failed */ - (void)Curl_posttransfer(data); + multi_posttransfer(data); + Curl_pgrsUpdate_nometer(data); } multistate(data, MSTATE_COMPLETED); rc = CURLM_CALL_MULTI_PERFORM; } - /* if there's still a connection to use, call the progress function */ + /* if there is still a connection to use, call the progress function */ else if(data->conn && Curl_pgrsUpdate(data)) { /* aborted due to progress callback return code must close the connection */ @@ -2671,8 +2943,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, streamclose(data->conn, "Aborted by callback"); /* if not yet in DONE state, go there, otherwise COMPLETED */ - multistate(data, (data->mstate < MSTATE_DONE)? - MSTATE_DONE: MSTATE_COMPLETED); + multistate(data, (data->mstate < MSTATE_DONE) ? + MSTATE_DONE : MSTATE_COMPLETED); rc = CURLM_CALL_MULTI_PERFORM; } } @@ -2695,11 +2967,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } multistate(data, MSTATE_MSGSENT); - /* add this handle to the list of msgsent handles */ - Curl_llist_insert_next(&multi->msgsent, multi->msgsent.tail, data, - &data->connect_queue); - /* unlink from the main list */ - unlink_easy(multi, data); + /* unlink from the process list */ + Curl_node_remove(&data->multi_queue); + /* add this handle msgsent list */ + Curl_llist_append(&multi->msgsent, data, &data->multi_queue); return CURLM_OK; } } while((rc == CURLM_CALL_MULTI_PERFORM) || multi_ischanged(multi, FALSE)); @@ -2709,12 +2980,15 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } -CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles) +CURLMcode curl_multi_perform(CURLM *m, int *running_handles) { - struct Curl_easy *data; CURLMcode returncode = CURLM_OK; - struct Curl_tree *t; + struct Curl_tree *t = NULL; struct curltime now = Curl_now(); + struct Curl_llist_node *e; + struct Curl_llist_node *n = NULL; + struct Curl_multi *multi = m; + SIGPIPE_VARIABLE(pipe_st); if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; @@ -2722,31 +2996,31 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles) if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - data = multi->easyp; - if(data) { + sigpipe_init(&pipe_st); + for(e = Curl_llist_head(&multi->process); e; e = n) { + struct Curl_easy *data = Curl_node_elem(e); CURLMcode result; - bool nosig = data->set.no_signal; - SIGPIPE_VARIABLE(pipe_st); - sigpipe_ignore(data, &pipe_st); /* Do the loop and only alter the signal ignore state if the next handle has a different NO_SIGNAL state than the previous */ - do { - /* the current node might be unlinked in multi_runsingle(), get the next - pointer now */ - struct Curl_easy *datanext = data->next; - if(data->set.no_signal != nosig) { - sigpipe_restore(&pipe_st); - sigpipe_ignore(data, &pipe_st); - nosig = data->set.no_signal; - } + + /* the current node might be unlinked in multi_runsingle(), get the next + pointer now */ + n = Curl_node_next(e); + + if(data != multi->cpool.idata) { + /* connection pool handle is processed below */ + sigpipe_apply(data, &pipe_st); result = multi_runsingle(multi, &now, data); if(result) returncode = result; - data = datanext; /* operate on next handle */ - } while(data); - sigpipe_restore(&pipe_st); + } } + sigpipe_apply(multi->cpool.idata, &pipe_st); + Curl_cpool_multi_perform(multi); + + sigpipe_restore(&pipe_st); + /* * Simply remove all expired timers from the splay since handles are dealt * with unconditionally by this function and curl_multi_timeout() requires @@ -2759,13 +3033,23 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles) */ do { multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); - if(t) + if(t) { /* the removed may have another timeout in queue */ - (void)add_next_timeout(now, multi, t->payload); - + struct Curl_easy *data = Curl_splayget(t); + if(data->mstate == MSTATE_PENDING) { + bool stream_unused; + CURLcode result_unused; + if(multi_handle_timeout(data, &now, &stream_unused, &result_unused)) { + infof(data, "PENDING handle timeout"); + move_pending_to_connect(multi, data); + } + } + (void)add_next_timeout(now, multi, Curl_splayget(t)); + } } while(t); - *running_handles = multi->num_alive; + if(running_handles) + *running_handles = (int)multi->num_alive; if(CURLM_OK >= returncode) returncode = Curl_update_timer(multi); @@ -2773,35 +3057,44 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles) return returncode; } -/* unlink_all_msgsent_handles() detaches all those easy handles from this - multi handle */ +/* unlink_all_msgsent_handles() moves all nodes back from the msgsent list to + the process list */ static void unlink_all_msgsent_handles(struct Curl_multi *multi) { - struct Curl_llist_element *e = multi->msgsent.head; - if(e) { - struct Curl_easy *data = e->ptr; - DEBUGASSERT(data->mstate == MSTATE_MSGSENT); - data->multi = NULL; + struct Curl_llist_node *e; + for(e = Curl_llist_head(&multi->msgsent); e; e = Curl_node_next(e)) { + struct Curl_easy *data = Curl_node_elem(e); + if(data) { + DEBUGASSERT(data->mstate == MSTATE_MSGSENT); + Curl_node_remove(&data->multi_queue); + /* put it into the process list */ + Curl_llist_append(&multi->process, data, &data->multi_queue); + } } } -CURLMcode curl_multi_cleanup(struct Curl_multi *multi) +CURLMcode curl_multi_cleanup(CURLM *m) { - struct Curl_easy *data; - struct Curl_easy *nextdata; - + struct Curl_multi *multi = m; if(GOOD_MULTI_HANDLE(multi)) { + struct Curl_llist_node *e; + struct Curl_llist_node *n; if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - multi->magic = 0; /* not good anymore */ - + /* move the pending and msgsent entries back to process + so that there is just one list to iterate over */ unlink_all_msgsent_handles(multi); process_pending_handles(multi); + /* First remove all remaining easy handles */ - data = multi->easyp; - while(data) { - nextdata = data->next; + for(e = Curl_llist_head(&multi->process); e; e = n) { + struct Curl_easy *data = Curl_node_elem(e); + + if(!GOOD_EASY_HANDLE(data)) + return CURLM_BAD_HANDLE; + + n = Curl_node_next(e); if(!data->state.done && data->conn) /* if DONE was never called for this handle */ (void)multi_done(data, CURLE_OK, TRUE); @@ -2812,23 +3105,20 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi) data->dns.hostcachetype = HCACHE_NONE; } - /* Clear the pointer to the connection cache */ - data->state.conn_cache = NULL; data->multi = NULL; /* clear the association */ #ifdef USE_LIBPSL if(data->psl == &multi->psl) data->psl = NULL; #endif - - data = nextdata; } - /* Close all the connections in the connection cache */ - Curl_conncache_close_all_connections(&multi->conn_cache); + Curl_cpool_destroy(&multi->cpool); + + multi->magic = 0; /* not good anymore */ sockhash_destroy(&multi->sockhash); - Curl_conncache_destroy(&multi->conn_cache); + Curl_hash_destroy(&multi->proto_hash); Curl_hash_destroy(&multi->hostcache); Curl_psl_destroy(&multi->psl); @@ -2837,14 +3127,13 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi) #else #ifdef ENABLE_WAKEUP wakeup_close(multi->wakeup_pair[0]); +#ifndef USE_EVENTFD wakeup_close(multi->wakeup_pair[1]); #endif #endif - -#ifdef USE_SSL - Curl_free_multi_ssl_backend_data(multi->ssl_backend_data); #endif + multi_xfer_bufs_free(multi); free(multi); return CURLM_OK; @@ -2862,9 +3151,10 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi) * beyond. The current design is fully O(1). */ -CURLMsg *curl_multi_info_read(struct Curl_multi *multi, int *msgs_in_queue) +CURLMsg *curl_multi_info_read(CURLM *m, int *msgs_in_queue) { struct Curl_message *msg; + struct Curl_multi *multi = m; *msgs_in_queue = 0; /* default to none */ @@ -2872,15 +3162,15 @@ CURLMsg *curl_multi_info_read(struct Curl_multi *multi, int *msgs_in_queue) !multi->in_callback && Curl_llist_count(&multi->msglist)) { /* there is one or more messages in the list */ - struct Curl_llist_element *e; + struct Curl_llist_node *e; /* extract the head of the list to return */ - e = multi->msglist.head; + e = Curl_llist_head(&multi->msglist); - msg = e->ptr; + msg = Curl_node_elem(e); /* remove the extracted entry */ - Curl_llist_remove(&multi->msglist, e, NULL); + Curl_node_remove(e); *msgs_in_queue = curlx_uztosi(Curl_llist_count(&multi->msglist)); @@ -2897,83 +3187,86 @@ CURLMsg *curl_multi_info_read(struct Curl_multi *multi, int *msgs_in_queue) static CURLMcode singlesocket(struct Curl_multi *multi, struct Curl_easy *data) { - curl_socket_t socks[MAX_SOCKSPEREASYHANDLE]; - int i; + struct easy_pollset cur_poll; + CURLMcode mresult; + + /* Fill in the 'current' struct with the state as it is now: what sockets to + supervise and for what actions */ + multi_getsock(data, &cur_poll); + mresult = Curl_multi_pollset_ev(multi, data, &cur_poll, &data->last_poll); + + if(!mresult) /* Remember for next time */ + memcpy(&data->last_poll, &cur_poll, sizeof(cur_poll)); + return mresult; +} + +CURLMcode Curl_multi_pollset_ev(struct Curl_multi *multi, + struct Curl_easy *data, + struct easy_pollset *ps, + struct easy_pollset *last_ps) +{ + unsigned int i; struct Curl_sh_entry *entry; curl_socket_t s; - int num; - unsigned int curraction; - unsigned char actions[MAX_SOCKSPEREASYHANDLE]; int rc; - for(i = 0; i< MAX_SOCKSPEREASYHANDLE; i++) - socks[i] = CURL_SOCKET_BAD; - - /* Fill in the 'current' struct with the state as it is now: what sockets to - supervise and for what actions */ - curraction = multi_getsock(data, socks); - /* We have 0 .. N sockets already and we get to know about the 0 .. M sockets we should have from now on. Detect the differences, remove no longer supervised ones and add new ones */ /* walk over the sockets we got right now */ - for(i = 0; (i< MAX_SOCKSPEREASYHANDLE) && - (curraction & GETSOCK_MASK_RW(i)); - i++) { - unsigned char action = CURL_POLL_NONE; - unsigned char prevaction = 0; + for(i = 0; i < ps->num; i++) { + unsigned char cur_action = ps->actions[i]; + unsigned char last_action = 0; int comboaction; - bool sincebefore = FALSE; - s = socks[i]; + s = ps->sockets[i]; /* get it from the hash */ entry = sh_getentry(&multi->sockhash, s); - - if(curraction & GETSOCK_READSOCK(i)) - action |= CURL_POLL_IN; - if(curraction & GETSOCK_WRITESOCK(i)) - action |= CURL_POLL_OUT; - - actions[i] = action; if(entry) { /* check if new for this transfer */ - int j; - for(j = 0; j< data->numsocks; j++) { - if(s == data->sockets[j]) { - prevaction = data->actions[j]; - sincebefore = TRUE; + unsigned int j; + for(j = 0; j < last_ps->num; j++) { + if(s == last_ps->sockets[j]) { + last_action = last_ps->actions[j]; break; } } } else { - /* this is a socket we didn't have before, add it to the hash! */ + /* this is a socket we did not have before, add it to the hash! */ entry = sh_addentry(&multi->sockhash, s); if(!entry) /* fatal */ return CURLM_OUT_OF_MEMORY; } - if(sincebefore && (prevaction != action)) { + if(last_action && (last_action != cur_action)) { /* Socket was used already, but different action now */ - if(prevaction & CURL_POLL_IN) + if(last_action & CURL_POLL_IN) { + DEBUGASSERT(entry->readers); entry->readers--; - if(prevaction & CURL_POLL_OUT) + } + if(last_action & CURL_POLL_OUT) { + DEBUGASSERT(entry->writers); entry->writers--; - if(action & CURL_POLL_IN) + } + if(cur_action & CURL_POLL_IN) { entry->readers++; - if(action & CURL_POLL_OUT) + } + if(cur_action & CURL_POLL_OUT) entry->writers++; } - else if(!sincebefore) { - /* a new user */ + else if(!last_action && + !Curl_hash_pick(&entry->transfers, (char *)&data, /* hash key */ + sizeof(struct Curl_easy *))) { + DEBUGASSERT(entry->users < 100000); /* detect weird values */ + /* a new transfer using this socket */ entry->users++; - if(action & CURL_POLL_IN) + if(cur_action & CURL_POLL_IN) entry->readers++; - if(action & CURL_POLL_OUT) + if(cur_action & CURL_POLL_OUT) entry->writers++; - /* add 'data' to the transfer hash on this socket! */ if(!Curl_hash_add(&entry->transfers, (char *)&data, /* hash key */ sizeof(struct Curl_easy *), data)) { @@ -2982,11 +3275,11 @@ static CURLMcode singlesocket(struct Curl_multi *multi, } } - comboaction = (entry->writers? CURL_POLL_OUT : 0) | + comboaction = (entry->writers ? CURL_POLL_OUT : 0) | (entry->readers ? CURL_POLL_IN : 0); /* socket existed before and has the same action set as before */ - if(sincebefore && ((int)entry->action == comboaction)) + if(last_action && ((int)entry->action == comboaction)) /* same, continue */ continue; @@ -2994,6 +3287,7 @@ static CURLMcode singlesocket(struct Curl_multi *multi, set_in_callback(multi, TRUE); rc = multi->socket_cb(data, s, comboaction, multi->socket_userp, entry->socketp); + set_in_callback(multi, FALSE); if(rc == -1) { multi->dead = TRUE; @@ -3001,19 +3295,19 @@ static CURLMcode singlesocket(struct Curl_multi *multi, } } - entry->action = comboaction; /* store the current action state */ + /* store the current action state */ + entry->action = (unsigned int)comboaction; } - num = i; /* number of sockets */ - - /* when we've walked over all the sockets we should have right now, we must - make sure to detect sockets that are removed */ - for(i = 0; i< data->numsocks; i++) { - int j; + /* Check for last_poll.sockets that no longer appear in ps->sockets. + * Need to remove the easy handle from the multi->sockhash->transfers and + * remove multi->sockhash entry when this was the last transfer */ + for(i = 0; i < last_ps->num; i++) { + unsigned int j; bool stillused = FALSE; - s = data->sockets[i]; - for(j = 0; j < num; j++) { - if(s == socks[j]) { + s = last_ps->sockets[i]; + for(j = 0; j < ps->num; j++) { + if(s == ps->sockets[j]) { /* this is still supervised */ stillused = TRUE; break; @@ -3026,25 +3320,29 @@ static CURLMcode singlesocket(struct Curl_multi *multi, /* if this is NULL here, the socket has been closed and notified so already by Curl_multi_closed() */ if(entry) { - unsigned char oldactions = data->actions[i]; + unsigned char oldactions = last_ps->actions[i]; /* this socket has been removed. Decrease user count */ + DEBUGASSERT(entry->users); entry->users--; if(oldactions & CURL_POLL_OUT) entry->writers--; if(oldactions & CURL_POLL_IN) entry->readers--; if(!entry->users) { + bool dead = FALSE; if(multi->socket_cb) { set_in_callback(multi, TRUE); rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, multi->socket_userp, entry->socketp); set_in_callback(multi, FALSE); - if(rc == -1) { - multi->dead = TRUE; - return CURLM_ABORTED_BY_CALLBACK; - } + if(rc == -1) + dead = TRUE; } sh_delentry(entry, &multi->sockhash, s); + if(dead) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } } else { /* still users, but remove this handle as a user of this socket */ @@ -3054,11 +3352,8 @@ static CURLMcode singlesocket(struct Curl_multi *multi, } } } - } /* for loop over numsocks */ + } /* for loop over num */ - memcpy(data->sockets, socks, num*sizeof(curl_socket_t)); - memcpy(data->actions, actions, num*sizeof(char)); - data->numsocks = num; return CURLM_OK; } @@ -3074,7 +3369,7 @@ CURLcode Curl_updatesocket(struct Curl_easy *data) * Curl_multi_closed() * * Used by the connect code to tell the multi_socket code that one of the - * sockets we were using is about to be closed. This function will then + * sockets we were using is about to be closed. This function will then * remove it from the sockethash for this handle to make the multi_socket API * behave properly, especially for the case when libcurl will create another * socket again and it gets the same file descriptor number. @@ -3083,13 +3378,17 @@ CURLcode Curl_updatesocket(struct Curl_easy *data) void Curl_multi_closed(struct Curl_easy *data, curl_socket_t s) { if(data) { - /* if there's still an easy handle associated with this connection */ + /* if there is still an easy handle associated with this connection */ struct Curl_multi *multi = data->multi; + DEBUGF(infof(data, "Curl_multi_closed, fd=%" FMT_SOCKET_T + " multi is %p", s, (void *)multi)); if(multi) { /* this is set if this connection is part of a handle that is added to a multi handle, and only then this is necessary */ struct Curl_sh_entry *entry = sh_getentry(&multi->sockhash, s); + DEBUGF(infof(data, "Curl_multi_closed, fd=%" FMT_SOCKET_T + " entry is %p", s, (void *)entry)); if(entry) { int rc = 0; if(multi->socket_cb) { @@ -3129,26 +3428,24 @@ static CURLMcode add_next_timeout(struct curltime now, { struct curltime *tv = &d->state.expiretime; struct Curl_llist *list = &d->state.timeoutlist; - struct Curl_llist_element *e; - struct time_node *node = NULL; + struct Curl_llist_node *e; /* move over the timeout list for this specific handle and remove all timeouts that are now passed tense and store the next pending timeout in *tv */ - for(e = list->head; e;) { - struct Curl_llist_element *n = e->next; - timediff_t diff; - node = (struct time_node *)e->ptr; - diff = Curl_timediff(node->time, now); + for(e = Curl_llist_head(list); e;) { + struct Curl_llist_node *n = Curl_node_next(e); + struct time_node *node = Curl_node_elem(e); + timediff_t diff = Curl_timediff_us(node->time, now); if(diff <= 0) /* remove outdated entry */ - Curl_llist_remove(list, e, NULL); + Curl_node_remove(e); else /* the list is sorted so get out on the first mismatch */ break; e = n; } - e = list->head; + e = Curl_llist_head(list); if(!e) { /* clear the expire times within the handles that we remove from the splay tree */ @@ -3156,10 +3453,11 @@ static CURLMcode add_next_timeout(struct curltime now, tv->tv_usec = 0; } else { + struct time_node *node = Curl_node_elem(e); /* copy the first entry to 'tv' */ memcpy(tv, &node->time, sizeof(*tv)); - /* Insert this node again into the splay. Keep the timer in the list in + /* Insert this node again into the splay. Keep the timer in the list in case we need to recompute future timers. */ multi->timetree = Curl_splayinsert(*tv, multi->timetree, &d->state.timenode); @@ -3167,6 +3465,59 @@ static CURLMcode add_next_timeout(struct curltime now, return CURLM_OK; } +struct multi_run_ctx { + struct Curl_multi *multi; + struct curltime now; + size_t run_xfers; + SIGPIPE_MEMBER(pipe_st); + bool run_cpool; +}; + +static CURLMcode multi_run_expired(struct multi_run_ctx *mrc) +{ + struct Curl_multi *multi = mrc->multi; + struct Curl_easy *data = NULL; + struct Curl_tree *t = NULL; + CURLMcode result = CURLM_OK; + + /* + * The loop following here will go on as long as there are expire-times left + * to process (compared to mrc->now) in the splay and 'data' will be + * re-assigned for every expired handle we deal with. + */ + while(1) { + /* Check if there is one (more) expired timer to deal with! This function + extracts a matching node if there is one */ + multi->timetree = Curl_splaygetbest(mrc->now, multi->timetree, &t); + if(!t) + goto out; + + data = Curl_splayget(t); /* assign this for next loop */ + if(!data) + continue; + + (void)add_next_timeout(mrc->now, multi, data); + if(data == multi->cpool.idata) { + mrc->run_cpool = TRUE; + continue; + } + + mrc->run_xfers++; + sigpipe_apply(data, &mrc->pipe_st); + result = multi_runsingle(multi, &mrc->now, data); + + if(CURLM_OK >= result) { + /* get the socket(s) and check if the state has been changed since + last */ + result = singlesocket(multi, data); + if(result) + goto out; + } + } + +out: + return result; +} static CURLMcode multi_socket(struct Curl_multi *multi, bool checkall, curl_socket_t s, @@ -3175,39 +3526,44 @@ static CURLMcode multi_socket(struct Curl_multi *multi, { CURLMcode result = CURLM_OK; struct Curl_easy *data = NULL; - struct Curl_tree *t; - struct curltime now = Curl_now(); - bool first = FALSE; - bool nosig = FALSE; - SIGPIPE_VARIABLE(pipe_st); + struct multi_run_ctx mrc; + + (void)ev_bitmask; + memset(&mrc, 0, sizeof(mrc)); + mrc.multi = multi; + mrc.now = Curl_now(); + sigpipe_init(&mrc.pipe_st); if(checkall) { + struct Curl_llist_node *e; /* *perform() deals with running_handles on its own */ result = curl_multi_perform(multi, running_handles); /* walk through each easy handle and do the socket state change magic and callbacks */ if(result != CURLM_BAD_HANDLE) { - data = multi->easyp; - while(data && !result) { - result = singlesocket(multi, data); - data = data->next; + for(e = Curl_llist_head(&multi->process); e && !result; + e = Curl_node_next(e)) { + result = singlesocket(multi, Curl_node_elem(e)); } } - - /* or should we fall-through and do the timer-based stuff? */ - return result; + mrc.run_cpool = TRUE; + goto out; } + if(s != CURL_SOCKET_TIMEOUT) { struct Curl_sh_entry *entry = sh_getentry(&multi->sockhash, s); - if(!entry) - /* Unmatched socket, we can't act on it but we ignore this fact. In + if(!entry) { + /* Unmatched socket, we cannot act on it but we ignore this fact. In real-world tests it has been proved that libevent can in fact give the application actions even though the socket was just previously asked to get removed, so thus we better survive stray socket actions and just move on. */ - ; + /* The socket might come from a connection that is being shut down + * by the multi's connection pool. */ + Curl_cpool_multi_socket(multi, s, ev_bitmask); + } else { struct Curl_hash_iterator iter; struct Curl_hash_element *he; @@ -3220,84 +3576,54 @@ static CURLMcode multi_socket(struct Curl_multi *multi, DEBUGASSERT(data); DEBUGASSERT(data->magic == CURLEASY_MAGIC_NUMBER); - if(data->conn && !(data->conn->handler->flags & PROTOPT_DIRLOCK)) - /* set socket event bitmask if they're not locked */ - data->conn->cselect_bits = (unsigned char)ev_bitmask; - - Curl_expire(data, 0, EXPIRE_RUN_NOW); + if(data == multi->cpool.idata) + mrc.run_cpool = TRUE; + else { + /* Expire with out current now, so we will get it below when + * asking the splaytree for expired transfers. */ + expire_ex(data, &mrc.now, 0, EXPIRE_RUN_NOW); + } } - - /* Now we fall-through and do the timer-based stuff, since we don't want - to force the user to have to deal with timeouts as long as at least - one connection in fact has traffic. */ - - data = NULL; /* set data to NULL again to avoid calling - multi_runsingle() in case there's no need to */ - now = Curl_now(); /* get a newer time since the multi_runsingle() loop - may have taken some time */ } } - else { - /* Asked to run due to time-out. Clear the 'lastcall' variable to force - Curl_update_timer() to trigger a callback to the app again even if the - same timeout is still the one to run after this call. That handles the - case when the application asks libcurl to run the timeout - prematurely. */ - memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall)); - } - - /* - * The loop following here will go on as long as there are expire-times left - * to process in the splay and 'data' will be re-assigned for every expired - * handle we deal with. - */ - do { - /* the first loop lap 'data' can be NULL */ - if(data) { - if(!first) { - first = TRUE; - nosig = data->set.no_signal; /* initial state */ - sigpipe_ignore(data, &pipe_st); - } - else if(data->set.no_signal != nosig) { - sigpipe_restore(&pipe_st); - sigpipe_ignore(data, &pipe_st); - nosig = data->set.no_signal; /* remember new state */ - } - result = multi_runsingle(multi, &now, data); - - if(CURLM_OK >= result) { - /* get the socket(s) and check if the state has been changed since - last */ - result = singlesocket(multi, data); - if(result) - break; - } - } - /* Check if there's one (more) expired timer to deal with! This function - extracts a matching node if there is one */ + result = multi_run_expired(&mrc); + if(result) + goto out; + + if(mrc.run_xfers) { + /* Running transfers takes time. With a new timestamp, we might catch + * other expires which are due now. Instead of telling the application + * to set a 0 timeout and call us again, we run them here. + * Do that only once or it might be unfair to transfers on other + * sockets. */ + mrc.now = Curl_now(); + result = multi_run_expired(&mrc); + } - multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); - if(t) { - data = t->payload; /* assign this for next loop */ - (void)add_next_timeout(now, multi, t->payload); - } +out: + if(mrc.run_cpool) { + sigpipe_apply(multi->cpool.idata, &mrc.pipe_st); + Curl_cpool_multi_perform(multi); + } + sigpipe_restore(&mrc.pipe_st); - } while(t); - if(first) - sigpipe_restore(&pipe_st); + if(running_handles) + *running_handles = (int)multi->num_alive; - *running_handles = multi->num_alive; + if(CURLM_OK >= result) + result = Curl_update_timer(multi); return result; } #undef curl_multi_setopt -CURLMcode curl_multi_setopt(struct Curl_multi *multi, +CURLMcode curl_multi_setopt(CURLM *m, CURLMoption option, ...) { CURLMcode res = CURLM_OK; va_list param; + unsigned long uarg; + struct Curl_multi *multi = m; if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; @@ -3330,13 +3656,18 @@ CURLMcode curl_multi_setopt(struct Curl_multi *multi, multi->timer_userp = va_arg(param, void *); break; case CURLMOPT_MAXCONNECTS: - multi->maxconnects = va_arg(param, long); + uarg = va_arg(param, unsigned long); + if(uarg <= UINT_MAX) + multi->maxconnects = (unsigned int)uarg; break; case CURLMOPT_MAX_HOST_CONNECTIONS: multi->max_host_connections = va_arg(param, long); break; case CURLMOPT_MAX_TOTAL_CONNECTIONS: multi->max_total_connections = va_arg(param, long); + /* for now, let this also decide the max number of connections + * in shutdown handling */ + multi->max_shutdown_connections = va_arg(param, long); break; /* options formerly used for pipelining */ case CURLMOPT_MAX_PIPELINE_LENGTH: @@ -3352,9 +3683,9 @@ CURLMcode curl_multi_setopt(struct Curl_multi *multi, case CURLMOPT_MAX_CONCURRENT_STREAMS: { long streams = va_arg(param, long); - if(streams < 1) + if((streams < 1) || (streams > INT_MAX)) streams = 100; - multi->max_concurrent_streams = curlx_sltoui(streams); + multi->max_concurrent_streams = (unsigned int)streams; } break; default: @@ -3368,42 +3699,33 @@ CURLMcode curl_multi_setopt(struct Curl_multi *multi, /* we define curl_multi_socket() in the public multi.h header */ #undef curl_multi_socket -CURLMcode curl_multi_socket(struct Curl_multi *multi, curl_socket_t s, - int *running_handles) +CURLMcode curl_multi_socket(CURLM *m, curl_socket_t s, int *running_handles) { - CURLMcode result; + struct Curl_multi *multi = m; if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - result = multi_socket(multi, FALSE, s, 0, running_handles); - if(CURLM_OK >= result) - result = Curl_update_timer(multi); - return result; + return multi_socket(multi, FALSE, s, 0, running_handles); } -CURLMcode curl_multi_socket_action(struct Curl_multi *multi, curl_socket_t s, +CURLMcode curl_multi_socket_action(CURLM *m, curl_socket_t s, int ev_bitmask, int *running_handles) { - CURLMcode result; + struct Curl_multi *multi = m; if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - result = multi_socket(multi, FALSE, s, ev_bitmask, running_handles); - if(CURLM_OK >= result) - result = Curl_update_timer(multi); - return result; + return multi_socket(multi, FALSE, s, ev_bitmask, running_handles); } -CURLMcode curl_multi_socket_all(struct Curl_multi *multi, int *running_handles) +CURLMcode curl_multi_socket_all(CURLM *m, int *running_handles) { - CURLMcode result; + struct Curl_multi *multi = m; if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - result = multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, running_handles); - if(CURLM_OK >= result) - result = Curl_update_timer(multi); - return result; + return multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, running_handles); } static CURLMcode multi_timeout(struct Curl_multi *multi, + struct curltime *expire_time, long *timeout_ms) { static const struct curltime tv_zero = {0, 0}; @@ -3419,37 +3741,39 @@ static CURLMcode multi_timeout(struct Curl_multi *multi, /* splay the lowest to the bottom */ multi->timetree = Curl_splay(tv_zero, multi->timetree); - - if(Curl_splaycomparekeys(multi->timetree->key, now) > 0) { + /* this will not return NULL from a non-emtpy tree, but some compilers + * are not convinced of that. Analyzers are hard. */ + *expire_time = multi->timetree ? multi->timetree->key : tv_zero; + + /* 'multi->timetree' will be non-NULL here but the compilers sometimes + yell at us if we assume so */ + if(multi->timetree && + Curl_timediff_us(multi->timetree->key, now) > 0) { /* some time left before expiration */ - timediff_t diff = Curl_timediff(multi->timetree->key, now); - if(diff <= 0) - /* - * Since we only provide millisecond resolution on the returned value - * and the diff might be less than one millisecond here, we don't - * return zero as that may cause short bursts of busyloops on fast - * processors while the diff is still present but less than one - * millisecond! instead we return 1 until the time is ripe. - */ - *timeout_ms = 1; - else - /* this should be safe even on 64 bit archs, as we don't use that - overly long timeouts */ - *timeout_ms = (long)diff; + timediff_t diff = Curl_timediff_ceil(multi->timetree->key, now); + /* this should be safe even on 32-bit archs, as we do not use that + overly long timeouts */ + *timeout_ms = (long)diff; } - else + else { /* 0 means immediately */ *timeout_ms = 0; + } } - else + else { + *expire_time = tv_zero; *timeout_ms = -1; + } return CURLM_OK; } -CURLMcode curl_multi_timeout(struct Curl_multi *multi, +CURLMcode curl_multi_timeout(CURLM *m, long *timeout_ms) { + struct curltime expire_time; + struct Curl_multi *multi = m; + /* First, make some basic checks that the CURLM handle is a good handle */ if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; @@ -3457,56 +3781,79 @@ CURLMcode curl_multi_timeout(struct Curl_multi *multi, if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - return multi_timeout(multi, timeout_ms); + return multi_timeout(multi, &expire_time, timeout_ms); } +#define DEBUG_UPDATE_TIMER 0 + /* * Tell the application it should update its timers, if it subscribes to the * update timer callback. */ CURLMcode Curl_update_timer(struct Curl_multi *multi) { + struct curltime expire_ts; long timeout_ms; int rc; + bool set_value = FALSE; if(!multi->timer_cb || multi->dead) return CURLM_OK; - if(multi_timeout(multi, &timeout_ms)) { - return CURLM_OK; - } - if(timeout_ms < 0) { - static const struct curltime none = {0, 0}; - if(Curl_splaycomparekeys(none, multi->timer_lastcall)) { - multi->timer_lastcall = none; - /* there's no timeout now but there was one previously, tell the app to - disable it */ - set_in_callback(multi, TRUE); - rc = multi->timer_cb(multi, -1, multi->timer_userp); - set_in_callback(multi, FALSE); - if(rc == -1) { - multi->dead = TRUE; - return CURLM_ABORTED_BY_CALLBACK; - } - return CURLM_OK; - } + if(multi_timeout(multi, &expire_ts, &timeout_ms)) { return CURLM_OK; } - /* When multi_timeout() is done, multi->timetree points to the node with the - * timeout we got the (relative) time-out time for. We can thus easily check - * if this is the same (fixed) time as we got in a previous call and then - * avoid calling the callback again. */ - if(Curl_splaycomparekeys(multi->timetree->key, multi->timer_lastcall) == 0) - return CURLM_OK; - - multi->timer_lastcall = multi->timetree->key; + if(timeout_ms < 0 && multi->last_timeout_ms < 0) { +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), still no timeout, no change\n"); +#endif + } + else if(timeout_ms < 0) { + /* there is no timeout now but there was one previously */ +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), remove timeout, " + " last_timeout=%ldms\n", multi->last_timeout_ms); +#endif + timeout_ms = -1; /* normalize */ + set_value = TRUE; + } + else if(multi->last_timeout_ms < 0) { +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), had no timeout, set now\n"); +#endif + set_value = TRUE; + } + else if(Curl_timediff_us(multi->last_expire_ts, expire_ts)) { + /* We had a timeout before and have one now, the absolute timestamp + * differs. The relative timeout_ms may be the same, but the starting + * point differs. Let the application restart its timer. */ +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), expire timestamp changed\n"); +#endif + set_value = TRUE; + } + else { + /* We have same expire time as previously. Our relative 'timeout_ms' + * may be different now, but the application has the timer running + * and we do not to tell it to start this again. */ +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), same expire timestamp, no change\n"); +#endif + } - set_in_callback(multi, TRUE); - rc = multi->timer_cb(multi, timeout_ms, multi->timer_userp); - set_in_callback(multi, FALSE); - if(rc == -1) { - multi->dead = TRUE; - return CURLM_ABORTED_BY_CALLBACK; + if(set_value) { +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), set timeout %ldms\n", timeout_ms); +#endif + multi->last_expire_ts = expire_ts; + multi->last_timeout_ms = timeout_ms; + set_in_callback(multi, TRUE); + rc = multi->timer_cb(multi, timeout_ms, multi->timer_userp); + set_in_callback(multi, FALSE); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } } return CURLM_OK; } @@ -3519,13 +3866,13 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) static void multi_deltimeout(struct Curl_easy *data, expire_id eid) { - struct Curl_llist_element *e; + struct Curl_llist_node *e; struct Curl_llist *timeoutlist = &data->state.timeoutlist; /* find and remove the specific node from the list */ - for(e = timeoutlist->head; e; e = e->next) { - struct time_node *n = (struct time_node *)e->ptr; + for(e = Curl_llist_head(timeoutlist); e; e = Curl_node_next(e)) { + struct time_node *n = Curl_node_elem(e); if(n->eid == eid) { - Curl_llist_remove(timeoutlist, e, NULL); + Curl_node_remove(e); return; } } @@ -3543,9 +3890,9 @@ multi_addtimeout(struct Curl_easy *data, struct curltime *stamp, expire_id eid) { - struct Curl_llist_element *e; + struct Curl_llist_node *e; struct time_node *node; - struct Curl_llist_element *prev = NULL; + struct Curl_llist_node *prev = NULL; size_t n; struct Curl_llist *timeoutlist = &data->state.timeoutlist; @@ -3558,8 +3905,8 @@ multi_addtimeout(struct Curl_easy *data, n = Curl_llist_count(timeoutlist); if(n) { /* find the correct spot in the list */ - for(e = timeoutlist->head; e; e = e->next) { - struct time_node *check = (struct time_node *)e->ptr; + for(e = Curl_llist_head(timeoutlist); e; e = Curl_node_next(e)) { + struct time_node *check = Curl_node_elem(e); timediff_t diff = Curl_timediff(check->time, node->time); if(diff > 0) break; @@ -3574,21 +3921,12 @@ multi_addtimeout(struct Curl_easy *data, return CURLM_OK; } -/* - * Curl_expire() - * - * given a number of milliseconds from now to use to set the 'act before - * this'-time for the transfer, to be extracted by curl_multi_timeout() - * - * The timeout will be added to a queue of timeouts if it defines a moment in - * time that is later than the current head of queue. - * - * Expire replaces a former timeout using the same id if already set. - */ -void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id) +static void expire_ex(struct Curl_easy *data, + const struct curltime *nowp, + timediff_t milli, expire_id id) { struct Curl_multi *multi = data->multi; - struct curltime *nowp = &data->state.expiretime; + struct curltime *curr_expire = &data->state.expiretime; struct curltime set; /* this is only interesting while there is still an associated multi struct @@ -3598,9 +3936,9 @@ void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id) DEBUGASSERT(id < EXPIRE_LAST); - set = Curl_now(); - set.tv_sec += (time_t)(milli/1000); /* might be a 64 to 32 bit conversion */ - set.tv_usec += (unsigned int)(milli%1000)*1000; + set = *nowp; + set.tv_sec += (time_t)(milli/1000); /* might be a 64 to 32 bits conversion */ + set.tv_usec += (int)(milli%1000)*1000; if(set.tv_usec >= 1000000) { set.tv_sec++; @@ -3610,20 +3948,20 @@ void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id) /* Remove any timer with the same id just in case. */ multi_deltimeout(data, id); - /* Add it to the timer list. It must stay in the list until it has expired + /* Add it to the timer list. It must stay in the list until it has expired in case we need to recompute the minimum timer later. */ multi_addtimeout(data, &set, id); - if(nowp->tv_sec || nowp->tv_usec) { + if(curr_expire->tv_sec || curr_expire->tv_usec) { /* This means that the struct is added as a node in the splay tree. Compare if the new time is earlier, and only remove-old/add-new if it is. */ - timediff_t diff = Curl_timediff(set, *nowp); + timediff_t diff = Curl_timediff(set, *curr_expire); int rc; if(diff > 0) { /* The current splay tree entry is sooner than this new expiry time. - We don't need to update our splay tree entry. */ + We do not need to update our splay tree entry. */ return; } @@ -3637,12 +3975,29 @@ void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id) /* Indicate that we are in the splay tree and insert the new timer expiry value since it is our local minimum. */ - *nowp = set; - data->state.timenode.payload = data; - multi->timetree = Curl_splayinsert(*nowp, multi->timetree, + *curr_expire = set; + Curl_splayset(&data->state.timenode, data); + multi->timetree = Curl_splayinsert(*curr_expire, multi->timetree, &data->state.timenode); } +/* + * Curl_expire() + * + * given a number of milliseconds from now to use to set the 'act before + * this'-time for the transfer, to be extracted by curl_multi_timeout() + * + * The timeout will be added to a queue of timeouts if it defines a moment in + * time that is later than the current head of queue. + * + * Expire replaces a former timeout using the same id if already set. + */ +void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id) +{ + struct curltime now = Curl_now(); + expire_ex(data, &now, milli, id); +} + /* * Curl_expire_done() * @@ -3660,7 +4015,7 @@ void Curl_expire_done(struct Curl_easy *data, expire_id id) * * Clear ALL timeout values for this handle. */ -void Curl_expire_clear(struct Curl_easy *data) +bool Curl_expire_clear(struct Curl_easy *data) { struct Curl_multi *multi = data->multi; struct curltime *nowp = &data->state.expiretime; @@ -3668,7 +4023,7 @@ void Curl_expire_clear(struct Curl_easy *data) /* this is only interesting while there is still an associated multi struct remaining! */ if(!multi) - return; + return FALSE; if(nowp->tv_sec || nowp->tv_usec) { /* Since this is an cleared time, we must remove the previous entry from @@ -3681,26 +4036,26 @@ void Curl_expire_clear(struct Curl_easy *data) if(rc) infof(data, "Internal error clearing splay node = %d", rc); - /* flush the timeout list too */ - while(list->size > 0) { - Curl_llist_remove(list, list->tail, NULL); - } + /* clear the timeout list too */ + Curl_llist_destroy(list, NULL); #ifdef DEBUGBUILD infof(data, "Expire cleared"); #endif nowp->tv_sec = 0; nowp->tv_usec = 0; + return TRUE; } + return FALSE; } - - - -CURLMcode curl_multi_assign(struct Curl_multi *multi, curl_socket_t s, +CURLMcode curl_multi_assign(CURLM *m, curl_socket_t s, void *hashp) { struct Curl_sh_entry *there = NULL; + struct Curl_multi *multi = m; + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; there = sh_getentry(&multi->sockhash, s); @@ -3712,112 +4067,271 @@ CURLMcode curl_multi_assign(struct Curl_multi *multi, curl_socket_t s, return CURLM_OK; } -size_t Curl_multi_max_host_connections(struct Curl_multi *multi) +static void move_pending_to_connect(struct Curl_multi *multi, + struct Curl_easy *data) { - return multi ? multi->max_host_connections : 0; -} + DEBUGASSERT(data->mstate == MSTATE_PENDING); -size_t Curl_multi_max_total_connections(struct Curl_multi *multi) -{ - return multi ? multi->max_total_connections : 0; -} + /* Remove this node from the pending list */ + Curl_node_remove(&data->multi_queue); -/* - * When information about a connection has appeared, call this! - */ + /* put it into the process list */ + Curl_llist_append(&multi->process, data, &data->multi_queue); -void Curl_multiuse_state(struct Curl_easy *data, - int bundlestate) /* use BUNDLE_* defines */ -{ - struct connectdata *conn; - DEBUGASSERT(data); - DEBUGASSERT(data->multi); - conn = data->conn; - DEBUGASSERT(conn); - DEBUGASSERT(conn->bundle); + multistate(data, MSTATE_CONNECT); - conn->bundle->multiuse = bundlestate; - process_pending_handles(data->multi); + /* Make sure that the handle will be processed soonish. */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); } -/* process_pending_handles() moves all handles from PENDING - back into the main list and change state to CONNECT */ +/* process_pending_handles() moves a handle from PENDING back into the process + list and change state to CONNECT. + + We do not move all transfers because that can be a significant amount. + Since this is tried every now and then doing too many too often becomes a + performance problem. + + When there is a change for connection limits like max host connections etc, + this likely only allows one new transfer. When there is a pipewait change, + it can potentially allow hundreds of new transfers. + + We could consider an improvement where we store the queue reason and allow + more pipewait rechecks than others. +*/ static void process_pending_handles(struct Curl_multi *multi) { - struct Curl_llist_element *e = multi->pending.head; + struct Curl_llist_node *e = Curl_llist_head(&multi->pending); if(e) { - struct Curl_easy *data = e->ptr; + struct Curl_easy *data = Curl_node_elem(e); + move_pending_to_connect(multi, data); + } +} - DEBUGASSERT(data->mstate == MSTATE_PENDING); +void Curl_set_in_callback(struct Curl_easy *data, bool value) +{ + if(data && data->multi) + data->multi->in_callback = value; +} - /* put it back into the main list */ - link_easy(multi, data); +bool Curl_is_in_callback(struct Curl_easy *data) +{ + return (data && data->multi && data->multi->in_callback); +} - multistate(data, MSTATE_CONNECT); +unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) +{ + DEBUGASSERT(multi); + return multi->max_concurrent_streams; +} - /* Remove this node from the list */ - Curl_llist_remove(&multi->pending, e, NULL); +CURL **curl_multi_get_handles(CURLM *m) +{ + struct Curl_multi *multi = m; + CURL **a = malloc(sizeof(struct Curl_easy *) * (multi->num_easy + 1)); + if(a) { + unsigned int i = 0; + struct Curl_llist_node *e; + for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *data = Curl_node_elem(e); + DEBUGASSERT(i < multi->num_easy); + if(!data->state.internal) + a[i++] = data; + } + a[i] = NULL; /* last entry is a NULL */ + } + return a; +} - /* Make sure that the handle will be processed soonish. */ - Curl_expire(data, 0, EXPIRE_RUN_NOW); +CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data, + char **pbuf, size_t *pbuflen) +{ + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + *pbuf = NULL; + *pbuflen = 0; + if(!data->multi) { + failf(data, "transfer has no multi handle"); + return CURLE_FAILED_INIT; + } + if(!data->set.buffer_size) { + failf(data, "transfer buffer size is 0"); + return CURLE_FAILED_INIT; + } + if(data->multi->xfer_buf_borrowed) { + failf(data, "attempt to borrow xfer_buf when already borrowed"); + return CURLE_AGAIN; + } - /* mark this as having been in the pending queue */ - data->state.previouslypending = TRUE; + if(data->multi->xfer_buf && + data->set.buffer_size > data->multi->xfer_buf_len) { + /* not large enough, get a new one */ + free(data->multi->xfer_buf); + data->multi->xfer_buf = NULL; + data->multi->xfer_buf_len = 0; + } + + if(!data->multi->xfer_buf) { + data->multi->xfer_buf = malloc((size_t)data->set.buffer_size); + if(!data->multi->xfer_buf) { + failf(data, "could not allocate xfer_buf of %zu bytes", + (size_t)data->set.buffer_size); + return CURLE_OUT_OF_MEMORY; + } + data->multi->xfer_buf_len = data->set.buffer_size; } + + data->multi->xfer_buf_borrowed = TRUE; + *pbuf = data->multi->xfer_buf; + *pbuflen = data->multi->xfer_buf_len; + return CURLE_OK; } -void Curl_set_in_callback(struct Curl_easy *data, bool value) +void Curl_multi_xfer_buf_release(struct Curl_easy *data, char *buf) { - /* might get called when there is no data pointer! */ - if(data) { - if(data->multi_easy) - data->multi_easy->in_callback = value; - else if(data->multi) - data->multi->in_callback = value; + (void)buf; + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + DEBUGASSERT(!buf || data->multi->xfer_buf == buf); + data->multi->xfer_buf_borrowed = FALSE; +} + +CURLcode Curl_multi_xfer_ulbuf_borrow(struct Curl_easy *data, + char **pbuf, size_t *pbuflen) +{ + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + *pbuf = NULL; + *pbuflen = 0; + if(!data->multi) { + failf(data, "transfer has no multi handle"); + return CURLE_FAILED_INIT; + } + if(!data->set.upload_buffer_size) { + failf(data, "transfer upload buffer size is 0"); + return CURLE_FAILED_INIT; + } + if(data->multi->xfer_ulbuf_borrowed) { + failf(data, "attempt to borrow xfer_ulbuf when already borrowed"); + return CURLE_AGAIN; + } + + if(data->multi->xfer_ulbuf && + data->set.upload_buffer_size > data->multi->xfer_ulbuf_len) { + /* not large enough, get a new one */ + free(data->multi->xfer_ulbuf); + data->multi->xfer_ulbuf = NULL; + data->multi->xfer_ulbuf_len = 0; + } + + if(!data->multi->xfer_ulbuf) { + data->multi->xfer_ulbuf = malloc((size_t)data->set.upload_buffer_size); + if(!data->multi->xfer_ulbuf) { + failf(data, "could not allocate xfer_ulbuf of %zu bytes", + (size_t)data->set.upload_buffer_size); + return CURLE_OUT_OF_MEMORY; + } + data->multi->xfer_ulbuf_len = data->set.upload_buffer_size; } + + data->multi->xfer_ulbuf_borrowed = TRUE; + *pbuf = data->multi->xfer_ulbuf; + *pbuflen = data->multi->xfer_ulbuf_len; + return CURLE_OK; } -bool Curl_is_in_callback(struct Curl_easy *easy) +void Curl_multi_xfer_ulbuf_release(struct Curl_easy *data, char *buf) { - return ((easy->multi && easy->multi->in_callback) || - (easy->multi_easy && easy->multi_easy->in_callback)); + (void)buf; + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + DEBUGASSERT(!buf || data->multi->xfer_ulbuf == buf); + data->multi->xfer_ulbuf_borrowed = FALSE; } -#ifdef DEBUGBUILD -void Curl_multi_dump(struct Curl_multi *multi) +CURLcode Curl_multi_xfer_sockbuf_borrow(struct Curl_easy *data, + size_t blen, char **pbuf) { - struct Curl_easy *data; - int i; - fprintf(stderr, "* Multi status: %d handles, %d alive\n", - multi->num_easy, multi->num_alive); - for(data = multi->easyp; data; data = data->next) { - if(data->mstate < MSTATE_COMPLETED) { - /* only display handles that are not completed */ - fprintf(stderr, "handle %p, state %s, %d sockets\n", - (void *)data, - multi_statename[data->mstate], data->numsocks); - for(i = 0; i < data->numsocks; i++) { - curl_socket_t s = data->sockets[i]; - struct Curl_sh_entry *entry = sh_getentry(&multi->sockhash, s); - - fprintf(stderr, "%d ", (int)s); - if(!entry) { - fprintf(stderr, "INTERNAL CONFUSION\n"); - continue; - } - fprintf(stderr, "[%s %s] ", - (entry->action&CURL_POLL_IN)?"RECVING":"", - (entry->action&CURL_POLL_OUT)?"SENDING":""); - } - if(data->numsocks) - fprintf(stderr, "\n"); + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + *pbuf = NULL; + if(!data->multi) { + failf(data, "transfer has no multi handle"); + return CURLE_FAILED_INIT; + } + if(data->multi->xfer_sockbuf_borrowed) { + failf(data, "attempt to borrow xfer_sockbuf when already borrowed"); + return CURLE_AGAIN; + } + + if(data->multi->xfer_sockbuf && blen > data->multi->xfer_sockbuf_len) { + /* not large enough, get a new one */ + free(data->multi->xfer_sockbuf); + data->multi->xfer_sockbuf = NULL; + data->multi->xfer_sockbuf_len = 0; + } + + if(!data->multi->xfer_sockbuf) { + data->multi->xfer_sockbuf = malloc(blen); + if(!data->multi->xfer_sockbuf) { + failf(data, "could not allocate xfer_sockbuf of %zu bytes", blen); + return CURLE_OUT_OF_MEMORY; } + data->multi->xfer_sockbuf_len = blen; } + + data->multi->xfer_sockbuf_borrowed = TRUE; + *pbuf = data->multi->xfer_sockbuf; + return CURLE_OK; } -#endif -unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) +void Curl_multi_xfer_sockbuf_release(struct Curl_easy *data, char *buf) +{ + (void)buf; + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + DEBUGASSERT(!buf || data->multi->xfer_sockbuf == buf); + data->multi->xfer_sockbuf_borrowed = FALSE; +} + +static void multi_xfer_bufs_free(struct Curl_multi *multi) { DEBUGASSERT(multi); - return multi->max_concurrent_streams; + Curl_safefree(multi->xfer_buf); + multi->xfer_buf_len = 0; + multi->xfer_buf_borrowed = FALSE; + Curl_safefree(multi->xfer_ulbuf); + multi->xfer_ulbuf_len = 0; + multi->xfer_ulbuf_borrowed = FALSE; + Curl_safefree(multi->xfer_sockbuf); + multi->xfer_sockbuf_len = 0; + multi->xfer_sockbuf_borrowed = FALSE; +} + +struct Curl_easy *Curl_multi_get_handle(struct Curl_multi *multi, + curl_off_t mid) +{ + + if(mid >= 0) { + struct Curl_easy *data; + struct Curl_llist_node *e; + + for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) { + data = Curl_node_elem(e); + if(data->mid == mid) + return data; + } + /* may be in msgsent queue */ + for(e = Curl_llist_head(&multi->msgsent); e; e = Curl_node_next(e)) { + data = Curl_node_elem(e); + if(data->mid == mid) + return data; + } + /* may be in pending queue */ + for(e = Curl_llist_head(&multi->pending); e; e = Curl_node_next(e)) { + data = Curl_node_elem(e); + if(data->mid == mid) + return data; + } + } + return NULL; } diff --git a/deps/curl/lib/multihandle.h b/deps/curl/lib/multihandle.h index 5b16bb605fe..9225d2d3f15 100644 --- a/deps/curl/lib/multihandle.h +++ b/deps/curl/lib/multihandle.h @@ -33,7 +33,7 @@ struct connectdata; struct Curl_message { - struct Curl_llist_element list; + struct Curl_llist_node list; /* the 'CURLMsg' is the part that is visible to the external user */ struct CURLMsg extmsg; }; @@ -44,24 +44,25 @@ struct Curl_message { typedef enum { MSTATE_INIT, /* 0 - start in this state */ MSTATE_PENDING, /* 1 - no connections, waiting for one */ - MSTATE_CONNECT, /* 2 - resolve/connect has been sent off */ - MSTATE_RESOLVING, /* 3 - awaiting the resolve to finalize */ - MSTATE_CONNECTING, /* 4 - awaiting the TCP connect to finalize */ - MSTATE_TUNNELING, /* 5 - awaiting HTTPS proxy SSL initialization to + MSTATE_SETUP, /* 2 - start a new transfer */ + MSTATE_CONNECT, /* 3 - resolve/connect has been sent off */ + MSTATE_RESOLVING, /* 4 - awaiting the resolve to finalize */ + MSTATE_CONNECTING, /* 5 - awaiting the TCP connect to finalize */ + MSTATE_TUNNELING, /* 6 - awaiting HTTPS proxy SSL initialization to complete and/or proxy CONNECT to finalize */ - MSTATE_PROTOCONNECT, /* 6 - initiate protocol connect procedure */ - MSTATE_PROTOCONNECTING, /* 7 - completing the protocol-specific connect + MSTATE_PROTOCONNECT, /* 7 - initiate protocol connect procedure */ + MSTATE_PROTOCONNECTING, /* 8 - completing the protocol-specific connect phase */ - MSTATE_DO, /* 8 - start send off the request (part 1) */ - MSTATE_DOING, /* 9 - sending off the request (part 1) */ - MSTATE_DOING_MORE, /* 10 - send off the request (part 2) */ - MSTATE_DID, /* 11 - done sending off request */ - MSTATE_PERFORMING, /* 12 - transfer data */ - MSTATE_RATELIMITING, /* 13 - wait because limit-rate exceeded */ - MSTATE_DONE, /* 14 - post data transfer operation */ - MSTATE_COMPLETED, /* 15 - operation complete */ - MSTATE_MSGSENT, /* 16 - the operation complete message is sent */ - MSTATE_LAST /* 17 - not a true state, never use this */ + MSTATE_DO, /* 9 - start send off the request (part 1) */ + MSTATE_DOING, /* 10 - sending off the request (part 1) */ + MSTATE_DOING_MORE, /* 11 - send off the request (part 2) */ + MSTATE_DID, /* 12 - done sending off request */ + MSTATE_PERFORMING, /* 13 - transfer data */ + MSTATE_RATELIMITING, /* 14 - wait because limit-rate exceeded */ + MSTATE_DONE, /* 15 - post data transfer operation */ + MSTATE_COMPLETED, /* 16 - operation complete */ + MSTATE_MSGSENT, /* 17 - the operation complete message is sent */ + MSTATE_LAST /* 18 - not a true state, never use this */ } CURLMstate; /* we support N sockets per easy handle. Set the corresponding bit to what @@ -79,30 +80,23 @@ typedef enum { /* value for MAXIMUM CONCURRENT STREAMS upper limit */ #define INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1) -/* Curl_multi SSL backend-specific data; declared differently by each SSL - backend */ -struct multi_ssl_backend_data; - /* This is the struct known as CURLM on the outside */ struct Curl_multi { /* First a simple identifier to easier detect if a user mix up this multi handle with an easy handle. Set this to CURL_MULTI_HANDLE. */ unsigned int magic; - /* We have a doubly-linked list with easy handles */ - struct Curl_easy *easyp; - struct Curl_easy *easylp; /* last node */ - - int num_easy; /* amount of entries in the linked list above. */ - int num_alive; /* amount of easy handles that are added but have not yet - reached COMPLETE state */ + unsigned int num_easy; /* amount of entries in the linked list above. */ + unsigned int num_alive; /* amount of easy handles that are added but have + not yet reached COMPLETE state */ struct Curl_llist msglist; /* a list of messages from completed transfers */ - struct Curl_llist pending; /* Curl_easys that are in the - MSTATE_PENDING state */ - struct Curl_llist msgsent; /* Curl_easys that are in the - MSTATE_MSGSENT state */ + /* Each added easy handle is added to ONE of these three lists */ + struct Curl_llist process; /* not in PENDING or MSGSENT */ + struct Curl_llist pending; /* in PENDING */ + struct Curl_llist msgsent; /* in MSGSENT */ + curl_off_t next_easy_mid; /* next multi-id for easy handle added */ /* callback function and user data pointer for the *socket() API */ curl_socket_callback socket_cb; @@ -124,42 +118,58 @@ struct Curl_multi { times of all currently set timers */ struct Curl_tree *timetree; -#if defined(USE_SSL) - struct multi_ssl_backend_data *ssl_backend_data; -#endif + /* buffer used for transfer data, lazy initialized */ + char *xfer_buf; /* the actual buffer */ + size_t xfer_buf_len; /* the allocated length */ + /* buffer used for upload data, lazy initialized */ + char *xfer_ulbuf; /* the actual buffer */ + size_t xfer_ulbuf_len; /* the allocated length */ + /* buffer used for socket I/O operations, lazy initialized */ + char *xfer_sockbuf; /* the actual buffer */ + size_t xfer_sockbuf_len; /* the allocated length */ /* 'sockhash' is the lookup hash for socket descriptor => easy handles (note the pluralis form, there can be more than one easy handle waiting on the same actual socket) */ struct Curl_hash sockhash; + /* `proto_hash` is a general key-value store for protocol implementations + * with the lifetime of the multi handle. The number of elements kept here + * should be in the order of supported protocols (and sub-protocols like + * TLS), *not* in the order of connections or current transfers! + * Elements need to be added with their own destructor to be invoked when + * the multi handle is cleaned up (see Curl_hash_add2()).*/ + struct Curl_hash proto_hash; /* Shared connection cache (bundles)*/ - struct conncache conn_cache; - - long maxconnects; /* if >0, a fixed limit of the maximum number of entries - we're allowed to grow the connection cache to */ + struct cpool cpool; long max_host_connections; /* if >0, a fixed limit of the maximum number of connections per host */ long max_total_connections; /* if >0, a fixed limit of the maximum number of connections in total */ + long max_shutdown_connections; /* if >0, a fixed limit of the maximum number + of connections in shutdown handling */ /* timer callback and user data pointer for the *socket() API */ curl_multi_timer_callback timer_cb; void *timer_userp; - struct curltime timer_lastcall; /* the fixed time for the timeout for the - previous callback */ - unsigned int max_concurrent_streams; + long last_timeout_ms; /* the last timeout value set via timer_cb */ + struct curltime last_expire_ts; /* timestamp of last expiry */ #ifdef USE_WINSOCK - WSAEVENT wsa_event; /* winsock event used for waits */ + WSAEVENT wsa_event; /* Winsock event used for waits */ #else #ifdef ENABLE_WAKEUP - curl_socket_t wakeup_pair[2]; /* socketpair() used for wakeup - 0 is used for read, 1 is used for write */ + curl_socket_t wakeup_pair[2]; /* eventfd()/pipe()/socketpair() used for + wakeup 0 is used for read, 1 is used + for write */ #endif #endif + unsigned int max_concurrent_streams; + unsigned int maxconnects; /* if >0, a fixed limit of the maximum number of + entries we are allowed to grow the connection + cache to */ #define IPV6_UNKNOWN 0 #define IPV6_DEAD 1 #define IPV6_WORKS 2 @@ -172,6 +182,9 @@ struct Curl_multi { #endif BIT(dead); /* a callback returned error, everything needs to crash and burn */ + BIT(xfer_buf_borrowed); /* xfer_buf is currently being borrowed */ + BIT(xfer_ulbuf_borrowed); /* xfer_ulbuf is currently being borrowed */ + BIT(xfer_sockbuf_borrowed); /* xfer_sockbuf is currently being borrowed */ #ifdef DEBUGBUILD BIT(warned); /* true after user warned of DEBUGBUILD */ #endif diff --git a/deps/curl/lib/multiif.h b/deps/curl/lib/multiif.h index 7f08ecc614b..fd0e21519fa 100644 --- a/deps/curl/lib/multiif.h +++ b/deps/curl/lib/multiif.h @@ -30,7 +30,7 @@ CURLcode Curl_updatesocket(struct Curl_easy *data); void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id); -void Curl_expire_clear(struct Curl_easy *data); +bool Curl_expire_clear(struct Curl_easy *data); void Curl_expire_done(struct Curl_easy *data, expire_id id); CURLMcode Curl_update_timer(struct Curl_multi *multi) WARN_UNUSED_RESULT; void Curl_attach_connection(struct Curl_easy *data, @@ -38,15 +38,16 @@ void Curl_attach_connection(struct Curl_easy *data, void Curl_detach_connection(struct Curl_easy *data); bool Curl_multiplex_wanted(const struct Curl_multi *multi); void Curl_set_in_callback(struct Curl_easy *data, bool value); -bool Curl_is_in_callback(struct Curl_easy *easy); +bool Curl_is_in_callback(struct Curl_easy *data); CURLcode Curl_preconnect(struct Curl_easy *data); void Curl_multi_connchanged(struct Curl_multi *multi); /* Internal version of curl_multi_init() accepts size parameters for the socket, connection and dns hashes */ -struct Curl_multi *Curl_multi_handle(int hashsize, int chashsize, - int dnssize); +struct Curl_multi *Curl_multi_handle(size_t hashsize, + size_t chashsize, + size_t dnssize); /* the write bits start at bit 16 for the *getsock() bitmap */ #define GETSOCK_WRITEBITSTART 16 @@ -62,29 +63,11 @@ struct Curl_multi *Curl_multi_handle(int hashsize, int chashsize, /* mask for checking if read and/or write is set for index x */ #define GETSOCK_MASK_RW(x) (GETSOCK_READSOCK(x)|GETSOCK_WRITESOCK(x)) -#ifdef DEBUGBUILD - /* - * Curl_multi_dump is not a stable public function, this is only meant to - * allow easier tracking of the internal handle's state and what sockets - * they use. Only for research and development DEBUGBUILD enabled builds. - */ -void Curl_multi_dump(struct Curl_multi *multi); -#endif - -/* Return the value of the CURLMOPT_MAX_HOST_CONNECTIONS option */ -size_t Curl_multi_max_host_connections(struct Curl_multi *multi); - -/* Return the value of the CURLMOPT_MAX_TOTAL_CONNECTIONS option */ -size_t Curl_multi_max_total_connections(struct Curl_multi *multi); - -void Curl_multiuse_state(struct Curl_easy *data, - int bundlestate); /* use BUNDLE_* defines */ - /* * Curl_multi_closed() * * Used by the connect code to tell the multi_socket code that one of the - * sockets we were using is about to be closed. This function will then + * sockets we were using is about to be closed. This function will then * remove it from the sockethash for this handle to make the multi_socket API * behave properly, especially for the case when libcurl will create another * socket again and it gets the same file descriptor number. @@ -92,6 +75,15 @@ void Curl_multiuse_state(struct Curl_easy *data, void Curl_multi_closed(struct Curl_easy *data, curl_socket_t s); +/* Compare the two pollsets to notify the multi_socket API of changes + * in socket polling, e.g calling multi->socket_cb() with the changes if + * differences are seen. + */ +CURLMcode Curl_multi_pollset_ev(struct Curl_multi *multi, + struct Curl_easy *data, + struct easy_pollset *ps, + struct easy_pollset *last_ps); + /* * Add a handle and move it into PERFORM state at once. For pushed streams. */ @@ -103,4 +95,83 @@ CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, /* Return the value of the CURLMOPT_MAX_CONCURRENT_STREAMS option */ unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi); +/** + * Borrow the transfer buffer from the multi, suitable + * for the given transfer `data`. The buffer may only be used in one + * multi processing of the easy handle. It MUST be returned to the + * multi before it can be borrowed again. + * Pointers into the buffer remain only valid as long as it is borrowed. + * + * @param data the easy handle + * @param pbuf on return, the buffer to use or NULL on error + * @param pbuflen on return, the size of *pbuf or 0 on error + * @return CURLE_OK when buffer is available and is returned. + * CURLE_OUT_OF_MEMORy on failure to allocate the buffer, + * CURLE_FAILED_INIT if the easy handle is without multi. + * CURLE_AGAIN if the buffer is borrowed already. + */ +CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data, + char **pbuf, size_t *pbuflen); +/** + * Release the borrowed buffer. All references into the buffer become + * invalid after this. + * @param buf the buffer pointer borrowed for coding error checks. + */ +void Curl_multi_xfer_buf_release(struct Curl_easy *data, char *buf); + +/** + * Borrow the upload buffer from the multi, suitable + * for the given transfer `data`. The buffer may only be used in one + * multi processing of the easy handle. It MUST be returned to the + * multi before it can be borrowed again. + * Pointers into the buffer remain only valid as long as it is borrowed. + * + * @param data the easy handle + * @param pbuf on return, the buffer to use or NULL on error + * @param pbuflen on return, the size of *pbuf or 0 on error + * @return CURLE_OK when buffer is available and is returned. + * CURLE_OUT_OF_MEMORy on failure to allocate the buffer, + * CURLE_FAILED_INIT if the easy handle is without multi. + * CURLE_AGAIN if the buffer is borrowed already. + */ +CURLcode Curl_multi_xfer_ulbuf_borrow(struct Curl_easy *data, + char **pbuf, size_t *pbuflen); + +/** + * Release the borrowed upload buffer. All references into the buffer become + * invalid after this. + * @param buf the upload buffer pointer borrowed for coding error checks. + */ +void Curl_multi_xfer_ulbuf_release(struct Curl_easy *data, char *buf); + +/** + * Borrow the socket scratch buffer from the multi, suitable + * for the given transfer `data`. The buffer may only be used for + * direct socket I/O operation by one connection at a time and MUST be + * returned to the multi before the I/O call returns. + * Pointers into the buffer remain only valid as long as it is borrowed. + * + * @param data the easy handle + * @param blen requested length of the buffer + * @param pbuf on return, the buffer to use or NULL on error + * @return CURLE_OK when buffer is available and is returned. + * CURLE_OUT_OF_MEMORy on failure to allocate the buffer, + * CURLE_FAILED_INIT if the easy handle is without multi. + * CURLE_AGAIN if the buffer is borrowed already. + */ +CURLcode Curl_multi_xfer_sockbuf_borrow(struct Curl_easy *data, + size_t blen, char **pbuf); +/** + * Release the borrowed buffer. All references into the buffer become + * invalid after this. + * @param buf the buffer pointer borrowed for coding error checks. + */ +void Curl_multi_xfer_sockbuf_release(struct Curl_easy *data, char *buf); + +/** + * Get the transfer handle for the given id. Returns NULL if not found. + */ +struct Curl_easy *Curl_multi_get_handle(struct Curl_multi *multi, + curl_off_t id); + #endif /* HEADER_CURL_MULTIIF_H */ diff --git a/deps/curl/lib/netrc.c b/deps/curl/lib/netrc.c index e6a09b187a5..c23f927cef3 100644 --- a/deps/curl/lib/netrc.c +++ b/deps/curl/lib/netrc.c @@ -31,7 +31,6 @@ #include #include "netrc.h" -#include "strtok.h" #include "strcase.h" #include "curl_get_line.h" @@ -49,19 +48,56 @@ enum host_lookup_state { MACDEF }; +enum found_state { + NONE, + LOGIN, + PASSWORD +}; + #define NETRC_FILE_MISSING 1 #define NETRC_FAILED -1 #define NETRC_SUCCESS 0 +#define MAX_NETRC_LINE 4096 +#define MAX_NETRC_FILE (64*1024) +#define MAX_NETRC_TOKEN 128 + +static CURLcode file2memory(const char *filename, struct dynbuf *filebuf) +{ + CURLcode result = CURLE_OK; + FILE *file = fopen(filename, FOPEN_READTEXT); + struct dynbuf linebuf; + Curl_dyn_init(&linebuf, MAX_NETRC_LINE); + + if(file) { + while(Curl_get_line(&linebuf, file)) { + const char *line = Curl_dyn_ptr(&linebuf); + /* skip comments on load */ + while(ISBLANK(*line)) + line++; + if(*line == '#') + continue; + result = Curl_dyn_add(filebuf, line); + if(result) + goto done; + } + } +done: + Curl_dyn_free(&linebuf); + if(file) + fclose(file); + return result; +} + /* * Returns zero on success. */ -static int parsenetrc(const char *host, +static int parsenetrc(struct store_netrc *store, + const char *host, char **loginp, char **passwordp, - char *netrcfile) + const char *netrcfile) { - FILE *file; int retcode = NETRC_FILE_MISSING; char *login = *loginp; char *password = *passwordp; @@ -69,202 +105,212 @@ static int parsenetrc(const char *host, bool login_alloc = FALSE; bool password_alloc = FALSE; enum host_lookup_state state = NOTHING; + enum found_state found = NONE; + bool our_login = TRUE; /* With specific_login, found *our* login name (or + login-less line) */ + bool done = FALSE; + char *netrcbuffer; + struct dynbuf token; + struct dynbuf *filebuf = &store->filebuf; + Curl_dyn_init(&token, MAX_NETRC_TOKEN); - char state_login = 0; /* Found a login keyword */ - char state_password = 0; /* Found a password keyword */ - int state_our_login = TRUE; /* With specific_login, found *our* login - name (or login-less line) */ - - DEBUGASSERT(netrcfile); + if(!store->loaded) { + if(file2memory(netrcfile, filebuf)) + return NETRC_FAILED; + store->loaded = TRUE; + } - file = fopen(netrcfile, FOPEN_READTEXT); - if(file) { - bool done = FALSE; - char netrcbuffer[4096]; - int netrcbuffsize = (int)sizeof(netrcbuffer); + netrcbuffer = Curl_dyn_ptr(filebuf); - while(!done && Curl_get_line(netrcbuffer, netrcbuffsize, file)) { - char *tok; + while(!done) { + char *tok = netrcbuffer; + while(tok) { char *tok_end; bool quoted; + Curl_dyn_reset(&token); + while(ISBLANK(*tok)) + tok++; + /* tok is first non-space letter */ if(state == MACDEF) { - if((netrcbuffer[0] == '\n') || (netrcbuffer[0] == '\r')) - state = NOTHING; - else - continue; + if((*tok == '\n') || (*tok == '\r')) + state = NOTHING; /* end of macro definition */ } - tok = netrcbuffer; - while(tok) { - while(ISBLANK(*tok)) - tok++; - /* tok is first non-space letter */ - if(!*tok || (*tok == '#')) - /* end of line or the rest is a comment */ - break; - /* leading double-quote means quoted string */ - quoted = (*tok == '\"'); + if(!*tok || (*tok == '\n')) + /* end of line */ + break; - tok_end = tok; - if(!quoted) { - while(!ISSPACE(*tok_end)) - tok_end++; - *tok_end = 0; + /* leading double-quote means quoted string */ + quoted = (*tok == '\"'); + + tok_end = tok; + if(!quoted) { + size_t len = 0; + while(!ISSPACE(*tok_end)) { + tok_end++; + len++; } - else { - bool escape = FALSE; - bool endquote = FALSE; - char *store = tok; - tok_end++; /* pass the leading quote */ - while(*tok_end) { - char s = *tok_end; - if(escape) { - escape = FALSE; - switch(s) { - case 'n': - s = '\n'; - break; - case 'r': - s = '\r'; - break; - case 't': - s = '\t'; - break; - } - } - else if(s == '\\') { - escape = TRUE; - tok_end++; - continue; - } - else if(s == '\"') { - tok_end++; /* pass the ending quote */ - endquote = TRUE; + if(!len || Curl_dyn_addn(&token, tok, len)) { + retcode = NETRC_FAILED; + goto out; + } + } + else { + bool escape = FALSE; + bool endquote = FALSE; + tok_end++; /* pass the leading quote */ + while(*tok_end) { + char s = *tok_end; + if(escape) { + escape = FALSE; + switch(s) { + case 'n': + s = '\n'; + break; + case 'r': + s = '\r'; + break; + case 't': + s = '\t'; break; } - *store++ = s; + } + else if(s == '\\') { + escape = TRUE; tok_end++; + continue; + } + else if(s == '\"') { + tok_end++; /* pass the ending quote */ + endquote = TRUE; + break; } - *store = 0; - if(escape || !endquote) { - /* bad syntax, get out */ + if(Curl_dyn_addn(&token, &s, 1)) { retcode = NETRC_FAILED; goto out; } + tok_end++; } - - if((login && *login) && (password && *password)) { - done = TRUE; - break; + if(escape || !endquote) { + /* bad syntax, get out */ + retcode = NETRC_FAILED; + goto out; } + } - switch(state) { - case NOTHING: - if(strcasecompare("macdef", tok)) { - /* Define a macro. A macro is defined with the specified name; its - contents begin with the next .netrc line and continue until a - null line (consecutive new-line characters) is encountered. */ - state = MACDEF; - } - else if(strcasecompare("machine", tok)) { - /* the next tok is the machine name, this is in itself the - delimiter that starts the stuff entered for this machine, - after this we need to search for 'login' and - 'password'. */ - state = HOSTFOUND; - } - else if(strcasecompare("default", tok)) { - state = HOSTVALID; - retcode = NETRC_SUCCESS; /* we did find our host */ - } - break; - case MACDEF: - if(!strlen(tok)) { - state = NOTHING; - } - break; - case HOSTFOUND: - if(strcasecompare(host, tok)) { - /* and yes, this is our host! */ - state = HOSTVALID; - retcode = NETRC_SUCCESS; /* we did find our host */ + if((login && *login) && (password && *password)) { + done = TRUE; + break; + } + + tok = Curl_dyn_ptr(&token); + + switch(state) { + case NOTHING: + if(strcasecompare("macdef", tok)) + /* Define a macro. A macro is defined with the specified name; its + contents begin with the next .netrc line and continue until a + null line (consecutive new-line characters) is encountered. */ + state = MACDEF; + else if(strcasecompare("machine", tok)) + /* the next tok is the machine name, this is in itself the delimiter + that starts the stuff entered for this machine, after this we + need to search for 'login' and 'password'. */ + state = HOSTFOUND; + else if(strcasecompare("default", tok)) { + state = HOSTVALID; + retcode = NETRC_SUCCESS; /* we did find our host */ + } + break; + case MACDEF: + if(!*tok) + state = NOTHING; + break; + case HOSTFOUND: + if(strcasecompare(host, tok)) { + /* and yes, this is our host! */ + state = HOSTVALID; + retcode = NETRC_SUCCESS; /* we did find our host */ + } + else + /* not our host */ + state = NOTHING; + break; + case HOSTVALID: + /* we are now parsing sub-keywords concerning "our" host */ + if(found == LOGIN) { + if(specific_login) { + our_login = !Curl_timestrcmp(login, tok); } - else - /* not our host */ - state = NOTHING; - break; - case HOSTVALID: - /* we are now parsing sub-keywords concerning "our" host */ - if(state_login) { - if(specific_login) { - state_our_login = !Curl_timestrcmp(login, tok); + else if(!login || Curl_timestrcmp(login, tok)) { + if(login_alloc) + free(login); + login = strdup(tok); + if(!login) { + retcode = NETRC_FAILED; /* allocation failed */ + goto out; } - else if(!login || Curl_timestrcmp(login, tok)) { - if(login_alloc) { - free(login); - login_alloc = FALSE; - } - login = strdup(tok); - if(!login) { - retcode = NETRC_FAILED; /* allocation failed */ - goto out; - } - login_alloc = TRUE; - } - state_login = 0; + login_alloc = TRUE; } - else if(state_password) { - if((state_our_login || !specific_login) - && (!password || Curl_timestrcmp(password, tok))) { - if(password_alloc) { - free(password); - password_alloc = FALSE; - } - password = strdup(tok); - if(!password) { - retcode = NETRC_FAILED; /* allocation failed */ - goto out; - } - password_alloc = TRUE; + found = NONE; + } + else if(found == PASSWORD) { + if((our_login || !specific_login) && + (!password || Curl_timestrcmp(password, tok))) { + if(password_alloc) + free(password); + password = strdup(tok); + if(!password) { + retcode = NETRC_FAILED; /* allocation failed */ + goto out; } - state_password = 0; - } - else if(strcasecompare("login", tok)) - state_login = 1; - else if(strcasecompare("password", tok)) - state_password = 1; - else if(strcasecompare("machine", tok)) { - /* ok, there's machine here go => */ - state = HOSTFOUND; - state_our_login = FALSE; + password_alloc = TRUE; } - break; - } /* switch (state) */ - tok = ++tok_end; - } - } /* while Curl_get_line() */ + found = NONE; + } + else if(strcasecompare("login", tok)) + found = LOGIN; + else if(strcasecompare("password", tok)) + found = PASSWORD; + else if(strcasecompare("machine", tok)) { + /* ok, there is machine here go => */ + state = HOSTFOUND; + found = NONE; + } + break; + } /* switch (state) */ + tok = ++tok_end; + } + if(!done) { + char *nl = NULL; + if(tok) + nl = strchr(tok, '\n'); + if(!nl) + break; + /* point to next line */ + netrcbuffer = &nl[1]; + } + } /* while !done */ out: - if(!retcode) { - /* success */ - if(login_alloc) { - if(*loginp) - free(*loginp); - *loginp = login; - } - if(password_alloc) { - if(*passwordp) - free(*passwordp); - *passwordp = password; - } + Curl_dyn_free(&token); + if(!retcode) { + /* success */ + if(login_alloc) { + free(*loginp); + *loginp = login; } - else { - if(login_alloc) - free(login); - if(password_alloc) - free(password); + if(password_alloc) { + free(*passwordp); + *passwordp = password; } - fclose(file); + } + else { + Curl_dyn_free(filebuf); + if(login_alloc) + free(login); + if(password_alloc) + free(password); } return retcode; @@ -273,10 +319,11 @@ static int parsenetrc(const char *host, /* * @unittest: 1304 * - * *loginp and *passwordp MUST be allocated if they aren't NULL when passed + * *loginp and *passwordp MUST be allocated if they are not NULL when passed * in. */ -int Curl_parsenetrc(const char *host, char **loginp, char **passwordp, +int Curl_parsenetrc(struct store_netrc *store, const char *host, + char **loginp, char **passwordp, char *netrcfile) { int retcode = 1; @@ -320,30 +367,40 @@ int Curl_parsenetrc(const char *host, char **loginp, char **passwordp, return retcode; /* no home directory found (or possibly out of memory) */ - filealloc = curl_maprintf("%s%s.netrc", home, DIR_CHAR); + filealloc = aprintf("%s%s.netrc", home, DIR_CHAR); if(!filealloc) { free(homea); return -1; } - retcode = parsenetrc(host, loginp, passwordp, filealloc); + retcode = parsenetrc(store, host, loginp, passwordp, filealloc); free(filealloc); -#ifdef WIN32 +#ifdef _WIN32 if(retcode == NETRC_FILE_MISSING) { /* fallback to the old-style "_netrc" file */ - filealloc = curl_maprintf("%s%s_netrc", home, DIR_CHAR); + filealloc = aprintf("%s%s_netrc", home, DIR_CHAR); if(!filealloc) { free(homea); return -1; } - retcode = parsenetrc(host, loginp, passwordp, filealloc); + retcode = parsenetrc(store, host, loginp, passwordp, filealloc); free(filealloc); } #endif free(homea); } else - retcode = parsenetrc(host, loginp, passwordp, netrcfile); + retcode = parsenetrc(store, host, loginp, passwordp, netrcfile); return retcode; } +void Curl_netrc_init(struct store_netrc *s) +{ + Curl_dyn_init(&s->filebuf, MAX_NETRC_FILE); + s->loaded = FALSE; +} +void Curl_netrc_cleanup(struct store_netrc *s) +{ + Curl_dyn_free(&s->filebuf); + s->loaded = FALSE; +} #endif diff --git a/deps/curl/lib/netrc.h b/deps/curl/lib/netrc.h index 9f2815f3bb9..0ef9ff78ea2 100644 --- a/deps/curl/lib/netrc.h +++ b/deps/curl/lib/netrc.h @@ -26,9 +26,19 @@ #include "curl_setup.h" #ifndef CURL_DISABLE_NETRC +#include "dynbuf.h" -/* returns -1 on failure, 0 if the host is found, 1 is the host isn't found */ -int Curl_parsenetrc(const char *host, char **loginp, +struct store_netrc { + struct dynbuf filebuf; + char *filename; + BIT(loaded); +}; + +void Curl_netrc_init(struct store_netrc *s); +void Curl_netrc_cleanup(struct store_netrc *s); + +/* returns -1 on failure, 0 if the host is found, 1 is the host is not found */ +int Curl_parsenetrc(struct store_netrc *s, const char *host, char **loginp, char **passwordp, char *filename); /* Assume: (*passwordp)[0]=0, host[0] != 0. * If (*loginp)[0] = 0, search for login and password within a machine @@ -38,6 +48,8 @@ int Curl_parsenetrc(const char *host, char **loginp, #else /* disabled */ #define Curl_parsenetrc(a,b,c,d,e,f) 1 +#define Curl_netrc_init(x) +#define Curl_netrc_cleanup(x) #endif #endif /* HEADER_CURL_NETRC_H */ diff --git a/deps/curl/lib/nonblock.c b/deps/curl/lib/nonblock.c index f4eb6561280..6dcf42a7eab 100644 --- a/deps/curl/lib/nonblock.c +++ b/deps/curl/lib/nonblock.c @@ -47,16 +47,25 @@ int curlx_nonblock(curl_socket_t sockfd, /* operate on this */ int nonblock /* TRUE or FALSE */) { #if defined(HAVE_FCNTL_O_NONBLOCK) - /* most recent unix versions */ + /* most recent Unix versions */ int flags; flags = sfcntl(sockfd, F_GETFL, 0); + if(flags < 0) + return -1; + /* Check if the current file status flags have already satisfied + * the request, if so, it is no need to call fcntl() to replicate it. + */ + if(!!(flags & O_NONBLOCK) == !!nonblock) + return 0; if(nonblock) - return sfcntl(sockfd, F_SETFL, flags | O_NONBLOCK); - return sfcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK)); + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + return sfcntl(sockfd, F_SETFL, flags); #elif defined(HAVE_IOCTL_FIONBIO) - /* older unix versions */ + /* older Unix versions */ int flags = nonblock ? 1 : 0; return ioctl(sockfd, FIONBIO, &flags); @@ -64,7 +73,7 @@ int curlx_nonblock(curl_socket_t sockfd, /* operate on this */ /* Windows */ unsigned long flags = nonblock ? 1UL : 0UL; - return ioctlsocket(sockfd, FIONBIO, &flags); + return ioctlsocket(sockfd, (long)FIONBIO, &flags); #elif defined(HAVE_IOCTLSOCKET_CAMEL_FIONBIO) diff --git a/deps/curl/lib/noproxy.c b/deps/curl/lib/noproxy.c index 2b9908d8941..dbfafc93eb8 100644 --- a/deps/curl/lib/noproxy.c +++ b/deps/curl/lib/noproxy.c @@ -78,23 +78,23 @@ UNITTEST bool Curl_cidr6_match(const char *ipv6, const char *network, unsigned int bits) { -#ifdef ENABLE_IPV6 - int bytes; - int rest; +#ifdef USE_IPV6 + unsigned int bytes; + unsigned int rest; unsigned char address[16]; unsigned char check[16]; if(!bits) bits = 128; - bytes = bits/8; + bytes = bits / 8; rest = bits & 0x07; + if((bytes > 16) || ((bytes == 16) && rest)) + return FALSE; if(1 != Curl_inet_pton(AF_INET6, ipv6, address)) return FALSE; if(1 != Curl_inet_pton(AF_INET6, network, check)) return FALSE; - if((bytes > 16) || ((bytes == 16) && rest)) - return FALSE; if(bytes && memcmp(address, check, bytes)) return FALSE; if(rest && !((address[bytes] ^ check[bytes]) & (0xff << (8 - rest)))) @@ -119,13 +119,12 @@ enum nametype { * Checks if the host is in the noproxy list. returns TRUE if it matches and * therefore the proxy should NOT be used. ****************************************************************/ -bool Curl_check_noproxy(const char *name, const char *no_proxy, - bool *spacesep) +bool Curl_check_noproxy(const char *name, const char *no_proxy) { char hostip[128]; - *spacesep = FALSE; + /* - * If we don't have a hostname at all, like for example with a FILE + * If we do not have a hostname at all, like for example with a FILE * transfer, we have nothing to interrogate the noproxy list with. */ if(!name || name[0] == '\0') @@ -143,7 +142,7 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy, if(!strcmp("*", no_proxy)) return TRUE; - /* NO_PROXY was specified and it wasn't just an asterisk */ + /* NO_PROXY was specified and it was not just an asterisk */ if(name[0] == '[') { char *endptr; @@ -166,7 +165,7 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy, if(1 == Curl_inet_pton(AF_INET, name, &address)) type = TYPE_IPV4; else { - /* ignore trailing dots in the host name */ + /* ignore trailing dots in the hostname */ if(name[namelen - 1] == '.') namelen--; } @@ -216,7 +215,6 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy, /* case C passes through, not a match */ break; case TYPE_IPV4: - /* FALLTHROUGH */ case TYPE_IPV6: { const char *check = token; char *slash; @@ -233,7 +231,9 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy, slash = strchr(check, '/'); /* if the slash is part of this token, use it */ if(slash) { - bits = atoi(slash + 1); + /* if the bits variable gets a crazy value here, that is fine as + the value will then be rejected in the cidr function */ + bits = (unsigned int)atoi(slash + 1); *slash = 0; /* null terminate there */ } if(type == TYPE_IPV6) @@ -249,16 +249,14 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy, /* pass blanks after pattern */ while(ISBLANK(*p)) p++; - /* if not a comma! */ - if(*p && (*p != ',')) { - *spacesep = TRUE; - continue; - } + /* if not a comma, this ends the loop */ + if(*p != ',') + break; /* pass any number of commas */ while(*p == ',') p++; } /* while(*p) */ - } /* NO_PROXY was specified and it wasn't just an asterisk */ + } /* NO_PROXY was specified and it was not just an asterisk */ return FALSE; } diff --git a/deps/curl/lib/noproxy.h b/deps/curl/lib/noproxy.h index a3a6807722d..71ae7eaafa0 100644 --- a/deps/curl/lib/noproxy.h +++ b/deps/curl/lib/noproxy.h @@ -27,7 +27,7 @@ #ifndef CURL_DISABLE_PROXY -#ifdef DEBUGBUILD +#ifdef UNITTESTS UNITTEST bool Curl_cidr4_match(const char *ipv4, /* 1.2.3.4 address */ const char *network, /* 1.2.3.4 address */ @@ -37,9 +37,7 @@ UNITTEST bool Curl_cidr6_match(const char *ipv6, unsigned int bits); #endif -bool Curl_check_noproxy(const char *name, const char *no_proxy, - bool *spacesep); - +bool Curl_check_noproxy(const char *name, const char *no_proxy); #endif #endif /* HEADER_CURL_NOPROXY_H */ diff --git a/deps/curl/lib/openldap.c b/deps/curl/lib/openldap.c index 41fecf9143c..8c4af22befd 100644 --- a/deps/curl/lib/openldap.c +++ b/deps/curl/lib/openldap.c @@ -117,7 +117,7 @@ static Curl_recv oldap_recv; */ const struct Curl_handler Curl_handler_ldap = { - "LDAP", /* scheme */ + "ldap", /* scheme */ oldap_setup_connection, /* setup_connection */ oldap_do, /* do_it */ oldap_done, /* done */ @@ -130,7 +130,8 @@ const struct Curl_handler Curl_handler_ldap = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ oldap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAP, /* defport */ @@ -145,7 +146,7 @@ const struct Curl_handler Curl_handler_ldap = { */ const struct Curl_handler Curl_handler_ldaps = { - "LDAPS", /* scheme */ + "ldaps", /* scheme */ oldap_setup_connection, /* setup_connection */ oldap_do, /* do_it */ oldap_done, /* done */ @@ -158,7 +159,8 @@ const struct Curl_handler Curl_handler_ldaps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ oldap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAPS, /* defport */ @@ -199,11 +201,11 @@ struct ldapreqinfo { }; /* - * state() + * oldap_state() * * This is the ONLY way to change LDAP state! */ -static void state(struct Curl_easy *data, ldapstate newstate) +static void oldap_state(struct Curl_easy *data, ldapstate newstate) { struct ldapconninfo *ldapc = data->conn->proto.ldapc; @@ -234,17 +236,13 @@ static CURLcode oldap_map_error(int rc, CURLcode result) { switch(rc) { case LDAP_NO_MEMORY: - result = CURLE_OUT_OF_MEMORY; - break; + return CURLE_OUT_OF_MEMORY; case LDAP_INVALID_CREDENTIALS: - result = CURLE_LOGIN_DENIED; - break; + return CURLE_LOGIN_DENIED; case LDAP_PROTOCOL_ERROR: - result = CURLE_UNSUPPORTED_PROTOCOL; - break; + return CURLE_UNSUPPORTED_PROTOCOL; case LDAP_INSUFFICIENT_ACCESS: - result = CURLE_REMOTE_ACCESS_DENIED; - break; + return CURLE_REMOTE_ACCESS_DENIED; } return result; } @@ -274,7 +272,8 @@ static CURLcode oldap_url_parse(struct Curl_easy *data, LDAPURLDesc **ludp) if(rc != LDAP_URL_SUCCESS) { const char *msg = "url parsing problem"; - result = rc == LDAP_URL_ERR_MEM? CURLE_OUT_OF_MEMORY: CURLE_URL_MALFORMAT; + result = rc == LDAP_URL_ERR_MEM ? CURLE_OUT_OF_MEMORY : + CURLE_URL_MALFORMAT; rc -= LDAP_URL_SUCCESS; if((size_t) rc < sizeof(url_errs) / sizeof(url_errs[0])) msg = url_errs[rc]; @@ -311,7 +310,7 @@ static CURLcode oldap_parse_login_options(struct connectdata *conn) ptr++; } - return result == CURLE_URL_MALFORMAT? CURLE_SETOPT_OPTION_SYNTAX: result; + return result == CURLE_URL_MALFORMAT ? CURLE_SETOPT_OPTION_SYNTAX : result; } static CURLcode oldap_setup_connection(struct Curl_easy *data, @@ -319,31 +318,12 @@ static CURLcode oldap_setup_connection(struct Curl_easy *data, { CURLcode result; LDAPURLDesc *lud; - struct ldapconninfo *li; + (void)conn; /* Early URL syntax check. */ result = oldap_url_parse(data, &lud); ldap_free_urldesc(lud); - if(!result) { - li = calloc(1, sizeof(struct ldapconninfo)); - if(!li) - result = CURLE_OUT_OF_MEMORY; - else { - li->proto = ldap_pvt_url_scheme2proto(data->state.up.scheme); - conn->proto.ldapc = li; - connkeep(conn, "OpenLDAP default"); - - /* Initialize the SASL storage */ - Curl_sasl_init(&li->sasl, data, &saslldap); - - /* Clear the TLS upgraded flag */ - conn->bits.tls_upgraded = FALSE; - - result = oldap_parse_login_options(conn); - } - } - return result; } @@ -368,7 +348,6 @@ static CURLcode oldap_perform_auth(struct Curl_easy *data, const char *mech, { struct connectdata *conn = data->conn; struct ldapconninfo *li = conn->proto.ldapc; - CURLcode result = CURLE_OK; struct berval cred; struct berval *pcred = &cred; int rc; @@ -379,8 +358,8 @@ static CURLcode oldap_perform_auth(struct Curl_easy *data, const char *mech, pcred = NULL; rc = ldap_sasl_bind(li->ld, NULL, mech, pcred, NULL, NULL, &li->msgid); if(rc != LDAP_SUCCESS) - result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); - return result; + return oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); + return CURLE_OK; } /* @@ -391,7 +370,6 @@ static CURLcode oldap_continue_auth(struct Curl_easy *data, const char *mech, { struct connectdata *conn = data->conn; struct ldapconninfo *li = conn->proto.ldapc; - CURLcode result = CURLE_OK; struct berval cred; struct berval *pcred = &cred; int rc; @@ -402,8 +380,8 @@ static CURLcode oldap_continue_auth(struct Curl_easy *data, const char *mech, pcred = NULL; rc = ldap_sasl_bind(li->ld, NULL, mech, pcred, NULL, NULL, &li->msgid); if(rc != LDAP_SUCCESS) - result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); - return result; + return oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); + return CURLE_OK; } /* @@ -412,20 +390,18 @@ static CURLcode oldap_continue_auth(struct Curl_easy *data, const char *mech, static CURLcode oldap_cancel_auth(struct Curl_easy *data, const char *mech) { struct ldapconninfo *li = data->conn->proto.ldapc; - CURLcode result = CURLE_OK; int rc = ldap_sasl_bind(li->ld, NULL, LDAP_SASL_NULL, NULL, NULL, NULL, &li->msgid); (void)mech; if(rc != LDAP_SUCCESS) - result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); - return result; + return oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); + return CURLE_OK; } /* Starts LDAP simple bind. */ static CURLcode oldap_perform_bind(struct Curl_easy *data, ldapstate newstate) { - CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; struct ldapconninfo *li = conn->proto.ldapc; char *binddn = NULL; @@ -443,19 +419,17 @@ static CURLcode oldap_perform_bind(struct Curl_easy *data, ldapstate newstate) rc = ldap_sasl_bind(li->ld, binddn, LDAP_SASL_SIMPLE, &passwd, NULL, NULL, &li->msgid); - if(rc == LDAP_SUCCESS) - state(data, newstate); - else - result = oldap_map_error(rc, - data->state.aptr.user? - CURLE_LOGIN_DENIED: CURLE_LDAP_CANNOT_BIND); - return result; + if(rc != LDAP_SUCCESS) + return oldap_map_error(rc, + data->state.aptr.user ? + CURLE_LOGIN_DENIED : CURLE_LDAP_CANNOT_BIND); + oldap_state(data, newstate); + return CURLE_OK; } /* Query the supported SASL authentication mechanisms. */ static CURLcode oldap_perform_mechs(struct Curl_easy *data) { - CURLcode result = CURLE_OK; struct ldapconninfo *li = data->conn->proto.ldapc; int rc; static const char * const supportedSASLMechanisms[] = { @@ -466,11 +440,10 @@ static CURLcode oldap_perform_mechs(struct Curl_easy *data) rc = ldap_search_ext(li->ld, "", LDAP_SCOPE_BASE, "(objectclass=*)", (char **) supportedSASLMechanisms, 0, NULL, NULL, NULL, 0, &li->msgid); - if(rc == LDAP_SUCCESS) - state(data, OLDAP_MECHS); - else - result = oldap_map_error(rc, CURLE_LOGIN_DENIED); - return result; + if(rc != LDAP_SUCCESS) + return oldap_map_error(rc, CURLE_LOGIN_DENIED); + oldap_state(data, OLDAP_MECHS); + return CURLE_OK; } /* Starts SASL bind. */ @@ -480,7 +453,7 @@ static CURLcode oldap_perform_sasl(struct Curl_easy *data) struct ldapconninfo *li = data->conn->proto.ldapc; CURLcode result = Curl_sasl_start(&li->sasl, data, TRUE, &progress); - state(data, OLDAP_SASL); + oldap_state(data, OLDAP_SASL); if(!result && progress != SASL_INPROGRESS) result = CURLE_LOGIN_DENIED; return result; @@ -496,14 +469,12 @@ static bool ssl_installed(struct connectdata *conn) static CURLcode oldap_ssl_connect(struct Curl_easy *data, ldapstate newstate) { - CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; struct ldapconninfo *li = conn->proto.ldapc; - bool ssldone = 0; - - result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); + bool ssldone = FALSE; + CURLcode result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); if(!result) { - state(data, newstate); + oldap_state(data, newstate); if(ssldone) { Sockbuf *sb; @@ -522,22 +493,20 @@ static CURLcode oldap_ssl_connect(struct Curl_easy *data, ldapstate newstate) /* Send the STARTTLS request */ static CURLcode oldap_perform_starttls(struct Curl_easy *data) { - CURLcode result = CURLE_OK; struct ldapconninfo *li = data->conn->proto.ldapc; int rc = ldap_start_tls(li->ld, NULL, NULL, &li->msgid); - if(rc == LDAP_SUCCESS) - state(data, OLDAP_STARTTLS); - else - result = oldap_map_error(rc, CURLE_USE_SSL_FAILED); - return result; + if(rc != LDAP_SUCCESS) + return oldap_map_error(rc, CURLE_USE_SSL_FAILED); + oldap_state(data, OLDAP_STARTTLS); + return CURLE_OK; } #endif static CURLcode oldap_connect(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li; static const int version = LDAP_VERSION3; int rc; char *hosturl; @@ -547,9 +516,32 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done) (void)done; - hosturl = aprintf("ldap%s://%s:%d", - conn->handler->flags & PROTOPT_SSL? "s": "", - conn->host.name, conn->remote_port); + DEBUGASSERT(!conn->proto.ldapc); + li = calloc(1, sizeof(struct ldapconninfo)); + if(!li) + return CURLE_OUT_OF_MEMORY; + else { + CURLcode result; + li->proto = ldap_pvt_url_scheme2proto(data->state.up.scheme); + conn->proto.ldapc = li; + + /* Initialize the SASL storage */ + Curl_sasl_init(&li->sasl, data, &saslldap); + + /* Clear the TLS upgraded flag */ + conn->bits.tls_upgraded = FALSE; + + result = oldap_parse_login_options(conn); + if(result) + return result; + } + + hosturl = aprintf("%s://%s%s%s:%d", + conn->handler->scheme, + conn->bits.ipv6_ip ? "[" : "", + conn->host.name, + conn->bits.ipv6_ip ? "]" : "", + conn->remote_port); if(!hosturl) return CURLE_OUT_OF_MEMORY; @@ -644,7 +636,7 @@ static CURLcode oldap_state_mechs_resp(struct Curl_easy *data, switch(code) { case LDAP_SIZELIMIT_EXCEEDED: infof(data, "Too many authentication mechanisms\n"); - /* FALLTHROUGH */ + FALLTHROUGH(); case LDAP_SUCCESS: case LDAP_NO_RESULTS_RETURNED: if(Curl_sasl_can_authenticate(&li->sasl, data)) @@ -682,7 +674,7 @@ static CURLcode oldap_state_sasl_resp(struct Curl_easy *data, else { result = Curl_sasl_continue(&li->sasl, data, code, &progress); if(!result && progress != SASL_INPROGRESS) - state(data, OLDAP_STOP); + oldap_state(data, OLDAP_STOP); } if(li->servercred) @@ -710,7 +702,7 @@ static CURLcode oldap_state_bind_resp(struct Curl_easy *data, LDAPMessage *msg, result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); } else - state(data, OLDAP_STOP); + oldap_state(data, OLDAP_STOP); if(bv) ber_bvfree(bv); @@ -792,10 +784,13 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done) result = oldap_perform_bind(data, OLDAP_BIND); break; } - /* FALLTHROUGH */ + result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + if(result) + break; + FALLTHROUGH(); case OLDAP_TLS: result = oldap_ssl_connect(data, OLDAP_TLS); - if(result && data->set.use_ssl != CURLUSESSL_TRY) + if(result) result = oldap_map_error(code, CURLE_USE_SSL_FAILED); else if(ssl_installed(conn)) { conn->bits.tls_upgraded = TRUE; @@ -804,7 +799,8 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done) else if(data->state.aptr.user) result = oldap_perform_bind(data, OLDAP_BIND); else { - state(data, OLDAP_STOP); /* Version 3 supported: no bind required */ + /* Version 3 supported: no bind required */ + oldap_state(data, OLDAP_STOP); result = CURLE_OK; } } @@ -885,6 +881,15 @@ static CURLcode oldap_do(struct Curl_easy *data, bool *done) result = oldap_url_parse(data, &lud); if(!result) { +#ifdef USE_SSL + if(ssl_installed(conn)) { + Sockbuf *sb; + /* re-install the libcurl SSL handlers into the sockbuf. */ + ldap_get_option(li->ld, LDAP_OPT_SOCKBUF, &sb); + ber_sockbuf_add_io(sb, &ldapsb_tls, LBER_SBIOD_LEVEL_TRANSPORT, data); + } +#endif + rc = ldap_search_ext(li->ld, lud->lud_dn, lud->lud_scope, lud->lud_filter, lud->lud_attrs, 0, NULL, NULL, NULL, 0, &msgid); @@ -902,7 +907,7 @@ static CURLcode oldap_do(struct Curl_easy *data, bool *done) else { lr->msgid = msgid; data->req.p.ldap = lr; - Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); *done = TRUE; } } @@ -946,18 +951,12 @@ static CURLcode client_write(struct Curl_easy *data, if(!len && plen && prefix[plen - 1] == ' ') plen--; result = Curl_client_write(data, CLIENTWRITE_BODY, (char *) prefix, plen); - if(!result) - data->req.bytecount += plen; } if(!result && value) { result = Curl_client_write(data, CLIENTWRITE_BODY, (char *) value, len); - if(!result) - data->req.bytecount += len; } if(!result && suffix) { result = Curl_client_write(data, CLIENTWRITE_BODY, (char *) suffix, slen); - if(!result) - data->req.bytecount += slen; } return result; } @@ -973,7 +972,7 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, BerElement *ber = NULL; struct timeval tv = {0, 0}; struct berval bv, *bvals; - int binary = 0; + bool binary = FALSE; CURLcode result = CURLE_AGAIN; int code; char *info = NULL; @@ -1013,7 +1012,7 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, switch(code) { case LDAP_SIZELIMIT_EXCEEDED: infof(data, "There are more than %d entries", lr->nument); - /* FALLTHROUGH */ + FALLTHROUGH(); case LDAP_SUCCESS: data->req.size = data->req.bytecount; break; @@ -1056,10 +1055,10 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, } binary = bv.bv_len > 7 && - !strncmp(bv.bv_val + bv.bv_len - 7, ";binary", 7); + !strncmp(bv.bv_val + bv.bv_len - 7, ";binary", 7); for(i = 0; bvals[i].bv_val != NULL; i++) { - int binval = 0; + bool binval = FALSE; result = client_write(data, STRCONST("\t"), bv.bv_val, bv.bv_len, STRCONST(":")); @@ -1070,13 +1069,13 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, /* check for leading or trailing whitespace */ if(ISBLANK(bvals[i].bv_val[0]) || ISBLANK(bvals[i].bv_val[bvals[i].bv_len - 1])) - binval = 1; + binval = TRUE; else { /* check for unprintable characters */ unsigned int j; for(j = 0; j < bvals[i].bv_len; j++) if(!ISPRINT(bvals[i].bv_val[j])) { - binval = 1; + binval = TRUE; break; } } @@ -1121,7 +1120,7 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, ldap_msgfree(msg); *err = result; - return result? -1: 0; + return result ? -1 : 0; } #ifdef USE_SSL @@ -1139,7 +1138,7 @@ ldapsb_tls_remove(Sockbuf_IO_Desc *sbiod) return 0; } -/* We don't need to do anything because libcurl does it already */ +/* We do not need to do anything because libcurl does it already */ static int ldapsb_tls_close(Sockbuf_IO_Desc *sbiod) { @@ -1188,7 +1187,7 @@ ldapsb_tls_write(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) if(conn) { struct ldapconninfo *li = conn->proto.ldapc; CURLcode err = CURLE_SEND_ERROR; - ret = (li->send)(data, FIRSTSOCKET, buf, len, &err); + ret = (li->send)(data, FIRSTSOCKET, buf, len, FALSE, &err); if(ret < 0 && err == CURLE_AGAIN) { SET_SOCKERRNO(EWOULDBLOCK); } diff --git a/deps/curl/lib/optiontable.pl b/deps/curl/lib/optiontable.pl new file mode 100755 index 00000000000..2727784e7bd --- /dev/null +++ b/deps/curl/lib/optiontable.pl @@ -0,0 +1,152 @@ +#!/usr/bin/env perl + +print <, 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.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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* This source code is generated by optiontable.pl - DO NOT EDIT BY HAND */ + +#include "curl_setup.h" +#include "easyoptions.h" + +/* all easy setopt options listed in alphabetical order */ +struct curl_easyoption Curl_easyopts[] = { +HEAD + ; + +my $lastnum=0; + +sub add { + my($opt, $type, $num)=@_; + my $name; + # remove all spaces from the type + $type =~ s/ //g; + my $ext = $type; + + if($opt =~ /OBSOLETE/) { + # skip obsolete options + next; + } + + if($opt =~ /^CURLOPT_(.*)/) { + $name=$1; + } + $ext =~ s/CURLOPTTYPE_//; + $ext =~ s/CBPOINT/CBPTR/; + $ext =~ s/POINT\z//; + $type = "CURLOT_$ext"; + + $opt{$name} = $opt; + $type{$name} = $type; + push @names, $name; + if($num < $lastnum) { + print STDERR "ERROR: $opt has bad number: $num < $lastnum\n"; + exit 2; + } + else { + $lastnum = $num; + } +} + + +my $fl; +while() { + my $l = $_; + if($fl) { + # continued deprecation + if($l =~ /(.*)\),/) { + $fl .= $1; + + # the end + my @p=split(/, */, $fl); + add($p[0], $p[1], $p[2]); + undef $fl; + } + else { + # another line to append + chomp $l; + $fl .= $l; + } + } + + if(/^ *CURLOPTDEPRECATED\((.*)/) { + $fl = $1; + chomp $fl; + } + + if(/^ *CURLOPT\(([^,]*), ([^,]*), (\d+)\)/) { + my($opt, $type, $num)=($1,$2,$3); + add($opt, $type, $num); + } + + # alias for an older option + # old = new + if(/^#define (CURLOPT_[^ ]*) *(CURLOPT_\S*)/) { + my ($o, $n)=($1, $2); + # skip obsolete ones + if(($n !~ /OBSOLETE/) && ($o !~ /OBSOLETE/)) { + $o =~ s/^CURLOPT_//; + $n =~ s/^CURLOPT_//; + $alias{$o} = $n; + push @names, $o, + } + } +} + + +for my $name (sort @names) { + my $oname = $name; + my $a = $alias{$name}; + my $flag = "0"; + if($a) { + $name = $alias{$name}; + $flag = "CURLOT_FLAG_ALIAS"; + } + $o = sprintf(" {\"%s\", %s, %s, %s},\n", + $oname, $opt{$name}, $type{$name}, $flag); + if(length($o) < 80) { + print $o; + } + else { + printf(" {\"%s\", %s,\n %s, %s},\n", + $oname, $opt{$name}, $type{$name}, $flag); + } +} + +print < 4) /* longer than any valid timezone */ return -1; - for(i = 0; i< sizeof(tz)/sizeof(tz[0]); i++) { + for(i = 0; i < sizeof(tz)/sizeof(tz[0]); i++) { size_t ilen = strlen(what->name); if((ilen == len) && strncasecompare(check, what->name, len)) @@ -265,7 +265,7 @@ static int checktz(const char *check, size_t len) static void skip(const char **date) { - /* skip everything that aren't letters or digits */ + /* skip everything that are not letters or digits */ while(**date && !ISALNUM(**date)) (*date)++; } @@ -277,7 +277,7 @@ enum assume { }; /* - * time2epoch: time stamp to seconds since epoch in GMT time zone. Similar to + * time2epoch: time stamp to seconds since epoch in GMT time zone. Similar to * mktime but for GMT only. */ static time_t time2epoch(int sec, int min, int hour, @@ -441,11 +441,11 @@ static int parsedate(const char *date, time_t *output) if((tzoff == -1) && ((end - date) == 4) && (val <= 1400) && - (indate< date) && + (indate < date) && ((date[-1] == '+' || date[-1] == '-'))) { /* four digits and a value less than or equal to 1400 (to take into account all sorts of funny time zone diffs) and it is preceded - with a plus or minus. This is a time zone indication. 1400 is + with a plus or minus. This is a time zone indication. 1400 is picked since +1300 is frequently used and +1400 is mentioned as an edge number in the document "ISO C 200X Proposal: Timezone Functions" at http://david.tribble.com/text/c0xtimezone.html If @@ -456,7 +456,7 @@ static int parsedate(const char *date, time_t *output) /* the + and - prefix indicates the local time compared to GMT, this we need their reversed math to get what we want */ - tzoff = date[-1]=='+'?-tzoff:tzoff; + tzoff = date[-1]=='+' ? -tzoff : tzoff; } if(((end - date) == 8) && @@ -471,7 +471,7 @@ static int parsedate(const char *date, time_t *output) } if(!found && (dignext == DATE_MDAY) && (mdaynum == -1)) { - if((val > 0) && (val<32)) { + if((val > 0) && (val < 32)) { mdaynum = val; found = TRUE; } @@ -521,13 +521,13 @@ static int parsedate(const char *date, time_t *output) #if (SIZEOF_TIME_T < 5) #ifdef HAVE_TIME_T_UNSIGNED - /* an unsigned 32 bit time_t can only hold dates to 2106 */ + /* an unsigned 32-bit time_t can only hold dates to 2106 */ if(yearnum > 2105) { *output = TIME_T_MAX; return PARSEDATE_LATER; } #else - /* a signed 32 bit time_t can only hold dates to the beginning of 2038 */ + /* a signed 32-bit time_t can only hold dates to the beginning of 2038 */ if(yearnum > 2037) { *output = TIME_T_MAX; return PARSEDATE_LATER; @@ -549,7 +549,7 @@ static int parsedate(const char *date, time_t *output) return PARSEDATE_FAIL; /* clearly an illegal date */ /* time2epoch() returns a time_t. time_t is often 32 bits, sometimes even on - architectures that feature 64 bit 'long' but ultimately time_t is the + architectures that feature a 64 bits 'long' but ultimately time_t is the correct data type to use. */ t = time2epoch(secnum, minnum, hournum, mdaynum, monnum, yearnum); diff --git a/deps/curl/lib/pingpong.c b/deps/curl/lib/pingpong.c index 136985fc920..bd0d3e3a2a4 100644 --- a/deps/curl/lib/pingpong.c +++ b/deps/curl/lib/pingpong.c @@ -36,6 +36,7 @@ #include "pingpong.h" #include "multiif.h" #include "vtls/vtls.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -51,8 +52,8 @@ timediff_t Curl_pp_state_timeout(struct Curl_easy *data, { struct connectdata *conn = data->conn; timediff_t timeout_ms; /* in milliseconds */ - timediff_t response_time = (data->set.server_response_timeout)? - data->set.server_response_timeout: pp->response_time; + timediff_t response_time = (data->set.server_response_timeout) ? + data->set.server_response_timeout : pp->response_time; /* if CURLOPT_SERVER_RESPONSE_TIMEOUT is set, use that to determine remaining time, or use pp->response because SERVER_RESPONSE_TIMEOUT is @@ -105,20 +106,20 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, if(Curl_conn_data_pending(data, FIRSTSOCKET)) rc = 1; - else if(Curl_pp_moredata(pp)) + else if(pp->overflow) /* We are receiving and there is data in the cache so just read it */ rc = 1; else if(!pp->sendleft && Curl_conn_data_pending(data, FIRSTSOCKET)) /* We are receiving and there is data ready in the SSL library */ rc = 1; else - rc = Curl_socket_check(pp->sendleft?CURL_SOCKET_BAD:sock, /* reading */ + rc = Curl_socket_check(pp->sendleft ? CURL_SOCKET_BAD : sock, /* reading */ CURL_SOCKET_BAD, - pp->sendleft?sock:CURL_SOCKET_BAD, /* writing */ + pp->sendleft ? sock : CURL_SOCKET_BAD, /* writing */ interval_ms); if(block) { - /* if we didn't wait, we don't have to spend time on this now */ + /* if we did not wait, we do not have to spend time on this now */ if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else @@ -139,19 +140,13 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, } /* initialize stuff to prepare for reading a fresh new response */ -void Curl_pp_init(struct Curl_easy *data, struct pingpong *pp) +void Curl_pp_init(struct pingpong *pp) { - DEBUGASSERT(data); pp->nread_resp = 0; - pp->linestart_resp = data->state.buffer; - pp->pending_resp = TRUE; pp->response = Curl_now(); /* start response time-out now! */ -} - -/* setup for the coming transfer */ -void Curl_pp_setup(struct pingpong *pp) -{ + pp->pending_resp = TRUE; Curl_dyn_init(&pp->sendbuf, DYN_PINGPPONG_CMD); + Curl_dyn_init(&pp->recvbuf, DYN_PINGPPONG_CMD); } /*********************************************************************** @@ -169,7 +164,7 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, const char *fmt, va_list args) { - ssize_t bytes_written = 0; + size_t bytes_written = 0; size_t write_len; char *s; CURLcode result; @@ -184,7 +179,7 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, DEBUGASSERT(pp->sendthis == NULL); if(!conn) - /* can't send without a connection! */ + /* cannot send without a connection! */ return CURLE_SEND_ERROR; Curl_dyn_reset(&pp->sendbuf); @@ -197,15 +192,19 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, if(result) return result; + pp->pending_resp = TRUE; write_len = Curl_dyn_len(&pp->sendbuf); s = Curl_dyn_ptr(&pp->sendbuf); - Curl_pp_init(data, pp); #ifdef HAVE_GSSAPI conn->data_prot = PROT_CMD; #endif - result = Curl_nwrite(data, FIRSTSOCKET, s, write_len, &bytes_written); - if(result) + result = Curl_conn_send(data, FIRSTSOCKET, s, write_len, FALSE, + &bytes_written); + if(result == CURLE_AGAIN) { + bytes_written = 0; + } + else if(result) return result; #ifdef HAVE_GSSAPI data_sec = conn->data_prot; @@ -213,9 +212,9 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, conn->data_prot = (unsigned char)data_sec; #endif - Curl_debug(data, CURLINFO_HEADER_OUT, s, (size_t)bytes_written); + Curl_debug(data, CURLINFO_HEADER_OUT, s, bytes_written); - if(bytes_written != (ssize_t)write_len) { + if(bytes_written != write_len) { /* the whole chunk was not sent, keep it around and adjust sizes */ pp->sendthis = s; pp->sendsize = write_len; @@ -255,192 +254,131 @@ CURLcode Curl_pp_sendf(struct Curl_easy *data, struct pingpong *pp, return result; } +static CURLcode pingpong_read(struct Curl_easy *data, + int sockindex, + char *buffer, + size_t buflen, + ssize_t *nread) +{ + CURLcode result; +#ifdef HAVE_GSSAPI + enum protection_level prot = data->conn->data_prot; + data->conn->data_prot = PROT_CLEAR; +#endif + result = Curl_conn_recv(data, sockindex, buffer, buflen, nread); +#ifdef HAVE_GSSAPI + DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST); + data->conn->data_prot = (unsigned char)prot; +#endif + return result; +} + /* * Curl_pp_readresp() * * Reads a piece of a server response. */ CURLcode Curl_pp_readresp(struct Curl_easy *data, - curl_socket_t sockfd, + int sockindex, struct pingpong *pp, int *code, /* return the server code if done */ size_t *size) /* size of the response */ { - ssize_t perline; /* count bytes per line */ - bool keepon = TRUE; - ssize_t gotbytes; - char *ptr; struct connectdata *conn = data->conn; - char * const buf = data->state.buffer; CURLcode result = CURLE_OK; + ssize_t gotbytes; + char buffer[900]; *code = 0; /* 0 for errors or not done */ *size = 0; - ptr = buf + pp->nread_resp; - - /* number of bytes in the current line, so far */ - perline = (ssize_t)(ptr-pp->linestart_resp); - - while((pp->nread_resp < (size_t)data->set.buffer_size) && - (keepon && !result)) { - - if(pp->cache) { - /* we had data in the "cache", copy that instead of doing an actual - * read - * - * pp->cache_size is cast to ssize_t here. This should be safe, because - * it would have been populated with something of size int to begin - * with, even though its datatype may be larger than an int. - */ - if((ptr + pp->cache_size) > (buf + data->set.buffer_size + 1)) { - failf(data, "cached response data too big to handle"); - return CURLE_WEIRD_SERVER_REPLY; - } - memcpy(ptr, pp->cache, pp->cache_size); - gotbytes = (ssize_t)pp->cache_size; - free(pp->cache); /* free the cache */ - pp->cache = NULL; /* clear the pointer */ - pp->cache_size = 0; /* zero the size just in case */ + do { + gotbytes = 0; + if(pp->nfinal) { + /* a previous call left this many bytes in the beginning of the buffer as + that was the final line; now ditch that */ + size_t full = Curl_dyn_len(&pp->recvbuf); + + /* trim off the "final" leading part */ + Curl_dyn_tail(&pp->recvbuf, full - pp->nfinal); + + pp->nfinal = 0; /* now gone */ } - else { -#ifdef HAVE_GSSAPI - enum protection_level prot = conn->data_prot; - conn->data_prot = PROT_CLEAR; -#endif - DEBUGASSERT((ptr + data->set.buffer_size - pp->nread_resp) <= - (buf + data->set.buffer_size + 1)); - result = Curl_read(data, sockfd, ptr, - data->set.buffer_size - pp->nread_resp, - &gotbytes); -#ifdef HAVE_GSSAPI - DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST); - conn->data_prot = (unsigned char)prot; -#endif + if(!pp->overflow) { + result = pingpong_read(data, sockindex, buffer, sizeof(buffer), + &gotbytes); if(result == CURLE_AGAIN) - return CURLE_OK; /* return */ + return CURLE_OK; if(result) - /* Set outer result variable to this error. */ - keepon = FALSE; - } + return result; - if(!keepon) - ; - else if(gotbytes <= 0) { - keepon = FALSE; - result = CURLE_RECV_ERROR; - failf(data, "response reading failed (errno: %d)", SOCKERRNO); - } - else { - /* we got a whole chunk of data, which can be anything from one - * byte to a set of lines and possible just a piece of the last - * line */ - ssize_t i; - ssize_t clipamount = 0; - bool restart = FALSE; + if(gotbytes <= 0) { + failf(data, "response reading failed (errno: %d)", SOCKERRNO); + return CURLE_RECV_ERROR; + } + + result = Curl_dyn_addn(&pp->recvbuf, buffer, gotbytes); + if(result) + return result; data->req.headerbytecount += (unsigned int)gotbytes; pp->nread_resp += gotbytes; - for(i = 0; i < gotbytes; ptr++, i++) { - perline++; - if(*ptr == '\n') { - /* a newline is CRLF in pp-talk, so the CR is ignored as - the line isn't really terminated until the LF comes */ + } - /* output debug output if that is requested */ + do { + char *line = Curl_dyn_ptr(&pp->recvbuf); + char *nl = memchr(line, '\n', Curl_dyn_len(&pp->recvbuf)); + if(nl) { + /* a newline is CRLF in pp-talk, so the CR is ignored as + the line is not really terminated until the LF comes */ + size_t length = nl - line + 1; + + /* output debug output if that is requested */ #ifdef HAVE_GSSAPI - if(!conn->sec_complete) + if(!conn->sec_complete) #endif - Curl_debug(data, CURLINFO_HEADER_IN, - pp->linestart_resp, (size_t)perline); - - /* - * We pass all response-lines to the callback function registered - * for "headers". The response lines can be seen as a kind of - * headers. - */ - result = Curl_client_write(data, CLIENTWRITE_HEADER, - pp->linestart_resp, perline); - if(result) - return result; - - if(pp->endofresp(data, conn, pp->linestart_resp, perline, code)) { - /* This is the end of the last line, copy the last line to the - start of the buffer and null-terminate, for old times sake */ - size_t n = ptr - pp->linestart_resp; - memmove(buf, pp->linestart_resp, n); - buf[n] = 0; /* null-terminate */ - keepon = FALSE; - pp->linestart_resp = ptr + 1; /* advance pointer */ - i++; /* skip this before getting out */ - - *size = pp->nread_resp; /* size of the response */ - pp->nread_resp = 0; /* restart */ - break; - } - perline = 0; /* line starts over here */ - pp->linestart_resp = ptr + 1; + Curl_debug(data, CURLINFO_HEADER_IN, line, length); + + /* + * Pass all response-lines to the callback function registered for + * "headers". The response lines can be seen as a kind of headers. + */ + result = Curl_client_write(data, CLIENTWRITE_INFO, line, length); + if(result) + return result; + + if(pp->endofresp(data, conn, line, length, code)) { + /* When at "end of response", keep the endofresp line first in the + buffer since it will be accessed outside (by pingpong + parsers). Store the overflow counter to inform about additional + data in this buffer after the endofresp line. */ + pp->nfinal = length; + if(Curl_dyn_len(&pp->recvbuf) > length) + pp->overflow = Curl_dyn_len(&pp->recvbuf) - length; + else + pp->overflow = 0; + *size = pp->nread_resp; /* size of the response */ + pp->nread_resp = 0; /* restart */ + gotbytes = 0; /* force break out of outer loop */ + break; } - } - - if(!keepon && (i != gotbytes)) { - /* We found the end of the response lines, but we didn't parse the - full chunk of data we have read from the server. We therefore need - to store the rest of the data to be checked on the next invoke as - it may actually contain another end of response already! */ - clipamount = gotbytes - i; - restart = TRUE; - DEBUGF(infof(data, "Curl_pp_readresp_ %d bytes of trailing " - "server response left", - (int)clipamount)); - } - else if(keepon) { - - if((perline == gotbytes) && - (gotbytes > (ssize_t)data->set.buffer_size/2)) { - /* We got an excessive line without newlines and we need to deal - with it. We keep the first bytes of the line then we throw - away the rest. */ - infof(data, "Excessive server response line length received, " - "%zd bytes. Stripping", gotbytes); - restart = TRUE; - - /* we keep 40 bytes since all our pingpong protocols are only - interested in the first piece */ - clipamount = 40; - } - else if(pp->nread_resp > (size_t)data->set.buffer_size/2) { - /* We got a large chunk of data and there's potentially still - trailing data to take care of, so we put any such part in the - "cache", clear the buffer to make space and restart. */ - clipamount = perline; - restart = TRUE; - } - } - else if(i == gotbytes) - restart = TRUE; - - if(clipamount) { - pp->cache_size = clipamount; - pp->cache = malloc(pp->cache_size); - if(pp->cache) - memcpy(pp->cache, pp->linestart_resp, pp->cache_size); + if(Curl_dyn_len(&pp->recvbuf) > length) + /* keep the remaining piece */ + Curl_dyn_tail((&pp->recvbuf), Curl_dyn_len(&pp->recvbuf) - length); else - return CURLE_OUT_OF_MEMORY; + Curl_dyn_reset(&pp->recvbuf); } - if(restart) { - /* now reset a few variables to start over nicely from the start of - the big buffer */ - pp->nread_resp = 0; /* start over from scratch in the buffer */ - ptr = pp->linestart_resp = buf; - perline = 0; + else { + /* without a newline, there is no overflow */ + pp->overflow = 0; + break; } - } /* there was data */ + } while(1); /* while there is buffer left to scan */ - } /* while there's buffer left and loop is requested */ + } while(gotbytes == sizeof(buffer)); pp->pending_resp = FALSE; @@ -462,18 +400,34 @@ int Curl_pp_getsock(struct Curl_easy *data, return GETSOCK_READSOCK(0); } +bool Curl_pp_needs_flush(struct Curl_easy *data, + struct pingpong *pp) +{ + (void)data; + return pp->sendleft > 0; +} + CURLcode Curl_pp_flushsend(struct Curl_easy *data, struct pingpong *pp) { /* we have a piece of a command still left to send */ - ssize_t written; - CURLcode result = Curl_nwrite(data, FIRSTSOCKET, - pp->sendthis + pp->sendsize - pp->sendleft, - pp->sendleft, &written); + size_t written; + CURLcode result; + + if(!Curl_pp_needs_flush(data, pp)) + return CURLE_OK; + + result = Curl_conn_send(data, FIRSTSOCKET, + pp->sendthis + pp->sendsize - pp->sendleft, + pp->sendleft, FALSE, &written); + if(result == CURLE_AGAIN) { + result = CURLE_OK; + written = 0; + } if(result) return result; - if(written != (ssize_t)pp->sendleft) { + if(written != pp->sendleft) { /* only a fraction was sent */ pp->sendleft -= written; } @@ -488,14 +442,13 @@ CURLcode Curl_pp_flushsend(struct Curl_easy *data, CURLcode Curl_pp_disconnect(struct pingpong *pp) { Curl_dyn_free(&pp->sendbuf); - Curl_safefree(pp->cache); + Curl_dyn_free(&pp->recvbuf); return CURLE_OK; } bool Curl_pp_moredata(struct pingpong *pp) { - return (!pp->sendleft && pp->cache && pp->nread_resp < pp->cache_size) ? - TRUE : FALSE; + return (!pp->sendleft && Curl_dyn_len(&pp->recvbuf) > pp->nfinal); } #endif diff --git a/deps/curl/lib/pingpong.h b/deps/curl/lib/pingpong.h index 80d3f7718c7..72239ff0591 100644 --- a/deps/curl/lib/pingpong.h +++ b/deps/curl/lib/pingpong.h @@ -37,7 +37,7 @@ struct connectdata; typedef enum { PPTRANSFER_BODY, /* yes do transfer a body */ PPTRANSFER_INFO, /* do still go through to get info/headers */ - PPTRANSFER_NONE /* don't get anything and don't get info */ + PPTRANSFER_NONE /* do not get anything and do not get info */ } curl_pp_transfer; /* @@ -47,16 +47,11 @@ typedef enum { * It holds response cache and non-blocking sending data. */ struct pingpong { - char *cache; /* data cache between getresponse()-calls */ - size_t cache_size; /* size of cache in bytes */ size_t nread_resp; /* number of bytes currently read of a server response */ - char *linestart_resp; /* line start pointer for the server response - reader function */ bool pending_resp; /* set TRUE when a server response is pending or in progress, and is cleared once the last response is read */ - char *sendthis; /* allocated pointer to a buffer that is to be sent to the - server */ + char *sendthis; /* pointer to a buffer that is to be sent to the server */ size_t sendleft; /* number of bytes left to send from the sendthis buffer */ size_t sendsize; /* total size of the sendthis buffer */ struct curltime response; /* set to Curl_now() when a command has been sent @@ -64,6 +59,10 @@ struct pingpong { timediff_t response_time; /* When no timeout is given, this is the amount of milliseconds we await for a server response. */ struct dynbuf sendbuf; + struct dynbuf recvbuf; + size_t overflow; /* number of bytes left after a final response line */ + size_t nfinal; /* number of bytes in the final response line, which + after a match is first in the receice buffer */ /* Function pointers the protocols MUST implement and provide for the pingpong layer to function */ @@ -84,16 +83,13 @@ struct pingpong { * Curl_pp_statemach() * * called repeatedly until done. Set 'wait' to make it wait a while on the - * socket if there's no traffic. + * socket if there is no traffic. */ CURLcode Curl_pp_statemach(struct Curl_easy *data, struct pingpong *pp, bool block, bool disconnecting); /* initialize stuff to prepare for reading a fresh new response */ -void Curl_pp_init(struct Curl_easy *data, struct pingpong *pp); - -/* setup for the transfer */ -void Curl_pp_setup(struct pingpong *pp); +void Curl_pp_init(struct pingpong *pp); /* Returns timeout in ms. 0 or negative number means the timeout has already triggered */ @@ -113,7 +109,7 @@ timediff_t Curl_pp_state_timeout(struct Curl_easy *data, */ CURLcode Curl_pp_sendf(struct Curl_easy *data, struct pingpong *pp, - const char *fmt, ...); + const char *fmt, ...) CURL_PRINTF(3, 4); /*********************************************************************** * @@ -128,7 +124,7 @@ CURLcode Curl_pp_sendf(struct Curl_easy *data, CURLcode Curl_pp_vsendf(struct Curl_easy *data, struct pingpong *pp, const char *fmt, - va_list args); + va_list args) CURL_PRINTF(3, 0); /* * Curl_pp_readresp() @@ -136,11 +132,13 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, * Reads a piece of a server response. */ CURLcode Curl_pp_readresp(struct Curl_easy *data, - curl_socket_t sockfd, + int sockindex, struct pingpong *pp, int *code, /* return the server code if done */ size_t *size); /* size of the response */ +bool Curl_pp_needs_flush(struct Curl_easy *data, + struct pingpong *pp); CURLcode Curl_pp_flushsend(struct Curl_easy *data, struct pingpong *pp); diff --git a/deps/curl/lib/pop3.c b/deps/curl/lib/pop3.c index a9d5fdd6980..db6ec04c7bf 100644 --- a/deps/curl/lib/pop3.c +++ b/deps/curl/lib/pop3.c @@ -77,11 +77,16 @@ #include "curl_sasl.h" #include "curl_md5.h" #include "warnless.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + /* Local API functions */ static CURLcode pop3_regular_transfer(struct Curl_easy *data, bool *done); static CURLcode pop3_do(struct Curl_easy *data, bool *done); @@ -106,12 +111,17 @@ static CURLcode pop3_continue_auth(struct Curl_easy *data, const char *mech, static CURLcode pop3_cancel_auth(struct Curl_easy *data, const char *mech); static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out); +/* This function scans the body after the end-of-body and writes everything + * until the end is found */ +static CURLcode pop3_write(struct Curl_easy *data, + const char *str, size_t nread, bool is_eos); + /* * POP3 protocol handler. */ const struct Curl_handler Curl_handler_pop3 = { - "POP3", /* scheme */ + "pop3", /* scheme */ pop3_setup_connection, /* setup_connection */ pop3_do, /* do_it */ pop3_done, /* done */ @@ -124,7 +134,8 @@ const struct Curl_handler Curl_handler_pop3 = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ pop3_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + pop3_write, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_POP3, /* defport */ @@ -140,7 +151,7 @@ const struct Curl_handler Curl_handler_pop3 = { */ const struct Curl_handler Curl_handler_pop3s = { - "POP3S", /* scheme */ + "pop3s", /* scheme */ pop3_setup_connection, /* setup_connection */ pop3_do, /* do_it */ pop3_done, /* done */ @@ -153,7 +164,8 @@ const struct Curl_handler Curl_handler_pop3s = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ pop3_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + pop3_write, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_POP3S, /* defport */ @@ -191,6 +203,53 @@ static void pop3_to_pop3s(struct connectdata *conn) #define pop3_to_pop3s(x) Curl_nop_stmt #endif +struct pop3_cmd { + const char *name; + unsigned short nlen; + BIT(multiline); /* response is multi-line with last '.' line */ + BIT(multiline_with_args); /* is multi-line when command has args */ +}; + +static const struct pop3_cmd pop3cmds[] = { + { "APOP", 4, FALSE, FALSE }, + { "AUTH", 4, FALSE, FALSE }, + { "CAPA", 4, TRUE, TRUE }, + { "DELE", 4, FALSE, FALSE }, + { "LIST", 4, TRUE, FALSE }, + { "MSG", 3, TRUE, TRUE }, + { "NOOP", 4, FALSE, FALSE }, + { "PASS", 4, FALSE, FALSE }, + { "QUIT", 4, FALSE, FALSE }, + { "RETR", 4, TRUE, TRUE }, + { "RSET", 4, FALSE, FALSE }, + { "STAT", 4, FALSE, FALSE }, + { "STLS", 4, FALSE, FALSE }, + { "TOP", 3, TRUE, TRUE }, + { "UIDL", 4, TRUE, FALSE }, + { "USER", 4, FALSE, FALSE }, + { "UTF8", 4, FALSE, FALSE }, + { "XTND", 4, TRUE, TRUE }, +}; + +/* Return iff a command is defined as "multi-line" (RFC 1939), + * has a response terminated by a last line with a '.'. + */ +static bool pop3_is_multiline(const char *cmdline) +{ + size_t i; + for(i = 0; i < ARRAYSIZE(pop3cmds); ++i) { + if(strncasecompare(pop3cmds[i].name, cmdline, pop3cmds[i].nlen)) { + if(!cmdline[pop3cmds[i].nlen]) + return pop3cmds[i].multiline; + else if(cmdline[pop3cmds[i].nlen] == ' ') + return pop3cmds[i].multiline_with_args; + } + } + /* Unknown command, assume multi-line for backward compatibility with + * earlier curl versions that only could do multi-line responses. */ + return TRUE; +} + /*********************************************************************** * * pop3_endofresp() @@ -251,8 +310,8 @@ static bool pop3_endofresp(struct Curl_easy *data, struct connectdata *conn, */ static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out) { - char *message = data->state.buffer; - size_t len = strlen(message); + char *message = Curl_dyn_ptr(&data->conn->proto.pop3c.pp.recvbuf); + size_t len = data->conn->proto.pop3c.pp.nfinal; if(len > 2) { /* Find the start of the message */ @@ -403,7 +462,7 @@ static CURLcode pop3_perform_user(struct Curl_easy *data, CURLcode result = CURLE_OK; /* Check we have a username and password to authenticate with and end the - connect phase if we don't */ + connect phase if we do not */ if(!data->state.aptr.user) { pop3_state(data, POP3_STOP); @@ -437,7 +496,7 @@ static CURLcode pop3_perform_apop(struct Curl_easy *data, char secret[2 * MD5_DIGEST_LEN + 1]; /* Check we have a username and password to authenticate with and end the - connect phase if we don't */ + connect phase if we do not */ if(!data->state.aptr.user) { pop3_state(data, POP3_STOP); @@ -445,7 +504,7 @@ static CURLcode pop3_perform_apop(struct Curl_easy *data, } /* Create the digest */ - ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); if(!ctxt) return CURLE_OUT_OF_MEMORY; @@ -547,7 +606,7 @@ static CURLcode pop3_perform_authentication(struct Curl_easy *data, saslprogress progress = SASL_IDLE; /* Check we have enough data to authenticate with and end the - connect phase if we don't */ + connect phase if we do not */ if(!Curl_sasl_can_authenticate(&pop3c->sasl, data)) { pop3_state(data, POP3_STOP); return result; @@ -606,18 +665,20 @@ static CURLcode pop3_perform_command(struct Curl_easy *data) else command = "RETR"; + if(pop3->custom && pop3->custom[0] != '\0') + command = pop3->custom; + /* Send the command */ if(pop3->id[0] != '\0') result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s %s", - (pop3->custom && pop3->custom[0] != '\0' ? - pop3->custom : command), pop3->id); + command, pop3->id); else - result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", - (pop3->custom && pop3->custom[0] != '\0' ? - pop3->custom : command)); + result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", command); - if(!result) + if(!result) { pop3_state(data, POP3_COMMAND); + data->req.no_body = !pop3_is_multiline(command); + } return result; } @@ -648,8 +709,8 @@ static CURLcode pop3_state_servergreet_resp(struct Curl_easy *data, CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; struct pop3_conn *pop3c = &conn->proto.pop3c; - const char *line = data->state.buffer; - size_t len = strlen(line); + const char *line = Curl_dyn_ptr(&data->conn->proto.pop3c.pp.recvbuf); + size_t len = data->conn->proto.pop3c.pp.nfinal; (void)instate; /* no use for this yet */ @@ -657,44 +718,35 @@ static CURLcode pop3_state_servergreet_resp(struct Curl_easy *data, failf(data, "Got unexpected pop3-server response"); result = CURLE_WEIRD_SERVER_REPLY; } - else { + else if(len > 3) { /* Does the server support APOP authentication? */ - if(len >= 4 && line[len - 2] == '>') { - /* Look for the APOP timestamp */ - size_t i; - for(i = 3; i < len - 2; ++i) { - if(line[i] == '<') { - /* Calculate the length of the timestamp */ - size_t timestamplen = len - 1 - i; - char *at; - if(!timestamplen) - break; - - /* Allocate some memory for the timestamp */ - pop3c->apoptimestamp = (char *)calloc(1, timestamplen + 1); - - if(!pop3c->apoptimestamp) - break; - - /* Copy the timestamp */ - memcpy(pop3c->apoptimestamp, line + i, timestamplen); - pop3c->apoptimestamp[timestamplen] = '\0'; - - /* If the timestamp does not contain '@' it is not (as required by - RFC-1939) conformant to the RFC-822 message id syntax, and we - therefore do not use APOP authentication. */ - at = strchr(pop3c->apoptimestamp, '@'); - if(!at) - Curl_safefree(pop3c->apoptimestamp); - else - /* Store the APOP capability */ - pop3c->authtypes |= POP3_TYPE_APOP; - break; - } + char *lt; + char *gt = NULL; + + /* Look for the APOP timestamp */ + lt = memchr(line, '<', len); + if(lt) + /* search the remainder for '>' */ + gt = memchr(lt, '>', len - (lt - line)); + if(gt) { + /* the length of the timestamp, including the brackets */ + size_t timestamplen = gt - lt + 1; + char *at = memchr(lt, '@', timestamplen); + /* If the timestamp does not contain '@' it is not (as required by + RFC-1939) conformant to the RFC-822 message id syntax, and we + therefore do not use APOP authentication. */ + if(at) { + /* dupe the timestamp */ + pop3c->apoptimestamp = Curl_memdup0(lt, timestamplen); + if(!pop3c->apoptimestamp) + return CURLE_OUT_OF_MEMORY; + /* Store the APOP capability */ + pop3c->authtypes |= POP3_TYPE_APOP; } } - result = pop3_perform_capa(data, conn); + if(!result) + result = pop3_perform_capa(data, conn); } return result; @@ -707,8 +759,8 @@ static CURLcode pop3_state_capa_resp(struct Curl_easy *data, int pop3code, CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; struct pop3_conn *pop3c = &conn->proto.pop3c; - const char *line = data->state.buffer; - size_t len = strlen(line); + const char *line = Curl_dyn_ptr(&data->conn->proto.pop3c.pp.recvbuf); + size_t len = data->conn->proto.pop3c.pp.nfinal; (void)instate; /* no use for this yet */ @@ -764,7 +816,7 @@ static CURLcode pop3_state_capa_resp(struct Curl_easy *data, int pop3code, } } else { - /* Clear text is supported when CAPA isn't recognised */ + /* Clear text is supported when CAPA is not recognised */ if(pop3code != '+') pop3c->authtypes |= POP3_TYPE_CLEARTEXT; @@ -795,7 +847,7 @@ static CURLcode pop3_state_starttls_resp(struct Curl_easy *data, (void)instate; /* no use for this yet */ /* Pipelining in response is forbidden. */ - if(data->conn->proto.pop3c.pp.cache_size) + if(data->conn->proto.pop3c.pp.overflow) return CURLE_WEIRD_SERVER_REPLY; if(pop3code != '+') { @@ -937,31 +989,36 @@ static CURLcode pop3_state_command_resp(struct Curl_easy *data, pop3c->eob = 2; /* But since this initial CR LF pair is not part of the actual body, we set - the strip counter here so that these bytes won't be delivered. */ + the strip counter here so that these bytes will not be delivered. */ pop3c->strip = 2; if(pop3->transfer == PPTRANSFER_BODY) { /* POP3 download */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); - if(pp->cache) { - /* The header "cache" contains a bunch of data that is actually body - content so send it as such. Note that there may even be additional - "headers" after the body */ + if(pp->overflow) { + /* The recv buffer contains data that is actually body content so send + it as such. Note that there may even be additional "headers" after + the body */ + + /* keep only the overflow */ + Curl_dyn_tail(&pp->recvbuf, pp->overflow); + pp->nfinal = 0; /* done */ if(!data->req.no_body) { - result = Curl_pop3_write(data, pp->cache, pp->cache_size); + result = pop3_write(data, Curl_dyn_ptr(&pp->recvbuf), + Curl_dyn_len(&pp->recvbuf), FALSE); if(result) return result; } - /* Free the cache */ - Curl_safefree(pp->cache); - - /* Reset the cache size */ - pp->cache_size = 0; + /* reset the buffer */ + Curl_dyn_reset(&pp->recvbuf); + pp->overflow = 0; } } + else + pp->overflow = 0; /* End of DO phase */ pop3_state(data, POP3_STOP); @@ -973,7 +1030,6 @@ static CURLcode pop3_statemachine(struct Curl_easy *data, struct connectdata *conn) { CURLcode result = CURLE_OK; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; int pop3code; struct pop3_conn *pop3c = &conn->proto.pop3c; struct pingpong *pp = &pop3c->pp; @@ -990,7 +1046,7 @@ static CURLcode pop3_statemachine(struct Curl_easy *data, do { /* Read the response from the server */ - result = Curl_pp_readresp(data, sock, pp, &pop3code, &nread); + result = Curl_pp_readresp(data, FIRSTSOCKET, pp, &pop3code, &nread); if(result) return result; @@ -1063,7 +1119,7 @@ static CURLcode pop3_multi_statemach(struct Curl_easy *data, bool *done) } result = Curl_pp_statemach(data, &pop3c->pp, FALSE, FALSE); - *done = (pop3c->state == POP3_STOP) ? TRUE : FALSE; + *done = (pop3c->state == POP3_STOP); return result; } @@ -1088,7 +1144,7 @@ static CURLcode pop3_init(struct Curl_easy *data) CURLcode result = CURLE_OK; struct POP3 *pop3; - pop3 = data->req.p.pop3 = calloc(sizeof(struct POP3), 1); + pop3 = data->req.p.pop3 = calloc(1, sizeof(struct POP3)); if(!pop3) result = CURLE_OUT_OF_MEMORY; @@ -1131,8 +1187,7 @@ static CURLcode pop3_connect(struct Curl_easy *data, bool *done) Curl_sasl_init(&pop3c->sasl, data, &saslpop3); /* Initialise the pingpong layer */ - Curl_pp_setup(pp); - Curl_pp_init(data, pp); + Curl_pp_init(pp); /* Parse the URL options */ result = pop3_parse_url_options(conn); @@ -1450,12 +1505,13 @@ static CURLcode pop3_parse_custom_request(struct Curl_easy *data) /*********************************************************************** * - * Curl_pop3_write() + * pop3_write() * * This function scans the body after the end-of-body and writes everything * until the end is found. */ -CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) +static CURLcode pop3_write(struct Curl_easy *data, const char *str, + size_t nread, bool is_eos) { /* This code could be made into a special function in the handler struct */ CURLcode result = CURLE_OK; @@ -1465,6 +1521,7 @@ CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) bool strip_dot = FALSE; size_t last = 0; size_t i; + (void)is_eos; /* Search through the buffer looking for the end-of-body marker which is 5 bytes (0d 0a 2e 0d 0a). Note that a line starting with a dot matches @@ -1480,7 +1537,7 @@ CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) pop3c->eob++; if(i) { - /* Write out the body part that didn't match */ + /* Write out the body part that did not match */ result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last], i - last); @@ -1493,7 +1550,7 @@ CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) else if(pop3c->eob == 3) pop3c->eob++; else - /* If the character match wasn't at position 0 or 3 then restart the + /* If the character match was not at position 0 or 3 then restart the pattern matching */ pop3c->eob = 1; break; @@ -1502,7 +1559,7 @@ CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) if(pop3c->eob == 1 || pop3c->eob == 4) pop3c->eob++; else - /* If the character match wasn't at position 1 or 4 then start the + /* If the character match was not at position 1 or 4 then start the search again */ pop3c->eob = 0; break; @@ -1516,7 +1573,7 @@ CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread) pop3c->eob = 0; } else - /* If the character match wasn't at position 2 then start the search + /* If the character match was not at position 2 then start the search again */ pop3c->eob = 0; break; diff --git a/deps/curl/lib/pop3.h b/deps/curl/lib/pop3.h index 83f0f831e6c..3d08dafa198 100644 --- a/deps/curl/lib/pop3.h +++ b/deps/curl/lib/pop3.h @@ -90,8 +90,4 @@ extern const struct Curl_handler Curl_handler_pop3s; #define POP3_EOB "\x0d\x0a\x2e\x0d\x0a" #define POP3_EOB_LEN 5 -/* This function scans the body after the end-of-body and writes everything - * until the end is found */ -CURLcode Curl_pop3_write(struct Curl_easy *data, char *str, size_t nread); - #endif /* HEADER_CURL_POP3_H */ diff --git a/deps/curl/lib/progress.c b/deps/curl/lib/progress.c index 6092b782c70..d3a1b9ac9af 100644 --- a/deps/curl/lib/progress.c +++ b/deps/curl/lib/progress.c @@ -48,8 +48,7 @@ static void time2str(char *r, curl_off_t seconds) if(h <= CURL_OFF_T_C(99)) { curl_off_t m = (seconds - (h*CURL_OFF_T_C(3600))) / CURL_OFF_T_C(60); curl_off_t s = (seconds - (h*CURL_OFF_T_C(3600))) - (m*CURL_OFF_T_C(60)); - msnprintf(r, 9, "%2" CURL_FORMAT_CURL_OFF_T ":%02" CURL_FORMAT_CURL_OFF_T - ":%02" CURL_FORMAT_CURL_OFF_T, h, m, s); + msnprintf(r, 9, "%2" FMT_OFF_T ":%02" FMT_OFF_T ":%02" FMT_OFF_T, h, m, s); } else { /* this equals to more than 99 hours, switch to a more suitable output @@ -57,10 +56,9 @@ static void time2str(char *r, curl_off_t seconds) curl_off_t d = seconds / CURL_OFF_T_C(86400); h = (seconds - (d*CURL_OFF_T_C(86400))) / CURL_OFF_T_C(3600); if(d <= CURL_OFF_T_C(999)) - msnprintf(r, 9, "%3" CURL_FORMAT_CURL_OFF_T - "d %02" CURL_FORMAT_CURL_OFF_T "h", d, h); + msnprintf(r, 9, "%3" FMT_OFF_T "d %02" FMT_OFF_T "h", d, h); else - msnprintf(r, 9, "%7" CURL_FORMAT_CURL_OFF_T "d", d); + msnprintf(r, 9, "%7" FMT_OFF_T "d", d); } } @@ -76,40 +74,40 @@ static char *max5data(curl_off_t bytes, char *max5) #define ONE_PETABYTE (CURL_OFF_T_C(1024) * ONE_TERABYTE) if(bytes < CURL_OFF_T_C(100000)) - msnprintf(max5, 6, "%5" CURL_FORMAT_CURL_OFF_T, bytes); + msnprintf(max5, 6, "%5" FMT_OFF_T, bytes); else if(bytes < CURL_OFF_T_C(10000) * ONE_KILOBYTE) - msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "k", bytes/ONE_KILOBYTE); + msnprintf(max5, 6, "%4" FMT_OFF_T "k", bytes/ONE_KILOBYTE); else if(bytes < CURL_OFF_T_C(100) * ONE_MEGABYTE) - /* 'XX.XM' is good as long as we're less than 100 megs */ - msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0" - CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE, + /* 'XX.XM' is good as long as we are less than 100 megs */ + msnprintf(max5, 6, "%2" FMT_OFF_T ".%0" + FMT_OFF_T "M", bytes/ONE_MEGABYTE, (bytes%ONE_MEGABYTE) / (ONE_MEGABYTE/CURL_OFF_T_C(10)) ); else if(bytes < CURL_OFF_T_C(10000) * ONE_MEGABYTE) - /* 'XXXXM' is good until we're at 10000MB or above */ - msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE); + /* 'XXXXM' is good until we are at 10000MB or above */ + msnprintf(max5, 6, "%4" FMT_OFF_T "M", bytes/ONE_MEGABYTE); else if(bytes < CURL_OFF_T_C(100) * ONE_GIGABYTE) /* 10000 MB - 100 GB, we show it as XX.XG */ - msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0" - CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE, + msnprintf(max5, 6, "%2" FMT_OFF_T ".%0" + FMT_OFF_T "G", bytes/ONE_GIGABYTE, (bytes%ONE_GIGABYTE) / (ONE_GIGABYTE/CURL_OFF_T_C(10)) ); else if(bytes < CURL_OFF_T_C(10000) * ONE_GIGABYTE) /* up to 10000GB, display without decimal: XXXXG */ - msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE); + msnprintf(max5, 6, "%4" FMT_OFF_T "G", bytes/ONE_GIGABYTE); else if(bytes < CURL_OFF_T_C(10000) * ONE_TERABYTE) /* up to 10000TB, display without decimal: XXXXT */ - msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "T", bytes/ONE_TERABYTE); + msnprintf(max5, 6, "%4" FMT_OFF_T "T", bytes/ONE_TERABYTE); else /* up to 10000PB, display without decimal: XXXXP */ - msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "P", bytes/ONE_PETABYTE); + msnprintf(max5, 6, "%4" FMT_OFF_T "P", bytes/ONE_PETABYTE); - /* 16384 petabytes (16 exabytes) is the maximum a 64 bit unsigned number can + /* 16384 petabytes (16 exabytes) is the maximum a 64-bit unsigned number can hold, but our data type is signed so 8192PB will be the maximum. */ return max5; @@ -140,7 +138,7 @@ int Curl_pgrsDone(struct Curl_easy *data) if(!(data->progress.flags & PGRS_HIDE) && !data->progress.callback) - /* only output if we don't use a progress callback and we're not + /* only output if we do not use a progress callback and we are not * hidden */ fprintf(data->set.err, "\n"); @@ -174,9 +172,17 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, data->progress.t_startop = timestamp; break; case TIMER_STARTSINGLE: - /* This is set at the start of each single fetch */ + /* This is set at the start of each single transfer */ data->progress.t_startsingle = timestamp; - data->progress.is_t_startransfer_set = false; + data->progress.is_t_startransfer_set = FALSE; + break; + case TIMER_POSTQUEUE: + /* Set when the transfer starts (after potentially having been brought + back from the waiting queue). It needs to count from t_startop and not + t_startsingle since the latter is reset when a connection is brought + back from the pending queue. */ + data->progress.t_postqueue = + Curl_timediff_us(timestamp, data->progress.t_startop); break; case TIMER_STARTACCEPT: data->progress.t_acceptdata = timestamp; @@ -196,7 +202,7 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, case TIMER_STARTTRANSFER: delta = &data->progress.t_starttransfer; /* prevent updating t_starttransfer unless: - * 1) this is the first time we're setting t_starttransfer + * 1) this is the first time we are setting t_starttransfer * 2) a redirect has occurred since the last time t_starttransfer was set * This prevents repeated invocations of the function from incorrectly * changing the t_starttransfer time. @@ -205,11 +211,11 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, return; } else { - data->progress.is_t_startransfer_set = true; + data->progress.is_t_startransfer_set = TRUE; break; } case TIMER_POSTRANSFER: - /* this is the normal end-of-transfer thing */ + delta = &data->progress.t_posttransfer; break; case TIMER_REDIRECT: data->progress.t_redirect = Curl_timediff_us(timestamp, @@ -243,13 +249,13 @@ void Curl_pgrsStartNow(struct Curl_easy *data) { data->progress.speeder_c = 0; /* reset the progress meter display */ data->progress.start = Curl_now(); - data->progress.is_t_startransfer_set = false; - data->progress.ul_limit_start = data->progress.start; - data->progress.dl_limit_start = data->progress.start; - data->progress.ul_limit_size = 0; - data->progress.dl_limit_size = 0; - data->progress.downloaded = 0; - data->progress.uploaded = 0; + data->progress.is_t_startransfer_set = FALSE; + data->progress.ul.limit.start = data->progress.start; + data->progress.dl.limit.start = data->progress.start; + data->progress.ul.limit.start_size = 0; + data->progress.dl.limit.start_size = 0; + data->progress.dl.cur_size = 0; + data->progress.ul.cur_size = 0; /* clear all bits except HIDE and HEADERS_OUT */ data->progress.flags &= PGRS_HIDE|PGRS_HEADERS_OUT; Curl_ratelimit(data, data->progress.start); @@ -257,11 +263,11 @@ void Curl_pgrsStartNow(struct Curl_easy *data) /* * This is used to handle speed limits, calculating how many milliseconds to - * wait until we're back under the speed limit, if needed. + * wait until we are back under the speed limit, if needed. * * The way it works is by having a "starting point" (time & amount of data * transferred by then) used in the speed computation, to be used instead of - * the start of the transfer. This starting point is regularly moved as + * the start of the transfer. This starting point is regularly moved as * transfer goes on, to keep getting accurate values (instead of average over * the entire transfer). * @@ -273,17 +279,15 @@ void Curl_pgrsStartNow(struct Curl_easy *data) * starting point should be reset (to current); or the number of milliseconds * to wait to get back under the speed limit. */ -timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, - curl_off_t startsize, - curl_off_t limit, - struct curltime start, +timediff_t Curl_pgrsLimitWaitTime(struct pgrs_dir *d, + curl_off_t speed_limit, struct curltime now) { - curl_off_t size = cursize - startsize; + curl_off_t size = d->cur_size - d->limit.start_size; timediff_t minimum; timediff_t actual; - if(!limit || !size) + if(!speed_limit || !size) return 0; /* @@ -291,9 +295,9 @@ timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, * stay below 'limit'. */ if(size < CURL_OFF_T_MAX/1000) - minimum = (timediff_t) (CURL_OFF_T_C(1000) * size / limit); + minimum = (timediff_t) (CURL_OFF_T_C(1000) * size / speed_limit); else { - minimum = (timediff_t) (size / limit); + minimum = (timediff_t) (size / speed_limit); if(minimum < TIMEDIFF_T_MAX/1000) minimum *= 1000; else @@ -304,7 +308,7 @@ timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, * 'actual' is the time in milliseconds it took to actually download the * last 'size' bytes. */ - actual = Curl_timediff(now, start); + actual = Curl_timediff_ceil(now, d->limit.start); if(actual < minimum) { /* if it downloaded the data faster than the limit, make it wait the difference */ @@ -317,9 +321,10 @@ timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, /* * Set the number of downloaded bytes so far. */ -void Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size) +CURLcode Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size) { - data->progress.downloaded = size; + data->progress.dl.cur_size = size; + return CURLE_OK; } /* @@ -327,19 +332,19 @@ void Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size) */ void Curl_ratelimit(struct Curl_easy *data, struct curltime now) { - /* don't set a new stamp unless the time since last update is long enough */ + /* do not set a new stamp unless the time since last update is long enough */ if(data->set.max_recv_speed) { - if(Curl_timediff(now, data->progress.dl_limit_start) >= + if(Curl_timediff(now, data->progress.dl.limit.start) >= MIN_RATE_LIMIT_PERIOD) { - data->progress.dl_limit_start = now; - data->progress.dl_limit_size = data->progress.downloaded; + data->progress.dl.limit.start = now; + data->progress.dl.limit.start_size = data->progress.dl.cur_size; } } if(data->set.max_send_speed) { - if(Curl_timediff(now, data->progress.ul_limit_start) >= + if(Curl_timediff(now, data->progress.ul.limit.start) >= MIN_RATE_LIMIT_PERIOD) { - data->progress.ul_limit_start = now; - data->progress.ul_limit_size = data->progress.uploaded; + data->progress.ul.limit.start = now; + data->progress.ul.limit.start_size = data->progress.ul.cur_size; } } } @@ -349,17 +354,17 @@ void Curl_ratelimit(struct Curl_easy *data, struct curltime now) */ void Curl_pgrsSetUploadCounter(struct Curl_easy *data, curl_off_t size) { - data->progress.uploaded = size; + data->progress.ul.cur_size = size; } void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size) { if(size >= 0) { - data->progress.size_dl = size; + data->progress.dl.total_size = size; data->progress.flags |= PGRS_DL_SIZE_KNOWN; } else { - data->progress.size_dl = 0; + data->progress.dl.total_size = 0; data->progress.flags &= ~PGRS_DL_SIZE_KNOWN; } } @@ -367,15 +372,20 @@ void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size) void Curl_pgrsSetUploadSize(struct Curl_easy *data, curl_off_t size) { if(size >= 0) { - data->progress.size_ul = size; + data->progress.ul.total_size = size; data->progress.flags |= PGRS_UL_SIZE_KNOWN; } else { - data->progress.size_ul = 0; + data->progress.ul.total_size = 0; data->progress.flags &= ~PGRS_UL_SIZE_KNOWN; } } +void Curl_pgrsEarlyData(struct Curl_easy *data, curl_off_t sent) +{ + data->progress.earlydata_sent = sent; +} + /* returns the average speed in bytes / second */ static curl_off_t trspeed(curl_off_t size, /* number of bytes */ curl_off_t us) /* microseconds */ @@ -390,7 +400,7 @@ static curl_off_t trspeed(curl_off_t size, /* number of bytes */ return CURL_OFF_T_MAX; } -/* returns TRUE if it's time to show the progress meter */ +/* returns TRUE if it is time to show the progress meter */ static bool progress_calc(struct Curl_easy *data, struct curltime now) { bool timetoshow = FALSE; @@ -398,8 +408,8 @@ static bool progress_calc(struct Curl_easy *data, struct curltime now) /* The time spent so far (from the start) in microseconds */ p->timespent = Curl_timediff_us(now, p->start); - p->dlspeed = trspeed(p->downloaded, p->timespent); - p->ulspeed = trspeed(p->uploaded, p->timespent); + p->dl.speed = trspeed(p->dl.cur_size, p->timespent); + p->ul.speed = trspeed(p->ul.cur_size, p->timespent); /* Calculations done at most once a second, unless end is reached */ if(p->lastshow != now.tv_sec) { @@ -410,7 +420,7 @@ static bool progress_calc(struct Curl_easy *data, struct curltime now) /* Let's do the "current speed" thing, with the dl + ul speeds combined. Store the speed at entry 'nowindex'. */ - p->speeder[ nowindex ] = p->downloaded + p->uploaded; + p->speeder[ nowindex ] = p->dl.cur_size + p->ul.cur_size; /* remember the exact time for this moment */ p->speeder_time [ nowindex ] = now; @@ -422,10 +432,10 @@ static bool progress_calc(struct Curl_easy *data, struct curltime now) /* figure out how many index entries of data we have stored in our speeder array. With N_ENTRIES filled in, we have about N_ENTRIES-1 seconds of transfer. Imagine, after one second we have filled in two entries, - after two seconds we've filled in three entries etc. */ - countindex = ((p->speeder_c >= CURR_TIME)? CURR_TIME:p->speeder_c) - 1; + after two seconds we have filled in three entries etc. */ + countindex = ((p->speeder_c >= CURR_TIME) ? CURR_TIME : p->speeder_c) - 1; - /* first of all, we don't do this if there's no counted seconds yet */ + /* first of all, we do not do this if there is no counted seconds yet */ if(countindex) { int checkindex; timediff_t span_ms; @@ -434,7 +444,7 @@ static bool progress_calc(struct Curl_easy *data, struct curltime now) /* Get the index position to compare with the 'nowindex' position. Get the oldest entry possible. While we have less than CURR_TIME entries, the first entry will remain the oldest. */ - checkindex = (p->speeder_c >= CURR_TIME)? p->speeder_c%CURR_TIME:0; + checkindex = (p->speeder_c >= CURR_TIME) ? p->speeder_c%CURR_TIME : 0; /* Figure out the exact time for the time span */ span_ms = Curl_timediff(now, p->speeder_time[checkindex]); @@ -456,113 +466,107 @@ static bool progress_calc(struct Curl_easy *data, struct curltime now) } else /* the first second we use the average */ - p->current_speed = p->ulspeed + p->dlspeed; + p->current_speed = p->ul.speed + p->dl.speed; } /* Calculations end */ return timetoshow; } #ifndef CURL_DISABLE_PROGRESS_METER + +struct pgrs_estimate { + curl_off_t secs; + curl_off_t percent; +}; + +static curl_off_t pgrs_est_percent(curl_off_t total, curl_off_t cur) +{ + if(total > CURL_OFF_T_C(10000)) + return cur / (total/CURL_OFF_T_C(100)); + else if(total > CURL_OFF_T_C(0)) + return (cur*100) / total; + return 0; +} + +static void pgrs_estimates(struct pgrs_dir *d, + bool total_known, + struct pgrs_estimate *est) +{ + est->secs = 0; + est->percent = 0; + if(total_known && (d->speed > CURL_OFF_T_C(0))) { + est->secs = d->total_size / d->speed; + est->percent = pgrs_est_percent(d->total_size, d->cur_size); + } +} + static void progress_meter(struct Curl_easy *data) { + struct Progress *p = &data->progress; char max5[6][10]; - curl_off_t dlpercen = 0; - curl_off_t ulpercen = 0; - curl_off_t total_percen = 0; - curl_off_t total_transfer; - curl_off_t total_expected_transfer; + struct pgrs_estimate dl_estm; + struct pgrs_estimate ul_estm; + struct pgrs_estimate total_estm; + curl_off_t total_cur_size; + curl_off_t total_expected_size; char time_left[10]; char time_total[10]; char time_spent[10]; - curl_off_t ulestimate = 0; - curl_off_t dlestimate = 0; - curl_off_t total_estimate; - curl_off_t timespent = - (curl_off_t)data->progress.timespent/1000000; /* seconds */ + curl_off_t cur_secs = (curl_off_t)p->timespent/1000000; /* seconds */ - if(!(data->progress.flags & PGRS_HEADERS_OUT)) { + if(!(p->flags & PGRS_HEADERS_OUT)) { if(data->state.resume_from) { fprintf(data->set.err, - "** Resuming transfer from byte position %" - CURL_FORMAT_CURL_OFF_T "\n", data->state.resume_from); + "** Resuming transfer from byte position %" FMT_OFF_T "\n", + data->state.resume_from); } fprintf(data->set.err, " %% Total %% Received %% Xferd Average Speed " "Time Time Time Current\n" " Dload Upload " "Total Spent Left Speed\n"); - data->progress.flags |= PGRS_HEADERS_OUT; /* headers are shown */ - } - - /* Figure out the estimated time of arrival for the upload */ - if((data->progress.flags & PGRS_UL_SIZE_KNOWN) && - (data->progress.ulspeed > CURL_OFF_T_C(0))) { - ulestimate = data->progress.size_ul / data->progress.ulspeed; - - if(data->progress.size_ul > CURL_OFF_T_C(10000)) - ulpercen = data->progress.uploaded / - (data->progress.size_ul/CURL_OFF_T_C(100)); - else if(data->progress.size_ul > CURL_OFF_T_C(0)) - ulpercen = (data->progress.uploaded*100) / - data->progress.size_ul; + p->flags |= PGRS_HEADERS_OUT; /* headers are shown */ } - /* ... and the download */ - if((data->progress.flags & PGRS_DL_SIZE_KNOWN) && - (data->progress.dlspeed > CURL_OFF_T_C(0))) { - dlestimate = data->progress.size_dl / data->progress.dlspeed; - - if(data->progress.size_dl > CURL_OFF_T_C(10000)) - dlpercen = data->progress.downloaded / - (data->progress.size_dl/CURL_OFF_T_C(100)); - else if(data->progress.size_dl > CURL_OFF_T_C(0)) - dlpercen = (data->progress.downloaded*100) / - data->progress.size_dl; - } - - /* Now figure out which of them is slower and use that one for the - total estimate! */ - total_estimate = ulestimate>dlestimate?ulestimate:dlestimate; + /* Figure out the estimated time of arrival for upload and download */ + pgrs_estimates(&p->ul, (p->flags & PGRS_UL_SIZE_KNOWN), &ul_estm); + pgrs_estimates(&p->dl, (p->flags & PGRS_DL_SIZE_KNOWN), &dl_estm); + /* Since both happen at the same time, total expected duration is max. */ + total_estm.secs = CURLMAX(ul_estm.secs, dl_estm.secs); /* create the three time strings */ - time2str(time_left, total_estimate > 0?(total_estimate - timespent):0); - time2str(time_total, total_estimate); - time2str(time_spent, timespent); + time2str(time_left, total_estm.secs > 0 ? (total_estm.secs - cur_secs) : 0); + time2str(time_total, total_estm.secs); + time2str(time_spent, cur_secs); /* Get the total amount of data expected to get transferred */ - total_expected_transfer = - ((data->progress.flags & PGRS_UL_SIZE_KNOWN)? - data->progress.size_ul:data->progress.uploaded)+ - ((data->progress.flags & PGRS_DL_SIZE_KNOWN)? - data->progress.size_dl:data->progress.downloaded); + total_expected_size = + ((p->flags & PGRS_UL_SIZE_KNOWN) ? p->ul.total_size : p->ul.cur_size) + + ((p->flags & PGRS_DL_SIZE_KNOWN) ? p->dl.total_size : p->dl.cur_size); /* We have transferred this much so far */ - total_transfer = data->progress.downloaded + data->progress.uploaded; + total_cur_size = p->dl.cur_size + p->ul.cur_size; /* Get the percentage of data transferred so far */ - if(total_expected_transfer > CURL_OFF_T_C(10000)) - total_percen = total_transfer / - (total_expected_transfer/CURL_OFF_T_C(100)); - else if(total_expected_transfer > CURL_OFF_T_C(0)) - total_percen = (total_transfer*100) / total_expected_transfer; + total_estm.percent = pgrs_est_percent(total_expected_size, total_cur_size); fprintf(data->set.err, "\r" - "%3" CURL_FORMAT_CURL_OFF_T " %s " - "%3" CURL_FORMAT_CURL_OFF_T " %s " - "%3" CURL_FORMAT_CURL_OFF_T " %s %s %s %s %s %s %s", - total_percen, /* 3 letters */ /* total % */ - max5data(total_expected_transfer, max5[2]), /* total size */ - dlpercen, /* 3 letters */ /* rcvd % */ - max5data(data->progress.downloaded, max5[0]), /* rcvd size */ - ulpercen, /* 3 letters */ /* xfer % */ - max5data(data->progress.uploaded, max5[1]), /* xfer size */ - max5data(data->progress.dlspeed, max5[3]), /* avrg dl speed */ - max5data(data->progress.ulspeed, max5[4]), /* avrg ul speed */ + "%3" FMT_OFF_T " %s " + "%3" FMT_OFF_T " %s " + "%3" FMT_OFF_T " %s %s %s %s %s %s %s", + total_estm.percent, /* 3 letters */ /* total % */ + max5data(total_expected_size, max5[2]), /* total size */ + dl_estm.percent, /* 3 letters */ /* rcvd % */ + max5data(p->dl.cur_size, max5[0]), /* rcvd size */ + ul_estm.percent, /* 3 letters */ /* xfer % */ + max5data(p->ul.cur_size, max5[1]), /* xfer size */ + max5data(p->dl.speed, max5[3]), /* avrg dl speed */ + max5data(p->ul.speed, max5[4]), /* avrg ul speed */ time_total, /* 8 letters */ /* total time */ time_spent, /* 8 letters */ /* time spent */ time_left, /* 8 letters */ /* time left */ - max5data(data->progress.current_speed, max5[5]) + max5data(p->current_speed, max5[5]) ); /* we flush the output stream to make it appear as soon as possible */ @@ -578,21 +582,19 @@ static void progress_meter(struct Curl_easy *data) * Curl_pgrsUpdate() returns 0 for success or the value returned by the * progress callback! */ -int Curl_pgrsUpdate(struct Curl_easy *data) +static int pgrsupdate(struct Curl_easy *data, bool showprogress) { - struct curltime now = Curl_now(); /* what time is it */ - bool showprogress = progress_calc(data, now); if(!(data->progress.flags & PGRS_HIDE)) { if(data->set.fxferinfo) { int result; - /* There's a callback set, call that */ - Curl_set_in_callback(data, true); + /* There is a callback set, call that */ + Curl_set_in_callback(data, TRUE); result = data->set.fxferinfo(data->set.progress_client, - data->progress.size_dl, - data->progress.downloaded, - data->progress.size_ul, - data->progress.uploaded); - Curl_set_in_callback(data, false); + data->progress.dl.total_size, + data->progress.dl.cur_size, + data->progress.ul.total_size, + data->progress.ul.cur_size); + Curl_set_in_callback(data, FALSE); if(result != CURL_PROGRESSFUNC_CONTINUE) { if(result) failf(data, "Callback aborted"); @@ -602,13 +604,13 @@ int Curl_pgrsUpdate(struct Curl_easy *data) else if(data->set.fprogress) { int result; /* The older deprecated callback is set, call that */ - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); result = data->set.fprogress(data->set.progress_client, - (double)data->progress.size_dl, - (double)data->progress.downloaded, - (double)data->progress.size_ul, - (double)data->progress.uploaded); - Curl_set_in_callback(data, false); + (double)data->progress.dl.total_size, + (double)data->progress.dl.cur_size, + (double)data->progress.ul.total_size, + (double)data->progress.ul.cur_size); + Curl_set_in_callback(data, FALSE); if(result != CURL_PROGRESSFUNC_CONTINUE) { if(result) failf(data, "Callback aborted"); @@ -622,3 +624,19 @@ int Curl_pgrsUpdate(struct Curl_easy *data) return 0; } + +int Curl_pgrsUpdate(struct Curl_easy *data) +{ + struct curltime now = Curl_now(); /* what time is it */ + bool showprogress = progress_calc(data, now); + return pgrsupdate(data, showprogress); +} + +/* + * Update all progress, do not do progress meter/callbacks. + */ +void Curl_pgrsUpdate_nometer(struct Curl_easy *data) +{ + struct curltime now = Curl_now(); /* what time is it */ + (void)progress_calc(data, now); +} diff --git a/deps/curl/lib/progress.h b/deps/curl/lib/progress.h index 0049cd04bee..326271ef1e4 100644 --- a/deps/curl/lib/progress.h +++ b/deps/curl/lib/progress.h @@ -30,7 +30,8 @@ typedef enum { TIMER_NONE, TIMER_STARTOP, - TIMER_STARTSINGLE, + TIMER_STARTSINGLE, /* start of transfer, might get queued */ + TIMER_POSTQUEUE, /* start, immediately after dequeue */ TIMER_NAMELOOKUP, TIMER_CONNECT, TIMER_APPCONNECT, @@ -46,16 +47,19 @@ int Curl_pgrsDone(struct Curl_easy *data); void Curl_pgrsStartNow(struct Curl_easy *data); void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size); void Curl_pgrsSetUploadSize(struct Curl_easy *data, curl_off_t size); -void Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size); + +/* It is fine to not check the return code if 'size' is set to 0 */ +CURLcode Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size); + void Curl_pgrsSetUploadCounter(struct Curl_easy *data, curl_off_t size); void Curl_ratelimit(struct Curl_easy *data, struct curltime now); int Curl_pgrsUpdate(struct Curl_easy *data); +void Curl_pgrsUpdate_nometer(struct Curl_easy *data); + void Curl_pgrsResetTransferSizes(struct Curl_easy *data); struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer); -timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, - curl_off_t startsize, - curl_off_t limit, - struct curltime start, +timediff_t Curl_pgrsLimitWaitTime(struct pgrs_dir *d, + curl_off_t speed_limit, struct curltime now); /** * Update progress timer with the elapsed time from its start to `timestamp`. @@ -65,6 +69,8 @@ timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, struct curltime timestamp); +void Curl_pgrsEarlyData(struct Curl_easy *data, curl_off_t sent); + #define PGRS_HIDE (1<<4) #define PGRS_UL_SIZE_KNOWN (1<<5) #define PGRS_DL_SIZE_KNOWN (1<<6) diff --git a/deps/curl/lib/psl.c b/deps/curl/lib/psl.c index 626a203a6d7..0b88b05b4c7 100644 --- a/deps/curl/lib/psl.c +++ b/deps/curl/lib/psl.c @@ -81,7 +81,7 @@ const psl_ctx_t *Curl_psl_use(struct Curl_easy *easy) psl = psl_latest(NULL); dynamic = psl != NULL; /* Take care of possible time computation overflow. */ - expires = now < TIME_T_MAX - PSL_TTL? now + PSL_TTL: TIME_T_MAX; + expires = now < TIME_T_MAX - PSL_TTL ? now + PSL_TTL : TIME_T_MAX; /* Only get the built-in PSL if we do not already have the "latest". */ if(!psl && !pslcache->dynamic) diff --git a/deps/curl/lib/psl.h b/deps/curl/lib/psl.h index 23cfa921c4b..dd5bee21fbc 100644 --- a/deps/curl/lib/psl.h +++ b/deps/curl/lib/psl.h @@ -27,6 +27,8 @@ #ifdef USE_LIBPSL #include +struct Curl_easy; + #define PSL_TTL (72 * 3600) /* PSL time to live before a refresh. */ struct PslCache { diff --git a/deps/curl/lib/rand.c b/deps/curl/lib/rand.c index a5620ea6ebd..8d55e260a4b 100644 --- a/deps/curl/lib/rand.c +++ b/deps/curl/lib/rand.c @@ -24,16 +24,14 @@ #include "curl_setup.h" +#include + #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_ARPA_INET_H #include #endif -#ifdef HAVE_ARC4RANDOM -/* Some platforms might have the prototype missing (ubuntu + libressl) */ -uint32_t arc4random(void); -#endif #include #include "urldata.h" @@ -41,20 +39,17 @@ uint32_t arc4random(void); #include "sendf.h" #include "timeval.h" #include "rand.h" +#include "escape.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -#ifdef WIN32 - -#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) -# define HAVE_MINGW_ORIGINAL -#endif +#ifdef _WIN32 #if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x600 && \ - !defined(HAVE_MINGW_ORIGINAL) + !defined(CURL_WINDOWS_UWP) # define HAVE_WIN_BCRYPTGENRANDOM # include # ifdef _MSC_VER @@ -105,82 +100,91 @@ CURLcode Curl_win32_random(unsigned char *entropy, size_t length) } #endif -static CURLcode randit(struct Curl_easy *data, unsigned int *rnd) +#if !defined(USE_SSL) +/* ---- possibly non-cryptographic version following ---- */ +static CURLcode weak_random(struct Curl_easy *data, + unsigned char *entropy, + size_t length) /* always 4, size of int */ { unsigned int r; - CURLcode result = CURLE_OK; - static unsigned int randseed; - static bool seeded = FALSE; + DEBUGASSERT(length == sizeof(int)); -#ifdef CURLDEBUG - char *force_entropy = getenv("CURL_ENTROPY"); - if(force_entropy) { - if(!seeded) { - unsigned int seed = 0; - size_t elen = strlen(force_entropy); - size_t clen = sizeof(seed); - size_t min = elen < clen ? elen : clen; - memcpy((char *)&seed, force_entropy, min); - randseed = ntohl(seed); - seeded = TRUE; - } - else - randseed++; - *rnd = randseed; - return CURLE_OK; + /* Trying cryptographically secure functions first */ +#ifdef _WIN32 + (void)data; + { + CURLcode result = Curl_win32_random(entropy, length); + if(result != CURLE_NOT_BUILT_IN) + return result; } #endif - /* data may be NULL! */ - result = Curl_ssl_random(data, (unsigned char *)rnd, sizeof(*rnd)); - if(result != CURLE_NOT_BUILT_IN) - /* only if there is no random function in the TLS backend do the non crypto - version, otherwise return result */ - return result; - - /* ---- non-cryptographic version following ---- */ +#if defined(HAVE_ARC4RANDOM) + (void)data; + r = (unsigned int)arc4random(); + memcpy(entropy, &r, length); +#else + infof(data, "WARNING: using weak random seed"); + { + static unsigned int randseed; + static bool seeded = FALSE; + unsigned int rnd; + if(!seeded) { + struct curltime now = Curl_now(); + randseed += (unsigned int)now.tv_usec + (unsigned int)now.tv_sec; + randseed = randseed * 1103515245 + 12345; + randseed = randseed * 1103515245 + 12345; + randseed = randseed * 1103515245 + 12345; + seeded = TRUE; + } -#ifdef WIN32 - if(!seeded) { - result = Curl_win32_random((unsigned char *)rnd, sizeof(*rnd)); - if(result != CURLE_NOT_BUILT_IN) - return result; + /* Return an unsigned 32-bit pseudo-random number. */ + r = randseed = randseed * 1103515245 + 12345; + rnd = (r << 16) | ((r >> 16) & 0xFFFF); + memcpy(entropy, &rnd, length); } #endif - -#ifdef HAVE_ARC4RANDOM - *rnd = (unsigned int)arc4random(); return CURLE_OK; +} #endif -#if defined(RANDOM_FILE) && !defined(WIN32) - if(!seeded) { - /* if there's a random file to read a seed from, use it */ - int fd = open(RANDOM_FILE, O_RDONLY); - if(fd > -1) { - /* read random data into the randseed variable */ - ssize_t nread = read(fd, &randseed, sizeof(randseed)); - if(nread == sizeof(randseed)) +#ifdef USE_SSL +#define _random(x,y,z) Curl_ssl_random(x,y,z) +#else +#define _random(x,y,z) weak_random(x,y,z) +#endif + +static CURLcode randit(struct Curl_easy *data, unsigned int *rnd, + bool env_override) +{ +#ifdef DEBUGBUILD + if(env_override) { + char *force_entropy = getenv("CURL_ENTROPY"); + if(force_entropy) { + static unsigned int randseed; + static bool seeded = FALSE; + + if(!seeded) { + unsigned int seed = 0; + size_t elen = strlen(force_entropy); + size_t clen = sizeof(seed); + size_t min = elen < clen ? elen : clen; + memcpy((char *)&seed, force_entropy, min); + randseed = ntohl(seed); seeded = TRUE; - close(fd); + } + else + randseed++; + *rnd = randseed; + return CURLE_OK; } } +#else + (void)env_override; #endif - if(!seeded) { - struct curltime now = Curl_now(); - infof(data, "WARNING: using weak random seed"); - randseed += (unsigned int)now.tv_usec + (unsigned int)now.tv_sec; - randseed = randseed * 1103515245 + 12345; - randseed = randseed * 1103515245 + 12345; - randseed = randseed * 1103515245 + 12345; - seeded = TRUE; - } - - /* Return an unsigned 32-bit pseudo-random number. */ - r = randseed = randseed * 1103515245 + 12345; - *rnd = (r << 16) | ((r >> 16) & 0xFFFF); - return CURLE_OK; + /* data may be NULL! */ + return _random(data, (unsigned char *)rnd, sizeof(*rnd)); } /* @@ -188,7 +192,7 @@ static CURLcode randit(struct Curl_easy *data, unsigned int *rnd) * 'rnd' points to. * * If libcurl is built without TLS support or with a TLS backend that lacks a - * proper random API (rustls or mbedTLS), this function will use "weak" + * proper random API (Rustls or mbedTLS), this function will use "weak" * random. * * When built *with* TLS support and a backend that offers strong random, it @@ -199,17 +203,24 @@ static CURLcode randit(struct Curl_easy *data, unsigned int *rnd) * */ -CURLcode Curl_rand(struct Curl_easy *data, unsigned char *rnd, size_t num) +CURLcode Curl_rand_bytes(struct Curl_easy *data, +#ifdef DEBUGBUILD + bool env_override, +#endif + unsigned char *rnd, size_t num) { CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; +#ifndef DEBUGBUILD + const bool env_override = FALSE; +#endif - DEBUGASSERT(num > 0); + DEBUGASSERT(num); while(num) { unsigned int r; size_t left = num < sizeof(unsigned int) ? num : sizeof(unsigned int); - result = randit(data, &r); + result = randit(data, &r, env_override); if(result) return result; @@ -234,9 +245,7 @@ CURLcode Curl_rand_hex(struct Curl_easy *data, unsigned char *rnd, size_t num) { CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; - const char *hex = "0123456789abcdef"; unsigned char buffer[128]; - unsigned char *bufp = buffer; DEBUGASSERT(num > 1); #ifdef __clang_analyzer__ @@ -245,9 +254,11 @@ CURLcode Curl_rand_hex(struct Curl_easy *data, unsigned char *rnd, memset(buffer, 0, sizeof(buffer)); #endif - if((num/2 >= sizeof(buffer)) || !(num&1)) + if((num/2 >= sizeof(buffer)) || !(num&1)) { /* make sure it fits in the local buffer and that it is an odd number! */ + DEBUGF(infof(data, "invalid buffer size with Curl_rand_hex")); return CURLE_BAD_FUNCTION_ARGUMENT; + } num--; /* save one for null-termination */ @@ -255,13 +266,37 @@ CURLcode Curl_rand_hex(struct Curl_easy *data, unsigned char *rnd, if(result) return result; + Curl_hexencode(buffer, num/2, rnd, num + 1); + return result; +} + +/* + * Curl_rand_alnum() fills the 'rnd' buffer with a given 'num' size with random + * alphanumerical chars PLUS a null-terminating byte. + */ + +static const char alnum[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +CURLcode Curl_rand_alnum(struct Curl_easy *data, unsigned char *rnd, + size_t num) +{ + CURLcode result = CURLE_OK; + const unsigned int alnumspace = sizeof(alnum) - 1; + unsigned int r; + DEBUGASSERT(num > 1); + + num--; /* save one for null-termination */ + while(num) { - /* clang-tidy warns on this line without this comment: */ - /* NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult) */ - *rnd++ = hex[(*bufp & 0xF0)>>4]; - *rnd++ = hex[*bufp & 0x0F]; - bufp++; - num -= 2; + do { + result = randit(data, &r, TRUE); + if(result) + return result; + } while(r >= (UINT_MAX - UINT_MAX % alnumspace)); + + *rnd++ = (unsigned char)alnum[r % alnumspace]; + num--; } *rnd = 0; diff --git a/deps/curl/lib/rand.h b/deps/curl/lib/rand.h index 45ce3e7c4ea..2ba60e72976 100644 --- a/deps/curl/lib/rand.h +++ b/deps/curl/lib/rand.h @@ -24,7 +24,17 @@ * ***************************************************************************/ -CURLcode Curl_rand(struct Curl_easy *data, unsigned char *rnd, size_t num); +CURLcode Curl_rand_bytes(struct Curl_easy *data, +#ifdef DEBUGBUILD + bool allow_env_override, +#endif + unsigned char *rnd, size_t num); + +#ifdef DEBUGBUILD +#define Curl_rand(a,b,c) Curl_rand_bytes((a), TRUE, (b), (c)) +#else +#define Curl_rand(a,b,c) Curl_rand_bytes((a), (b), (c)) +#endif /* * Curl_rand_hex() fills the 'rnd' buffer with a given 'num' size with random @@ -34,7 +44,14 @@ CURLcode Curl_rand(struct Curl_easy *data, unsigned char *rnd, size_t num); CURLcode Curl_rand_hex(struct Curl_easy *data, unsigned char *rnd, size_t num); -#ifdef WIN32 +/* + * Curl_rand_alnum() fills the 'rnd' buffer with a given 'num' size with random + * alphanumerical chars PLUS a null-terminating byte. + */ +CURLcode Curl_rand_alnum(struct Curl_easy *data, unsigned char *rnd, + size_t num); + +#ifdef _WIN32 /* Random generator shared between the Schannel vtls and Curl_rand*() functions */ CURLcode Curl_win32_random(unsigned char *entropy, size_t length); diff --git a/deps/curl/lib/rename.c b/deps/curl/lib/rename.c index 97a66e947e8..8715a4306d9 100644 --- a/deps/curl/lib/rename.c +++ b/deps/curl/lib/rename.c @@ -40,8 +40,8 @@ /* return 0 on success, 1 on error */ int Curl_rename(const char *oldpath, const char *newpath) { -#ifdef WIN32 - /* rename() on Windows doesn't overwrite, so we can't use it here. +#ifdef _WIN32 + /* rename() on Windows does not overwrite, so we cannot use it here. MoveFileEx() will overwrite and is usually atomic, however it fails when there are open handles to the file. */ const int max_wait_ms = 1000; diff --git a/deps/curl/lib/request.c b/deps/curl/lib/request.c new file mode 100644 index 00000000000..310e4eac000 --- /dev/null +++ b/deps/curl/lib/request.c @@ -0,0 +1,479 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 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.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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "urldata.h" +#include "cfilters.h" +#include "dynbuf.h" +#include "doh.h" +#include "multiif.h" +#include "progress.h" +#include "request.h" +#include "sendf.h" +#include "transfer.h" +#include "url.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +void Curl_req_init(struct SingleRequest *req) +{ + memset(req, 0, sizeof(*req)); +} + +CURLcode Curl_req_soft_reset(struct SingleRequest *req, + struct Curl_easy *data) +{ + CURLcode result; + + req->done = FALSE; + req->upload_done = FALSE; + req->upload_aborted = FALSE; + req->download_done = FALSE; + req->eos_written = FALSE; + req->eos_read = FALSE; + req->eos_sent = FALSE; + req->ignorebody = FALSE; + req->shutdown = FALSE; + req->bytecount = 0; + req->writebytecount = 0; + req->header = TRUE; /* assume header */ + req->headerline = 0; + req->headerbytecount = 0; + req->allheadercount = 0; + req->deductheadercount = 0; + + result = Curl_client_start(data); + if(result) + return result; + + if(!req->sendbuf_init) { + Curl_bufq_init2(&req->sendbuf, data->set.upload_buffer_size, 1, + BUFQ_OPT_SOFT_LIMIT); + req->sendbuf_init = TRUE; + } + else { + Curl_bufq_reset(&req->sendbuf); + if(data->set.upload_buffer_size != req->sendbuf.chunk_size) { + Curl_bufq_free(&req->sendbuf); + Curl_bufq_init2(&req->sendbuf, data->set.upload_buffer_size, 1, + BUFQ_OPT_SOFT_LIMIT); + } + } + + return CURLE_OK; +} + +CURLcode Curl_req_start(struct SingleRequest *req, + struct Curl_easy *data) +{ + req->start = Curl_now(); + return Curl_req_soft_reset(req, data); +} + +static CURLcode req_flush(struct Curl_easy *data); + +CURLcode Curl_req_done(struct SingleRequest *req, + struct Curl_easy *data, bool aborted) +{ + (void)req; + if(!aborted) + (void)req_flush(data); + Curl_client_reset(data); +#ifndef CURL_DISABLE_DOH + Curl_doh_close(data); +#endif + return CURLE_OK; +} + +void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data) +{ + struct curltime t0 = {0, 0}; + + /* This is a bit ugly. `req->p` is a union and we assume we can + * free this safely without leaks. */ + Curl_safefree(req->p.ftp); + Curl_safefree(req->newurl); + Curl_client_reset(data); + if(req->sendbuf_init) + Curl_bufq_reset(&req->sendbuf); + +#ifndef CURL_DISABLE_DOH + Curl_doh_close(data); +#endif + /* Can no longer memset() this struct as we need to keep some state */ + req->size = -1; + req->maxdownload = -1; + req->bytecount = 0; + req->writebytecount = 0; + req->start = t0; + req->headerbytecount = 0; + req->allheadercount = 0; + req->deductheadercount = 0; + req->headerline = 0; + req->offset = 0; + req->httpcode = 0; + req->keepon = 0; + req->upgr101 = UPGR101_INIT; + req->timeofdoc = 0; + req->location = NULL; + req->newurl = NULL; +#ifndef CURL_DISABLE_COOKIES + req->setcookies = 0; +#endif + req->header = FALSE; + req->content_range = FALSE; + req->download_done = FALSE; + req->eos_written = FALSE; + req->eos_read = FALSE; + req->eos_sent = FALSE; + req->upload_done = FALSE; + req->upload_aborted = FALSE; + req->ignorebody = FALSE; + req->http_bodyless = FALSE; + req->chunk = FALSE; + req->ignore_cl = FALSE; + req->upload_chunky = FALSE; + req->getheader = FALSE; + req->no_body = data->set.opt_no_body; + req->authneg = FALSE; + req->shutdown = FALSE; +#ifdef USE_HYPER + req->bodywritten = FALSE; +#endif +} + +void Curl_req_free(struct SingleRequest *req, struct Curl_easy *data) +{ + /* This is a bit ugly. `req->p` is a union and we assume we can + * free this safely without leaks. */ + Curl_safefree(req->p.ftp); + Curl_safefree(req->newurl); + if(req->sendbuf_init) + Curl_bufq_free(&req->sendbuf); + Curl_client_cleanup(data); + +#ifndef CURL_DISABLE_DOH + Curl_doh_cleanup(data); +#endif +} + +static CURLcode xfer_send(struct Curl_easy *data, + const char *buf, size_t blen, + size_t hds_len, size_t *pnwritten) +{ + CURLcode result = CURLE_OK; + bool eos = FALSE; + + *pnwritten = 0; + DEBUGASSERT(hds_len <= blen); +#ifdef DEBUGBUILD + { + /* Allow debug builds to override this logic to force short initial + sends */ + size_t body_len = blen - hds_len; + char *p = getenv("CURL_SMALLREQSEND"); + if(p) { + size_t body_small = (size_t)strtoul(p, NULL, 10); + if(body_small && body_small < body_len) + blen = hds_len + body_small; + } + } +#endif + /* Make sure this does not send more body bytes than what the max send + speed says. The headers do not count to the max speed. */ + if(data->set.max_send_speed) { + size_t body_bytes = blen - hds_len; + if((curl_off_t)body_bytes > data->set.max_send_speed) + blen = hds_len + (size_t)data->set.max_send_speed; + } + + if(data->req.eos_read && + (Curl_bufq_is_empty(&data->req.sendbuf) || + Curl_bufq_len(&data->req.sendbuf) == blen)) { + DEBUGF(infof(data, "sending last upload chunk of %zu bytes", blen)); + eos = TRUE; + } + result = Curl_xfer_send(data, buf, blen, eos, pnwritten); + if(!result) { + if(eos && (blen == *pnwritten)) + data->req.eos_sent = TRUE; + if(*pnwritten) { + if(hds_len) + Curl_debug(data, CURLINFO_HEADER_OUT, (char *)buf, + CURLMIN(hds_len, *pnwritten)); + if(*pnwritten > hds_len) { + size_t body_len = *pnwritten - hds_len; + Curl_debug(data, CURLINFO_DATA_OUT, (char *)buf + hds_len, body_len); + data->req.writebytecount += body_len; + Curl_pgrsSetUploadCounter(data, data->req.writebytecount); + } + } + } + return result; +} + +static CURLcode req_send_buffer_flush(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + const unsigned char *buf; + size_t blen; + + while(Curl_bufq_peek(&data->req.sendbuf, &buf, &blen)) { + size_t nwritten, hds_len = CURLMIN(data->req.sendbuf_hds_len, blen); + result = xfer_send(data, (const char *)buf, blen, hds_len, &nwritten); + if(result) + break; + + Curl_bufq_skip(&data->req.sendbuf, nwritten); + if(hds_len) { + data->req.sendbuf_hds_len -= CURLMIN(hds_len, nwritten); + } + /* leave if we could not send all. Maybe network blocking or + * speed limits on transfer */ + if(nwritten < blen) + break; + } + return result; +} + +CURLcode Curl_req_set_upload_done(struct Curl_easy *data) +{ + DEBUGASSERT(!data->req.upload_done); + data->req.upload_done = TRUE; + data->req.keepon &= ~(KEEP_SEND|KEEP_SEND_TIMED); /* we are done sending */ + + Curl_pgrsTime(data, TIMER_POSTRANSFER); + Curl_creader_done(data, data->req.upload_aborted); + + if(data->req.upload_aborted) { + Curl_bufq_reset(&data->req.sendbuf); + if(data->req.writebytecount) + infof(data, "abort upload after having sent %" FMT_OFF_T " bytes", + data->req.writebytecount); + else + infof(data, "abort upload"); + } + else if(data->req.writebytecount) + infof(data, "upload completely sent off: %" FMT_OFF_T " bytes", + data->req.writebytecount); + else if(!data->req.download_done) { + DEBUGASSERT(Curl_bufq_is_empty(&data->req.sendbuf)); + infof(data, Curl_creader_total_length(data) ? + "We are completely uploaded and fine" : + "Request completely sent off"); + } + + return Curl_xfer_send_close(data); +} + +static CURLcode req_flush(struct Curl_easy *data) +{ + CURLcode result; + + if(!data || !data->conn) + return CURLE_FAILED_INIT; + + if(!Curl_bufq_is_empty(&data->req.sendbuf)) { + result = req_send_buffer_flush(data); + if(result) + return result; + if(!Curl_bufq_is_empty(&data->req.sendbuf)) { + DEBUGF(infof(data, "Curl_req_flush(len=%zu) -> EAGAIN", + Curl_bufq_len(&data->req.sendbuf))); + return CURLE_AGAIN; + } + } + else if(Curl_xfer_needs_flush(data)) { + DEBUGF(infof(data, "Curl_req_flush(), xfer send_pending")); + return Curl_xfer_flush(data); + } + + if(data->req.eos_read && !data->req.eos_sent) { + char tmp; + size_t nwritten; + result = xfer_send(data, &tmp, 0, 0, &nwritten); + if(result) + return result; + DEBUGASSERT(data->req.eos_sent); + } + + if(!data->req.upload_done && data->req.eos_read && data->req.eos_sent) { + DEBUGASSERT(Curl_bufq_is_empty(&data->req.sendbuf)); + if(data->req.shutdown) { + bool done; + result = Curl_xfer_send_shutdown(data, &done); + if(result && data->req.shutdown_err_ignore) { + infof(data, "Shutdown send direction error: %d. Broken server? " + "Proceeding as if everything is ok.", result); + result = CURLE_OK; + done = TRUE; + } + + if(result) + return result; + if(!done) + return CURLE_AGAIN; + } + return Curl_req_set_upload_done(data); + } + return CURLE_OK; +} + +static ssize_t add_from_client(void *reader_ctx, + unsigned char *buf, size_t buflen, + CURLcode *err) +{ + struct Curl_easy *data = reader_ctx; + size_t nread; + bool eos; + + *err = Curl_client_read(data, (char *)buf, buflen, &nread, &eos); + if(*err) + return -1; + if(eos) + data->req.eos_read = TRUE; + return (ssize_t)nread; +} + +#ifndef USE_HYPER + +static CURLcode req_send_buffer_add(struct Curl_easy *data, + const char *buf, size_t blen, + size_t hds_len) +{ + CURLcode result = CURLE_OK; + ssize_t n; + n = Curl_bufq_write(&data->req.sendbuf, + (const unsigned char *)buf, blen, &result); + if(n < 0) + return result; + /* We rely on a SOFTLIMIT on sendbuf, so it can take all data in */ + DEBUGASSERT((size_t)n == blen); + data->req.sendbuf_hds_len += hds_len; + return CURLE_OK; +} + +CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *req) +{ + CURLcode result; + const char *buf; + size_t blen, nwritten; + + if(!data || !data->conn) + return CURLE_FAILED_INIT; + + buf = Curl_dyn_ptr(req); + blen = Curl_dyn_len(req); + if(!Curl_creader_total_length(data)) { + /* Request without body. Try to send directly from the buf given. */ + data->req.eos_read = TRUE; + result = xfer_send(data, buf, blen, blen, &nwritten); + if(result) + return result; + buf += nwritten; + blen -= nwritten; + } + + if(blen) { + /* Either we have a request body, or we could not send the complete + * request in one go. Buffer the remainder and try to add as much + * body bytes as room is left in the buffer. Then flush. */ + result = req_send_buffer_add(data, buf, blen, blen); + if(result) + return result; + + return Curl_req_send_more(data); + } + return CURLE_OK; +} +#endif /* !USE_HYPER */ + +bool Curl_req_sendbuf_empty(struct Curl_easy *data) +{ + return !data->req.sendbuf_init || Curl_bufq_is_empty(&data->req.sendbuf); +} + +bool Curl_req_want_send(struct Curl_easy *data) +{ + /* Not done and + * - KEEP_SEND and not PAUSEd. + * - or request has buffered data to send + * - or transfer connection has pending data to send */ + return !data->req.done && + (((data->req.keepon & KEEP_SENDBITS) == KEEP_SEND) || + !Curl_req_sendbuf_empty(data) || + Curl_xfer_needs_flush(data)); +} + +bool Curl_req_done_sending(struct Curl_easy *data) +{ + return data->req.upload_done && !Curl_req_want_send(data); +} + +CURLcode Curl_req_send_more(struct Curl_easy *data) +{ + CURLcode result; + + /* Fill our send buffer if more from client can be read. */ + if(!data->req.upload_aborted && + !data->req.eos_read && + !(data->req.keepon & KEEP_SEND_PAUSE) && + !Curl_bufq_is_full(&data->req.sendbuf)) { + ssize_t nread = Curl_bufq_sipn(&data->req.sendbuf, 0, + add_from_client, data, &result); + if(nread < 0 && result != CURLE_AGAIN) + return result; + } + + result = req_flush(data); + if(result == CURLE_AGAIN) + result = CURLE_OK; + + return result; +} + +CURLcode Curl_req_abort_sending(struct Curl_easy *data) +{ + if(!data->req.upload_done) { + Curl_bufq_reset(&data->req.sendbuf); + data->req.upload_aborted = TRUE; + /* no longer KEEP_SEND and KEEP_SEND_PAUSE */ + data->req.keepon &= ~KEEP_SENDBITS; + return Curl_req_set_upload_done(data); + } + return CURLE_OK; +} + +CURLcode Curl_req_stop_send_recv(struct Curl_easy *data) +{ + /* stop receiving and ALL sending as well, including PAUSE and HOLD. + * We might still be paused on receive client writes though, so + * keep those bits around. */ + data->req.keepon &= ~(KEEP_RECV|KEEP_SENDBITS); + return Curl_req_abort_sending(data); +} diff --git a/deps/curl/lib/request.h b/deps/curl/lib/request.h new file mode 100644 index 00000000000..bb722477882 --- /dev/null +++ b/deps/curl/lib/request.h @@ -0,0 +1,251 @@ +#ifndef HEADER_CURL_REQUEST_H +#define HEADER_CURL_REQUEST_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 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.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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* This file is for lib internal stuff */ + +#include "curl_setup.h" + +#include "bufq.h" + +/* forward declarations */ +struct UserDefined; +#ifndef CURL_DISABLE_DOH +struct doh_probes; +#endif + +enum expect100 { + EXP100_SEND_DATA, /* enough waiting, just send the body now */ + EXP100_AWAITING_CONTINUE, /* waiting for the 100 Continue header */ + EXP100_SENDING_REQUEST, /* still sending the request but will wait for + the 100 header once done with the request */ + EXP100_FAILED /* used on 417 Expectation Failed */ +}; + +enum upgrade101 { + UPGR101_INIT, /* default state */ + UPGR101_WS, /* upgrade to WebSockets requested */ + UPGR101_H2, /* upgrade to HTTP/2 requested */ + UPGR101_RECEIVED, /* 101 response received */ + UPGR101_WORKING /* talking upgraded protocol */ +}; + + +/* + * Request specific data in the easy handle (Curl_easy). Previously, + * these members were on the connectdata struct but since a conn struct may + * now be shared between different Curl_easys, we store connection-specific + * data here. This struct only keeps stuff that is interesting for *this* + * request, as it will be cleared between multiple ones + */ +struct SingleRequest { + curl_off_t size; /* -1 if unknown at this point */ + curl_off_t maxdownload; /* in bytes, the maximum amount of data to fetch, + -1 means unlimited */ + curl_off_t bytecount; /* total number of bytes read */ + curl_off_t writebytecount; /* number of bytes written */ + + struct curltime start; /* transfer started at this time */ + unsigned int headerbytecount; /* received server headers (not CONNECT + headers) */ + unsigned int allheadercount; /* all received headers (server + CONNECT) */ + unsigned int deductheadercount; /* this amount of bytes does not count when + we check if anything has been transferred + at the end of a connection. We use this + counter to make only a 100 reply (without + a following second response code) result + in a CURLE_GOT_NOTHING error code */ + int headerline; /* counts header lines to better track the + first one */ + curl_off_t offset; /* possible resume offset read from the + Content-Range: header */ + int httpversion; /* Version in response (09, 10, 11, etc.) */ + int httpcode; /* error code from the 'HTTP/1.? XXX' or + 'RTSP/1.? XXX' line */ + int keepon; + enum upgrade101 upgr101; /* 101 upgrade state */ + + /* Client Writer stack, handles transfer- and content-encodings, protocol + * checks, pausing by client callbacks. */ + struct Curl_cwriter *writer_stack; + /* Client Reader stack, handles transfer- and content-encodings, protocol + * checks, pausing by client callbacks. */ + struct Curl_creader *reader_stack; + struct bufq sendbuf; /* data which needs to be send to the server */ + size_t sendbuf_hds_len; /* amount of header bytes in sendbuf */ + time_t timeofdoc; + char *location; /* This points to an allocated version of the Location: + header data */ + char *newurl; /* Set to the new URL to use when a redirect or a retry is + wanted */ + + /* Allocated protocol-specific data. Each protocol handler makes sure this + points to data it needs. */ + union { + struct FILEPROTO *file; + struct FTP *ftp; + struct IMAP *imap; + struct ldapreqinfo *ldap; + struct MQTT *mqtt; + struct POP3 *pop3; + struct RTSP *rtsp; + struct smb_request *smb; + struct SMTP *smtp; + struct SSHPROTO *ssh; + struct TELNET *telnet; + } p; +#ifndef CURL_DISABLE_DOH + struct doh_probes *doh; /* DoH specific data for this request */ +#endif +#ifndef CURL_DISABLE_COOKIES + unsigned char setcookies; +#endif + BIT(header); /* incoming data has HTTP header */ + BIT(done); /* request is done, e.g. no more send/recv should + * happen. This can be TRUE before `upload_done` or + * `download_done` is TRUE. */ + BIT(content_range); /* set TRUE if Content-Range: was found */ + BIT(download_done); /* set to TRUE when download is complete */ + BIT(eos_written); /* iff EOS has been written to client */ + BIT(eos_read); /* iff EOS has been read from the client */ + BIT(eos_sent); /* iff EOS has been sent to the server */ + BIT(rewind_read); /* iff reader needs rewind at next start */ + BIT(upload_done); /* set to TRUE when all request data has been sent */ + BIT(upload_aborted); /* set to TRUE when upload was aborted. Will also + * show `upload_done` as TRUE. */ + BIT(ignorebody); /* we read a response-body but we ignore it! */ + BIT(http_bodyless); /* HTTP response status code is between 100 and 199, + 204 or 304 */ + BIT(chunk); /* if set, this is a chunked transfer-encoding */ + BIT(resp_trailer); /* response carried 'Trailer:' header field */ + BIT(ignore_cl); /* ignore content-length */ + BIT(upload_chunky); /* set TRUE if we are doing chunked transfer-encoding + on upload */ + BIT(getheader); /* TRUE if header parsing is wanted */ + BIT(no_body); /* the response has no body */ + BIT(authneg); /* TRUE when the auth phase has started, which means + that we are creating a request with an auth header, + but it is not the final request in the auth + negotiation. */ + BIT(sendbuf_init); /* sendbuf is initialized */ + BIT(shutdown); /* request end will shutdown connection */ + BIT(shutdown_err_ignore); /* errors in shutdown will not fail request */ +#ifdef USE_HYPER + BIT(bodywritten); +#endif +}; + +/** + * Initialize the state of the request for first use. + */ +void Curl_req_init(struct SingleRequest *req); + +/** + * The request is about to start. Record time and do a soft reset. + */ +CURLcode Curl_req_start(struct SingleRequest *req, + struct Curl_easy *data); + +/** + * The request may continue with a follow up. Reset + * members, but keep start time for overall duration calc. + */ +CURLcode Curl_req_soft_reset(struct SingleRequest *req, + struct Curl_easy *data); + +/** + * The request is done. If not aborted, make sure that buffers are + * flushed to the client. + * @param req the request + * @param data the transfer + * @param aborted TRUE iff the request was aborted/errored + */ +CURLcode Curl_req_done(struct SingleRequest *req, + struct Curl_easy *data, bool aborted); + +/** + * Free the state of the request, not usable afterwards. + */ +void Curl_req_free(struct SingleRequest *req, struct Curl_easy *data); + +/** + * Hard reset the state of the request to virgin state base on + * transfer settings. + */ +void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data); + +#ifndef USE_HYPER +/** + * Send request headers. If not all could be sent + * they will be buffered. Use `Curl_req_flush()` to make sure + * bytes are really send. + * @param data the transfer making the request + * @param buf the complete header bytes, no body + * @return CURLE_OK (on blocking with *pnwritten == 0) or error. + */ +CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *buf); + +#endif /* !USE_HYPER */ + +/** + * TRUE iff the request has sent all request headers and data. + */ +bool Curl_req_done_sending(struct Curl_easy *data); + +/* + * Read more from client and flush all buffered request bytes. + * @return CURLE_OK on success or the error on the sending. + * Never returns CURLE_AGAIN. + */ +CURLcode Curl_req_send_more(struct Curl_easy *data); + +/** + * TRUE iff the request wants to send, e.g. has buffered bytes. + */ +bool Curl_req_want_send(struct Curl_easy *data); + +/** + * TRUE iff the request has no buffered bytes yet to send. + */ +bool Curl_req_sendbuf_empty(struct Curl_easy *data); + +/** + * Stop sending any more request data to the server. + * Will clear the send buffer and mark request sending as done. + */ +CURLcode Curl_req_abort_sending(struct Curl_easy *data); + +/** + * Stop sending and receiving any more request data. + * Will abort sending if not done. + */ +CURLcode Curl_req_stop_send_recv(struct Curl_easy *data); + +/** + * Invoked when all request data has been uploaded. + */ +CURLcode Curl_req_set_upload_done(struct Curl_easy *data); + +#endif /* HEADER_CURL_REQUEST_H */ diff --git a/deps/curl/lib/rtsp.c b/deps/curl/lib/rtsp.c index ccd7264b00e..0bd6ad6281e 100644 --- a/deps/curl/lib/rtsp.c +++ b/deps/curl/lib/rtsp.c @@ -45,8 +45,8 @@ #include "curl_memory.h" #include "memdebug.h" -#define RTP_PKT_LENGTH(p) ((((int)((unsigned char)((p)[2]))) << 8) | \ - ((int)((unsigned char)((p)[3])))) +#define RTP_PKT_LENGTH(p) ((((unsigned int)((unsigned char)((p)[2]))) << 8) | \ + ((unsigned int)((unsigned char)((p)[3])))) /* protocol-specific functions set up to be called by the main engine */ static CURLcode rtsp_do(struct Curl_easy *data, bool *done); @@ -58,16 +58,19 @@ static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks); /* - * Parse and write out any available RTP data. - * - * nread: amount of data left after k->str. will be modified if RTP - * data is parsed and k->str is moved up - * readmore: whether or not the RTP parser needs more data right away + * Parse and write out an RTSP response. + * @param data the transfer + * @param conn the connection + * @param buf data read from connection + * @param blen amount of data in buf + * @param is_eos TRUE iff this is the last write + * @param readmore out, TRUE iff complete buf was consumed and more data + * is needed */ -static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data, - struct connectdata *conn, - ssize_t *nread, - bool *readmore); +static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, + const char *buf, + size_t blen, + bool is_eos); static CURLcode rtsp_setup_connection(struct Curl_easy *data, struct connectdata *conn); @@ -76,7 +79,7 @@ static unsigned int rtsp_conncheck(struct Curl_easy *data, unsigned int checks_to_perform); /* this returns the socket to wait for in the DO and DOING state for the multi - interface and then we're always _sending_ a request and thus we wait for + interface and then we are always _sending_ a request and thus we wait for the single socket to become writable only */ static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks) @@ -88,16 +91,16 @@ static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn, } static -CURLcode rtp_client_write(struct Curl_easy *data, char *ptr, size_t len); +CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len); static -CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport); +CURLcode rtsp_parse_transport(struct Curl_easy *data, const char *transport); /* * RTSP handler interface. */ const struct Curl_handler Curl_handler_rtsp = { - "RTSP", /* scheme */ + "rtsp", /* scheme */ rtsp_setup_connection, /* setup_connection */ rtsp_do, /* do_it */ rtsp_done, /* done */ @@ -110,7 +113,8 @@ const struct Curl_handler Curl_handler_rtsp = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtsp_disconnect, /* disconnect */ - rtsp_rtp_readwrite, /* readwrite */ + rtsp_rtp_write_resp, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ rtsp_conncheck, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTSP, /* defport */ @@ -221,8 +225,6 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) Curl_RtspReq rtspreq = data->set.rtspreq; struct RTSP *rtsp = data->req.p.rtsp; struct dynbuf req_buffer; - curl_off_t postsize = 0; /* for ANNOUNCE and SET_PARAMETER */ - curl_off_t putsize = 0; /* for ANNOUNCE and SET_PARAMETER */ const char *p_request = NULL; const char *p_session_id = NULL; @@ -237,6 +239,8 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) const char *p_userpwd = NULL; *done = TRUE; + /* Initialize a dynamic send buffer */ + Curl_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER); rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq; rtsp->CSeq_recv = 0; @@ -257,7 +261,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) * Since all RTSP requests are included here, there is no need to * support custom requests like HTTP. **/ - data->req.no_body = TRUE; /* most requests don't contain a body */ + data->req.no_body = TRUE; /* most requests do not contain a body */ switch(rtspreq) { default: failf(data, "Got invalid RTSP request"); @@ -306,17 +310,19 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) } if(rtspreq == RTSPREQ_RECEIVE) { - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); - - return result; + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, TRUE); + goto out; } p_session_id = data->set.str[STRING_RTSP_SESSION_ID]; if(!p_session_id && - (rtspreq & ~(RTSPREQ_OPTIONS | RTSPREQ_DESCRIBE | RTSPREQ_SETUP))) { + (rtspreq & ~(Curl_RtspReq)(RTSPREQ_OPTIONS | + RTSPREQ_DESCRIBE | + RTSPREQ_SETUP))) { failf(data, "Refusing to issue an RTSP request [%s] without a session ID.", p_request); - return CURLE_BAD_FUNCTION_ARGUMENT; + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } /* Stream URI. Default to server '*' if not specified */ @@ -343,7 +349,8 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) else { failf(data, "Refusing to issue an RTSP SETUP without a Transport: header."); - return CURLE_BAD_FUNCTION_ARGUMENT; + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } p_transport = data->state.aptr.rtsp_transport; @@ -352,8 +359,8 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) /* Accept Headers for DESCRIBE requests */ if(rtspreq == RTSPREQ_DESCRIBE) { /* Accept Header */ - p_accept = Curl_checkheaders(data, STRCONST("Accept"))? - NULL:"Accept: application/sdp\r\n"; + p_accept = Curl_checkheaders(data, STRCONST("Accept")) ? + NULL : "Accept: application/sdp\r\n"; /* Accept-Encoding header */ if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && @@ -362,9 +369,10 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) data->state.aptr.accept_encoding = aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); - if(!data->state.aptr.accept_encoding) - return CURLE_OUT_OF_MEMORY; - + if(!data->state.aptr.accept_encoding) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } p_accept_encoding = data->state.aptr.accept_encoding; } } @@ -386,9 +394,11 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) result = Curl_http_output_auth(data, conn, p_request, HTTPREQ_GET, p_stream_uri, FALSE); if(result) - return result; + goto out; +#ifndef CURL_DISABLE_PROXY p_proxyuserpwd = data->state.aptr.proxyuserpwd; +#endif p_userpwd = data->state.aptr.userpwd; /* Referrer */ @@ -420,23 +430,22 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) */ if(Curl_checkheaders(data, STRCONST("CSeq"))) { failf(data, "CSeq cannot be set as a custom header."); - return CURLE_RTSP_CSEQ_ERROR; + result = CURLE_RTSP_CSEQ_ERROR; + goto out; } if(Curl_checkheaders(data, STRCONST("Session"))) { failf(data, "Session ID cannot be set as a custom header."); - return CURLE_BAD_FUNCTION_ARGUMENT; + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } - /* Initialize a dynamic send buffer */ - Curl_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER); - result = Curl_dyn_addf(&req_buffer, "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */ "CSeq: %ld\r\n", /* CSeq */ p_request, p_stream_uri, rtsp->CSeq_sent); if(result) - return result; + goto out; /* * Rather than do a normal alloc line, keep the session_id unformatted @@ -445,7 +454,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) if(p_session_id) { result = Curl_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id); if(result) - return result; + goto out; } /* @@ -477,44 +486,57 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) Curl_safefree(data->state.aptr.userpwd); if(result) - return result; + goto out; if((rtspreq == RTSPREQ_SETUP) || (rtspreq == RTSPREQ_DESCRIBE)) { result = Curl_add_timecondition(data, &req_buffer); if(result) - return result; + goto out; } result = Curl_add_custom_headers(data, FALSE, &req_buffer); if(result) - return result; + goto out; if(rtspreq == RTSPREQ_ANNOUNCE || rtspreq == RTSPREQ_SET_PARAMETER || rtspreq == RTSPREQ_GET_PARAMETER) { + curl_off_t req_clen; /* request content length */ if(data->state.upload) { - putsize = data->state.infilesize; + req_clen = data->state.infilesize; data->state.httpreq = HTTPREQ_PUT; - + result = Curl_creader_set_fread(data, req_clen); + if(result) + goto out; } else { - postsize = (data->state.infilesize != -1)? - data->state.infilesize: - (data->set.postfields? (curl_off_t)strlen(data->set.postfields):0); - data->state.httpreq = HTTPREQ_POST; + if(data->set.postfields) { + size_t plen = strlen(data->set.postfields); + req_clen = (curl_off_t)plen; + result = Curl_creader_set_buf(data, data->set.postfields, plen); + } + else if(data->state.infilesize >= 0) { + req_clen = data->state.infilesize; + result = Curl_creader_set_fread(data, req_clen); + } + else { + req_clen = 0; + result = Curl_creader_set_null(data); + } + if(result) + goto out; } - if(putsize > 0 || postsize > 0) { + if(req_clen > 0) { /* As stated in the http comments, it is probably not wise to * actually set a custom Content-Length in the headers */ if(!Curl_checkheaders(data, STRCONST("Content-Length"))) { result = - Curl_dyn_addf(&req_buffer, - "Content-Length: %" CURL_FORMAT_CURL_OFF_T"\r\n", - (data->state.upload ? putsize : postsize)); + Curl_dyn_addf(&req_buffer, "Content-Length: %" FMT_OFF_T"\r\n", + req_clen); if(result) - return result; + goto out; } if(rtspreq == RTSPREQ_SET_PARAMETER || @@ -524,7 +546,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) STRCONST("Content-Type: " "text/parameters\r\n")); if(result) - return result; + goto out; } } @@ -534,11 +556,9 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) STRCONST("Content-Type: " "application/sdp\r\n")); if(result) - return result; + goto out; } } - - data->state.expect100header = FALSE; /* RTSP posts are simple/small */ } else if(rtspreq == RTSPREQ_GET_PARAMETER) { /* Check for an empty GET_PARAMETER (heartbeat) request */ @@ -546,31 +566,26 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) data->req.no_body = TRUE; } } + else { + result = Curl_creader_set_null(data); + if(result) + goto out; + } - /* RTSP never allows chunked transfer */ - data->req.forbidchunk = TRUE; /* Finish the request buffer */ result = Curl_dyn_addn(&req_buffer, STRCONST("\r\n")); if(result) - return result; + goto out; - if(postsize > 0) { - result = Curl_dyn_addn(&req_buffer, data->set.postfields, - (size_t)postsize); - if(result) - return result; - } + Curl_xfer_setup1(data, CURL_XFER_SENDRECV, -1, TRUE); /* issue the request */ - result = Curl_buffer_send(&req_buffer, data, data->req.p.http, - &data->info.request_size, 0, FIRSTSOCKET); + result = Curl_req_send(data, &req_buffer); if(result) { failf(data, "Failed sending RTSP request"); - return result; + goto out; } - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, putsize?FIRSTSOCKET:-1); - /* Increment the CSeq on success */ data->state.rtsp_next_client_CSeq++; @@ -581,157 +596,285 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; } - +out: + Curl_dyn_free(&req_buffer); return result; } - -static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data, - struct connectdata *conn, - ssize_t *nread, - bool *readmore) { - struct SingleRequest *k = &data->req; - struct rtsp_conn *rtspc = &(conn->proto.rtspc); - unsigned char *rtp_channel_mask = data->state.rtp_channel_mask; - - char *rtp; /* moving pointer to rtp data */ - ssize_t rtp_dataleft; /* how much data left to parse in this round */ - CURLcode result; - bool interleaved = false; - size_t skip_size = 0; - - if(Curl_dyn_len(&rtspc->buf)) { - /* There was some leftover data the last time. Append new buffers */ - if(Curl_dyn_addn(&rtspc->buf, k->str, *nread)) - return CURLE_OUT_OF_MEMORY; - rtp = Curl_dyn_ptr(&rtspc->buf); - rtp_dataleft = Curl_dyn_len(&rtspc->buf); +/** + * write any BODY bytes missing to the client, ignore the rest. + */ +static CURLcode rtp_write_body_junk(struct Curl_easy *data, + const char *buf, + size_t blen) +{ + struct rtsp_conn *rtspc = &(data->conn->proto.rtspc); + curl_off_t body_remain; + bool in_body; + + in_body = (data->req.headerline && !rtspc->in_header) && + (data->req.size >= 0) && + (data->req.bytecount < data->req.size); + body_remain = in_body ? (data->req.size - data->req.bytecount) : 0; + DEBUGASSERT(body_remain >= 0); + if(body_remain) { + if((curl_off_t)blen > body_remain) + blen = (size_t)body_remain; + return Curl_client_write(data, CLIENTWRITE_BODY, (char *)buf, blen); } - else { - /* Just parse the request buffer directly */ - rtp = k->str; - rtp_dataleft = *nread; - } - - while(rtp_dataleft > 0) { - if(rtp[0] == '$') { - if(rtp_dataleft > 4) { - unsigned char rtp_channel; - int rtp_length; - int idx; - int off; - - /* Parse the header */ - /* The channel identifier immediately follows and is 1 byte */ - rtp_channel = (unsigned char)rtp[1]; - idx = rtp_channel / 8; - off = rtp_channel % 8; - if(!(rtp_channel_mask[idx] & (1 << off))) { - /* invalid channel number, maybe not an RTP packet */ - rtp++; - rtp_dataleft--; - skip_size++; - continue; + return CURLE_OK; +} + +static CURLcode rtsp_filter_rtp(struct Curl_easy *data, + const char *buf, + size_t blen, + size_t *pconsumed) +{ + struct rtsp_conn *rtspc = &(data->conn->proto.rtspc); + CURLcode result = CURLE_OK; + size_t skip_len = 0; + + *pconsumed = 0; + while(blen) { + bool in_body = (data->req.headerline && !rtspc->in_header) && + (data->req.size >= 0) && + (data->req.bytecount < data->req.size); + switch(rtspc->state) { + + case RTP_PARSE_SKIP: { + DEBUGASSERT(Curl_dyn_len(&rtspc->buf) == 0); + while(blen && buf[0] != '$') { + if(!in_body && buf[0] == 'R' && + data->set.rtspreq != RTSPREQ_RECEIVE) { + if(strncmp(buf, "RTSP/", (blen < 5) ? blen : 5) == 0) { + /* This could be the next response, no consume and return */ + if(*pconsumed) { + DEBUGF(infof(data, "RTP rtsp_filter_rtp[SKIP] RTSP/ prefix, " + "skipping %zd bytes of junk", *pconsumed)); + } + rtspc->state = RTP_PARSE_SKIP; + rtspc->in_header = TRUE; + goto out; + } } - if(skip_size > 0) { - DEBUGF(infof(data, "Skip the malformed interleaved data %lu " - "bytes", skip_size)); + /* junk/BODY, consume without buffering */ + *pconsumed += 1; + ++buf; + --blen; + ++skip_len; + } + if(blen && buf[0] == '$') { + /* possible start of an RTP message, buffer */ + if(skip_len) { + /* end of junk/BODY bytes, flush */ + result = rtp_write_body_junk(data, + (char *)(buf - skip_len), skip_len); + skip_len = 0; + if(result) + goto out; } - skip_size = 0; - rtspc->rtp_channel = rtp_channel; - - /* The length is two bytes */ - rtp_length = RTP_PKT_LENGTH(rtp); + if(Curl_dyn_addn(&rtspc->buf, buf, 1)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + *pconsumed += 1; + ++buf; + --blen; + rtspc->state = RTP_PARSE_CHANNEL; + } + break; + } - if(rtp_dataleft < rtp_length + 4) { - /* Need more - incomplete payload */ - *readmore = TRUE; - break; + case RTP_PARSE_CHANNEL: { + int idx = ((unsigned char)buf[0]) / 8; + int off = ((unsigned char)buf[0]) % 8; + DEBUGASSERT(Curl_dyn_len(&rtspc->buf) == 1); + if(!(data->state.rtp_channel_mask[idx] & (1 << off))) { + /* invalid channel number, junk or BODY data */ + rtspc->state = RTP_PARSE_SKIP; + DEBUGASSERT(skip_len == 0); + /* we do not consume this byte, it is BODY data */ + DEBUGF(infof(data, "RTSP: invalid RTP channel %d, skipping", idx)); + if(*pconsumed == 0) { + /* We did not consume the initial '$' in our buffer, but had + * it from an earlier call. We cannot un-consume it and have + * to write it directly as BODY data */ + result = rtp_write_body_junk(data, Curl_dyn_ptr(&rtspc->buf), 1); + if(result) + goto out; } - interleaved = true; - /* We have the full RTP interleaved packet - * Write out the header including the leading '$' */ - DEBUGF(infof(data, "RTP write channel %d rtp_length %d", - rtspc->rtp_channel, rtp_length)); - result = rtp_client_write(data, &rtp[0], rtp_length + 4); - if(result) { - *readmore = FALSE; - return result; + else { + /* count the '$' as skip and continue */ + skip_len = 1; } + Curl_dyn_free(&rtspc->buf); + break; + } + /* a valid channel, so we expect this to be a real RTP message */ + rtspc->rtp_channel = (unsigned char)buf[0]; + if(Curl_dyn_addn(&rtspc->buf, buf, 1)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + *pconsumed += 1; + ++buf; + --blen; + rtspc->state = RTP_PARSE_LEN; + break; + } - /* Move forward in the buffer */ - rtp_dataleft -= rtp_length + 4; - rtp += rtp_length + 4; + case RTP_PARSE_LEN: { + size_t rtp_len = Curl_dyn_len(&rtspc->buf); + const char *rtp_buf; + DEBUGASSERT(rtp_len >= 2 && rtp_len < 4); + if(Curl_dyn_addn(&rtspc->buf, buf, 1)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + *pconsumed += 1; + ++buf; + --blen; + if(rtp_len == 2) + break; + rtp_buf = Curl_dyn_ptr(&rtspc->buf); + rtspc->rtp_len = RTP_PKT_LENGTH(rtp_buf) + 4; + rtspc->state = RTP_PARSE_DATA; + break; + } - if(data->set.rtspreq == RTSPREQ_RECEIVE) { - /* If we are in a passive receive, give control back - * to the app as often as we can. - */ - k->keepon &= ~KEEP_RECV; + case RTP_PARSE_DATA: { + size_t rtp_len = Curl_dyn_len(&rtspc->buf); + size_t needed; + DEBUGASSERT(rtp_len < rtspc->rtp_len); + needed = rtspc->rtp_len - rtp_len; + if(needed <= blen) { + if(Curl_dyn_addn(&rtspc->buf, buf, needed)) { + result = CURLE_OUT_OF_MEMORY; + goto out; } + *pconsumed += needed; + buf += needed; + blen -= needed; + /* complete RTP message in buffer */ + DEBUGF(infof(data, "RTP write channel %d rtp_len %zu", + rtspc->rtp_channel, rtspc->rtp_len)); + result = rtp_client_write(data, Curl_dyn_ptr(&rtspc->buf), + rtspc->rtp_len); + Curl_dyn_free(&rtspc->buf); + rtspc->state = RTP_PARSE_SKIP; + if(result) + goto out; } else { - /* Need more - incomplete header */ - *readmore = TRUE; - break; - } - } - else { - /* If the following data begins with 'RTSP/', which might be an RTSP - message, we should stop skipping the data. */ - /* If `k-> headerline> 0 && !interleaved` is true, we are maybe in the - middle of an RTSP message. It is difficult to determine this, so we - stop skipping. */ - size_t prefix_len = (rtp_dataleft < 5) ? rtp_dataleft : 5; - if((k->headerline > 0 && !interleaved) || - strncmp(rtp, "RTSP/", prefix_len) == 0) { - if(skip_size > 0) { - DEBUGF(infof(data, "Skip the malformed interleaved data %lu " - "bytes", skip_size)); + if(Curl_dyn_addn(&rtspc->buf, buf, blen)) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - break; /* maybe is an RTSP message */ + *pconsumed += blen; + buf += blen; + blen = 0; } - /* Skip incorrect data util the next RTP packet or RTSP message */ - do { - rtp++; - rtp_dataleft--; - skip_size++; - } while(rtp_dataleft > 0 && rtp[0] != '$' && rtp[0] != 'R'); + break; + } + + default: + DEBUGASSERT(0); + return CURLE_RECV_ERROR; } } +out: + if(!result && skip_len) + result = rtp_write_body_junk(data, (char *)(buf - skip_len), skip_len); + return result; +} - if(rtp_dataleft && rtp[0] == '$') { - DEBUGF(infof(data, "RTP Rewinding %zd %s", rtp_dataleft, - *readmore ? "(READMORE)" : "")); +static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, + const char *buf, + size_t blen, + bool is_eos) +{ + struct rtsp_conn *rtspc = &(data->conn->proto.rtspc); + CURLcode result = CURLE_OK; + size_t consumed = 0; - /* Store the incomplete RTP packet for a "rewind" */ - if(!Curl_dyn_len(&rtspc->buf)) { - /* nothing was stored, add this data */ - if(Curl_dyn_addn(&rtspc->buf, rtp, rtp_dataleft)) - return CURLE_OUT_OF_MEMORY; - } - else { - /* keep the remainder */ - Curl_dyn_tail(&rtspc->buf, rtp_dataleft); - } + if(!data->req.header) + rtspc->in_header = FALSE; + if(!blen) { + goto out; + } - /* As far as the transfer is concerned, this data is consumed */ - *nread = 0; - return CURLE_OK; + DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, eos=%d)", + blen, rtspc->in_header, is_eos)); + + /* If header parsing is not ongoing, extract RTP messages */ + if(!rtspc->in_header) { + result = rtsp_filter_rtp(data, buf, blen, &consumed); + if(result) + goto out; + buf += consumed; + blen -= consumed; + /* either we consumed all or are at the start of header parsing */ + if(blen && !data->req.header) + DEBUGF(infof(data, "RTSP: %zu bytes, possibly excess in response body", + blen)); } - /* Fix up k->str to point just after the last RTP packet */ - k->str += *nread - rtp_dataleft; - *nread = rtp_dataleft; + /* we want to parse headers, do so */ + if(data->req.header && blen) { + rtspc->in_header = TRUE; + result = Curl_http_write_resp_hds(data, buf, blen, &consumed); + if(result) + goto out; - /* If we get here, we have finished with the leftover/merge buffer */ - Curl_dyn_free(&rtspc->buf); + buf += consumed; + blen -= consumed; - return CURLE_OK; + if(!data->req.header) + rtspc->in_header = FALSE; + + if(!rtspc->in_header) { + /* If header parsing is done, extract interleaved RTP messages */ + if(data->req.size <= -1) { + /* Respect section 4.4 of rfc2326: If the Content-Length header is + absent, a length 0 must be assumed. */ + data->req.size = 0; + data->req.download_done = TRUE; + } + result = rtsp_filter_rtp(data, buf, blen, &consumed); + if(result) + goto out; + blen -= consumed; + } + } + + if(rtspc->state != RTP_PARSE_SKIP) + data->req.done = FALSE; + /* we SHOULD have consumed all bytes, unless the response is borked. + * In which case we write out the left over bytes, letting the client + * writer deal with it (it will report EXCESS and fail the transfer). */ + DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, done=%d " + " rtspc->state=%d, req.size=%" FMT_OFF_T ")", + blen, rtspc->in_header, data->req.done, rtspc->state, + data->req.size)); + if(!result && (is_eos || blen)) { + result = Curl_client_write(data, CLIENTWRITE_BODY| + (is_eos ? CLIENTWRITE_EOS : 0), + (char *)buf, blen); + } + +out: + if((data->set.rtspreq == RTSPREQ_RECEIVE) && + (rtspc->state == RTP_PARSE_SKIP)) { + /* In special mode RECEIVE, we just process one chunk of network + * data, so we stop the transfer here, if we have no incomplete + * RTP message pending. */ + data->req.download_done = TRUE; + } + return result; } static -CURLcode rtp_client_write(struct Curl_easy *data, char *ptr, size_t len) +CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len) { size_t wrote; curl_write_callback writeit; @@ -755,9 +898,9 @@ CURLcode rtp_client_write(struct Curl_easy *data, char *ptr, size_t len) user_ptr = data->set.out; } - Curl_set_in_callback(data, true); - wrote = writeit(ptr, 1, len, user_ptr); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, TRUE); + wrote = writeit((char *)ptr, 1, len, user_ptr); + Curl_set_in_callback(data, FALSE); if(CURL_WRITEFUNC_PAUSE == wrote) { failf(data, "Cannot pause RTP"); @@ -772,12 +915,12 @@ CURLcode rtp_client_write(struct Curl_easy *data, char *ptr, size_t len) return CURLE_OK; } -CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) +CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header) { if(checkprefix("CSeq:", header)) { long CSeq = 0; char *endp; - char *p = &header[5]; + const char *p = &header[5]; while(ISBLANK(*p)) p++; CSeq = strtol(p, &endp, 10); @@ -792,8 +935,7 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) } } else if(checkprefix("Session:", header)) { - char *start; - char *end; + const char *start, *end; size_t idlen; /* Find the first non-space letter */ @@ -809,7 +951,7 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) /* Find the end of Session ID * * Allow any non whitespace content, up to the field separator or end of - * line. RFC 2326 isn't 100% clear on the session ID and for example + * line. RFC 2326 is not 100% clear on the session ID and for example * gstreamer does url-encoded session ID's not covered by the standard. */ end = start; @@ -821,7 +963,7 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) /* If the Session ID is set, then compare */ if(strlen(data->set.str[STRING_RTSP_SESSION_ID]) != idlen || - strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], idlen) != 0) { + strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], idlen)) { failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]", start, data->set.str[STRING_RTSP_SESSION_ID]); return CURLE_RTSP_SESSION_ERROR; @@ -833,11 +975,9 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) */ /* Copy the id substring into a new buffer */ - data->set.str[STRING_RTSP_SESSION_ID] = malloc(idlen + 1); + data->set.str[STRING_RTSP_SESSION_ID] = Curl_memdup0(start, idlen); if(!data->set.str[STRING_RTSP_SESSION_ID]) return CURLE_OUT_OF_MEMORY; - memcpy(data->set.str[STRING_RTSP_SESSION_ID], start, idlen); - (data->set.str[STRING_RTSP_SESSION_ID])[idlen] = '\0'; } } else if(checkprefix("Transport:", header)) { @@ -850,14 +990,13 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) } static -CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport) +CURLcode rtsp_parse_transport(struct Curl_easy *data, const char *transport) { /* If we receive multiple Transport response-headers, the linterleaved channels of each response header is recorded and used together for subsequent data validity checks.*/ /* e.g.: ' RTP/AVP/TCP;unicast;interleaved=5-6' */ - char *start; - char *end; + const char *start, *end; start = transport; while(start && *start) { while(*start && ISBLANK(*start) ) @@ -866,7 +1005,7 @@ CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport) if(checkprefix("interleaved=", start)) { long chan1, chan2, chan; char *endp; - char *p = start + 12; + const char *p = start + 12; chan1 = strtol(p, &endp, 10); if(p != endp && chan1 >= 0 && chan1 <= 255) { unsigned char *rtp_channel_mask = data->state.rtp_channel_mask; diff --git a/deps/curl/lib/rtsp.h b/deps/curl/lib/rtsp.h index 111bac2a612..41b09503ffb 100644 --- a/deps/curl/lib/rtsp.h +++ b/deps/curl/lib/rtsp.h @@ -31,7 +31,7 @@ extern const struct Curl_handler Curl_handler_rtsp; -CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header); +CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header); #else /* disabled */ @@ -39,6 +39,12 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header); #endif /* CURL_DISABLE_RTSP */ +typedef enum { + RTP_PARSE_SKIP, + RTP_PARSE_CHANNEL, + RTP_PARSE_LEN, + RTP_PARSE_DATA +} rtp_parse_st; /* * RTSP Connection data * @@ -47,22 +53,15 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header); struct rtsp_conn { struct dynbuf buf; int rtp_channel; + size_t rtp_len; + rtp_parse_st state; + BIT(in_header); }; /**************************************************************************** * RTSP unique setup ***************************************************************************/ struct RTSP { - /* - * http_wrapper MUST be the first element of this structure for the wrap - * logic to work. In this way, we get a cheap polymorphism because - * &(data->state.proto.rtsp) == &(data->state.proto.http) per the C spec - * - * HTTP functions can safely treat this as an HTTP struct, but RTSP aware - * functions can also index into the later elements. - */ - struct HTTP http_wrapper; /* wrap HTTP to do the heavy lifting */ - long CSeq_sent; /* CSeq of this request */ long CSeq_recv; /* CSeq received */ }; diff --git a/deps/curl/lib/select.c b/deps/curl/lib/select.c index cae9beb6c7c..c1779ad1e51 100644 --- a/deps/curl/lib/select.c +++ b/deps/curl/lib/select.c @@ -24,6 +24,10 @@ #include "curl_setup.h" +#if !defined(HAVE_SELECT) && !defined(HAVE_POLL) +#error "We cannot compile without select() or poll() support." +#endif + #include #ifdef HAVE_SYS_SELECT_H @@ -32,10 +36,6 @@ #include #endif -#if !defined(HAVE_SELECT) && !defined(HAVE_POLL_FINE) -#error "We can't compile without select() or poll() support." -#endif - #ifdef MSDOS #include /* delay() */ #endif @@ -47,18 +47,21 @@ #include "select.h" #include "timediff.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" /* - * Internal function used for waiting a specific amount of ms - * in Curl_socket_check() and Curl_poll() when no file descriptor - * is provided to wait on, just being used to delay execution. - * WinSock select() and poll() timeout mechanisms need a valid - * socket descriptor in a not null file descriptor set to work. - * Waiting indefinitely with this function is not allowed, a - * zero or negative timeout value will return immediately. - * Timeout resolution, accuracy, as well as maximum supported - * value is system dependent, neither factor is a critical issue - * for the intended use of this function in the library. + * Internal function used for waiting a specific amount of ms in + * Curl_socket_check() and Curl_poll() when no file descriptor is provided to + * wait on, just being used to delay execution. Winsock select() and poll() + * timeout mechanisms need a valid socket descriptor in a not null file + * descriptor set to work. Waiting indefinitely with this function is not + * allowed, a zero or negative timeout value will return immediately. Timeout + * resolution, accuracy, as well as maximum supported value is system + * dependent, neither factor is a critical issue for the intended use of this + * function in the library. * * Return values: * -1 = system call error, or invalid timeout value @@ -76,29 +79,22 @@ int Curl_wait_ms(timediff_t timeout_ms) } #if defined(MSDOS) delay(timeout_ms); -#elif defined(WIN32) +#elif defined(_WIN32) /* prevent overflow, timeout_ms is typecast to ULONG/DWORD. */ #if TIMEDIFF_T_MAX >= ULONG_MAX if(timeout_ms >= ULONG_MAX) timeout_ms = ULONG_MAX-1; - /* don't use ULONG_MAX, because that is equal to INFINITE */ + /* do not use ULONG_MAX, because that is equal to INFINITE */ #endif Sleep((ULONG)timeout_ms); #else -#if defined(HAVE_POLL_FINE) - /* prevent overflow, timeout_ms is typecast to int. */ -#if TIMEDIFF_T_MAX > INT_MAX - if(timeout_ms > INT_MAX) - timeout_ms = INT_MAX; -#endif - r = poll(NULL, 0, (int)timeout_ms); -#else + /* avoid using poll() for this since it behaves incorrectly with no sockets + on Apple operating systems */ { struct timeval pending_tv; r = select(0, NULL, NULL, NULL, curlx_mstotv(&pending_tv, timeout_ms)); } -#endif /* HAVE_POLL_FINE */ -#endif /* USE_WINSOCK */ +#endif /* _WIN32 */ if(r) { if((r == -1) && (SOCKERRNO == EINTR)) /* make EINTR from select or poll not a "lethal" error */ @@ -109,12 +105,12 @@ int Curl_wait_ms(timediff_t timeout_ms) return r; } -#ifndef HAVE_POLL_FINE +#ifndef HAVE_POLL /* - * This is a wrapper around select() to aid in Windows compatibility. - * A negative timeout value makes this function wait indefinitely, - * unless no valid file descriptor is given, when this happens the - * negative timeout is ignored and the function times out immediately. + * This is a wrapper around select() to aid in Windows compatibility. A + * negative timeout value makes this function wait indefinitely, unless no + * valid file descriptor is given, when this happens the negative timeout is + * ignored and the function times out immediately. * * Return values: * -1 = system call error or fd >= FD_SETSIZE @@ -131,7 +127,7 @@ static int our_select(curl_socket_t maxfd, /* highest socket number */ struct timeval *ptimeout; #ifdef USE_WINSOCK - /* WinSock select() can't handle zero events. See the comment below. */ + /* Winsock select() cannot handle zero events. See the comment below. */ if((!fds_read || fds_read->fd_count == 0) && (!fds_write || fds_write->fd_count == 0) && (!fds_err || fds_err->fd_count == 0)) { @@ -143,16 +139,16 @@ static int our_select(curl_socket_t maxfd, /* highest socket number */ ptimeout = curlx_mstotv(&pending_tv, timeout_ms); #ifdef USE_WINSOCK - /* WinSock select() must not be called with an fd_set that contains zero - fd flags, or it will return WSAEINVAL. But, it also can't be called + /* Winsock select() must not be called with an fd_set that contains zero + fd flags, or it will return WSAEINVAL. But, it also cannot be called with no fd_sets at all! From the documentation: Any two of the parameters, readfds, writefds, or exceptfds, can be given as null. At least one must be non-null, and any non-null descriptor set must contain at least one handle to a socket. - It is unclear why WinSock doesn't just handle this for us instead of - calling this an error. Luckily, with WinSock, we can _also_ ask how + It is unclear why Winsock does not just handle this for us instead of + calling this an error. Luckily, with Winsock, we can _also_ ask how many bits are set on an fd_set. So, let's just check it beforehand. */ return select((int)maxfd + 1, @@ -168,13 +164,13 @@ static int our_select(curl_socket_t maxfd, /* highest socket number */ /* * Wait for read or write events on a set of file descriptors. It uses poll() - * when a fine poll() is available, in order to avoid limits with FD_SETSIZE, - * otherwise select() is used. An error is returned if select() is being used + * when poll() is available, in order to avoid limits with FD_SETSIZE, + * otherwise select() is used. An error is returned if select() is being used * and a file descriptor is too large for FD_SETSIZE. * - * A negative timeout value makes this function wait indefinitely, - * unless no valid file descriptor is given, when this happens the - * negative timeout is ignored and the function times out immediately. + * A negative timeout value makes this function wait indefinitely, unless no + * valid file descriptor is given, when this happens the negative timeout is + * ignored and the function times out immediately. * * Return values: * -1 = system call error or fd >= FD_SETSIZE @@ -226,7 +222,7 @@ int Curl_socket_check(curl_socket_t readfd0, /* two sockets to read from */ num++; } - r = Curl_poll(pfd, num, timeout_ms); + r = Curl_poll(pfd, (unsigned int)num, timeout_ms); if(r <= 0) return r; @@ -257,8 +253,8 @@ int Curl_socket_check(curl_socket_t readfd0, /* two sockets to read from */ } /* - * This is a wrapper around poll(). If poll() does not exist, then - * select() is used instead. An error is returned if select() is + * This is a wrapper around poll(). If poll() does not exist, then + * select() is used instead. An error is returned if select() is * being used and a file descriptor is too large for FD_SETSIZE. * A negative timeout value makes this function wait indefinitely, * unless no valid file descriptor is given, when this happens the @@ -271,7 +267,7 @@ int Curl_socket_check(curl_socket_t readfd0, /* two sockets to read from */ */ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) { -#ifdef HAVE_POLL_FINE +#ifdef HAVE_POLL int pending_ms; #else fd_set fds_read; @@ -301,7 +297,7 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) when function is called with a zero timeout or a negative timeout value indicating a blocking call should be performed. */ -#ifdef HAVE_POLL_FINE +#ifdef HAVE_POLL /* prevent overflow, timeout_ms is typecast to int. */ #if TIMEDIFF_T_MAX > INT_MAX @@ -331,7 +327,7 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) ufds[i].revents |= POLLIN|POLLOUT; } -#else /* HAVE_POLL_FINE */ +#else /* HAVE_POLL */ FD_ZERO(&fds_read); FD_ZERO(&fds_write); @@ -357,8 +353,8 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) } /* - Note also that WinSock ignores the first argument, so we don't worry - about the fact that maxfd is computed incorrectly with WinSock (since + Note also that Winsock ignores the first argument, so we do not worry + about the fact that maxfd is computed incorrectly with Winsock (since curl_socket_t is unsigned in such cases and thus -1 is the largest value). */ @@ -397,7 +393,151 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) r++; } -#endif /* HAVE_POLL_FINE */ +#endif /* HAVE_POLL */ return r; } + +void Curl_pollfds_init(struct curl_pollfds *cpfds, + struct pollfd *static_pfds, + unsigned int static_count) +{ + DEBUGASSERT(cpfds); + memset(cpfds, 0, sizeof(*cpfds)); + if(static_pfds && static_count) { + cpfds->pfds = static_pfds; + cpfds->count = static_count; + } +} + +void Curl_pollfds_cleanup(struct curl_pollfds *cpfds) +{ + DEBUGASSERT(cpfds); + if(cpfds->allocated_pfds) { + free(cpfds->pfds); + } + memset(cpfds, 0, sizeof(*cpfds)); +} + +static CURLcode cpfds_increase(struct curl_pollfds *cpfds, unsigned int inc) +{ + struct pollfd *new_fds; + unsigned int new_count = cpfds->count + inc; + + new_fds = calloc(new_count, sizeof(struct pollfd)); + if(!new_fds) + return CURLE_OUT_OF_MEMORY; + + memcpy(new_fds, cpfds->pfds, cpfds->count * sizeof(struct pollfd)); + if(cpfds->allocated_pfds) + free(cpfds->pfds); + cpfds->pfds = new_fds; + cpfds->count = new_count; + cpfds->allocated_pfds = TRUE; + return CURLE_OK; +} + +static CURLcode cpfds_add_sock(struct curl_pollfds *cpfds, + curl_socket_t sock, short events, bool fold) +{ + int i; + + if(fold && cpfds->n <= INT_MAX) { + for(i = (int)cpfds->n - 1; i >= 0; --i) { + if(sock == cpfds->pfds[i].fd) { + cpfds->pfds[i].events |= events; + return CURLE_OK; + } + } + } + /* not folded, add new entry */ + if(cpfds->n >= cpfds->count) { + if(cpfds_increase(cpfds, 100)) + return CURLE_OUT_OF_MEMORY; + } + cpfds->pfds[cpfds->n].fd = sock; + cpfds->pfds[cpfds->n].events = events; + ++cpfds->n; + return CURLE_OK; +} + +CURLcode Curl_pollfds_add_sock(struct curl_pollfds *cpfds, + curl_socket_t sock, short events) +{ + return cpfds_add_sock(cpfds, sock, events, FALSE); +} + +CURLcode Curl_pollfds_add_ps(struct curl_pollfds *cpfds, + struct easy_pollset *ps) +{ + size_t i; + + DEBUGASSERT(cpfds); + DEBUGASSERT(ps); + for(i = 0; i < ps->num; i++) { + short events = 0; + if(ps->actions[i] & CURL_POLL_IN) + events |= POLLIN; + if(ps->actions[i] & CURL_POLL_OUT) + events |= POLLOUT; + if(events) { + if(cpfds_add_sock(cpfds, ps->sockets[i], events, TRUE)) + return CURLE_OUT_OF_MEMORY; + } + } + return CURLE_OK; +} + +void Curl_waitfds_init(struct curl_waitfds *cwfds, + struct curl_waitfd *static_wfds, + unsigned int static_count) +{ + DEBUGASSERT(cwfds); + DEBUGASSERT(static_wfds); + memset(cwfds, 0, sizeof(*cwfds)); + cwfds->wfds = static_wfds; + cwfds->count = static_count; +} + +static CURLcode cwfds_add_sock(struct curl_waitfds *cwfds, + curl_socket_t sock, short events) +{ + int i; + + if(cwfds->n <= INT_MAX) { + for(i = (int)cwfds->n - 1; i >= 0; --i) { + if(sock == cwfds->wfds[i].fd) { + cwfds->wfds[i].events |= events; + return CURLE_OK; + } + } + } + /* not folded, add new entry */ + if(cwfds->n >= cwfds->count) + return CURLE_OUT_OF_MEMORY; + cwfds->wfds[cwfds->n].fd = sock; + cwfds->wfds[cwfds->n].events = events; + ++cwfds->n; + return CURLE_OK; +} + +CURLcode Curl_waitfds_add_ps(struct curl_waitfds *cwfds, + struct easy_pollset *ps) +{ + size_t i; + + DEBUGASSERT(cwfds); + DEBUGASSERT(ps); + for(i = 0; i < ps->num; i++) { + short events = 0; + if(ps->actions[i] & CURL_POLL_IN) + events |= CURL_WAIT_POLLIN; + if(ps->actions[i] & CURL_POLL_OUT) + events |= CURL_WAIT_POLLOUT; + if(events) { + if(cwfds_add_sock(cwfds, ps->sockets[i], events)) + return CURLE_OUT_OF_MEMORY; + } + } + return CURLE_OK; +} diff --git a/deps/curl/lib/select.h b/deps/curl/lib/select.h index 5b1ca23eb1b..f01acbdefc2 100644 --- a/deps/curl/lib/select.h +++ b/deps/curl/lib/select.h @@ -111,4 +111,37 @@ int Curl_wait_ms(timediff_t timeout_ms); } while(0) #endif +struct curl_pollfds { + struct pollfd *pfds; + unsigned int n; + unsigned int count; + BIT(allocated_pfds); +}; + +void Curl_pollfds_init(struct curl_pollfds *cpfds, + struct pollfd *static_pfds, + unsigned int static_count); + +void Curl_pollfds_cleanup(struct curl_pollfds *cpfds); + +CURLcode Curl_pollfds_add_ps(struct curl_pollfds *cpfds, + struct easy_pollset *ps); + +CURLcode Curl_pollfds_add_sock(struct curl_pollfds *cpfds, + curl_socket_t sock, short events); + +struct curl_waitfds { + struct curl_waitfd *wfds; + unsigned int n; + unsigned int count; +}; + +void Curl_waitfds_init(struct curl_waitfds *cwfds, + struct curl_waitfd *static_wfds, + unsigned int static_count); + +CURLcode Curl_waitfds_add_ps(struct curl_waitfds *cwfds, + struct easy_pollset *ps); + + #endif /* HEADER_CURL_SELECT_H */ diff --git a/deps/curl/lib/sendf.c b/deps/curl/lib/sendf.c index d79ad08fa56..30a3517206d 100644 --- a/deps/curl/lib/sendf.c +++ b/deps/curl/lib/sendf.c @@ -40,6 +40,8 @@ #include "sendf.h" #include "cfilters.h" #include "connect.h" +#include "content_encoding.h" +#include "cw-out.h" #include "vtls/vtls.h" #include "vssh/ssh.h" #include "easyif.h" @@ -48,7 +50,8 @@ #include "select.h" #include "strdup.h" #include "http2.h" -#include "headers.h" +#include "progress.h" +#include "warnless.h" #include "ws.h" /* The last 3 #include files should be in this order */ @@ -56,384 +59,1385 @@ #include "curl_memory.h" #include "memdebug.h" -#if defined(CURL_DO_LINEEND_CONV) && !defined(CURL_DISABLE_FTP) -/* - * convert_lineends() changes CRLF (\r\n) end-of-line markers to a single LF - * (\n), with special processing for CRLF sequences that are split between two - * blocks of data. Remaining, bare CRs are changed to LFs. The possibly new - * size of the data is returned. + +static CURLcode do_init_writer_stack(struct Curl_easy *data); + +/* Curl_client_write() sends data to the write callback(s) + + The bit pattern defines to what "streams" to write to. Body and/or header. + The defines are in sendf.h of course. */ -static size_t convert_lineends(struct Curl_easy *data, - char *startPtr, size_t size) -{ - char *inPtr, *outPtr; - - /* sanity check */ - if(!startPtr || (size < 1)) { - return size; - } - - if(data->state.prev_block_had_trailing_cr) { - /* The previous block of incoming data - had a trailing CR, which was turned into a LF. */ - if(*startPtr == '\n') { - /* This block of incoming data starts with the - previous block's LF so get rid of it */ - memmove(startPtr, startPtr + 1, size-1); - size--; - /* and it wasn't a bare CR but a CRLF conversion instead */ - data->state.crlf_conversions++; - } - data->state.prev_block_had_trailing_cr = FALSE; /* reset the flag */ - } - - /* find 1st CR, if any */ - inPtr = outPtr = memchr(startPtr, '\r', size); - if(inPtr) { - /* at least one CR, now look for CRLF */ - while(inPtr < (startPtr + size-1)) { - /* note that it's size-1, so we'll never look past the last byte */ - if(memcmp(inPtr, "\r\n", 2) == 0) { - /* CRLF found, bump past the CR and copy the NL */ - inPtr++; - *outPtr = *inPtr; - /* keep track of how many CRLFs we converted */ - data->state.crlf_conversions++; - } - else { - if(*inPtr == '\r') { - /* lone CR, move LF instead */ - *outPtr = '\n'; - } - else { - /* not a CRLF nor a CR, just copy whatever it is */ - *outPtr = *inPtr; - } - } - outPtr++; - inPtr++; - } /* end of while loop */ - - if(inPtr < startPtr + size) { - /* handle last byte */ - if(*inPtr == '\r') { - /* deal with a CR at the end of the buffer */ - *outPtr = '\n'; /* copy a NL instead */ - /* note that a CRLF might be split across two blocks */ - data->state.prev_block_had_trailing_cr = TRUE; - } - else { - /* copy last byte */ - *outPtr = *inPtr; +CURLcode Curl_client_write(struct Curl_easy *data, + int type, const char *buf, size_t blen) +{ + CURLcode result; + + /* it is one of those, at least */ + DEBUGASSERT(type & (CLIENTWRITE_BODY|CLIENTWRITE_HEADER|CLIENTWRITE_INFO)); + /* BODY is only BODY (with optional EOS) */ + DEBUGASSERT(!(type & CLIENTWRITE_BODY) || + ((type & ~(CLIENTWRITE_BODY|CLIENTWRITE_EOS)) == 0)); + /* INFO is only INFO (with optional EOS) */ + DEBUGASSERT(!(type & CLIENTWRITE_INFO) || + ((type & ~(CLIENTWRITE_INFO|CLIENTWRITE_EOS)) == 0)); + + if(!data->req.writer_stack) { + result = do_init_writer_stack(data); + if(result) + return result; + DEBUGASSERT(data->req.writer_stack); + } + + result = Curl_cwriter_write(data, data->req.writer_stack, type, buf, blen); + CURL_TRC_WRITE(data, "client_write(type=%x, len=%zu) -> %d", + type, blen, result); + return result; +} + +static void cl_reset_writer(struct Curl_easy *data) +{ + struct Curl_cwriter *writer = data->req.writer_stack; + while(writer) { + data->req.writer_stack = writer->next; + writer->cwt->do_close(data, writer); + free(writer); + writer = data->req.writer_stack; + } +} + +static void cl_reset_reader(struct Curl_easy *data) +{ + struct Curl_creader *reader = data->req.reader_stack; + while(reader) { + data->req.reader_stack = reader->next; + reader->crt->do_close(data, reader); + free(reader); + reader = data->req.reader_stack; + } +} + +void Curl_client_cleanup(struct Curl_easy *data) +{ + cl_reset_reader(data); + cl_reset_writer(data); + + data->req.bytecount = 0; + data->req.headerline = 0; +} + +void Curl_client_reset(struct Curl_easy *data) +{ + if(data->req.rewind_read) { + /* already requested */ + CURL_TRC_READ(data, "client_reset, will rewind reader"); + } + else { + CURL_TRC_READ(data, "client_reset, clear readers"); + cl_reset_reader(data); + } + cl_reset_writer(data); + + data->req.bytecount = 0; + data->req.headerline = 0; +} + +CURLcode Curl_client_start(struct Curl_easy *data) +{ + if(data->req.rewind_read) { + struct Curl_creader *r = data->req.reader_stack; + CURLcode result = CURLE_OK; + + CURL_TRC_READ(data, "client start, rewind readers"); + while(r) { + result = r->crt->rewind(data, r); + if(result) { + failf(data, "rewind of client reader '%s' failed: %d", + r->crt->name, result); + return result; } - outPtr++; + r = r->next; } - if(outPtr < startPtr + size) - /* tidy up by null terminating the now shorter data */ - *outPtr = '\0'; + data->req.rewind_read = FALSE; + cl_reset_reader(data); + } + return CURLE_OK; +} + +bool Curl_creader_will_rewind(struct Curl_easy *data) +{ + return data->req.rewind_read; +} + +void Curl_creader_set_rewind(struct Curl_easy *data, bool enable) +{ + data->req.rewind_read = !!enable; +} + +/* Write data using an unencoding writer stack. */ +CURLcode Curl_cwriter_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + if(!writer) + return CURLE_WRITE_ERROR; + return writer->cwt->do_write(data, writer, type, buf, nbytes); +} - return (outPtr - startPtr); +CURLcode Curl_cwriter_def_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + (void)data; + (void)writer; + return CURLE_OK; +} + +CURLcode Curl_cwriter_def_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); +} + +void Curl_cwriter_def_close(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + (void) data; + (void) writer; +} + +static size_t get_max_body_write_len(struct Curl_easy *data, curl_off_t limit) +{ + if(limit != -1) { + /* How much more are we allowed to write? */ + curl_off_t remain_diff; + remain_diff = limit - data->req.bytecount; + if(remain_diff < 0) { + /* already written too much! */ + return 0; + } +#if SIZEOF_CURL_OFF_T > SIZEOF_SIZE_T + else if(remain_diff > SSIZE_T_MAX) { + return SIZE_T_MAX; + } +#endif + else { + return (size_t)remain_diff; + } } - return size; + return SIZE_T_MAX; } -#endif /* CURL_DO_LINEEND_CONV && !CURL_DISABLE_FTP */ -/* - * Curl_nwrite() is an internal write function that sends data to the - * server. Works with a socket index for the connection. - * - * If the write would block (CURLE_AGAIN), it returns CURLE_OK and - * (*nwritten == 0). Otherwise we return regular CURLcode value. - */ -CURLcode Curl_nwrite(struct Curl_easy *data, - int sockindex, - const void *buf, - size_t blen, - ssize_t *pnwritten) +struct cw_download_ctx { + struct Curl_cwriter super; + BIT(started_response); +}; +/* Download client writer in phase CURL_CW_PROTOCOL that + * sees the "real" download body data. */ +static CURLcode cw_download_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) { - ssize_t nwritten; - CURLcode result = CURLE_OK; - struct connectdata *conn; + struct cw_download_ctx *ctx = writer->ctx; + CURLcode result; + size_t nwrite, excess_len = 0; + bool is_connect = !!(type & CLIENTWRITE_CONNECT); - DEBUGASSERT(sockindex >= 0 && sockindex < 2); - DEBUGASSERT(pnwritten); - DEBUGASSERT(data); - DEBUGASSERT(data->conn); - conn = data->conn; -#ifdef CURLDEBUG - { - /* Allow debug builds to override this logic to force short sends - */ - char *p = getenv("CURL_SMALLSENDS"); - if(p) { - size_t altsize = (size_t)strtoul(p, NULL, 10); - if(altsize) - blen = CURLMIN(blen, altsize); + if(!is_connect && !ctx->started_response) { + Curl_pgrsTime(data, TIMER_STARTTRANSFER); + ctx->started_response = TRUE; + } + + if(!(type & CLIENTWRITE_BODY)) { + if(is_connect && data->set.suppress_connect_headers) + return CURLE_OK; + result = Curl_cwriter_write(data, writer->next, type, buf, nbytes); + CURL_TRC_WRITE(data, "download_write header(type=%x, blen=%zu) -> %d", + type, nbytes, result); + return result; + } + + /* Here, we deal with REAL BODY bytes. All filtering and transfer + * encodings have been applied and only the true content, e.g. BODY, + * bytes are passed here. + * This allows us to check sizes, update stats, etc. independent + * from the protocol in play. */ + + if(data->req.no_body && nbytes > 0) { + /* BODY arrives although we want none, bail out */ + streamclose(data->conn, "ignoring body"); + CURL_TRC_WRITE(data, "download_write body(type=%x, blen=%zu), " + "did not want a BODY", type, nbytes); + data->req.download_done = TRUE; + if(data->info.header_size) + /* if headers have been received, this is fine */ + return CURLE_OK; + return CURLE_WEIRD_SERVER_REPLY; + } + + /* Determine if we see any bytes in excess to what is allowed. + * We write the allowed bytes and handle excess further below. + * This gives deterministic BODY writes on varying buffer receive + * lengths. */ + nwrite = nbytes; + if(-1 != data->req.maxdownload) { + size_t wmax = get_max_body_write_len(data, data->req.maxdownload); + if(nwrite > wmax) { + excess_len = nbytes - wmax; + nwrite = wmax; + } + + if(nwrite == wmax) { + data->req.download_done = TRUE; } + + if((type & CLIENTWRITE_EOS) && !data->req.no_body && + (data->req.maxdownload > data->req.bytecount)) { + failf(data, "end of response with %" FMT_OFF_T " bytes missing", + data->req.maxdownload - data->req.bytecount); + return CURLE_PARTIAL_FILE; + } + } + + /* Error on too large filesize is handled below, after writing + * the permitted bytes */ + if(data->set.max_filesize && !data->req.ignorebody) { + size_t wmax = get_max_body_write_len(data, data->set.max_filesize); + if(nwrite > wmax) { + nwrite = wmax; + } + } + + if(!data->req.ignorebody && (nwrite || (type & CLIENTWRITE_EOS))) { + result = Curl_cwriter_write(data, writer->next, type, buf, nwrite); + CURL_TRC_WRITE(data, "download_write body(type=%x, blen=%zu) -> %d", + type, nbytes, result); + if(result) + return result; } + /* Update stats, write and report progress */ + data->req.bytecount += nwrite; +#ifdef USE_HYPER + data->req.bodywritten = TRUE; #endif - nwritten = conn->send[sockindex](data, sockindex, buf, blen, &result); - if(result == CURLE_AGAIN) { - nwritten = 0; - result = CURLE_OK; + result = Curl_pgrsSetDownloadCounter(data, data->req.bytecount); + if(result) + return result; + + if(excess_len) { + if(!data->req.ignorebody) { + infof(data, + "Excess found writing body:" + " excess = %zu" + ", size = %" FMT_OFF_T + ", maxdownload = %" FMT_OFF_T + ", bytecount = %" FMT_OFF_T, + excess_len, data->req.size, data->req.maxdownload, + data->req.bytecount); + connclose(data->conn, "excess found in a read"); + } } - else if(result) { - nwritten = -1; /* make sure */ + else if((nwrite < nbytes) && !data->req.ignorebody) { + failf(data, "Exceeded the maximum allowed file size " + "(%" FMT_OFF_T ") with %" FMT_OFF_T " bytes", + data->set.max_filesize, data->req.bytecount); + return CURLE_FILESIZE_EXCEEDED; } - else { - DEBUGASSERT(nwritten >= 0); + + return CURLE_OK; +} + +static const struct Curl_cwtype cw_download = { + "protocol", + NULL, + Curl_cwriter_def_init, + cw_download_write, + Curl_cwriter_def_close, + sizeof(struct cw_download_ctx) +}; + +/* RAW client writer in phase CURL_CW_RAW that + * enabled tracing of raw data. */ +static CURLcode cw_raw_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + if(type & CLIENTWRITE_BODY && data->set.verbose && !data->req.ignorebody) { + Curl_debug(data, CURLINFO_DATA_IN, (char *)buf, nbytes); + } + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); +} + +static const struct Curl_cwtype cw_raw = { + "raw", + NULL, + Curl_cwriter_def_init, + cw_raw_write, + Curl_cwriter_def_close, + sizeof(struct Curl_cwriter) +}; + +/* Create an unencoding writer stage using the given handler. */ +CURLcode Curl_cwriter_create(struct Curl_cwriter **pwriter, + struct Curl_easy *data, + const struct Curl_cwtype *cwt, + Curl_cwriter_phase phase) +{ + struct Curl_cwriter *writer = NULL; + CURLcode result = CURLE_OUT_OF_MEMORY; + void *p; + + DEBUGASSERT(cwt->cwriter_size >= sizeof(struct Curl_cwriter)); + p = calloc(1, cwt->cwriter_size); + if(!p) + goto out; + + writer = (struct Curl_cwriter *)p; + writer->cwt = cwt; + writer->ctx = p; + writer->phase = phase; + result = cwt->do_init(data, writer); + +out: + *pwriter = result ? NULL : writer; + if(result) + free(writer); + return result; +} + +void Curl_cwriter_free(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + if(writer) { + writer->cwt->do_close(data, writer); + free(writer); + } +} + +size_t Curl_cwriter_count(struct Curl_easy *data, Curl_cwriter_phase phase) +{ + struct Curl_cwriter *w; + size_t n = 0; + + for(w = data->req.writer_stack; w; w = w->next) { + if(w->phase == phase) + ++n; } + return n; +} - *pnwritten = nwritten; +static CURLcode do_init_writer_stack(struct Curl_easy *data) +{ + struct Curl_cwriter *writer; + CURLcode result; + + DEBUGASSERT(!data->req.writer_stack); + result = Curl_cwriter_create(&data->req.writer_stack, + data, &Curl_cwt_out, CURL_CW_CLIENT); + if(result) + return result; + + result = Curl_cwriter_create(&writer, data, &cw_download, CURL_CW_PROTOCOL); + if(result) + return result; + result = Curl_cwriter_add(data, writer); + if(result) { + Curl_cwriter_free(data, writer); + } + + result = Curl_cwriter_create(&writer, data, &cw_raw, CURL_CW_RAW); + if(result) + return result; + result = Curl_cwriter_add(data, writer); + if(result) { + Curl_cwriter_free(data, writer); + } return result; } -/* - * Curl_write() is an internal write function that sends data to the - * server. Works with plain sockets, SCP, SSL or kerberos. - * - * If the write would block (CURLE_AGAIN), we return CURLE_OK and - * (*written == 0). Otherwise we return regular CURLcode value. - */ -CURLcode Curl_write(struct Curl_easy *data, - curl_socket_t sockfd, - const void *mem, - size_t len, - ssize_t *written) +CURLcode Curl_cwriter_add(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + CURLcode result; + struct Curl_cwriter **anchor = &data->req.writer_stack; + + if(!*anchor) { + result = do_init_writer_stack(data); + if(result) + return result; + } + + /* Insert the writer as first in its phase. + * Skip existing writers of lower phases. */ + while(*anchor && (*anchor)->phase < writer->phase) + anchor = &((*anchor)->next); + writer->next = *anchor; + *anchor = writer; + return CURLE_OK; +} + +struct Curl_cwriter *Curl_cwriter_get_by_name(struct Curl_easy *data, + const char *name) +{ + struct Curl_cwriter *writer; + for(writer = data->req.writer_stack; writer; writer = writer->next) { + if(!strcmp(name, writer->cwt->name)) + return writer; + } + return NULL; +} + +struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data, + const struct Curl_cwtype *cwt) +{ + struct Curl_cwriter *writer; + for(writer = data->req.writer_stack; writer; writer = writer->next) { + if(writer->cwt == cwt) + return writer; + } + return NULL; +} + +void Curl_cwriter_remove_by_name(struct Curl_easy *data, + const char *name) +{ + struct Curl_cwriter **anchor = &data->req.writer_stack; + + while(*anchor) { + if(!strcmp(name, (*anchor)->cwt->name)) { + struct Curl_cwriter *w = (*anchor); + *anchor = w->next; + Curl_cwriter_free(data, w); + continue; + } + anchor = &((*anchor)->next); + } +} + +bool Curl_cwriter_is_paused(struct Curl_easy *data) +{ + return Curl_cw_out_is_paused(data); +} + +CURLcode Curl_cwriter_unpause(struct Curl_easy *data) +{ + return Curl_cw_out_unpause(data); +} + +CURLcode Curl_creader_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, size_t *nread, bool *eos) +{ + *nread = 0; + *eos = FALSE; + if(!reader) + return CURLE_READ_ERROR; + return reader->crt->do_read(data, reader, buf, blen, nread, eos); +} + +CURLcode Curl_creader_def_init(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return CURLE_OK; +} + +void Curl_creader_def_close(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; +} + +CURLcode Curl_creader_def_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *nread, bool *eos) +{ + if(reader->next) + return reader->next->crt->do_read(data, reader->next, buf, blen, + nread, eos); + else { + *nread = 0; + *eos = FALSE; + return CURLE_READ_ERROR; + } +} + +bool Curl_creader_def_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return FALSE; +} + +curl_off_t Curl_creader_def_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + return reader->next ? + reader->next->crt->total_length(data, reader->next) : -1; +} + +CURLcode Curl_creader_def_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset) +{ + (void)data; + (void)reader; + (void)offset; + return CURLE_READ_ERROR; +} + +CURLcode Curl_creader_def_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return CURLE_OK; +} + +CURLcode Curl_creader_def_unpause(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return CURLE_OK; +} + +bool Curl_creader_def_is_paused(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return FALSE; +} + +void Curl_creader_def_done(struct Curl_easy *data, + struct Curl_creader *reader, int premature) +{ + (void)data; + (void)reader; + (void)premature; +} + +struct cr_in_ctx { + struct Curl_creader super; + curl_read_callback read_cb; + void *cb_user_data; + curl_off_t total_len; + curl_off_t read_len; + CURLcode error_result; + BIT(seen_eos); + BIT(errored); + BIT(has_used_cb); + BIT(is_paused); +}; + +static CURLcode cr_in_init(struct Curl_easy *data, struct Curl_creader *reader) { - struct connectdata *conn; - int num; + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + ctx->read_cb = data->state.fread_func; + ctx->cb_user_data = data->state.in; + ctx->total_len = -1; + ctx->read_len = 0; + return CURLE_OK; +} + +/* Real client reader to installed client callbacks. */ +static CURLcode cr_in_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_in_ctx *ctx = reader->ctx; + size_t nread; + + ctx->is_paused = FALSE; + + /* Once we have errored, we will return the same error forever */ + if(ctx->errored) { + *pnread = 0; + *peos = FALSE; + return ctx->error_result; + } + if(ctx->seen_eos) { + *pnread = 0; + *peos = TRUE; + return CURLE_OK; + } + /* respect length limitations */ + if(ctx->total_len >= 0) { + curl_off_t remain = ctx->total_len - ctx->read_len; + if(remain <= 0) + blen = 0; + else if(remain < (curl_off_t)blen) + blen = (size_t)remain; + } + nread = 0; + if(ctx->read_cb && blen) { + Curl_set_in_callback(data, TRUE); + nread = ctx->read_cb(buf, 1, blen, ctx->cb_user_data); + Curl_set_in_callback(data, FALSE); + ctx->has_used_cb = TRUE; + } + + switch(nread) { + case 0: + if((ctx->total_len >= 0) && (ctx->read_len < ctx->total_len)) { + failf(data, "client read function EOF fail, " + "only %"FMT_OFF_T"/%"FMT_OFF_T " of needed bytes read", + ctx->read_len, ctx->total_len); + return CURLE_READ_ERROR; + } + *pnread = 0; + *peos = TRUE; + ctx->seen_eos = TRUE; + break; + + case CURL_READFUNC_ABORT: + failf(data, "operation aborted by callback"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_ABORTED_BY_CALLBACK; + return CURLE_ABORTED_BY_CALLBACK; + + case CURL_READFUNC_PAUSE: + if(data->conn->handler->flags & PROTOPT_NONETWORK) { + /* protocols that work without network cannot be paused. This is + actually only FILE:// just now, and it cannot pause since the transfer + is not done using the "normal" procedure. */ + failf(data, "Read callback asked for PAUSE when not supported"); + return CURLE_READ_ERROR; + } + /* CURL_READFUNC_PAUSE pauses read callbacks that feed socket writes */ + CURL_TRC_READ(data, "cr_in_read, callback returned CURL_READFUNC_PAUSE"); + ctx->is_paused = TRUE; + data->req.keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */ + *pnread = 0; + *peos = FALSE; + break; /* nothing was read */ + + default: + if(nread > blen) { + /* the read function returned a too large value */ + failf(data, "read function returned funny value"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_READ_ERROR; + return CURLE_READ_ERROR; + } + ctx->read_len += nread; + if(ctx->total_len >= 0) + ctx->seen_eos = (ctx->read_len >= ctx->total_len); + *pnread = nread; + *peos = ctx->seen_eos; + break; + } + CURL_TRC_READ(data, "cr_in_read(len=%zu, total=%"FMT_OFF_T + ", read=%"FMT_OFF_T") -> %d, nread=%zu, eos=%d", + blen, ctx->total_len, ctx->read_len, CURLE_OK, + *pnread, *peos); + return CURLE_OK; +} + +static bool cr_in_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + return ctx->has_used_cb; +} + +static curl_off_t cr_in_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + return ctx->total_len; +} + +static CURLcode cr_in_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset) +{ + struct cr_in_ctx *ctx = reader->ctx; + int seekerr = CURL_SEEKFUNC_CANTSEEK; - DEBUGASSERT(data); DEBUGASSERT(data->conn); - conn = data->conn; - num = (sockfd != CURL_SOCKET_BAD && sockfd == conn->sock[SECONDARYSOCKET]); - return Curl_nwrite(data, num, mem, len, written); -} - -static CURLcode pausewrite(struct Curl_easy *data, - int type, /* what type of data */ - const char *ptr, - size_t len) -{ - /* signalled to pause sending on this connection, but since we have data - we want to send we need to dup it to save a copy for when the sending - is again enabled */ - struct SingleRequest *k = &data->req; - struct UrlState *s = &data->state; - unsigned int i; - bool newtype = TRUE; - - Curl_conn_ev_data_pause(data, TRUE); - - if(s->tempcount) { - for(i = 0; i< s->tempcount; i++) { - if(s->tempwrite[i].type == type) { - /* data for this type exists */ - newtype = FALSE; - break; + /* already started reading? */ + if(ctx->read_len) + return CURLE_READ_ERROR; + + if(data->set.seek_func) { + Curl_set_in_callback(data, TRUE); + seekerr = data->set.seek_func(data->set.seek_client, offset, SEEK_SET); + Curl_set_in_callback(data, FALSE); + } + + if(seekerr != CURL_SEEKFUNC_OK) { + curl_off_t passed = 0; + + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + return CURLE_READ_ERROR; + } + /* when seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ + do { + char scratch[4*1024]; + size_t readthisamountnow = + (offset - passed > (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : + curlx_sotouz(offset - passed); + size_t actuallyread; + + Curl_set_in_callback(data, TRUE); + actuallyread = ctx->read_cb(scratch, 1, readthisamountnow, + ctx->cb_user_data); + Curl_set_in_callback(data, FALSE); + + passed += actuallyread; + if((actuallyread == 0) || (actuallyread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Could only read %" FMT_OFF_T " bytes from the input", + passed); + return CURLE_READ_ERROR; } + } while(passed < offset); + } + + /* now, decrease the size of the read */ + if(ctx->total_len > 0) { + ctx->total_len -= offset; + + if(ctx->total_len <= 0) { + failf(data, "File already completely uploaded"); + return CURLE_PARTIAL_FILE; } - DEBUGASSERT(i < 3); - if(i >= 3) - /* There are more types to store than what fits: very bad */ - return CURLE_OUT_OF_MEMORY; } - else - i = 0; + /* we have passed, proceed as normal */ + return CURLE_OK; +} + +static CURLcode cr_in_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_in_ctx *ctx = reader->ctx; + + /* If we never invoked the callback, there is noting to rewind */ + if(!ctx->has_used_cb) + return CURLE_OK; - if(newtype) { - /* store this information in the state struct for later use */ - Curl_dyn_init(&s->tempwrite[i].b, DYN_PAUSE_BUFFER); - s->tempwrite[i].type = type; - s->tempcount++; + if(data->set.seek_func) { + int err; + + Curl_set_in_callback(data, TRUE); + err = (data->set.seek_func)(data->set.seek_client, 0, SEEK_SET); + Curl_set_in_callback(data, FALSE); + CURL_TRC_READ(data, "cr_in, rewind via set.seek_func -> %d", err); + if(err) { + failf(data, "seek callback returned error %d", (int)err); + return CURLE_SEND_FAIL_REWIND; + } } + else if(data->set.ioctl_func) { + curlioerr err; - if(Curl_dyn_addn(&s->tempwrite[i].b, (unsigned char *)ptr, len)) - return CURLE_OUT_OF_MEMORY; + Curl_set_in_callback(data, TRUE); + err = (data->set.ioctl_func)(data, CURLIOCMD_RESTARTREAD, + data->set.ioctl_client); + Curl_set_in_callback(data, FALSE); + CURL_TRC_READ(data, "cr_in, rewind via set.ioctl_func -> %d", (int)err); + if(err) { + failf(data, "ioctl callback returned error %d", (int)err); + return CURLE_SEND_FAIL_REWIND; + } + } + else { + /* If no CURLOPT_READFUNCTION is used, we know that we operate on a + given FILE * stream and we can actually attempt to rewind that + ourselves with fseek() */ + if(data->state.fread_func == (curl_read_callback)fread) { + int err = fseek(data->state.in, 0, SEEK_SET); + CURL_TRC_READ(data, "cr_in, rewind via fseek -> %d(%d)", + (int)err, (int)errno); + if(-1 != err) + /* successful rewind */ + return CURLE_OK; + } - /* mark the connection as RECV paused */ - k->keepon |= KEEP_RECV_PAUSE; + /* no callback set or failure above, makes us fail at once */ + failf(data, "necessary data rewind was not possible"); + return CURLE_SEND_FAIL_REWIND; + } + return CURLE_OK; +} +static CURLcode cr_in_unpause(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + ctx->is_paused = FALSE; return CURLE_OK; } +static bool cr_in_is_paused(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + return ctx->is_paused; +} -/* chop_write() writes chunks of data not larger than CURL_MAX_WRITE_SIZE via - * client write callback(s) and takes care of pause requests from the - * callbacks. - */ -static CURLcode chop_write(struct Curl_easy *data, - int type, - char *optr, - size_t olen) -{ - struct connectdata *conn = data->conn; - curl_write_callback writeheader = NULL; - curl_write_callback writebody = NULL; - char *ptr = optr; - size_t len = olen; - void *writebody_ptr = data->set.out; - - if(!len) +static const struct Curl_crtype cr_in = { + "cr-in", + cr_in_init, + cr_in_read, + Curl_creader_def_close, + cr_in_needs_rewind, + cr_in_total_length, + cr_in_resume_from, + cr_in_rewind, + cr_in_unpause, + cr_in_is_paused, + Curl_creader_def_done, + sizeof(struct cr_in_ctx) +}; + +CURLcode Curl_creader_create(struct Curl_creader **preader, + struct Curl_easy *data, + const struct Curl_crtype *crt, + Curl_creader_phase phase) +{ + struct Curl_creader *reader = NULL; + CURLcode result = CURLE_OUT_OF_MEMORY; + void *p; + + DEBUGASSERT(crt->creader_size >= sizeof(struct Curl_creader)); + p = calloc(1, crt->creader_size); + if(!p) + goto out; + + reader = (struct Curl_creader *)p; + reader->crt = crt; + reader->ctx = p; + reader->phase = phase; + result = crt->do_init(data, reader); + +out: + *preader = result ? NULL : reader; + if(result) + free(reader); + return result; +} + +void Curl_creader_free(struct Curl_easy *data, struct Curl_creader *reader) +{ + if(reader) { + reader->crt->do_close(data, reader); + free(reader); + } +} + +struct cr_lc_ctx { + struct Curl_creader super; + struct bufq buf; + BIT(read_eos); /* we read an EOS from the next reader */ + BIT(eos); /* we have returned an EOS */ + BIT(prev_cr); /* the last byte was a CR */ +}; + +static CURLcode cr_lc_init(struct Curl_easy *data, struct Curl_creader *reader) +{ + struct cr_lc_ctx *ctx = reader->ctx; + (void)data; + Curl_bufq_init2(&ctx->buf, (16 * 1024), 1, BUFQ_OPT_SOFT_LIMIT); + return CURLE_OK; +} + +static void cr_lc_close(struct Curl_easy *data, struct Curl_creader *reader) +{ + struct cr_lc_ctx *ctx = reader->ctx; + (void)data; + Curl_bufq_free(&ctx->buf); +} + +/* client reader doing line end conversions. */ +static CURLcode cr_lc_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_lc_ctx *ctx = reader->ctx; + CURLcode result; + size_t nread, i, start, n; + bool eos; + + if(ctx->eos) { + *pnread = 0; + *peos = TRUE; return CURLE_OK; + } - /* If reading is paused, append this data to the already held data for this - type. */ - if(data->req.keepon & KEEP_RECV_PAUSE) - return pausewrite(data, type, ptr, len); - - /* Determine the callback(s) to use. */ - if(type & CLIENTWRITE_BODY) { -#ifdef USE_WEBSOCKETS - if(conn->handler->protocol & (CURLPROTO_WS|CURLPROTO_WSS)) { - writebody = Curl_ws_writecb; - writebody_ptr = data; + if(Curl_bufq_is_empty(&ctx->buf)) { + if(ctx->read_eos) { + ctx->eos = TRUE; + *pnread = 0; + *peos = TRUE; + return CURLE_OK; } - else -#endif - writebody = data->set.fwrite_func; - } - if((type & CLIENTWRITE_HEADER) && - (data->set.fwrite_header || data->set.writeheader)) { - /* - * Write headers to the same callback or to the especially setup - * header callback function (added after version 7.7.1). - */ - writeheader = - data->set.fwrite_header? data->set.fwrite_header: data->set.fwrite_func; - } - - /* Chop data, write chunks. */ - while(len) { - size_t chunklen = len <= CURL_MAX_WRITE_SIZE? len: CURL_MAX_WRITE_SIZE; - - if(writebody) { - size_t wrote; - Curl_set_in_callback(data, true); - wrote = writebody(ptr, 1, chunklen, writebody_ptr); - Curl_set_in_callback(data, false); - - if(CURL_WRITEFUNC_PAUSE == wrote) { - if(conn->handler->flags & PROTOPT_NONETWORK) { - /* Protocols that work without network cannot be paused. This is - actually only FILE:// just now, and it can't pause since the - transfer isn't done using the "normal" procedure. */ - failf(data, "Write callback asked for PAUSE when not supported"); - return CURLE_WRITE_ERROR; - } - return pausewrite(data, type, ptr, len); + /* Still getting data form the next reader, ctx->buf is empty */ + result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos); + if(result) + return result; + ctx->read_eos = eos; + + if(!nread || !memchr(buf, '\n', nread)) { + /* nothing to convert, return this right away */ + if(ctx->read_eos) + ctx->eos = TRUE; + *pnread = nread; + *peos = ctx->eos; + goto out; + } + + /* at least one \n might need conversion to '\r\n', place into ctx->buf */ + for(i = start = 0; i < nread; ++i) { + /* if this byte is not an LF character, or if the preceding character is + a CR (meaning this already is a CRLF pair), go to next */ + if((buf[i] != '\n') || ctx->prev_cr) { + ctx->prev_cr = (buf[i] == '\r'); + continue; } - if(wrote != chunklen) { - failf(data, "Failure writing output to destination"); - return CURLE_WRITE_ERROR; + ctx->prev_cr = FALSE; + /* on a soft limit bufq, we do not need to check length */ + result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n); + if(!result) + result = Curl_bufq_cwrite(&ctx->buf, STRCONST("\r\n"), &n); + if(result) + return result; + start = i + 1; + if(!data->set.crlf && (data->state.infilesize != -1)) { + /* we are here only because FTP is in ASCII mode... + bump infilesize for the LF we just added */ + data->state.infilesize++; + /* comment: this might work for FTP, but in HTTP we could not change + * the content length after having started the request... */ } } - ptr += chunklen; - len -= chunklen; + if(start < i) { /* leftover */ + result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n); + if(result) + return result; + } + } + + DEBUGASSERT(!Curl_bufq_is_empty(&ctx->buf)); + *peos = FALSE; + result = Curl_bufq_cread(&ctx->buf, buf, blen, pnread); + if(!result && ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { + /* no more data, read all, done. */ + ctx->eos = TRUE; + *peos = TRUE; } -#ifndef CURL_DISABLE_HTTP - /* HTTP header, but not status-line */ - if((conn->handler->protocol & PROTO_FAMILY_HTTP) && - (type & CLIENTWRITE_HEADER) && !(type & CLIENTWRITE_STATUS) ) { - unsigned char htype = (unsigned char) - (type & CLIENTWRITE_CONNECT ? CURLH_CONNECT : - (type & CLIENTWRITE_1XX ? CURLH_1XX : - (type & CLIENTWRITE_TRAILER ? CURLH_TRAILER : - CURLH_HEADER))); - CURLcode result = Curl_headers_push(data, optr, htype); +out: + CURL_TRC_READ(data, "cr_lc_read(len=%zu) -> %d, nread=%zu, eos=%d", + blen, result, *pnread, *peos); + return result; +} + +static curl_off_t cr_lc_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + /* this reader changes length depending on input */ + (void)data; + (void)reader; + return -1; +} + +static const struct Curl_crtype cr_lc = { + "cr-lineconv", + cr_lc_init, + cr_lc_read, + cr_lc_close, + Curl_creader_def_needs_rewind, + cr_lc_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct cr_lc_ctx) +}; + +static CURLcode cr_lc_add(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; + + result = Curl_creader_create(&reader, data, &cr_lc, + CURL_CR_CONTENT_ENCODE); + if(!result) + result = Curl_creader_add(data, reader); + + if(result && reader) + Curl_creader_free(data, reader); + return result; +} + +static CURLcode do_init_reader_stack(struct Curl_easy *data, + struct Curl_creader *r) +{ + CURLcode result = CURLE_OK; + curl_off_t clen; + + DEBUGASSERT(r); + DEBUGASSERT(r->crt); + DEBUGASSERT(r->phase == CURL_CR_CLIENT); + DEBUGASSERT(!data->req.reader_stack); + + data->req.reader_stack = r; + clen = r->crt->total_length(data, r); + /* if we do not have 0 length init, and crlf conversion is wanted, + * add the reader for it */ + if(clen && (data->set.crlf +#ifdef CURL_PREFER_LF_LINEENDS + || data->state.prefer_ascii +#endif + )) { + result = cr_lc_add(data); if(result) return result; } -#endif - if(writeheader) { - size_t wrote; - - Curl_set_in_callback(data, true); - wrote = writeheader(optr, 1, olen, data->set.writeheader); - Curl_set_in_callback(data, false); - - if(CURL_WRITEFUNC_PAUSE == wrote) - /* here we pass in the HEADER bit only since if this was body as well - then it was passed already and clearly that didn't trigger the - pause, so this is saved for later with the HEADER bit only */ - return pausewrite(data, CLIENTWRITE_HEADER | - (type & (CLIENTWRITE_STATUS|CLIENTWRITE_CONNECT| - CLIENTWRITE_1XX|CLIENTWRITE_TRAILER)), - optr, olen); - if(wrote != olen) { - failf(data, "Failed writing header"); - return CURLE_WRITE_ERROR; + return result; +} + +CURLcode Curl_creader_set_fread(struct Curl_easy *data, curl_off_t len) +{ + CURLcode result; + struct Curl_creader *r; + struct cr_in_ctx *ctx; + + result = Curl_creader_create(&r, data, &cr_in, CURL_CR_CLIENT); + if(result) + goto out; + ctx = r->ctx; + ctx->total_len = len; + + cl_reset_reader(data); + result = do_init_reader_stack(data, r); +out: + CURL_TRC_READ(data, "add fread reader, len=%"FMT_OFF_T " -> %d", + len, result); + return result; +} + +CURLcode Curl_creader_add(struct Curl_easy *data, + struct Curl_creader *reader) +{ + CURLcode result; + struct Curl_creader **anchor = &data->req.reader_stack; + + if(!*anchor) { + result = Curl_creader_set_fread(data, data->state.infilesize); + if(result) + return result; + } + + /* Insert the writer as first in its phase. + * Skip existing readers of lower phases. */ + while(*anchor && (*anchor)->phase < reader->phase) + anchor = &((*anchor)->next); + reader->next = *anchor; + *anchor = reader; + return CURLE_OK; +} + +CURLcode Curl_creader_set(struct Curl_easy *data, struct Curl_creader *r) +{ + CURLcode result; + + DEBUGASSERT(r); + DEBUGASSERT(r->crt); + DEBUGASSERT(r->phase == CURL_CR_CLIENT); + + cl_reset_reader(data); + result = do_init_reader_stack(data, r); + if(result) + Curl_creader_free(data, r); + return result; +} + +CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen, + size_t *nread, bool *eos) +{ + CURLcode result; + + DEBUGASSERT(buf); + DEBUGASSERT(blen); + DEBUGASSERT(nread); + DEBUGASSERT(eos); + + if(!data->req.reader_stack) { + result = Curl_creader_set_fread(data, data->state.infilesize); + if(result) + return result; + DEBUGASSERT(data->req.reader_stack); + } + + result = Curl_creader_read(data, data->req.reader_stack, buf, blen, + nread, eos); + CURL_TRC_READ(data, "client_read(len=%zu) -> %d, nread=%zu, eos=%d", + blen, result, *nread, *eos); + return result; +} + +bool Curl_creader_needs_rewind(struct Curl_easy *data) +{ + struct Curl_creader *reader = data->req.reader_stack; + while(reader) { + if(reader->crt->needs_rewind(data, reader)) { + CURL_TRC_READ(data, "client reader needs rewind before next request"); + return TRUE; } + reader = reader->next; } + return FALSE; +} +static CURLcode cr_null_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + (void)data; + (void)reader; + (void)buf; + (void)blen; + *pnread = 0; + *peos = TRUE; return CURLE_OK; } +static curl_off_t cr_null_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + /* this reader changes length depending on input */ + (void)data; + (void)reader; + return 0; +} -/* Curl_client_write() sends data to the write callback(s) +static const struct Curl_crtype cr_null = { + "cr-null", + Curl_creader_def_init, + cr_null_read, + Curl_creader_def_close, + Curl_creader_def_needs_rewind, + cr_null_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct Curl_creader) +}; - The bit pattern defines to what "streams" to write to. Body and/or header. - The defines are in sendf.h of course. +CURLcode Curl_creader_set_null(struct Curl_easy *data) +{ + struct Curl_creader *r; + CURLcode result; - If CURL_DO_LINEEND_CONV is enabled, data is converted IN PLACE to the - local character encoding. This is a problem and should be changed in - the future to leave the original data alone. - */ -CURLcode Curl_client_write(struct Curl_easy *data, - int type, - char *ptr, - size_t len) -{ -#if !defined(CURL_DISABLE_FTP) && defined(CURL_DO_LINEEND_CONV) - /* FTP data may need conversion. */ - if((type & CLIENTWRITE_BODY) && - (data->conn->handler->protocol & PROTO_FAMILY_FTP) && - data->conn->proto.ftpc.transfertype == 'A') { - /* convert end-of-line markers */ - len = convert_lineends(data, ptr, len); + result = Curl_creader_create(&r, data, &cr_null, CURL_CR_CLIENT); + if(result) + return result; + + cl_reset_reader(data); + return do_init_reader_stack(data, r); +} + +struct cr_buf_ctx { + struct Curl_creader super; + const char *buf; + size_t blen; + size_t index; +}; + +static CURLcode cr_buf_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_buf_ctx *ctx = reader->ctx; + size_t nread = ctx->blen - ctx->index; + + (void)data; + if(!nread || !ctx->buf) { + *pnread = 0; + *peos = TRUE; } -#endif - return chop_write(data, type, ptr, len); + else { + if(nread > blen) + nread = blen; + memcpy(buf, ctx->buf + ctx->index, nread); + *pnread = nread; + ctx->index += nread; + *peos = (ctx->index == ctx->blen); + } + CURL_TRC_READ(data, "cr_buf_read(len=%zu) -> 0, nread=%zu, eos=%d", + blen, *pnread, *peos); + return CURLE_OK; } -/* - * Internal read-from-socket function. This is meant to deal with plain - * sockets, SSL sockets and kerberos sockets. - * - * Returns a regular CURLcode value. - */ -CURLcode Curl_read(struct Curl_easy *data, /* transfer */ - curl_socket_t sockfd, /* read from this socket */ - char *buf, /* store read data here */ - size_t sizerequested, /* max amount to read */ - ssize_t *n) /* amount bytes read */ -{ - CURLcode result = CURLE_RECV_ERROR; - ssize_t nread = 0; - size_t bytesfromsocket = 0; - char *buffertofill = NULL; - struct connectdata *conn = data->conn; - - /* Set 'num' to 0 or 1, depending on which socket that has been sent here. - If it is the second socket, we set num to 1. Otherwise to 0. This lets - us use the correct ssl handle. */ - int num = (sockfd == conn->sock[SECONDARYSOCKET]); - - *n = 0; /* reset amount to zero */ - - bytesfromsocket = CURLMIN(sizerequested, (size_t)data->set.buffer_size); - buffertofill = buf; - - nread = conn->recv[num](data, num, buffertofill, bytesfromsocket, &result); - if(nread < 0) +static bool cr_buf_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_buf_ctx *ctx = reader->ctx; + (void)data; + return ctx->index > 0; +} + +static curl_off_t cr_buf_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_buf_ctx *ctx = reader->ctx; + (void)data; + return (curl_off_t)ctx->blen; +} + +static CURLcode cr_buf_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset) +{ + struct cr_buf_ctx *ctx = reader->ctx; + size_t boffset; + + (void)data; + DEBUGASSERT(data->conn); + /* already started reading? */ + if(ctx->index) + return CURLE_READ_ERROR; + if(offset <= 0) + return CURLE_OK; + boffset = (size_t)offset; + if(boffset > ctx->blen) + return CURLE_READ_ERROR; + + ctx->buf += boffset; + ctx->blen -= boffset; + return CURLE_OK; +} + +static const struct Curl_crtype cr_buf = { + "cr-buf", + Curl_creader_def_init, + cr_buf_read, + Curl_creader_def_close, + cr_buf_needs_rewind, + cr_buf_total_length, + cr_buf_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct cr_buf_ctx) +}; + +CURLcode Curl_creader_set_buf(struct Curl_easy *data, + const char *buf, size_t blen) +{ + CURLcode result; + struct Curl_creader *r; + struct cr_buf_ctx *ctx; + + result = Curl_creader_create(&r, data, &cr_buf, CURL_CR_CLIENT); + if(result) goto out; + ctx = r->ctx; + ctx->buf = buf; + ctx->blen = blen; + ctx->index = 0; - *n += nread; - result = CURLE_OK; + cl_reset_reader(data); + result = do_init_reader_stack(data, r); out: + CURL_TRC_READ(data, "add buf reader, len=%zu -> %d", blen, result); return result; } + +curl_off_t Curl_creader_total_length(struct Curl_easy *data) +{ + struct Curl_creader *r = data->req.reader_stack; + return r ? r->crt->total_length(data, r) : -1; +} + +curl_off_t Curl_creader_client_length(struct Curl_easy *data) +{ + struct Curl_creader *r = data->req.reader_stack; + while(r && r->phase != CURL_CR_CLIENT) + r = r->next; + return r ? r->crt->total_length(data, r) : -1; +} + +CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset) +{ + struct Curl_creader *r = data->req.reader_stack; + while(r && r->phase != CURL_CR_CLIENT) + r = r->next; + return r ? r->crt->resume_from(data, r, offset) : CURLE_READ_ERROR; +} + +CURLcode Curl_creader_unpause(struct Curl_easy *data) +{ + struct Curl_creader *reader = data->req.reader_stack; + CURLcode result = CURLE_OK; + + while(reader) { + result = reader->crt->unpause(data, reader); + if(result) + break; + reader = reader->next; + } + return result; +} + +bool Curl_creader_is_paused(struct Curl_easy *data) +{ + struct Curl_creader *reader = data->req.reader_stack; + + while(reader) { + if(reader->crt->is_paused(data, reader)) + return TRUE; + reader = reader->next; + } + return FALSE; +} + +void Curl_creader_done(struct Curl_easy *data, int premature) +{ + struct Curl_creader *reader = data->req.reader_stack; + while(reader) { + reader->crt->done(data, reader, premature); + reader = reader->next; + } +} + +struct Curl_creader *Curl_creader_get_by_type(struct Curl_easy *data, + const struct Curl_crtype *crt) +{ + struct Curl_creader *r; + for(r = data->req.reader_stack; r; r = r->next) { + if(r->crt == crt) + return r; + } + return NULL; + +} diff --git a/deps/curl/lib/sendf.h b/deps/curl/lib/sendf.h index 5ef2b91df77..dc1b82edfec 100644 --- a/deps/curl/lib/sendf.h +++ b/deps/curl/lib/sendf.h @@ -28,34 +28,388 @@ #include "curl_trc.h" +/** + * Type of data that is being written to the client (application) + * - data written can be either BODY or META data + * - META data is either INFO or HEADER + * - INFO is meta information, e.g. not BODY, that cannot be interpreted + * as headers of a response. Example FTP/IMAP pingpong answers. + * - HEADER can have additional bits set (more than one) + * - STATUS special "header", e.g. response status line in HTTP + * - CONNECT header was received during proxying the connection + * - 1XX header is part of an intermediate response, e.g. HTTP 1xx code + * - TRAILER header is trailing response data, e.g. HTTP trailers + * BODY, INFO and HEADER should not be mixed, as this would lead to + * confusion on how to interpret/format/convert the data. + */ +#define CLIENTWRITE_BODY (1<<0) /* non-meta information, BODY */ +#define CLIENTWRITE_INFO (1<<1) /* meta information, not a HEADER */ +#define CLIENTWRITE_HEADER (1<<2) /* meta information, HEADER */ +#define CLIENTWRITE_STATUS (1<<3) /* a special status HEADER */ +#define CLIENTWRITE_CONNECT (1<<4) /* a CONNECT related HEADER */ +#define CLIENTWRITE_1XX (1<<5) /* a 1xx response related HEADER */ +#define CLIENTWRITE_TRAILER (1<<6) /* a trailer HEADER */ +#define CLIENTWRITE_EOS (1<<7) /* End Of transfer download Stream */ -#define CLIENTWRITE_BODY (1<<0) -#define CLIENTWRITE_HEADER (1<<1) -#define CLIENTWRITE_STATUS (1<<2) /* the first "header" is the status line */ -#define CLIENTWRITE_CONNECT (1<<3) /* a CONNECT response */ -#define CLIENTWRITE_1XX (1<<4) /* a 1xx response */ -#define CLIENTWRITE_TRAILER (1<<5) /* a trailer header */ -#define CLIENTWRITE_BOTH (CLIENTWRITE_BODY|CLIENTWRITE_HEADER) - -CURLcode Curl_client_write(struct Curl_easy *data, int type, char *ptr, +/** + * Write `len` bytes at `prt` to the client. `type` indicates what + * kind of data is being written. + */ +CURLcode Curl_client_write(struct Curl_easy *data, int type, const char *ptr, size_t len) WARN_UNUSED_RESULT; -/* internal read-function, does plain socket, SSL and krb4 */ -CURLcode Curl_read(struct Curl_easy *data, curl_socket_t sockfd, - char *buf, size_t buffersize, - ssize_t *n); - -/* internal write-function, does plain socket, SSL, SCP, SFTP and krb4 */ -CURLcode Curl_write(struct Curl_easy *data, - curl_socket_t sockfd, - const void *mem, size_t len, - ssize_t *written); - -/* internal write-function, using sockindex for connection destination */ -CURLcode Curl_nwrite(struct Curl_easy *data, - int sockindex, - const void *buf, - size_t blen, - ssize_t *pnwritten); +/** + * Free all resources related to client writing. + */ +void Curl_client_cleanup(struct Curl_easy *data); + +/** + * Reset readers and writer chains, keep rewind information + * when necessary. + */ +void Curl_client_reset(struct Curl_easy *data); + +/** + * A new request is starting, perform any ops like rewinding + * previous readers when needed. + */ +CURLcode Curl_client_start(struct Curl_easy *data); + +/** + * Client Writers - a chain passing transfer BODY data to the client. + * Main application: HTTP and related protocols + * Other uses: monitoring of download progress + * + * Writers in the chain are order by their `phase`. First come all + * writers in CURL_CW_RAW, followed by any in CURL_CW_TRANSFER_DECODE, + * followed by any in CURL_CW_PROTOCOL, etc. + * + * When adding a writer, it is inserted as first in its phase. This means + * the order of adding writers of the same phase matters, but writers for + * different phases may be added in any order. + * + * Writers which do modify the BODY data written are expected to be of + * phases TRANSFER_DECODE or CONTENT_DECODE. The other phases are intended + * for monitoring writers. Which do *not* modify the data but gather + * statistics or update progress reporting. + */ + +/* Phase a writer operates at. */ +typedef enum { + CURL_CW_RAW, /* raw data written, before any decoding */ + CURL_CW_TRANSFER_DECODE, /* remove transfer-encodings */ + CURL_CW_PROTOCOL, /* after transfer, but before content decoding */ + CURL_CW_CONTENT_DECODE, /* remove content-encodings */ + CURL_CW_CLIENT /* data written to client */ +} Curl_cwriter_phase; + +/* Client Writer Type, provides the implementation */ +struct Curl_cwtype { + const char *name; /* writer name. */ + const char *alias; /* writer name alias, maybe NULL. */ + CURLcode (*do_init)(struct Curl_easy *data, + struct Curl_cwriter *writer); + CURLcode (*do_write)(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes); + void (*do_close)(struct Curl_easy *data, + struct Curl_cwriter *writer); + size_t cwriter_size; /* sizeof() allocated struct Curl_cwriter */ +}; + +/* Client writer instance, allocated on creation. + * `void *ctx` is the pointer from the allocation of + * the `struct Curl_cwriter` itself. This is suitable for "downcasting" + * by the writers implementation. See https://github.com/curl/curl/pull/13054 + * for the alignment problems that arise otherwise. + */ +struct Curl_cwriter { + const struct Curl_cwtype *cwt; /* type implementation */ + struct Curl_cwriter *next; /* Downstream writer. */ + void *ctx; /* allocated instance pointer */ + Curl_cwriter_phase phase; /* phase at which it operates */ +}; + +/** + * Create a new cwriter instance with given type and phase. Is not + * inserted into the writer chain by this call. + * Invokes `writer->do_init()`. + */ +CURLcode Curl_cwriter_create(struct Curl_cwriter **pwriter, + struct Curl_easy *data, + const struct Curl_cwtype *ce_handler, + Curl_cwriter_phase phase); + +/** + * Free a cwriter instance. + * Invokes `writer->do_close()`. + */ +void Curl_cwriter_free(struct Curl_easy *data, + struct Curl_cwriter *writer); + +/** + * Count the number of writers installed of the given phase. + */ +size_t Curl_cwriter_count(struct Curl_easy *data, Curl_cwriter_phase phase); + +/** + * Adds a writer to the transfer's writer chain. + * The writers `phase` determines where in the chain it is inserted. + */ +CURLcode Curl_cwriter_add(struct Curl_easy *data, + struct Curl_cwriter *writer); + +/** + * Look up an installed client writer on `data` by its type. + * @return first writer with that type or NULL + */ +struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data, + const struct Curl_cwtype *cwt); + +void Curl_cwriter_remove_by_name(struct Curl_easy *data, + const char *name); + +struct Curl_cwriter *Curl_cwriter_get_by_name(struct Curl_easy *data, + const char *name); + +/** + * Convenience method for calling `writer->do_write()` that + * checks for NULL writer. + */ +CURLcode Curl_cwriter_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes); + +/** + * Return TRUE iff client writer is paused. + */ +bool Curl_cwriter_is_paused(struct Curl_easy *data); + +/** + * Unpause client writer and flush any buffered date to the client. + */ +CURLcode Curl_cwriter_unpause(struct Curl_easy *data); + +/** + * Default implementations for do_init, do_write, do_close that + * do nothing and pass the data through. + */ +CURLcode Curl_cwriter_def_init(struct Curl_easy *data, + struct Curl_cwriter *writer); +CURLcode Curl_cwriter_def_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes); +void Curl_cwriter_def_close(struct Curl_easy *data, + struct Curl_cwriter *writer); + + + +/* Client Reader Type, provides the implementation */ +struct Curl_crtype { + const char *name; /* writer name. */ + CURLcode (*do_init)(struct Curl_easy *data, struct Curl_creader *reader); + CURLcode (*do_read)(struct Curl_easy *data, struct Curl_creader *reader, + char *buf, size_t blen, size_t *nread, bool *eos); + void (*do_close)(struct Curl_easy *data, struct Curl_creader *reader); + bool (*needs_rewind)(struct Curl_easy *data, struct Curl_creader *reader); + curl_off_t (*total_length)(struct Curl_easy *data, + struct Curl_creader *reader); + CURLcode (*resume_from)(struct Curl_easy *data, + struct Curl_creader *reader, curl_off_t offset); + CURLcode (*rewind)(struct Curl_easy *data, struct Curl_creader *reader); + CURLcode (*unpause)(struct Curl_easy *data, struct Curl_creader *reader); + bool (*is_paused)(struct Curl_easy *data, struct Curl_creader *reader); + void (*done)(struct Curl_easy *data, + struct Curl_creader *reader, int premature); + size_t creader_size; /* sizeof() allocated struct Curl_creader */ +}; + +/* Phase a reader operates at. */ +typedef enum { + CURL_CR_NET, /* data send to the network (connection filters) */ + CURL_CR_TRANSFER_ENCODE, /* add transfer-encodings */ + CURL_CR_PROTOCOL, /* before transfer, but after content decoding */ + CURL_CR_CONTENT_ENCODE, /* add content-encodings */ + CURL_CR_CLIENT /* data read from client */ +} Curl_creader_phase; + +/* Client reader instance, allocated on creation. + * `void *ctx` is the pointer from the allocation of + * the `struct Curl_cwriter` itself. This is suitable for "downcasting" + * by the writers implementation. See https://github.com/curl/curl/pull/13054 + * for the alignment problems that arise otherwise. + */ +struct Curl_creader { + const struct Curl_crtype *crt; /* type implementation */ + struct Curl_creader *next; /* Downstream reader. */ + void *ctx; + Curl_creader_phase phase; /* phase at which it operates */ +}; + +/** + * Default implementations for do_init, do_write, do_close that + * do nothing and pass the data through. + */ +CURLcode Curl_creader_def_init(struct Curl_easy *data, + struct Curl_creader *reader); +void Curl_creader_def_close(struct Curl_easy *data, + struct Curl_creader *reader); +CURLcode Curl_creader_def_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *nread, bool *eos); +bool Curl_creader_def_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader); +curl_off_t Curl_creader_def_total_length(struct Curl_easy *data, + struct Curl_creader *reader); +CURLcode Curl_creader_def_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset); +CURLcode Curl_creader_def_rewind(struct Curl_easy *data, + struct Curl_creader *reader); +CURLcode Curl_creader_def_unpause(struct Curl_easy *data, + struct Curl_creader *reader); +bool Curl_creader_def_is_paused(struct Curl_easy *data, + struct Curl_creader *reader); +void Curl_creader_def_done(struct Curl_easy *data, + struct Curl_creader *reader, int premature); + +/** + * Convenience method for calling `reader->do_read()` that + * checks for NULL reader. + */ +CURLcode Curl_creader_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, size_t *nread, bool *eos); + +/** + * Create a new creader instance with given type and phase. Is not + * inserted into the writer chain by this call. + * Invokes `reader->do_init()`. + */ +CURLcode Curl_creader_create(struct Curl_creader **preader, + struct Curl_easy *data, + const struct Curl_crtype *cr_handler, + Curl_creader_phase phase); + +/** + * Free a creader instance. + * Invokes `reader->do_close()`. + */ +void Curl_creader_free(struct Curl_easy *data, struct Curl_creader *reader); + +/** + * Adds a reader to the transfer's reader chain. + * The readers `phase` determines where in the chain it is inserted. + */ +CURLcode Curl_creader_add(struct Curl_easy *data, + struct Curl_creader *reader); + +/** + * Set the given reader, which needs to be of type CURL_CR_CLIENT, + * as the new first reader. Discard any installed readers and init + * the reader chain anew. + * The function takes ownership of `r`. + */ +CURLcode Curl_creader_set(struct Curl_easy *data, struct Curl_creader *r); + +/** + * Read at most `blen` bytes at `buf` from the client. + * @param data the transfer to read client bytes for + * @param buf the memory location to read to + * @param blen the amount of memory at `buf` + * @param nread on return the number of bytes read into `buf` + * @param eos TRUE iff bytes are the end of data from client + * @return CURLE_OK on successful read (even 0 length) or error + */ +CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen, + size_t *nread, bool *eos) WARN_UNUSED_RESULT; + +/** + * TRUE iff client reader needs rewing before it can be used for + * a retry request. + */ +bool Curl_creader_needs_rewind(struct Curl_easy *data); + +/** + * TRUE iff client reader will rewind at next start + */ +bool Curl_creader_will_rewind(struct Curl_easy *data); + +/** + * En-/disable rewind of client reader at next start. + */ +void Curl_creader_set_rewind(struct Curl_easy *data, bool enable); + +/** + * Get the total length of bytes provided by the installed readers. + * This is independent of the amount already delivered and is calculated + * by all readers in the stack. If a reader like "chunked" or + * "crlf conversion" is installed, the returned length will be -1. + * @return -1 if length is indeterminate + */ +curl_off_t Curl_creader_total_length(struct Curl_easy *data); + +/** + * Get the total length of bytes provided by the reader at phase + * CURL_CR_CLIENT. This may not match the amount of bytes read + * for a request, depending if other, encoding readers are also installed. + * However it allows for rough estimation of the overall length. + * @return -1 if length is indeterminate + */ +curl_off_t Curl_creader_client_length(struct Curl_easy *data); + +/** + * Ask the installed reader at phase CURL_CR_CLIENT to start + * reading from the given offset. On success, this will reduce + * the `total_length()` by the amount. + * @param data the transfer to read client bytes for + * @param offset the offset where to start reads from, negative + * values will be ignored. + * @return CURLE_OK if offset could be set + * CURLE_READ_ERROR if not supported by reader or seek/read failed + * of offset larger then total length + * CURLE_PARTIAL_FILE if offset led to 0 total length + */ +CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset); + +/** + * Unpause all installed readers. + */ +CURLcode Curl_creader_unpause(struct Curl_easy *data); + +/** + * Return TRUE iff any of the installed readers is paused. + */ +bool Curl_creader_is_paused(struct Curl_easy *data); + +/** + * Tell all client readers that they are done. + */ +void Curl_creader_done(struct Curl_easy *data, int premature); + +/** + * Look up an installed client reader on `data` by its type. + * @return first reader with that type or NULL + */ +struct Curl_creader *Curl_creader_get_by_type(struct Curl_easy *data, + const struct Curl_crtype *crt); + + +/** + * Set the client reader to provide 0 bytes, immediate EOS. + */ +CURLcode Curl_creader_set_null(struct Curl_easy *data); + +/** + * Set the client reader the reads from fread callback. + */ +CURLcode Curl_creader_set_fread(struct Curl_easy *data, curl_off_t len); + +/** + * Set the client reader the reads from the supplied buf (NOT COPIED). + */ +CURLcode Curl_creader_set_buf(struct Curl_easy *data, + const char *buf, size_t blen); #endif /* HEADER_CURL_SENDF_H */ diff --git a/deps/curl/lib/setopt.c b/deps/curl/lib/setopt.c index 2cef1b3d828..4f069721273 100644 --- a/deps/curl/lib/setopt.c +++ b/deps/curl/lib/setopt.c @@ -50,6 +50,9 @@ #include "multiif.h" #include "altsvc.h" #include "hsts.h" +#include "tftp.h" +#include "strdup.h" +#include "escape.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -108,52 +111,75 @@ CURLcode Curl_setblobopt(struct curl_blob **blobp, static CURLcode setstropt_userpwd(char *option, char **userp, char **passwdp) { - CURLcode result = CURLE_OK; char *user = NULL; char *passwd = NULL; + DEBUGASSERT(userp); + DEBUGASSERT(passwdp); + /* Parse the login details if specified. It not then we treat NULL as a hint to clear the existing data */ if(option) { size_t len = strlen(option); + CURLcode result; if(len > CURL_MAX_INPUT_LENGTH) return CURLE_BAD_FUNCTION_ARGUMENT; - result = Curl_parse_login_details(option, len, - (userp ? &user : NULL), - (passwdp ? &passwd : NULL), - NULL); + result = Curl_parse_login_details(option, len, &user, &passwd, NULL); + if(result) + return result; } - if(!result) { - /* Store the username part of option if required */ - if(userp) { - if(!user && option && option[0] == ':') { - /* Allocate an empty string instead of returning NULL as user name */ - user = strdup(""); - if(!user) - result = CURLE_OUT_OF_MEMORY; - } + free(*userp); + *userp = user; - Curl_safefree(*userp); - *userp = user; - } + free(*passwdp); + *passwdp = passwd; - /* Store the password part of option if required */ - if(passwdp) { - Curl_safefree(*passwdp); - *passwdp = passwd; - } + return CURLE_OK; +} + +static CURLcode setstropt_interface(char *option, char **devp, + char **ifacep, char **hostp) +{ + char *dev = NULL; + char *iface = NULL; + char *host = NULL; + CURLcode result; + + DEBUGASSERT(devp); + DEBUGASSERT(ifacep); + DEBUGASSERT(hostp); + + if(option) { + /* Parse the interface details if set, otherwise clear them all */ + result = Curl_parse_interface(option, &dev, &iface, &host); + if(result) + return result; } + free(*devp); + *devp = dev; - return result; + free(*ifacep); + *ifacep = iface; + + free(*hostp); + *hostp = host; + + return CURLE_OK; } #define C_SSLVERSION_VALUE(x) (x & 0xffff) -#define C_SSLVERSION_MAX_VALUE(x) (x & 0xffff0000) +#define C_SSLVERSION_MAX_VALUE(x) ((unsigned long)x & 0xffff0000) static CURLcode protocol2num(const char *str, curl_prot_t *val) { + /* + * We are asked to cherry-pick protocols, so play it safe and disallow all + * protocols to start with, and re-add the wanted ones back in. + */ + *val = 0; + if(!str) return CURLE_BAD_FUNCTION_ARGUMENT; @@ -162,16 +188,14 @@ static CURLcode protocol2num(const char *str, curl_prot_t *val) return CURLE_OK; } - *val = 0; - do { const char *token = str; size_t tlen; str = strchr(str, ','); - tlen = str? (size_t) (str - token): strlen(token); + tlen = str ? (size_t) (str - token) : strlen(token); if(tlen) { - const struct Curl_handler *h = Curl_builtin_scheme(token, tlen); + const struct Curl_handler *h = Curl_getn_scheme_handler(token, tlen); if(!h) return CURLE_UNSUPPORTED_PROTOCOL; @@ -186,21 +210,58 @@ static CURLcode protocol2num(const char *str, curl_prot_t *val) return CURLE_OK; } -/* - * Do not make Curl_vsetopt() static: it is called from - * packages/OS400/ccsidcurl.c. - */ -CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) +static CURLcode httpauth(struct Curl_easy *data, bool proxy, + unsigned long auth) { - char *argptr; - CURLcode result = CURLE_OK; - long arg; - unsigned long uarg; - curl_off_t bigsize; + if(auth != CURLAUTH_NONE) { + int bitcheck = 0; + bool authbits = FALSE; + /* the DIGEST_IE bit is only used to set a special marker, for all the + rest we need to handle it as normal DIGEST */ + bool iestyle = !!(auth & CURLAUTH_DIGEST_IE); + if(proxy) + data->state.authproxy.iestyle = iestyle; + else + data->state.authhost.iestyle = iestyle; + + if(auth & CURLAUTH_DIGEST_IE) { + auth |= CURLAUTH_DIGEST; /* set standard digest bit */ + auth &= ~CURLAUTH_DIGEST_IE; /* unset ie digest bit */ + } + + /* switch off bits we cannot support */ +#ifndef USE_NTLM + auth &= ~CURLAUTH_NTLM; /* no NTLM support */ +#endif +#ifndef USE_SPNEGO + auth &= ~CURLAUTH_NEGOTIATE; /* no Negotiate (SPNEGO) auth without GSS-API + or SSPI */ +#endif + + /* check if any auth bit lower than CURLAUTH_ONLY is still set */ + while(bitcheck < 31) { + if(auth & (1UL << bitcheck++)) { + authbits = TRUE; + break; + } + } + if(!authbits) + return CURLE_NOT_BUILT_IN; /* no supported types left! */ + } + if(proxy) + data->set.proxyauth = auth; + else + data->set.httpauth = auth; + return CURLE_OK; +} +static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, + long arg) +{ + bool enabled = (0 != arg); + unsigned long uarg = (unsigned long)arg; switch(option) { case CURLOPT_DNS_CACHE_TIMEOUT: - arg = va_arg(param, long); if(arg < -1) return CURLE_BAD_FUNCTION_ARGUMENT; else if(arg > INT_MAX) @@ -209,95 +270,58 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) data->set.dns_cache_timeout = (int)arg; break; case CURLOPT_CA_CACHE_TIMEOUT: - arg = va_arg(param, long); - if(arg < -1) - return CURLE_BAD_FUNCTION_ARGUMENT; - else if(arg > INT_MAX) - arg = INT_MAX; + if(Curl_ssl_supports(data, SSLSUPP_CA_CACHE)) { + if(arg < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + else if(arg > INT_MAX) + arg = INT_MAX; - data->set.general_ssl.ca_cache_timeout = (int)arg; - break; - case CURLOPT_DNS_USE_GLOBAL_CACHE: - /* deprecated */ - break; - case CURLOPT_SSL_CIPHER_LIST: - /* set a list of cipher we want to use in the SSL connection */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CIPHER_LIST], - va_arg(param, char *)); - break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSL_CIPHER_LIST: - /* set a list of cipher we want to use in the SSL connection for proxy */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CIPHER_LIST_PROXY], - va_arg(param, char *)); - break; -#endif - case CURLOPT_TLS13_CIPHERS: - if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) { - /* set preferred list of TLS 1.3 cipher suites */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CIPHER13_LIST], - va_arg(param, char *)); - } - else - return CURLE_NOT_BUILT_IN; - break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_TLS13_CIPHERS: - if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) { - /* set preferred list of TLS 1.3 cipher suites for proxy */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CIPHER13_LIST_PROXY], - va_arg(param, char *)); + data->set.general_ssl.ca_cache_timeout = (int)arg; } else return CURLE_NOT_BUILT_IN; break; -#endif - case CURLOPT_RANDOM_FILE: - break; - case CURLOPT_EGDSOCKET: - break; case CURLOPT_MAXCONNECTS: /* * Set the absolute number of maximum simultaneous alive connection that * libcurl is allowed to have. */ - arg = va_arg(param, long); - if(arg < 0) + if(uarg > UINT_MAX) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.maxconnects = arg; + data->set.maxconnects = (unsigned int)uarg; break; - case CURLOPT_FORBID_REUSE: + case CURLOPT_FORBID_REUSE: /* * When this transfer is done, it must not be left to be reused by a * subsequent transfer but shall be closed immediately. */ - data->set.reuse_forbid = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.reuse_forbid = enabled; break; case CURLOPT_FRESH_CONNECT: /* * This transfer shall not use a previously cached connection but * should be made with a fresh new connect! */ - data->set.reuse_fresh = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.reuse_fresh = enabled; break; case CURLOPT_VERBOSE: /* * Verbose means infof() calls that give a lot of information about * the connection and transfer procedures as well as internal choices. */ - data->set.verbose = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.verbose = enabled; break; case CURLOPT_HEADER: /* * Set to include the header in the general data output stream. */ - data->set.include_header = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.include_header = enabled; break; case CURLOPT_NOPROGRESS: /* * Shut off the internal supported progress meter */ - data->set.hide_progress = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.hide_progress = enabled; if(data->set.hide_progress) data->progress.flags |= PGRS_HIDE; else @@ -307,7 +331,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) /* * Do not include the body part in the output data stream. */ - data->set.opt_no_body = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.opt_no_body = enabled; #ifndef CURL_DISABLE_HTTP if(data->set.opt_no_body) /* in HTTP lingo, no body means using the HEAD request... */ @@ -318,14 +342,13 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) break; case CURLOPT_FAILONERROR: /* - * Don't output the >=400 error code HTML-page, but instead only + * Do not output the >=400 error code HTML-page, but instead only * return error. */ - data->set.http_fail_on_error = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.http_fail_on_error = enabled; break; case CURLOPT_KEEP_SENDING_ON_ERROR: - data->set.http_keep_sending_on_error = (0 != va_arg(param, long)) ? - TRUE : FALSE; + data->set.http_keep_sending_on_error = enabled; break; case CURLOPT_UPLOAD: case CURLOPT_PUT: @@ -333,7 +356,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * We want to sent data to the remote host. If this is HTTP, that equals * using the PUT request. */ - arg = va_arg(param, long); if(arg) { /* If this is HTTP, PUT is what's needed to "upload" */ data->set.method = HTTPREQ_PUT; @@ -344,43 +366,49 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) then this can be changed to HEAD later on) */ data->set.method = HTTPREQ_GET; break; - case CURLOPT_REQUEST_TARGET: - result = Curl_setstropt(&data->set.str[STRING_TARGET], - va_arg(param, char *)); - break; case CURLOPT_FILETIME: /* * Try to get the file time of the remote document. The time will * later (possibly) become available using curl_easy_getinfo(). */ - data->set.get_filetime = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.get_filetime = enabled; break; case CURLOPT_SERVER_RESPONSE_TIMEOUT: /* * Option that specifies how quickly a server response must be obtained * before it is considered failure. For pingpong protocols. */ - arg = va_arg(param, long); if((arg >= 0) && (arg <= (INT_MAX/1000))) data->set.server_response_timeout = (unsigned int)arg * 1000; else return CURLE_BAD_FUNCTION_ARGUMENT; break; + case CURLOPT_SERVER_RESPONSE_TIMEOUT_MS: + /* + * Option that specifies how quickly a server response must be obtained + * before it is considered failure. For pingpong protocols. + */ + if((arg >= 0) && (arg <= INT_MAX)) + data->set.server_response_timeout = (unsigned int)arg; + else + return CURLE_BAD_FUNCTION_ARGUMENT; + break; #ifndef CURL_DISABLE_TFTP case CURLOPT_TFTP_NO_OPTIONS: /* * Option that prevents libcurl from sending TFTP option requests to the * server. */ - data->set.tftp_no_options = va_arg(param, long) != 0; + data->set.tftp_no_options = enabled; break; case CURLOPT_TFTP_BLKSIZE: /* * TFTP option that specifies the block size to use for data transmission. */ - arg = va_arg(param, long); - if(arg < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; + if(arg < TFTP_BLKSIZE_MIN) + arg = 512; + else if(arg > TFTP_BLKSIZE_MAX) + arg = TFTP_BLKSIZE_MAX; data->set.tftp_blksize = arg; break; #endif @@ -389,18 +417,10 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) /* * Parse the $HOME/.netrc file */ - arg = va_arg(param, long); if((arg < CURL_NETRC_IGNORED) || (arg >= CURL_NETRC_LAST)) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.use_netrc = (unsigned char)arg; break; - case CURLOPT_NETRC_FILE: - /* - * Use this file instead of the $HOME/.netrc file - */ - result = Curl_setstropt(&data->set.str[STRING_NETRC_FILE], - va_arg(param, char *)); - break; #endif case CURLOPT_TRANSFERTEXT: /* @@ -409,14 +429,13 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * * Transfer using ASCII (instead of BINARY). */ - data->set.prefer_ascii = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.prefer_ascii = enabled; break; case CURLOPT_TIMECONDITION: /* * Set HTTP time condition. This must be one of the defines in the * curl/curl.h header file. */ - arg = va_arg(param, long); if((arg < CURL_TIMECOND_NONE) || (arg >= CURL_TIMECOND_LAST)) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.timecondition = (unsigned char)(curl_TimeCond)arg; @@ -426,17 +445,8 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * This is the value to compare with the remote document with the * method set with CURLOPT_TIMECONDITION */ - data->set.timevalue = (time_t)va_arg(param, long); - break; - - case CURLOPT_TIMEVALUE_LARGE: - /* - * This is the value to compare with the remote document with the - * method set with CURLOPT_TIMECONDITION - */ - data->set.timevalue = (time_t)va_arg(param, curl_off_t); + data->set.timevalue = (time_t)arg; break; - case CURLOPT_SSLVERSION: #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_SSLVERSION: @@ -453,11 +463,8 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) if(option != CURLOPT_SSLVERSION) primary = &data->set.proxy_ssl.primary; #endif - - arg = va_arg(param, long); - version = C_SSLVERSION_VALUE(arg); - version_max = C_SSLVERSION_MAX_VALUE(arg); + version_max = (long)C_SSLVERSION_MAX_VALUE(arg); if(version < CURL_SSLVERSION_DEFAULT || version == CURL_SSLVERSION_SSLv2 || @@ -471,149 +478,54 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) primary->version_max = (unsigned int)version_max; } #else - result = CURLE_NOT_BUILT_IN; + return CURLE_NOT_BUILT_IN; #endif break; - - /* MQTT "borrows" some of the HTTP options */ -#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_MQTT) - case CURLOPT_COPYPOSTFIELDS: - /* - * A string with POST data. Makes curl HTTP POST. Even if it is NULL. - * If needed, CURLOPT_POSTFIELDSIZE must have been set prior to - * CURLOPT_COPYPOSTFIELDS and not altered later. - */ - argptr = va_arg(param, char *); - - if(!argptr || data->set.postfieldsize == -1) - result = Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], argptr); - else { - /* - * Check that requested length does not overflow the size_t type. - */ - - if((data->set.postfieldsize < 0) || - ((sizeof(curl_off_t) != sizeof(size_t)) && - (data->set.postfieldsize > (curl_off_t)((size_t)-1)))) - result = CURLE_OUT_OF_MEMORY; - else { - char *p; - - (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); - - /* Allocate even when size == 0. This satisfies the need of possible - later address compare to detect the COPYPOSTFIELDS mode, and - to mark that postfields is used rather than read function or - form data. - */ - p = malloc((size_t)(data->set.postfieldsize? - data->set.postfieldsize:1)); - - if(!p) - result = CURLE_OUT_OF_MEMORY; - else { - if(data->set.postfieldsize) - memcpy(p, argptr, (size_t)data->set.postfieldsize); - - data->set.str[STRING_COPYPOSTFIELDS] = p; - } - } - } - - data->set.postfields = data->set.str[STRING_COPYPOSTFIELDS]; - data->set.method = HTTPREQ_POST; - break; - - case CURLOPT_POSTFIELDS: - /* - * Like above, but use static data instead of copying it. - */ - data->set.postfields = va_arg(param, void *); - /* Release old copied data. */ - (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); - data->set.method = HTTPREQ_POST; - break; - case CURLOPT_POSTFIELDSIZE: /* * The size of the POSTFIELD data to prevent libcurl to do strlen() to * figure it out. Enables binary posts. */ - bigsize = va_arg(param, long); - if(bigsize < -1) + if(arg < -1) return CURLE_BAD_FUNCTION_ARGUMENT; - if(data->set.postfieldsize < bigsize && + if(data->set.postfieldsize < arg && data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ - (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); + Curl_safefree(data->set.str[STRING_COPYPOSTFIELDS]); data->set.postfields = NULL; } - data->set.postfieldsize = bigsize; + data->set.postfieldsize = arg; break; - - case CURLOPT_POSTFIELDSIZE_LARGE: +#ifndef CURL_DISABLE_HTTP +#if !defined(CURL_DISABLE_COOKIES) + case CURLOPT_COOKIESESSION: /* - * The size of the POSTFIELD data to prevent libcurl to do strlen() to - * figure it out. Enables binary posts. + * Set this option to TRUE to start a new "cookie session". It will + * prevent the forthcoming read-cookies-from-file actions to accept + * cookies that are marked as being session cookies, as they belong to a + * previous session. */ - bigsize = va_arg(param, curl_off_t); - if(bigsize < -1) - return CURLE_BAD_FUNCTION_ARGUMENT; - - if(data->set.postfieldsize < bigsize && - data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { - /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ - (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); - data->set.postfields = NULL; - } - - data->set.postfieldsize = bigsize; + data->set.cookiesession = enabled; break; #endif -#ifndef CURL_DISABLE_HTTP case CURLOPT_AUTOREFERER: /* * Switch on automatic referer that gets set if curl follows locations. */ - data->set.http_auto_referer = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - - case CURLOPT_ACCEPT_ENCODING: - /* - * String to use at the value of Accept-Encoding header. - * - * If the encoding is set to "" we use an Accept-Encoding header that - * encompasses all the encodings we support. - * If the encoding is set to NULL we don't send an Accept-Encoding header - * and ignore an received Content-Encoding header. - * - */ - argptr = va_arg(param, char *); - if(argptr && !*argptr) { - argptr = Curl_all_content_encodings(); - if(!argptr) - result = CURLE_OUT_OF_MEMORY; - else { - result = Curl_setstropt(&data->set.str[STRING_ENCODING], argptr); - free(argptr); - } - } - else - result = Curl_setstropt(&data->set.str[STRING_ENCODING], argptr); + data->set.http_auto_referer = enabled; break; case CURLOPT_TRANSFER_ENCODING: - data->set.http_transfer_encoding = (0 != va_arg(param, long)) ? - TRUE : FALSE; + data->set.http_transfer_encoding = enabled; break; case CURLOPT_FOLLOWLOCATION: /* * Follow Location: header hints on an HTTP-server. */ - data->set.http_follow_location = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.http_follow_location = enabled; break; case CURLOPT_UNRESTRICTED_AUTH: @@ -621,8 +533,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * Send authentication (user+password) when following locations, even when * hostname changed. */ - data->set.allow_auth_to_other_hosts = - (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.allow_auth_to_other_hosts = enabled; break; case CURLOPT_MAXREDIRS: @@ -630,7 +541,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * The maximum amount of hops you allow curl to follow Location: * headers. This should mostly be used to detect never-ending loops. */ - arg = va_arg(param, long); if(arg < -1) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.maxredirs = arg; @@ -646,7 +556,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * CURL_REDIR_POST_ALL - POST is kept as POST after 301, 302 and 303 * other - POST is kept as POST after 301 and 302 */ - arg = va_arg(param, long); if(arg < CURL_REDIR_GET_ALL) /* no return error on too high numbers since the bitmask could be extended in a future */ @@ -656,251 +565,39 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) case CURLOPT_POST: /* Does this option serve a purpose anymore? Yes it does, when - CURLOPT_POSTFIELDS isn't used and the POST data is read off the + CURLOPT_POSTFIELDS is not used and the POST data is read off the callback! */ - if(va_arg(param, long)) { + if(arg) { data->set.method = HTTPREQ_POST; data->set.opt_no_body = FALSE; /* this is implied */ } else data->set.method = HTTPREQ_GET; break; - -#ifndef CURL_DISABLE_FORM_API - case CURLOPT_HTTPPOST: - /* - * Set to make us do HTTP POST. Legacy API-style. - */ - data->set.httppost = va_arg(param, struct curl_httppost *); - data->set.method = HTTPREQ_POST_FORM; - data->set.opt_no_body = FALSE; /* this is implied */ - Curl_mime_cleanpart(data->state.formp); - Curl_safefree(data->state.formp); - break; -#endif - -#if !defined(CURL_DISABLE_AWS) - case CURLOPT_AWS_SIGV4: - /* - * String that is merged to some authentication - * parameters are used by the algorithm. - */ - result = Curl_setstropt(&data->set.str[STRING_AWS_SIGV4], - va_arg(param, char *)); + case CURLOPT_HEADEROPT: /* - * Basic been set by default it need to be unset here + * Set header option. */ - if(data->set.str[STRING_AWS_SIGV4]) - data->set.httpauth = CURLAUTH_AWS_SIGV4; + data->set.sep_headers = !!(arg & CURLHEADER_SEPARATE); break; -#endif + case CURLOPT_HTTPAUTH: + return httpauth(data, FALSE, uarg); - case CURLOPT_REFERER: + case CURLOPT_HTTPGET: /* - * String to set in the HTTP Referer: field. + * Set to force us do HTTP GET */ - if(data->state.referer_alloc) { - Curl_safefree(data->state.referer); - data->state.referer_alloc = FALSE; + if(enabled) { + data->set.method = HTTPREQ_GET; + data->set.opt_no_body = FALSE; /* this is implied */ } - result = Curl_setstropt(&data->set.str[STRING_SET_REFERER], - va_arg(param, char *)); - data->state.referer = data->set.str[STRING_SET_REFERER]; - break; - - case CURLOPT_USERAGENT: - /* - * String to use in the HTTP User-Agent field - */ - result = Curl_setstropt(&data->set.str[STRING_USERAGENT], - va_arg(param, char *)); break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXYHEADER: + case CURLOPT_HTTP_VERSION: /* - * Set a list with proxy headers to use (or replace internals with) - * - * Since CURLOPT_HTTPHEADER was the only way to set HTTP headers for a - * long time we remain doing it this way until CURLOPT_PROXYHEADER is - * used. As soon as this option has been used, if set to anything but - * NULL, custom headers for proxies are only picked from this list. - * - * Set this option to NULL to restore the previous behavior. + * This sets a requested HTTP version to be used. The value is one of + * the listed enums in curl/curl.h. */ - data->set.proxyheaders = va_arg(param, struct curl_slist *); - break; -#endif - case CURLOPT_HEADEROPT: - /* - * Set header option. - */ - arg = va_arg(param, long); - data->set.sep_headers = (bool)((arg & CURLHEADER_SEPARATE)? TRUE: FALSE); - break; - -#if !defined(CURL_DISABLE_COOKIES) - case CURLOPT_COOKIE: - /* - * Cookie string to send to the remote server in the request. - */ - result = Curl_setstropt(&data->set.str[STRING_COOKIE], - va_arg(param, char *)); - break; - - case CURLOPT_COOKIEFILE: - /* - * Set cookie file to read and parse. Can be used multiple times. - */ - argptr = (char *)va_arg(param, void *); - if(argptr) { - struct curl_slist *cl; - /* general protection against mistakes and abuse */ - if(strlen(argptr) > CURL_MAX_INPUT_LENGTH) - return CURLE_BAD_FUNCTION_ARGUMENT; - /* append the cookie file name to the list of file names, and deal with - them later */ - cl = curl_slist_append(data->set.cookielist, argptr); - if(!cl) { - curl_slist_free_all(data->set.cookielist); - data->set.cookielist = NULL; - return CURLE_OUT_OF_MEMORY; - } - data->set.cookielist = cl; /* store the list for later use */ - } - else { - /* clear the list of cookie files */ - curl_slist_free_all(data->set.cookielist); - data->set.cookielist = NULL; - - if(!data->share || !data->share->cookies) { - /* throw away all existing cookies if this isn't a shared cookie - container */ - Curl_cookie_clearall(data->cookies); - Curl_cookie_cleanup(data->cookies); - } - /* disable the cookie engine */ - data->cookies = NULL; - } - break; - - case CURLOPT_COOKIEJAR: - /* - * Set cookie file name to dump all cookies to when we're done. - */ - { - struct CookieInfo *newcookies; - result = Curl_setstropt(&data->set.str[STRING_COOKIEJAR], - va_arg(param, char *)); - - /* - * Activate the cookie parser. This may or may not already - * have been made. - */ - newcookies = Curl_cookie_init(data, NULL, data->cookies, - data->set.cookiesession); - if(!newcookies) - result = CURLE_OUT_OF_MEMORY; - data->cookies = newcookies; - } - break; - - case CURLOPT_COOKIESESSION: - /* - * Set this option to TRUE to start a new "cookie session". It will - * prevent the forthcoming read-cookies-from-file actions to accept - * cookies that are marked as being session cookies, as they belong to a - * previous session. - * - * In the original Netscape cookie spec, "session cookies" are cookies - * with no expire date set. RFC2109 describes the same action if no - * 'Max-Age' is set and RFC2965 includes the RFC2109 description and adds - * a 'Discard' action that can enforce the discard even for cookies that - * have a Max-Age. - * - * We run mostly with the original cookie spec, as hardly anyone implements - * anything else. - */ - data->set.cookiesession = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - - case CURLOPT_COOKIELIST: - argptr = va_arg(param, char *); - - if(!argptr) - break; - - if(strcasecompare(argptr, "ALL")) { - /* clear all cookies */ - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - Curl_cookie_clearall(data->cookies); - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - } - else if(strcasecompare(argptr, "SESS")) { - /* clear session cookies */ - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - Curl_cookie_clearsess(data->cookies); - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - } - else if(strcasecompare(argptr, "FLUSH")) { - /* flush cookies to file, takes care of the locking */ - Curl_flush_cookies(data, FALSE); - } - else if(strcasecompare(argptr, "RELOAD")) { - /* reload cookies from file */ - Curl_cookie_loadfiles(data); - break; - } - else { - if(!data->cookies) - /* if cookie engine was not running, activate it */ - data->cookies = Curl_cookie_init(data, NULL, NULL, TRUE); - - /* general protection against mistakes and abuse */ - if(strlen(argptr) > CURL_MAX_INPUT_LENGTH) - return CURLE_BAD_FUNCTION_ARGUMENT; - argptr = strdup(argptr); - if(!argptr || !data->cookies) { - result = CURLE_OUT_OF_MEMORY; - free(argptr); - } - else { - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - - if(checkprefix("Set-Cookie:", argptr)) - /* HTTP Header format line */ - Curl_cookie_add(data, data->cookies, TRUE, FALSE, argptr + 11, NULL, - NULL, TRUE); - - else - /* Netscape format line */ - Curl_cookie_add(data, data->cookies, FALSE, FALSE, argptr, NULL, - NULL, TRUE); - - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - free(argptr); - } - } - - break; -#endif /* !CURL_DISABLE_COOKIES */ - - case CURLOPT_HTTPGET: - /* - * Set to force us do HTTP GET - */ - if(va_arg(param, long)) { - data->set.method = HTTPREQ_GET; - data->set.opt_no_body = FALSE; /* this is implied */ - } - break; - - case CURLOPT_HTTP_VERSION: - /* - * This sets a requested HTTP version to be used. The value is one of - * the listed enums in curl/curl.h. - */ - arg = va_arg(param, long); switch(arg) { case CURL_HTTP_VERSION_NONE: #ifdef USE_HTTP2 @@ -921,7 +618,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) /* accepted */ break; #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 case CURL_HTTP_VERSION_3: case CURL_HTTP_VERSION_3ONLY: /* accepted */ @@ -941,233 +638,51 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * Time to wait for a response to an HTTP request containing an * Expect: 100-continue header before sending the data anyway. */ - arg = va_arg(param, long); if(arg < 0) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.expect_100_timeout = arg; break; case CURLOPT_HTTP09_ALLOWED: - arg = va_arg(param, unsigned long); - if(arg > 1L) - return CURLE_BAD_FUNCTION_ARGUMENT; #ifdef USE_HYPER /* Hyper does not support HTTP/0.9 */ - if(arg) + if(enabled) return CURLE_BAD_FUNCTION_ARGUMENT; #else - data->set.http09_allowed = arg ? TRUE : FALSE; -#endif - break; - - case CURLOPT_HTTP200ALIASES: - /* - * Set a list of aliases for HTTP 200 in response header - */ - data->set.http200aliases = va_arg(param, struct curl_slist *); - break; -#endif /* CURL_DISABLE_HTTP */ - -#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) || \ - !defined(CURL_DISABLE_IMAP) -# if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_MIME) - case CURLOPT_HTTPHEADER: - /* - * Set a list with HTTP headers to use (or replace internals with) - */ - data->set.headers = va_arg(param, struct curl_slist *); - break; -# endif - -# ifndef CURL_DISABLE_MIME - case CURLOPT_MIMEPOST: - /* - * Set to make us do MIME POST - */ - result = Curl_mime_set_subparts(&data->set.mimepost, - va_arg(param, curl_mime *), FALSE); - if(!result) { - data->set.method = HTTPREQ_POST_MIME; - data->set.opt_no_body = FALSE; /* this is implied */ -#ifndef CURL_DISABLE_FORM_API - Curl_mime_cleanpart(data->state.formp); - Curl_safefree(data->state.formp); + data->set.http09_allowed = enabled; #endif - } break; +#endif /* ! CURL_DISABLE_HTTP */ +#ifndef CURL_DISABLE_MIME case CURLOPT_MIME_OPTIONS: - data->set.mime_options = (unsigned int)va_arg(param, long); + data->set.mime_formescape = !!(arg & CURLMIMEOPT_FORMESCAPE); break; -# endif -#endif - - case CURLOPT_HTTPAUTH: - /* - * Set HTTP Authentication type BITMASK. - */ - { - int bitcheck; - bool authbits; - unsigned long auth = va_arg(param, unsigned long); - - if(auth == CURLAUTH_NONE) { - data->set.httpauth = auth; - break; - } - - /* the DIGEST_IE bit is only used to set a special marker, for all the - rest we need to handle it as normal DIGEST */ - data->state.authhost.iestyle = - (bool)((auth & CURLAUTH_DIGEST_IE) ? TRUE : FALSE); - - if(auth & CURLAUTH_DIGEST_IE) { - auth |= CURLAUTH_DIGEST; /* set standard digest bit */ - auth &= ~CURLAUTH_DIGEST_IE; /* unset ie digest bit */ - } - - /* switch off bits we can't support */ -#ifndef USE_NTLM - auth &= ~CURLAUTH_NTLM; /* no NTLM support */ - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ -#elif !defined(NTLM_WB_ENABLED) - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ -#endif -#ifndef USE_SPNEGO - auth &= ~CURLAUTH_NEGOTIATE; /* no Negotiate (SPNEGO) auth without - GSS-API or SSPI */ #endif - - /* check if any auth bit lower than CURLAUTH_ONLY is still set */ - bitcheck = 0; - authbits = FALSE; - while(bitcheck < 31) { - if(auth & (1UL << bitcheck++)) { - authbits = TRUE; - break; - } - } - if(!authbits) - return CURLE_NOT_BUILT_IN; /* no supported types left! */ - - data->set.httpauth = auth; - } - break; - - case CURLOPT_CUSTOMREQUEST: - /* - * Set a custom string to use as request - */ - result = Curl_setstropt(&data->set.str[STRING_CUSTOMREQUEST], - va_arg(param, char *)); - - /* we don't set - data->set.method = HTTPREQ_CUSTOM; - here, we continue as if we were using the already set type - and this just changes the actual request keyword */ - break; - #ifndef CURL_DISABLE_PROXY case CURLOPT_HTTPPROXYTUNNEL: /* * Tunnel operations through the proxy instead of normal proxy use */ - data->set.tunnel_thru_httpproxy = (0 != va_arg(param, long)) ? - TRUE : FALSE; + data->set.tunnel_thru_httpproxy = enabled; break; case CURLOPT_PROXYPORT: /* * Explicitly set HTTP proxy port number. */ - arg = va_arg(param, long); if((arg < 0) || (arg > 65535)) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.proxyport = (unsigned short)arg; break; case CURLOPT_PROXYAUTH: - /* - * Set HTTP Authentication type BITMASK. - */ - { - int bitcheck; - bool authbits; - unsigned long auth = va_arg(param, unsigned long); - - if(auth == CURLAUTH_NONE) { - data->set.proxyauth = auth; - break; - } - - /* the DIGEST_IE bit is only used to set a special marker, for all the - rest we need to handle it as normal DIGEST */ - data->state.authproxy.iestyle = - (bool)((auth & CURLAUTH_DIGEST_IE) ? TRUE : FALSE); - - if(auth & CURLAUTH_DIGEST_IE) { - auth |= CURLAUTH_DIGEST; /* set standard digest bit */ - auth &= ~CURLAUTH_DIGEST_IE; /* unset ie digest bit */ - } - /* switch off bits we can't support */ -#ifndef USE_NTLM - auth &= ~CURLAUTH_NTLM; /* no NTLM support */ - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ -#elif !defined(NTLM_WB_ENABLED) - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ -#endif -#ifndef USE_SPNEGO - auth &= ~CURLAUTH_NEGOTIATE; /* no Negotiate (SPNEGO) auth without - GSS-API or SSPI */ -#endif - - /* check if any auth bit lower than CURLAUTH_ONLY is still set */ - bitcheck = 0; - authbits = FALSE; - while(bitcheck < 31) { - if(auth & (1UL << bitcheck++)) { - authbits = TRUE; - break; - } - } - if(!authbits) - return CURLE_NOT_BUILT_IN; /* no supported types left! */ - - data->set.proxyauth = auth; - } - break; - - case CURLOPT_PROXY: - /* - * Set proxy server:port to use as proxy. - * - * If the proxy is set to "" (and CURLOPT_SOCKS_PROXY is set to "" or NULL) - * we explicitly say that we don't want to use a proxy - * (even though there might be environment variables saying so). - * - * Setting it to NULL, means no proxy but allows the environment variables - * to decide for us (if CURLOPT_SOCKS_PROXY setting it to NULL). - */ - result = Curl_setstropt(&data->set.str[STRING_PROXY], - va_arg(param, char *)); - break; - - case CURLOPT_PRE_PROXY: - /* - * Set proxy server:port to use as SOCKS proxy. - * - * If the proxy is set to "" or NULL we explicitly say that we don't want - * to use the socks proxy. - */ - result = Curl_setstropt(&data->set.str[STRING_PRE_PROXY], - va_arg(param, char *)); - break; + return httpauth(data, TRUE, uarg); case CURLOPT_PROXYTYPE: /* * Set proxy type. */ - arg = va_arg(param, long); if((arg < CURLPROXY_HTTP) || (arg > CURLPROXY_SOCKS5_HOSTNAME)) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.proxytype = (unsigned char)(curl_proxytype)arg; @@ -1177,81 +692,57 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) /* * set transfer mode (;type=) when doing FTP via an HTTP proxy */ - switch(va_arg(param, long)) { - case 0: - data->set.proxy_transfer_mode = FALSE; - break; - case 1: - data->set.proxy_transfer_mode = TRUE; - break; - default: + if(uarg > 1) /* reserve other values for future use */ - result = CURLE_BAD_FUNCTION_ARGUMENT; - break; - } + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.proxy_transfer_mode = (bool)uarg; break; - case CURLOPT_SOCKS5_AUTH: - data->set.socks5auth = (unsigned char)va_arg(param, unsigned long); if(data->set.socks5auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) - result = CURLE_NOT_BUILT_IN; + return CURLE_NOT_BUILT_IN; + data->set.socks5auth = (unsigned char)uarg; break; -#endif /* CURL_DISABLE_PROXY */ - -#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) - case CURLOPT_SOCKS5_GSSAPI_NEC: - /* - * Set flag for NEC SOCK5 support - */ - data->set.socks5_gssapi_nec = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; -#endif -#ifndef CURL_DISABLE_PROXY - case CURLOPT_SOCKS5_GSSAPI_SERVICE: - case CURLOPT_PROXY_SERVICE_NAME: + case CURLOPT_HAPROXYPROTOCOL: /* - * Set proxy authentication service name for Kerberos 5 and SPNEGO + * Set to send the HAProxy Proxy Protocol header */ - result = Curl_setstropt(&data->set.str[STRING_PROXY_SERVICE_NAME], - va_arg(param, char *)); + data->set.haproxyprotocol = enabled; break; -#endif - case CURLOPT_SERVICE_NAME: + case CURLOPT_PROXY_SSL_VERIFYPEER: /* - * Set authentication service name for DIGEST-MD5, Kerberos 5 and SPNEGO + * Enable peer SSL verifying for proxy. */ - result = Curl_setstropt(&data->set.str[STRING_SERVICE_NAME], - va_arg(param, char *)); - break; + data->set.proxy_ssl.primary.verifypeer = enabled; - case CURLOPT_HEADERDATA: - /* - * Custom pointer to pass the header write callback function - */ - data->set.writeheader = (void *)va_arg(param, void *); + /* Update the current connection proxy_ssl_config. */ + Curl_ssl_conn_config_update(data, TRUE); break; - case CURLOPT_ERRORBUFFER: + case CURLOPT_PROXY_SSL_VERIFYHOST: /* - * Error buffer provided by the caller to get the human readable - * error string in. + * Enable verification of the hostname in the peer certificate for proxy */ - data->set.errorbuffer = va_arg(param, char *); + data->set.proxy_ssl.primary.verifyhost = enabled; + + /* Update the current connection proxy_ssl_config. */ + Curl_ssl_conn_config_update(data, TRUE); break; - case CURLOPT_WRITEDATA: +#endif /* ! CURL_DISABLE_PROXY */ + +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + case CURLOPT_SOCKS5_GSSAPI_NEC: /* - * FILE pointer to write to. Or possibly - * used as argument to the write callback. + * Set flag for NEC SOCK5 support */ - data->set.out = va_arg(param, void *); + data->set.socks5_gssapi_nec = enabled; break; - +#endif #ifdef CURL_LIST_ONLY_PROTOCOL case CURLOPT_DIRLISTONLY: /* * An option that changes the command to one that asks for a list only, no * file info details. Used for FTP, POP3 and SFTP. */ - data->set.list_only = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.list_only = enabled; break; #endif case CURLOPT_APPEND: @@ -1259,7 +750,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * We want to upload and append to an existing file. Used for FTP and * SFTP. */ - data->set.remote_append = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.remote_append = enabled; break; #ifndef CURL_DISABLE_FTP @@ -1267,37 +758,26 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) /* * How do access files over FTP. */ - arg = va_arg(param, long); if((arg < CURLFTPMETHOD_DEFAULT) || (arg >= CURLFTPMETHOD_LAST)) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.ftp_filemethod = (unsigned char)(curl_ftpfile)arg; - break; - case CURLOPT_FTPPORT: - /* - * Use FTP PORT, this also specifies which IP address to use - */ - result = Curl_setstropt(&data->set.str[STRING_FTPPORT], - va_arg(param, char *)); - data->set.ftp_use_port = (data->set.str[STRING_FTPPORT]) ? TRUE : FALSE; + data->set.ftp_filemethod = (unsigned char)arg; break; - case CURLOPT_FTP_USE_EPRT: - data->set.ftp_use_eprt = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.ftp_use_eprt = enabled; break; case CURLOPT_FTP_USE_EPSV: - data->set.ftp_use_epsv = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.ftp_use_epsv = enabled; break; case CURLOPT_FTP_USE_PRET: - data->set.ftp_use_pret = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.ftp_use_pret = enabled; break; case CURLOPT_FTP_SSL_CCC: - arg = va_arg(param, long); if((arg < CURLFTPSSL_CCC_NONE) || (arg >= CURLFTPSSL_CCC_LAST)) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.ftp_ccc = (unsigned char)(curl_ftpccc)arg; + data->set.ftp_ccc = (unsigned char)arg; break; case CURLOPT_FTP_SKIP_PASV_IP: @@ -1305,162 +785,72 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * Enable or disable FTP_SKIP_PASV_IP, which will disable/enable the * bypass of the IP address in PASV responses. */ - data->set.ftp_skip_ip = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - - case CURLOPT_FTP_ACCOUNT: - result = Curl_setstropt(&data->set.str[STRING_FTP_ACCOUNT], - va_arg(param, char *)); - break; - - case CURLOPT_FTP_ALTERNATIVE_TO_USER: - result = Curl_setstropt(&data->set.str[STRING_FTP_ALTERNATIVE_TO_USER], - va_arg(param, char *)); + data->set.ftp_skip_ip = enabled; break; case CURLOPT_FTPSSLAUTH: /* * Set a specific auth for FTP-SSL transfers. */ - arg = va_arg(param, long); if((arg < CURLFTPAUTH_DEFAULT) || (arg >= CURLFTPAUTH_LAST)) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.ftpsslauth = (unsigned char)(curl_ftpauth)arg; break; - case CURLOPT_KRBLEVEL: + case CURLOPT_ACCEPTTIMEOUT_MS: /* - * A string that defines the kerberos security level. + * The maximum time for curl to wait for FTP server connect */ - result = Curl_setstropt(&data->set.str[STRING_KRB_LEVEL], - va_arg(param, char *)); - data->set.krb = (data->set.str[STRING_KRB_LEVEL]) ? TRUE : FALSE; + if(uarg > UINT_MAX) + uarg = UINT_MAX; + data->set.accepttimeout = (unsigned int)uarg; break; -#endif + case CURLOPT_WILDCARDMATCH: + data->set.wildcard_enabled = enabled; + break; +#endif /* ! CURL_DISABLE_FTP */ #if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) case CURLOPT_FTP_CREATE_MISSING_DIRS: /* * An FTP/SFTP option that modifies an upload to create missing * directories on the server. */ - arg = va_arg(param, long); /* reserve other values for future use */ - if((arg < CURLFTP_CREATE_DIR_NONE) || - (arg > CURLFTP_CREATE_DIR_RETRY)) - result = CURLE_BAD_FUNCTION_ARGUMENT; - else - data->set.ftp_create_missing_dirs = (unsigned char)arg; - break; - - case CURLOPT_POSTQUOTE: - /* - * List of RAW FTP commands to use after a transfer - */ - data->set.postquote = va_arg(param, struct curl_slist *); - break; - case CURLOPT_PREQUOTE: - /* - * List of RAW FTP commands to use prior to RETR (Wesley Laxton) - */ - data->set.prequote = va_arg(param, struct curl_slist *); - break; - case CURLOPT_QUOTE: - /* - * List of RAW FTP commands to use before a transfer - */ - data->set.quote = va_arg(param, struct curl_slist *); - break; -#endif - case CURLOPT_READDATA: - /* - * FILE pointer to read the file to be uploaded from. Or possibly - * used as argument to the read callback. - */ - data->set.in_set = va_arg(param, void *); + if((arg < CURLFTP_CREATE_DIR_NONE) || (arg > CURLFTP_CREATE_DIR_RETRY)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.ftp_create_missing_dirs = (unsigned char)arg; break; +#endif /* ! CURL_DISABLE_FTP || USE_SSH */ case CURLOPT_INFILESIZE: /* * If known, this should inform curl about the file size of the * to-be-uploaded file. */ - arg = va_arg(param, long); if(arg < -1) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.filesize = arg; break; - case CURLOPT_INFILESIZE_LARGE: - /* - * If known, this should inform curl about the file size of the - * to-be-uploaded file. - */ - bigsize = va_arg(param, curl_off_t); - if(bigsize < -1) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.filesize = bigsize; - break; case CURLOPT_LOW_SPEED_LIMIT: /* * The low speed limit that if transfers are below this for * CURLOPT_LOW_SPEED_TIME, the transfer is aborted. */ - arg = va_arg(param, long); if(arg < 0) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.low_speed_limit = arg; break; - case CURLOPT_MAX_SEND_SPEED_LARGE: - /* - * When transfer uploads are faster then CURLOPT_MAX_SEND_SPEED_LARGE - * bytes per second the transfer is throttled.. - */ - bigsize = va_arg(param, curl_off_t); - if(bigsize < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.max_send_speed = bigsize; - break; - case CURLOPT_MAX_RECV_SPEED_LARGE: - /* - * When receiving data faster than CURLOPT_MAX_RECV_SPEED_LARGE bytes per - * second the transfer is throttled.. - */ - bigsize = va_arg(param, curl_off_t); - if(bigsize < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.max_recv_speed = bigsize; - break; case CURLOPT_LOW_SPEED_TIME: /* * The low speed time that if transfers are below the set * CURLOPT_LOW_SPEED_LIMIT during this time, the transfer is aborted. */ - arg = va_arg(param, long); if(arg < 0) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.low_speed_time = arg; break; - case CURLOPT_CURLU: - /* - * pass CURLU to set URL - */ - data->set.uh = va_arg(param, CURLU *); - break; - case CURLOPT_URL: - /* - * The URL to fetch. - */ - if(data->state.url_alloc) { - /* the already set URL is allocated, free it first! */ - Curl_safefree(data->state.url); - data->state.url_alloc = FALSE; - } - result = Curl_setstropt(&data->set.str[STRING_SET_URL], - va_arg(param, char *)); - data->state.url = data->set.str[STRING_SET_URL]; - break; case CURLOPT_PORT: /* * The port number to use when getting the URL. 0 disables it. */ - arg = va_arg(param, long); if((arg < 0) || (arg > 65535)) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.use_port = (unsigned short)arg; @@ -1470,7 +860,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * The maximum time you allow curl to use for a single transfer * operation. */ - arg = va_arg(param, long); if((arg >= 0) && (arg <= (INT_MAX/1000))) data->set.timeout = (unsigned int)arg * 1000; else @@ -1478,7 +867,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) break; case CURLOPT_TIMEOUT_MS: - uarg = va_arg(param, unsigned long); if(uarg > UINT_MAX) uarg = UINT_MAX; data->set.timeout = (unsigned int)uarg; @@ -1488,7 +876,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) /* * The maximum time you allow curl to use to connect. */ - arg = va_arg(param, long); if((arg >= 0) && (arg <= (INT_MAX/1000))) data->set.connecttimeout = (unsigned int)arg * 1000; else @@ -1496,1687 +883,2177 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) break; case CURLOPT_CONNECTTIMEOUT_MS: - uarg = va_arg(param, unsigned long); if(uarg > UINT_MAX) uarg = UINT_MAX; data->set.connecttimeout = (unsigned int)uarg; break; -#ifndef CURL_DISABLE_FTP - case CURLOPT_ACCEPTTIMEOUT_MS: + case CURLOPT_RESUME_FROM: /* - * The maximum time for curl to wait for FTP server connect + * Resume transfer at the given file position */ - uarg = va_arg(param, unsigned long); - if(uarg > UINT_MAX) - uarg = UINT_MAX; - data->set.accepttimeout = (unsigned int)uarg; + if(arg < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.set_resume_from = arg; break; -#endif - case CURLOPT_USERPWD: + case CURLOPT_CRLF: /* - * user:password to use in the operation + * Kludgy option to enable CRLF conversions. Subject for removal. */ - result = setstropt_userpwd(va_arg(param, char *), - &data->set.str[STRING_USERNAME], - &data->set.str[STRING_PASSWORD]); + data->set.crlf = enabled; break; - case CURLOPT_USERNAME: +#ifndef CURL_DISABLE_BINDLOCAL + case CURLOPT_LOCALPORT: /* - * authentication user name to use in the operation + * Set what local port to bind the socket to when performing an operation. */ - result = Curl_setstropt(&data->set.str[STRING_USERNAME], - va_arg(param, char *)); + if((arg < 0) || (arg > 65535)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.localport = curlx_sltous(arg); break; - case CURLOPT_PASSWORD: + case CURLOPT_LOCALPORTRANGE: /* - * authentication password to use in the operation + * Set number of local ports to try, starting with CURLOPT_LOCALPORT. */ - result = Curl_setstropt(&data->set.str[STRING_PASSWORD], - va_arg(param, char *)); + if((arg < 0) || (arg > 65535)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.localportrange = curlx_sltous(arg); break; +#endif - case CURLOPT_LOGIN_OPTIONS: + case CURLOPT_GSSAPI_DELEGATION: /* - * authentication options to use in the operation + * GSS-API credential delegation bitmask */ - result = Curl_setstropt(&data->set.str[STRING_OPTIONS], - va_arg(param, char *)); + data->set.gssapi_delegation = (unsigned char)uarg& + (CURLGSSAPI_DELEGATION_POLICY_FLAG|CURLGSSAPI_DELEGATION_FLAG); break; - - case CURLOPT_XOAUTH2_BEARER: + case CURLOPT_SSL_VERIFYPEER: /* - * OAuth 2.0 bearer token to use in the operation + * Enable peer SSL verifying. */ - result = Curl_setstropt(&data->set.str[STRING_BEARER], - va_arg(param, char *)); - break; + data->set.ssl.primary.verifypeer = enabled; - case CURLOPT_RESOLVE: + /* Update the current connection ssl_config. */ + Curl_ssl_conn_config_update(data, FALSE); + break; +#ifndef CURL_DISABLE_DOH + case CURLOPT_DOH_SSL_VERIFYPEER: /* - * List of HOST:PORT:[addresses] strings to populate the DNS cache with - * Entries added this way will remain in the cache until explicitly - * removed or the handle is cleaned up. - * - * Prefix the HOST with plus sign (+) to have the entry expire just like - * automatically added entries. - * - * Prefix the HOST with dash (-) to _remove_ the entry from the cache. - * - * This API can remove any entry from the DNS cache, but only entries - * that aren't actually in use right now will be pruned immediately. + * Enable peer SSL verifying for DoH. */ - data->set.resolve = va_arg(param, struct curl_slist *); - data->state.resolve = data->set.resolve; + data->set.doh_verifypeer = enabled; break; - case CURLOPT_PROGRESSFUNCTION: + case CURLOPT_DOH_SSL_VERIFYHOST: /* - * Progress callback function + * Enable verification of the hostname in the peer certificate for DoH */ - data->set.fprogress = va_arg(param, curl_progress_callback); - if(data->set.fprogress) - data->progress.callback = TRUE; /* no longer internal */ - else - data->progress.callback = FALSE; /* NULL enforces internal */ + data->set.doh_verifyhost = enabled; break; - - case CURLOPT_XFERINFOFUNCTION: + case CURLOPT_DOH_SSL_VERIFYSTATUS: /* - * Transfer info callback function + * Enable certificate status verifying for DoH. */ - data->set.fxferinfo = va_arg(param, curl_xferinfo_callback); - if(data->set.fxferinfo) - data->progress.callback = TRUE; /* no longer internal */ - else - data->progress.callback = FALSE; /* NULL enforces internal */ + if(!Curl_ssl_cert_status_request()) + return CURLE_NOT_BUILT_IN; + data->set.doh_verifystatus = enabled; break; - - case CURLOPT_PROGRESSDATA: +#endif /* ! CURL_DISABLE_DOH */ + case CURLOPT_SSL_VERIFYHOST: /* - * Custom client data to pass to the progress callback + * Enable verification of the hostname in the peer certificate */ - data->set.progress_client = va_arg(param, void *); - break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXYUSERPWD: + /* Obviously people are not reading documentation and too many thought + this argument took a boolean when it was not and misused it. + Treat 1 and 2 the same */ + data->set.ssl.primary.verifyhost = enabled; + + /* Update the current connection ssl_config. */ + Curl_ssl_conn_config_update(data, FALSE); + break; + case CURLOPT_SSL_VERIFYSTATUS: /* - * user:password needed to use the proxy + * Enable certificate status verifying. */ - result = setstropt_userpwd(va_arg(param, char *), - &data->set.str[STRING_PROXYUSERNAME], - &data->set.str[STRING_PROXYPASSWORD]); + if(!Curl_ssl_cert_status_request()) + return CURLE_NOT_BUILT_IN; + + data->set.ssl.primary.verifystatus = enabled; + + /* Update the current connection ssl_config. */ + Curl_ssl_conn_config_update(data, FALSE); break; - case CURLOPT_PROXYUSERNAME: + case CURLOPT_SSL_FALSESTART: /* - * authentication user name to use in the operation + * Enable TLS false start. */ - result = Curl_setstropt(&data->set.str[STRING_PROXYUSERNAME], - va_arg(param, char *)); + if(!Curl_ssl_false_start(data)) + return CURLE_NOT_BUILT_IN; + + data->set.ssl.falsestart = enabled; break; - case CURLOPT_PROXYPASSWORD: + case CURLOPT_CERTINFO: +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CERTINFO)) + data->set.ssl.certinfo = enabled; + else +#endif + return CURLE_NOT_BUILT_IN; + break; + case CURLOPT_BUFFERSIZE: /* - * authentication password to use in the operation + * The application kindly asks for a differently sized receive buffer. + * If it seems reasonable, we will use it. */ - result = Curl_setstropt(&data->set.str[STRING_PROXYPASSWORD], - va_arg(param, char *)); + if(arg > READBUFFER_MAX) + arg = READBUFFER_MAX; + else if(arg < 1) + arg = READBUFFER_SIZE; + else if(arg < READBUFFER_MIN) + arg = READBUFFER_MIN; + + data->set.buffer_size = (unsigned int)arg; break; - case CURLOPT_NOPROXY: + + case CURLOPT_UPLOAD_BUFFERSIZE: /* - * proxy exception list + * The application kindly asks for a differently sized upload buffer. + * Cap it to sensible. */ - result = Curl_setstropt(&data->set.str[STRING_NOPROXY], - va_arg(param, char *)); + if(arg > UPLOADBUFFER_MAX) + arg = UPLOADBUFFER_MAX; + else if(arg < UPLOADBUFFER_MIN) + arg = UPLOADBUFFER_MIN; + + data->set.upload_buffer_size = (unsigned int)arg; break; -#endif - case CURLOPT_RANGE: + case CURLOPT_NOSIGNAL: /* - * What range of the file you want to transfer + * The application asks not to set any signal() or alarm() handlers, + * even when using a timeout. */ - result = Curl_setstropt(&data->set.str[STRING_SET_RANGE], - va_arg(param, char *)); + data->set.no_signal = enabled; break; - case CURLOPT_RESUME_FROM: + case CURLOPT_MAXFILESIZE: /* - * Resume transfer at the given file position + * Set the maximum size of a file to download. */ - arg = va_arg(param, long); - if(arg < -1) + if(arg < 0) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.set_resume_from = arg; + data->set.max_filesize = arg; break; - case CURLOPT_RESUME_FROM_LARGE: + +#ifdef USE_SSL + case CURLOPT_USE_SSL: /* - * Resume transfer at the given file position + * Make transfers attempt to use SSL/TLS. */ - bigsize = va_arg(param, curl_off_t); - if(bigsize < -1) + if((arg < CURLUSESSL_NONE) || (arg >= CURLUSESSL_LAST)) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.set_resume_from = bigsize; - break; - case CURLOPT_DEBUGFUNCTION: - /* - * stderr write callback. - */ - data->set.fdebug = va_arg(param, curl_debug_callback); - /* - * if the callback provided is NULL, it'll use the default callback - */ - break; - case CURLOPT_DEBUGDATA: - /* - * Set to a void * that should receive all error writes. This - * defaults to CURLOPT_STDERR for normal operations. - */ - data->set.debugdata = va_arg(param, void *); - break; - case CURLOPT_STDERR: - /* - * Set to a FILE * that should receive all error writes. This - * defaults to stderr for normal operations. - */ - data->set.err = va_arg(param, FILE *); - if(!data->set.err) - data->set.err = stderr; - break; - case CURLOPT_HEADERFUNCTION: - /* - * Set header write callback - */ - data->set.fwrite_header = va_arg(param, curl_write_callback); - break; - case CURLOPT_WRITEFUNCTION: - /* - * Set data write callback - */ - data->set.fwrite_func = va_arg(param, curl_write_callback); - if(!data->set.fwrite_func) - /* When set to NULL, reset to our internal default function */ - data->set.fwrite_func = (curl_write_callback)fwrite; - break; - case CURLOPT_READFUNCTION: - /* - * Read data callback - */ - data->set.fread_func_set = va_arg(param, curl_read_callback); - if(!data->set.fread_func_set) { - data->set.is_fread_set = 0; - /* When set to NULL, reset to our internal default function */ - data->set.fread_func_set = (curl_read_callback)fread; - } - else - data->set.is_fread_set = 1; - break; - case CURLOPT_SEEKFUNCTION: - /* - * Seek callback. Might be NULL. - */ - data->set.seek_func = va_arg(param, curl_seek_callback); - break; - case CURLOPT_SEEKDATA: - /* - * Seek control callback. Might be NULL. - */ - data->set.seek_client = va_arg(param, void *); - break; - case CURLOPT_IOCTLFUNCTION: - /* - * I/O control callback. Might be NULL. - */ - data->set.ioctl_func = va_arg(param, curl_ioctl_callback); + data->set.use_ssl = (unsigned char)arg; break; - case CURLOPT_IOCTLDATA: - /* - * I/O control data pointer. Might be NULL. - */ - data->set.ioctl_client = va_arg(param, void *); + case CURLOPT_SSL_OPTIONS: + data->set.ssl.primary.ssl_options = (unsigned char)(arg & 0xff); + data->set.ssl.enable_beast = !!(arg & CURLSSLOPT_ALLOW_BEAST); + data->set.ssl.no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE); + data->set.ssl.no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN); + data->set.ssl.revoke_best_effort = !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT); + data->set.ssl.native_ca_store = !!(arg & CURLSSLOPT_NATIVE_CA); + data->set.ssl.auto_client_cert = !!(arg & CURLSSLOPT_AUTO_CLIENT_CERT); + data->set.ssl.earlydata = !!(arg & CURLSSLOPT_EARLYDATA); + /* If a setting is added here it should also be added in dohprobe() + which sets its own CURLOPT_SSL_OPTIONS based on these settings. */ break; - case CURLOPT_SSLCERT: - /* - * String that holds file name of the SSL certificate to use - */ - result = Curl_setstropt(&data->set.str[STRING_CERT], - va_arg(param, char *)); + +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSL_OPTIONS: + data->set.proxy_ssl.primary.ssl_options = (unsigned char)(arg & 0xff); + data->set.proxy_ssl.enable_beast = !!(arg & CURLSSLOPT_ALLOW_BEAST); + data->set.proxy_ssl.no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE); + data->set.proxy_ssl.no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN); + data->set.proxy_ssl.revoke_best_effort = + !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT); + data->set.proxy_ssl.native_ca_store = !!(arg & CURLSSLOPT_NATIVE_CA); + data->set.proxy_ssl.auto_client_cert = + !!(arg & CURLSSLOPT_AUTO_CLIENT_CERT); break; - case CURLOPT_SSLCERT_BLOB: - /* - * Blob that holds file content of the SSL certificate to use - */ - result = Curl_setblobopt(&data->set.blobs[BLOB_CERT], - va_arg(param, struct curl_blob *)); +#endif + +#endif /* USE_SSL */ + case CURLOPT_IPRESOLVE: + if((arg < CURL_IPRESOLVE_WHATEVER) || (arg > CURL_IPRESOLVE_V6)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.ipver = (unsigned char) arg; break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSLCERT: + case CURLOPT_TCP_NODELAY: /* - * String that holds file name of the SSL certificate to use for proxy + * Enable or disable TCP_NODELAY, which will disable/enable the Nagle + * algorithm */ - result = Curl_setstropt(&data->set.str[STRING_CERT_PROXY], - va_arg(param, char *)); + data->set.tcp_nodelay = enabled; break; - case CURLOPT_PROXY_SSLCERT_BLOB: - /* - * Blob that holds file content of the SSL certificate to use for proxy - */ - result = Curl_setblobopt(&data->set.blobs[BLOB_CERT_PROXY], - va_arg(param, struct curl_blob *)); + + case CURLOPT_IGNORE_CONTENT_LENGTH: + data->set.ignorecl = enabled; break; -#endif - case CURLOPT_SSLCERTTYPE: + + case CURLOPT_CONNECT_ONLY: /* - * String that holds file type of the SSL certificate to use + * No data transfer. + * (1) - only do connection + * (2) - do first get request but get no content */ - result = Curl_setstropt(&data->set.str[STRING_CERT_TYPE], - va_arg(param, char *)); + if(arg > 2) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.connect_only = (unsigned char)arg; break; + + case CURLOPT_SSL_SESSIONID_CACHE: + data->set.ssl.primary.cache_session = enabled; #ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSLCERTTYPE: - /* - * String that holds file type of the SSL certificate to use for proxy - */ - result = Curl_setstropt(&data->set.str[STRING_CERT_TYPE_PROXY], - va_arg(param, char *)); - break; + data->set.proxy_ssl.primary.cache_session = + data->set.ssl.primary.cache_session; #endif - case CURLOPT_SSLKEY: - /* - * String that holds file name of the SSL key to use - */ - result = Curl_setstropt(&data->set.str[STRING_KEY], - va_arg(param, char *)); break; - case CURLOPT_SSLKEY_BLOB: - /* - * Blob that holds file content of the SSL key to use - */ - result = Curl_setblobopt(&data->set.blobs[BLOB_KEY], - va_arg(param, struct curl_blob *)); + +#ifdef USE_SSH + /* we only include SSH options if explicitly built to support SSH */ + case CURLOPT_SSH_AUTH_TYPES: + data->set.ssh_auth_types = (int)arg; break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSLKEY: - /* - * String that holds file name of the SSL key to use for proxy - */ - result = Curl_setstropt(&data->set.str[STRING_KEY_PROXY], - va_arg(param, char *)); + case CURLOPT_SSH_COMPRESSION: + data->set.ssh_compression = enabled; break; - case CURLOPT_PROXY_SSLKEY_BLOB: +#endif + + case CURLOPT_HTTP_TRANSFER_DECODING: /* - * Blob that holds file content of the SSL key to use for proxy + * disable libcurl transfer encoding is used */ - result = Curl_setblobopt(&data->set.blobs[BLOB_KEY_PROXY], - va_arg(param, struct curl_blob *)); +#ifndef USE_HYPER + data->set.http_te_skip = !enabled; /* reversed */ break; +#else + return CURLE_NOT_BUILT_IN; /* hyper does not support */ #endif - case CURLOPT_SSLKEYTYPE: + + case CURLOPT_HTTP_CONTENT_DECODING: /* - * String that holds file type of the SSL key to use + * raw data passed to the application when content encoding is used */ - result = Curl_setstropt(&data->set.str[STRING_KEY_TYPE], - va_arg(param, char *)); + data->set.http_ce_skip = enabled; break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSLKEYTYPE: + +#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) + case CURLOPT_NEW_FILE_PERMS: /* - * String that holds file type of the SSL key to use for proxy + * Uses these permissions instead of 0644 */ - result = Curl_setstropt(&data->set.str[STRING_KEY_TYPE_PROXY], - va_arg(param, char *)); + if((arg < 0) || (arg > 0777)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.new_file_perms = (unsigned int)arg; break; #endif - case CURLOPT_KEYPASSWD: +#ifdef USE_SSH + case CURLOPT_NEW_DIRECTORY_PERMS: /* - * String that holds the SSL or SSH private key password. + * Uses these permissions instead of 0755 */ - result = Curl_setstropt(&data->set.str[STRING_KEY_PASSWD], - va_arg(param, char *)); + if((arg < 0) || (arg > 0777)) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.new_directory_perms = (unsigned int)arg; break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_KEYPASSWD: +#endif +#ifdef USE_IPV6 + case CURLOPT_ADDRESS_SCOPE: /* - * String that holds the SSL private key password for proxy. + * Use this scope id when using IPv6 + * We always get longs when passed plain numericals so we should check + * that the value fits into an unsigned 32-bit integer. */ - result = Curl_setstropt(&data->set.str[STRING_KEY_PASSWD_PROXY], - va_arg(param, char *)); +#if SIZEOF_LONG > 4 + if(uarg > UINT_MAX) + return CURLE_BAD_FUNCTION_ARGUMENT; +#endif + data->set.scope_id = (unsigned int)uarg; break; #endif - case CURLOPT_SSLENGINE: - /* - * String that holds the SSL crypto engine. - */ - argptr = va_arg(param, char *); - if(argptr && argptr[0]) { - result = Curl_setstropt(&data->set.str[STRING_SSL_ENGINE], argptr); - if(!result) { - result = Curl_ssl_set_engine(data, argptr); - } - } + case CURLOPT_PROTOCOLS: + /* set the bitmask for the protocols that are allowed to be used for the + transfer, which thus helps the app which takes URLs from users or other + external inputs and want to restrict what protocol(s) to deal with. + Defaults to CURLPROTO_ALL. */ + data->set.allowed_protocols = (curl_prot_t)arg; break; - case CURLOPT_SSLENGINE_DEFAULT: - /* - * flag to set engine as default. - */ - Curl_setstropt(&data->set.str[STRING_SSL_ENGINE], NULL); - result = Curl_ssl_set_engine_default(data); - break; - case CURLOPT_CRLF: - /* - * Kludgy option to enable CRLF conversions. Subject for removal. - */ - data->set.crlf = (0 != va_arg(param, long)) ? TRUE : FALSE; + case CURLOPT_REDIR_PROTOCOLS: + /* set the bitmask for the protocols that libcurl is allowed to follow to, + as a subset of the CURLOPT_PROTOCOLS ones. That means the protocol + needs to be set in both bitmasks to be allowed to get redirected to. */ + data->set.redir_protocols = (curl_prot_t)arg; break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_HAPROXYPROTOCOL: - /* - * Set to send the HAProxy Proxy Protocol header - */ - data->set.haproxyprotocol = (0 != va_arg(param, long)) ? TRUE : FALSE; + +#ifndef CURL_DISABLE_SMTP + case CURLOPT_MAIL_RCPT_ALLOWFAILS: + /* allow RCPT TO command to fail for some recipients */ + data->set.mail_rcpt_allowfails = enabled; break; - case CURLOPT_HAPROXY_CLIENT_IP: - /* - * Set the client IP to send through HAProxy PROXY protocol - */ - result = Curl_setstropt(&data->set.str[STRING_HAPROXY_CLIENT_IP], - va_arg(param, char *)); - /* We enable implicitly the HAProxy protocol if we use this flag. */ - data->set.haproxyprotocol = TRUE; +#endif /* !CURL_DISABLE_SMTP */ + case CURLOPT_SASL_IR: + /* Enable/disable SASL initial response */ + data->set.sasl_ir = enabled; break; -#endif - case CURLOPT_INTERFACE: +#ifndef CURL_DISABLE_RTSP + case CURLOPT_RTSP_REQUEST: + { /* - * Set what interface or address/hostname to bind the socket to when - * performing an operation and thus what from-IP your connection will use. + * Set the RTSP request method (OPTIONS, SETUP, PLAY, etc...) + * Would this be better if the RTSPREQ_* were just moved into here? */ - result = Curl_setstropt(&data->set.str[STRING_DEVICE], - va_arg(param, char *)); + Curl_RtspReq rtspreq = RTSPREQ_NONE; + switch(arg) { + case CURL_RTSPREQ_OPTIONS: + rtspreq = RTSPREQ_OPTIONS; + break; + + case CURL_RTSPREQ_DESCRIBE: + rtspreq = RTSPREQ_DESCRIBE; + break; + + case CURL_RTSPREQ_ANNOUNCE: + rtspreq = RTSPREQ_ANNOUNCE; + break; + + case CURL_RTSPREQ_SETUP: + rtspreq = RTSPREQ_SETUP; + break; + + case CURL_RTSPREQ_PLAY: + rtspreq = RTSPREQ_PLAY; + break; + + case CURL_RTSPREQ_PAUSE: + rtspreq = RTSPREQ_PAUSE; + break; + + case CURL_RTSPREQ_TEARDOWN: + rtspreq = RTSPREQ_TEARDOWN; + break; + + case CURL_RTSPREQ_GET_PARAMETER: + rtspreq = RTSPREQ_GET_PARAMETER; + break; + + case CURL_RTSPREQ_SET_PARAMETER: + rtspreq = RTSPREQ_SET_PARAMETER; + break; + + case CURL_RTSPREQ_RECORD: + rtspreq = RTSPREQ_RECORD; + break; + + case CURL_RTSPREQ_RECEIVE: + rtspreq = RTSPREQ_RECEIVE; + break; + default: + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + data->set.rtspreq = rtspreq; break; -#ifndef CURL_DISABLE_BINDLOCAL - case CURLOPT_LOCALPORT: + } + case CURLOPT_RTSP_CLIENT_CSEQ: /* - * Set what local port to bind the socket to when performing an operation. + * Set the CSEQ number to issue for the next RTSP request. Useful if the + * application is resuming a previously broken connection. The CSEQ + * will increment from this new number henceforth. */ - arg = va_arg(param, long); - if((arg < 0) || (arg > 65535)) + data->state.rtsp_next_client_CSeq = arg; + break; + + case CURLOPT_RTSP_SERVER_CSEQ: + /* Same as the above, but for server-initiated requests */ + data->state.rtsp_next_server_CSeq = arg; + break; + +#endif /* ! CURL_DISABLE_RTSP */ + + case CURLOPT_TCP_KEEPALIVE: + data->set.tcp_keepalive = enabled; + break; + case CURLOPT_TCP_KEEPIDLE: + if(arg < 0) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.localport = curlx_sltous(arg); + else if(arg > INT_MAX) + arg = INT_MAX; + data->set.tcp_keepidle = (int)arg; break; - case CURLOPT_LOCALPORTRANGE: + case CURLOPT_TCP_KEEPINTVL: + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + else if(arg > INT_MAX) + arg = INT_MAX; + data->set.tcp_keepintvl = (int)arg; + break; + case CURLOPT_TCP_KEEPCNT: + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + else if(arg > INT_MAX) + arg = INT_MAX; + data->set.tcp_keepcnt = (int)arg; + break; + case CURLOPT_TCP_FASTOPEN: +#if defined(CONNECT_DATA_IDEMPOTENT) || defined(MSG_FASTOPEN) || \ + defined(TCP_FASTOPEN_CONNECT) + data->set.tcp_fastopen = enabled; +#else + return CURLE_NOT_BUILT_IN; +#endif + break; + case CURLOPT_SSL_ENABLE_NPN: + break; + case CURLOPT_SSL_ENABLE_ALPN: + data->set.ssl_enable_alpn = enabled; + break; + case CURLOPT_PATH_AS_IS: + data->set.path_as_is = enabled; + break; + case CURLOPT_PIPEWAIT: + data->set.pipewait = enabled; + break; + case CURLOPT_STREAM_WEIGHT: +#if defined(USE_HTTP2) || defined(USE_HTTP3) + if((arg >= 1) && (arg <= 256)) + data->set.priority.weight = (int)arg; + break; +#else + return CURLE_NOT_BUILT_IN; +#endif + case CURLOPT_SUPPRESS_CONNECT_HEADERS: + data->set.suppress_connect_headers = enabled; + break; + case CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS: + if(uarg > UINT_MAX) + uarg = UINT_MAX; + data->set.happy_eyeballs_timeout = (unsigned int)uarg; + break; +#ifndef CURL_DISABLE_SHUFFLE_DNS + case CURLOPT_DNS_SHUFFLE_ADDRESSES: + data->set.dns_shuffle_addresses = enabled; + break; +#endif + case CURLOPT_DISALLOW_USERNAME_IN_URL: + data->set.disallow_username_in_url = enabled; + break; + + case CURLOPT_UPKEEP_INTERVAL_MS: + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.upkeep_interval_ms = arg; + break; + case CURLOPT_MAXAGE_CONN: + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.maxage_conn = arg; + break; + case CURLOPT_MAXLIFETIME_CONN: + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.maxlifetime_conn = arg; + break; +#ifndef CURL_DISABLE_HSTS + case CURLOPT_HSTS_CTRL: + if(arg & CURLHSTS_ENABLE) { + if(!data->hsts) { + data->hsts = Curl_hsts_init(); + if(!data->hsts) + return CURLE_OUT_OF_MEMORY; + } + } + else + Curl_hsts_cleanup(&data->hsts); + break; +#endif /* ! CURL_DISABLE_HSTS */ +#ifndef CURL_DISABLE_ALTSVC + case CURLOPT_ALTSVC_CTRL: + if(!arg) { + DEBUGF(infof(data, "bad CURLOPT_ALTSVC_CTRL input")); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(!data->asi) { + data->asi = Curl_altsvc_init(); + if(!data->asi) + return CURLE_OUT_OF_MEMORY; + } + return Curl_altsvc_ctrl(data->asi, arg); +#endif /* ! CURL_DISABLE_ALTSVC */ +#ifndef CURL_DISABLE_WEBSOCKETS + case CURLOPT_WS_OPTIONS: + data->set.ws_raw_mode = (bool)(arg & CURLWS_RAW_MODE); + break; +#endif + case CURLOPT_QUICK_EXIT: + data->set.quick_exit = enabled; + break; + case CURLOPT_DNS_USE_GLOBAL_CACHE: + /* deprecated */ + break; + case CURLOPT_SSLENGINE_DEFAULT: /* - * Set number of local ports to try, starting with CURLOPT_LOCALPORT. + * flag to set engine as default. */ - arg = va_arg(param, long); - if((arg < 0) || (arg > 65535)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.localportrange = curlx_sltous(arg); + Curl_safefree(data->set.str[STRING_SSL_ENGINE]); + return Curl_ssl_set_engine_default(data); + + default: + /* unknown option */ + return CURLE_UNKNOWN_OPTION; + } + return CURLE_OK; +} + +static CURLcode setopt_slist(struct Curl_easy *data, CURLoption option, + struct curl_slist *slist) +{ + CURLcode result = CURLE_OK; + switch(option) { +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXYHEADER: + /* + * Set a list with proxy headers to use (or replace internals with) + * + * Since CURLOPT_HTTPHEADER was the only way to set HTTP headers for a + * long time we remain doing it this way until CURLOPT_PROXYHEADER is + * used. As soon as this option has been used, if set to anything but + * NULL, custom headers for proxies are only picked from this list. + * + * Set this option to NULL to restore the previous behavior. + */ + data->set.proxyheaders = slist; break; #endif - case CURLOPT_GSSAPI_DELEGATION: +#ifndef CURL_DISABLE_HTTP + case CURLOPT_HTTP200ALIASES: /* - * GSS-API credential delegation bitmask + * Set a list of aliases for HTTP 200 in response header */ - uarg = va_arg(param, unsigned long); - data->set.gssapi_delegation = (unsigned char)uarg& - (CURLGSSAPI_DELEGATION_POLICY_FLAG|CURLGSSAPI_DELEGATION_FLAG); + data->set.http200aliases = slist; break; - case CURLOPT_SSL_VERIFYPEER: +#endif +#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) + case CURLOPT_POSTQUOTE: /* - * Enable peer SSL verifying. + * List of RAW FTP commands to use after a transfer + */ + data->set.postquote = slist; + break; + case CURLOPT_PREQUOTE: + /* + * List of RAW FTP commands to use prior to RETR (Wesley Laxton) + */ + data->set.prequote = slist; + break; + case CURLOPT_QUOTE: + /* + * List of RAW FTP commands to use before a transfer + */ + data->set.quote = slist; + break; +#endif + case CURLOPT_RESOLVE: + /* + * List of HOST:PORT:[addresses] strings to populate the DNS cache with + * Entries added this way will remain in the cache until explicitly + * removed or the handle is cleaned up. + * + * Prefix the HOST with plus sign (+) to have the entry expire just like + * automatically added entries. + * + * Prefix the HOST with dash (-) to _remove_ the entry from the cache. + * + * This API can remove any entry from the DNS cache, but only entries + * that are not actually in use right now will be pruned immediately. */ - data->set.ssl.primary.verifypeer = (0 != va_arg(param, long)) ? - TRUE : FALSE; + data->set.resolve = slist; + data->state.resolve = data->set.resolve; + break; +#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_MIME) + case CURLOPT_HTTPHEADER: + /* + * Set a list with HTTP headers to use (or replace internals with) + */ + data->set.headers = slist; + break; +#endif +#ifndef CURL_DISABLE_TELNET + case CURLOPT_TELNETOPTIONS: + /* + * Set a linked list of telnet options + */ + data->set.telnet_options = slist; + break; +#endif +#ifndef CURL_DISABLE_SMTP + case CURLOPT_MAIL_RCPT: + /* Set the list of mail recipients */ + data->set.mail_rcpt = slist; + break; +#endif + case CURLOPT_CONNECT_TO: + data->set.connect_to = slist; + break; + default: + return CURLE_UNKNOWN_OPTION; + } + return result; +} - /* Update the current connection ssl_config. */ - if(data->conn) { - data->conn->ssl_config.verifypeer = - data->set.ssl.primary.verifypeer; +/* assorted pointer type arguments */ +static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option, + va_list param) +{ + CURLcode result = CURLE_OK; + switch(option) { +#ifndef CURL_DISABLE_HTTP +#ifndef CURL_DISABLE_FORM_API + case CURLOPT_HTTPPOST: + /* + * Set to make us do HTTP POST. Legacy API-style. + */ + data->set.httppost = va_arg(param, struct curl_httppost *); + data->set.method = HTTPREQ_POST_FORM; + data->set.opt_no_body = FALSE; /* this is implied */ + Curl_mime_cleanpart(data->state.formp); + Curl_safefree(data->state.formp); + data->state.mimepost = NULL; + break; +#endif /* ! CURL_DISABLE_FORM_API */ +#endif /* ! CURL_DISABLE_HTTP */ +#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) || \ + !defined(CURL_DISABLE_IMAP) +# ifndef CURL_DISABLE_MIME + case CURLOPT_MIMEPOST: + /* + * Set to make us do MIME POST + */ + result = Curl_mime_set_subparts(&data->set.mimepost, + va_arg(param, curl_mime *), + FALSE); + if(!result) { + data->set.method = HTTPREQ_POST_MIME; + data->set.opt_no_body = FALSE; /* this is implied */ +#ifndef CURL_DISABLE_FORM_API + Curl_mime_cleanpart(data->state.formp); + Curl_safefree(data->state.formp); + data->state.mimepost = NULL; +#endif } break; -#ifndef CURL_DISABLE_DOH - case CURLOPT_DOH_SSL_VERIFYPEER: +#endif /* ! CURL_DISABLE_MIME */ +#endif /* ! disabled HTTP, SMTP or IMAP */ + case CURLOPT_STDERR: /* - * Enable peer SSL verifying for DoH. + * Set to a FILE * that should receive all error writes. This + * defaults to stderr for normal operations. */ - data->set.doh_verifypeer = (0 != va_arg(param, long)) ? - TRUE : FALSE; + data->set.err = va_arg(param, FILE *); + if(!data->set.err) + data->set.err = stderr; break; + case CURLOPT_SHARE: + { + struct Curl_share *set = va_arg(param, struct Curl_share *); + + /* disconnect from old share, if any */ + if(data->share) { + Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); + + if(data->dns.hostcachetype == HCACHE_SHARED) { + data->dns.hostcache = NULL; + data->dns.hostcachetype = HCACHE_NONE; + } + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + if(data->share->cookies == data->cookies) + data->cookies = NULL; #endif + +#ifndef CURL_DISABLE_HSTS + if(data->share->hsts == data->hsts) + data->hsts = NULL; +#endif +#ifdef USE_SSL + if(data->share->sslsession == data->state.session) + data->state.session = NULL; +#endif +#ifdef USE_LIBPSL + if(data->psl == &data->share->psl) + data->psl = data->multi ? &data->multi->psl : NULL; +#endif + + data->share->dirty--; + + Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); + data->share = NULL; + } + + if(GOOD_SHARE_HANDLE(set)) + /* use new share if it set */ + data->share = set; + if(data->share) { + + Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); + + data->share->dirty++; + + if(data->share->specifier & (1 << CURL_LOCK_DATA_DNS)) { + /* use shared host cache */ + data->dns.hostcache = &data->share->hostcache; + data->dns.hostcachetype = HCACHE_SHARED; + } +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + if(data->share->cookies) { + /* use shared cookie list, first free own one if any */ + Curl_cookie_cleanup(data->cookies); + /* enable cookies since we now use a share that uses cookies! */ + data->cookies = data->share->cookies; + } +#endif /* CURL_DISABLE_HTTP */ +#ifndef CURL_DISABLE_HSTS + if(data->share->hsts) { + /* first free the private one if any */ + Curl_hsts_cleanup(&data->hsts); + data->hsts = data->share->hsts; + } +#endif +#ifdef USE_SSL + if(data->share->sslsession) { + data->set.general_ssl.max_ssl_sessions = data->share->max_ssl_sessions; + data->state.session = data->share->sslsession; + } +#endif +#ifdef USE_LIBPSL + if(data->share->specifier & (1 << CURL_LOCK_DATA_PSL)) + data->psl = &data->share->psl; +#endif + + Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); + } + /* check for host cache not needed, + * it will be done by curl_easy_perform */ + } + break; + +#ifdef USE_HTTP2 + case CURLOPT_STREAM_DEPENDS: + case CURLOPT_STREAM_DEPENDS_E: { + struct Curl_easy *dep = va_arg(param, struct Curl_easy *); + if(!dep || GOOD_EASY_HANDLE(dep)) + return Curl_data_priority_add_child(dep, data, + option == CURLOPT_STREAM_DEPENDS_E); + break; + } +#endif + + default: + return CURLE_UNKNOWN_OPTION; + } + return result; +} + +static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, + char *ptr) +{ + CURLcode result = CURLE_OK; + switch(option) { + case CURLOPT_SSL_CIPHER_LIST: + if(Curl_ssl_supports(data, SSLSUPP_CIPHER_LIST)) + /* set a list of cipher we want to use in the SSL connection */ + return Curl_setstropt(&data->set.str[STRING_SSL_CIPHER_LIST], ptr); + return CURLE_NOT_BUILT_IN; + break; #ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSL_VERIFYPEER: + case CURLOPT_PROXY_SSL_CIPHER_LIST: + if(Curl_ssl_supports(data, SSLSUPP_CIPHER_LIST)) { + /* set a list of cipher we want to use in the SSL connection for proxy */ + return Curl_setstropt(&data->set.str[STRING_SSL_CIPHER_LIST_PROXY], + ptr); + } + else + return CURLE_NOT_BUILT_IN; + break; +#endif + case CURLOPT_TLS13_CIPHERS: + if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) { + /* set preferred list of TLS 1.3 cipher suites */ + return Curl_setstropt(&data->set.str[STRING_SSL_CIPHER13_LIST], ptr); + } + else + return CURLE_NOT_BUILT_IN; + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_TLS13_CIPHERS: + if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) + /* set preferred list of TLS 1.3 cipher suites for proxy */ + return Curl_setstropt(&data->set.str[STRING_SSL_CIPHER13_LIST_PROXY], + ptr); + else + return CURLE_NOT_BUILT_IN; + break; +#endif + case CURLOPT_RANDOM_FILE: + break; + case CURLOPT_EGDSOCKET: + break; + case CURLOPT_REQUEST_TARGET: + return Curl_setstropt(&data->set.str[STRING_TARGET], ptr); +#ifndef CURL_DISABLE_NETRC + case CURLOPT_NETRC_FILE: /* - * Enable peer SSL verifying for proxy. + * Use this file instead of the $HOME/.netrc file */ - data->set.proxy_ssl.primary.verifypeer = - (0 != va_arg(param, long))?TRUE:FALSE; + return Curl_setstropt(&data->set.str[STRING_NETRC_FILE], ptr); +#endif - /* Update the current connection proxy_ssl_config. */ - if(data->conn) { - data->conn->proxy_ssl_config.verifypeer = - data->set.proxy_ssl.primary.verifypeer; +#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_MQTT) + case CURLOPT_COPYPOSTFIELDS: + /* + * A string with POST data. Makes curl HTTP POST. Even if it is NULL. + * If needed, CURLOPT_POSTFIELDSIZE must have been set prior to + * CURLOPT_COPYPOSTFIELDS and not altered later. + */ + if(!ptr || data->set.postfieldsize == -1) + result = Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], ptr); + else { + if(data->set.postfieldsize < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; +#if SIZEOF_CURL_OFF_T > SIZEOF_SIZE_T + /* + * Check that requested length does not overflow the size_t type. + */ + else if(data->set.postfieldsize > SIZE_T_MAX) + return CURLE_OUT_OF_MEMORY; +#endif + else { + /* Allocate even when size == 0. This satisfies the need of possible + later address compare to detect the COPYPOSTFIELDS mode, and to + mark that postfields is used rather than read function or form + data. + */ + char *p = Curl_memdup0(ptr, (size_t)data->set.postfieldsize); + if(!p) + return CURLE_OUT_OF_MEMORY; + else { + free(data->set.str[STRING_COPYPOSTFIELDS]); + data->set.str[STRING_COPYPOSTFIELDS] = p; + } + } } + + data->set.postfields = data->set.str[STRING_COPYPOSTFIELDS]; + data->set.method = HTTPREQ_POST; break; -#endif - case CURLOPT_SSL_VERIFYHOST: + + case CURLOPT_POSTFIELDS: /* - * Enable verification of the host name in the peer certificate + * Like above, but use static data instead of copying it. */ - arg = va_arg(param, long); + data->set.postfields = ptr; + /* Release old copied data. */ + Curl_safefree(data->set.str[STRING_COPYPOSTFIELDS]); + data->set.method = HTTPREQ_POST; + break; - /* Obviously people are not reading documentation and too many thought - this argument took a boolean when it wasn't and misused it. - Treat 1 and 2 the same */ - data->set.ssl.primary.verifyhost = (bool)((arg & 3) ? TRUE : FALSE); +#ifndef CURL_DISABLE_HTTP + case CURLOPT_ACCEPT_ENCODING: + /* + * String to use at the value of Accept-Encoding header. + * + * If the encoding is set to "" we use an Accept-Encoding header that + * encompasses all the encodings we support. + * If the encoding is set to NULL we do not send an Accept-Encoding header + * and ignore an received Content-Encoding header. + * + */ + if(ptr && !*ptr) { + char all[256]; + Curl_all_content_encodings(all, sizeof(all)); + return Curl_setstropt(&data->set.str[STRING_ENCODING], all); + } + return Curl_setstropt(&data->set.str[STRING_ENCODING], ptr); - /* Update the current connection ssl_config. */ - if(data->conn) { - data->conn->ssl_config.verifyhost = - data->set.ssl.primary.verifyhost; +#if !defined(CURL_DISABLE_AWS) + case CURLOPT_AWS_SIGV4: + /* + * String that is merged to some authentication + * parameters are used by the algorithm. + */ + result = Curl_setstropt(&data->set.str[STRING_AWS_SIGV4], ptr); + /* + * Basic been set by default it need to be unset here + */ + if(data->set.str[STRING_AWS_SIGV4]) + data->set.httpauth = CURLAUTH_AWS_SIGV4; + break; +#endif + case CURLOPT_REFERER: + /* + * String to set in the HTTP Referer: field. + */ + if(data->state.referer_alloc) { + Curl_safefree(data->state.referer); + data->state.referer_alloc = FALSE; } + result = Curl_setstropt(&data->set.str[STRING_SET_REFERER], ptr); + data->state.referer = data->set.str[STRING_SET_REFERER]; break; -#ifndef CURL_DISABLE_DOH - case CURLOPT_DOH_SSL_VERIFYHOST: + + case CURLOPT_USERAGENT: /* - * Enable verification of the host name in the peer certificate for DoH + * String to use in the HTTP User-Agent field */ - arg = va_arg(param, long); + return Curl_setstropt(&data->set.str[STRING_USERAGENT], ptr); - /* Treat both 1 and 2 as TRUE */ - data->set.doh_verifyhost = (bool)((arg & 3) ? TRUE : FALSE); - break; -#endif -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSL_VERIFYHOST: +#if !defined(CURL_DISABLE_COOKIES) + case CURLOPT_COOKIE: /* - * Enable verification of the host name in the peer certificate for proxy + * Cookie string to send to the remote server in the request. */ - arg = va_arg(param, long); + return Curl_setstropt(&data->set.str[STRING_COOKIE], ptr); - /* Treat both 1 and 2 as TRUE */ - data->set.proxy_ssl.primary.verifyhost = (bool)((arg & 3)?TRUE:FALSE); + case CURLOPT_COOKIEFILE: + /* + * Set cookie file to read and parse. Can be used multiple times. + */ + if(ptr) { + struct curl_slist *cl; + /* general protection against mistakes and abuse */ + if(strlen(ptr) > CURL_MAX_INPUT_LENGTH) + return CURLE_BAD_FUNCTION_ARGUMENT; + /* append the cookie filename to the list of filenames, and deal with + them later */ + cl = curl_slist_append(data->state.cookielist, ptr); + if(!cl) { + curl_slist_free_all(data->state.cookielist); + data->state.cookielist = NULL; + return CURLE_OUT_OF_MEMORY; + } + data->state.cookielist = cl; /* store the list for later use */ + } + else { + /* clear the list of cookie files */ + curl_slist_free_all(data->state.cookielist); + data->state.cookielist = NULL; - /* Update the current connection proxy_ssl_config. */ - if(data->conn) { - data->conn->proxy_ssl_config.verifyhost = - data->set.proxy_ssl.primary.verifyhost; + if(!data->share || !data->share->cookies) { + /* throw away all existing cookies if this is not a shared cookie + container */ + Curl_cookie_clearall(data->cookies); + Curl_cookie_cleanup(data->cookies); + } + /* disable the cookie engine */ + data->cookies = NULL; } break; -#endif - case CURLOPT_SSL_VERIFYSTATUS: + + case CURLOPT_COOKIEJAR: /* - * Enable certificate status verifying. + * Set cookie filename to dump all cookies to when we are done. */ - if(!Curl_ssl_cert_status_request()) { - result = CURLE_NOT_BUILT_IN; - break; + result = Curl_setstropt(&data->set.str[STRING_COOKIEJAR], ptr); + if(!result) { + /* + * Activate the cookie parser. This may or may not already + * have been made. + */ + struct CookieInfo *newcookies = + Curl_cookie_init(data, NULL, data->cookies, data->set.cookiesession); + if(!newcookies) + result = CURLE_OUT_OF_MEMORY; + data->cookies = newcookies; } + break; - data->set.ssl.primary.verifystatus = (0 != va_arg(param, long)) ? - TRUE : FALSE; + case CURLOPT_COOKIELIST: + if(!ptr) + break; - /* Update the current connection ssl_config. */ - if(data->conn) { - data->conn->ssl_config.verifystatus = - data->set.ssl.primary.verifystatus; + if(strcasecompare(ptr, "ALL")) { + /* clear all cookies */ + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + Curl_cookie_clearall(data->cookies); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); } - break; -#ifndef CURL_DISABLE_DOH - case CURLOPT_DOH_SSL_VERIFYSTATUS: - /* - * Enable certificate status verifying for DoH. - */ - if(!Curl_ssl_cert_status_request()) { - result = CURLE_NOT_BUILT_IN; + else if(strcasecompare(ptr, "SESS")) { + /* clear session cookies */ + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + Curl_cookie_clearsess(data->cookies); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + } + else if(strcasecompare(ptr, "FLUSH")) { + /* flush cookies to file, takes care of the locking */ + Curl_flush_cookies(data, FALSE); + } + else if(strcasecompare(ptr, "RELOAD")) { + /* reload cookies from file */ + Curl_cookie_loadfiles(data); break; } + else { + if(!data->cookies) { + /* if cookie engine was not running, activate it */ + data->cookies = Curl_cookie_init(data, NULL, NULL, TRUE); + if(!data->cookies) + return CURLE_OUT_OF_MEMORY; + } + + /* general protection against mistakes and abuse */ + if(strlen(ptr) > CURL_MAX_INPUT_LENGTH) + return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.doh_verifystatus = (0 != va_arg(param, long)) ? - TRUE : FALSE; + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + if(checkprefix("Set-Cookie:", ptr)) + /* HTTP Header format line */ + Curl_cookie_add(data, data->cookies, TRUE, FALSE, ptr + 11, NULL, + NULL, TRUE); + else + /* Netscape format line */ + Curl_cookie_add(data, data->cookies, FALSE, FALSE, ptr, NULL, + NULL, TRUE); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + } break; -#endif - case CURLOPT_SSL_CTX_FUNCTION: +#endif /* !CURL_DISABLE_COOKIES */ + +#endif /* ! CURL_DISABLE_HTTP */ + + case CURLOPT_CUSTOMREQUEST: /* - * Set a SSL_CTX callback + * Set a custom string to use as request */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) - data->set.ssl.fsslctx = va_arg(param, curl_ssl_ctx_callback); - else -#endif - result = CURLE_NOT_BUILT_IN; - break; - case CURLOPT_SSL_CTX_DATA: + return Curl_setstropt(&data->set.str[STRING_CUSTOMREQUEST], ptr); + + /* we do not set + data->set.method = HTTPREQ_CUSTOM; + here, we continue as if we were using the already set type + and this just changes the actual request keyword */ + +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY: /* - * Set a SSL_CTX callback parameter pointer + * Set proxy server:port to use as proxy. + * + * If the proxy is set to "" (and CURLOPT_SOCKS_PROXY is set to "" or NULL) + * we explicitly say that we do not want to use a proxy + * (even though there might be environment variables saying so). + * + * Setting it to NULL, means no proxy but allows the environment variables + * to decide for us (if CURLOPT_SOCKS_PROXY setting it to NULL). */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) - data->set.ssl.fsslctxp = va_arg(param, void *); - else -#endif - result = CURLE_NOT_BUILT_IN; + return Curl_setstropt(&data->set.str[STRING_PROXY], ptr); break; - case CURLOPT_SSL_FALSESTART: - /* - * Enable TLS false start. - */ - if(!Curl_ssl_false_start(data)) { - result = CURLE_NOT_BUILT_IN; - break; - } - data->set.ssl.falsestart = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - case CURLOPT_CERTINFO: -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_CERTINFO)) - data->set.ssl.certinfo = (0 != va_arg(param, long)) ? TRUE : FALSE; - else -#endif - result = CURLE_NOT_BUILT_IN; - break; - case CURLOPT_PINNEDPUBLICKEY: + case CURLOPT_PRE_PROXY: /* - * Set pinned public key for SSL connection. - * Specify file name of the public key in DER format. + * Set proxy server:port to use as SOCKS proxy. + * + * If the proxy is set to "" or NULL we explicitly say that we do not want + * to use the socks proxy. */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) - result = Curl_setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY], - va_arg(param, char *)); - else -#endif - result = CURLE_NOT_BUILT_IN; - break; + return Curl_setstropt(&data->set.str[STRING_PRE_PROXY], ptr); +#endif /* CURL_DISABLE_PROXY */ + #ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_PINNEDPUBLICKEY: + case CURLOPT_SOCKS5_GSSAPI_SERVICE: + case CURLOPT_PROXY_SERVICE_NAME: /* - * Set pinned public key for SSL connection. - * Specify file name of the public key in DER format. + * Set proxy authentication service name for Kerberos 5 and SPNEGO */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) - result = Curl_setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY], - va_arg(param, char *)); - else + return Curl_setstropt(&data->set.str[STRING_PROXY_SERVICE_NAME], ptr); #endif - result = CURLE_NOT_BUILT_IN; - break; -#endif - case CURLOPT_CAINFO: + case CURLOPT_SERVICE_NAME: /* - * Set CA info for SSL connection. Specify file name of the CA certificate + * Set authentication service name for DIGEST-MD5, Kerberos 5 and SPNEGO */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CAFILE], - va_arg(param, char *)); + return Curl_setstropt(&data->set.str[STRING_SERVICE_NAME], ptr); break; - case CURLOPT_CAINFO_BLOB: + + case CURLOPT_HEADERDATA: /* - * Blob that holds CA info for SSL connection. - * Specify entire PEM of the CA certificate + * Custom pointer to pass the header write callback function */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) - result = Curl_setblobopt(&data->set.blobs[BLOB_CAINFO], - va_arg(param, struct curl_blob *)); - else -#endif - return CURLE_NOT_BUILT_IN; - + data->set.writeheader = (void *)ptr; break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_CAINFO: + case CURLOPT_READDATA: /* - * Set CA info SSL connection for proxy. Specify file name of the - * CA certificate + * FILE pointer to read the file to be uploaded from. Or possibly used as + * argument to the read callback. */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CAFILE_PROXY], - va_arg(param, char *)); + data->set.in_set = (void *)ptr; break; - case CURLOPT_PROXY_CAINFO_BLOB: + case CURLOPT_WRITEDATA: /* - * Blob that holds CA info for SSL connection proxy. - * Specify entire PEM of the CA certificate + * FILE pointer to write to. Or possibly used as argument to the write + * callback. */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) - result = Curl_setblobopt(&data->set.blobs[BLOB_CAINFO_PROXY], - va_arg(param, struct curl_blob *)); - else -#endif - return CURLE_NOT_BUILT_IN; + data->set.out = (void *)ptr; break; -#endif - case CURLOPT_CAPATH: + case CURLOPT_DEBUGDATA: /* - * Set CA path info for SSL connection. Specify directory name of the CA - * certificates which have been prepared using openssl c_rehash utility. + * Set to a void * that should receive all error writes. This + * defaults to CURLOPT_STDERR for normal operations. */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) - /* This does not work on windows. */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CAPATH], - va_arg(param, char *)); - else -#endif - result = CURLE_NOT_BUILT_IN; + data->set.debugdata = (void *)ptr; break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_CAPATH: + case CURLOPT_PROGRESSDATA: /* - * Set CA path info for SSL connection proxy. Specify directory name of the - * CA certificates which have been prepared using openssl c_rehash utility. + * Custom client data to pass to the progress callback */ -#ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) - /* This does not work on windows. */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CAPATH_PROXY], - va_arg(param, char *)); - else -#endif - result = CURLE_NOT_BUILT_IN; + data->set.progress_client = (void *)ptr; break; -#endif - case CURLOPT_CRLFILE: + case CURLOPT_SEEKDATA: /* - * Set CRL file info for SSL connection. Specify file name of the CRL - * to check certificates revocation + * Seek control callback. Might be NULL. */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CRLFILE], - va_arg(param, char *)); + data->set.seek_client = (void *)ptr; break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_CRLFILE: + case CURLOPT_IOCTLDATA: /* - * Set CRL file info for SSL connection for proxy. Specify file name of the - * CRL to check certificates revocation + * I/O control data pointer. Might be NULL. */ - result = Curl_setstropt(&data->set.str[STRING_SSL_CRLFILE_PROXY], - va_arg(param, char *)); + data->set.ioctl_client = (void *)ptr; break; -#endif - case CURLOPT_ISSUERCERT: + case CURLOPT_SSL_CTX_DATA: /* - * Set Issuer certificate file - * to check certificates issuer + * Set a SSL_CTX callback parameter pointer */ - result = Curl_setstropt(&data->set.str[STRING_SSL_ISSUERCERT], - va_arg(param, char *)); +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) + data->set.ssl.fsslctxp = (void *)ptr; + else +#endif + return CURLE_NOT_BUILT_IN; break; - case CURLOPT_ISSUERCERT_BLOB: + case CURLOPT_SOCKOPTDATA: /* - * Blob that holds Issuer certificate to check certificates issuer + * socket callback data pointer. Might be NULL. */ - result = Curl_setblobopt(&data->set.blobs[BLOB_SSL_ISSUERCERT], - va_arg(param, struct curl_blob *)); + data->set.sockopt_client = (void *)ptr; break; -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_ISSUERCERT: + case CURLOPT_OPENSOCKETDATA: /* - * Set Issuer certificate file - * to check certificates issuer + * socket callback data pointer. Might be NULL. */ - result = Curl_setstropt(&data->set.str[STRING_SSL_ISSUERCERT_PROXY], - va_arg(param, char *)); + data->set.opensocket_client = (void *)ptr; break; - case CURLOPT_PROXY_ISSUERCERT_BLOB: + case CURLOPT_RESOLVER_START_DATA: /* - * Blob that holds Issuer certificate to check certificates issuer + * resolver start callback data pointer. Might be NULL. */ - result = Curl_setblobopt(&data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY], - va_arg(param, struct curl_blob *)); + data->set.resolver_start_client = (void *)ptr; break; -#endif -#ifndef CURL_DISABLE_TELNET - case CURLOPT_TELNETOPTIONS: + case CURLOPT_CLOSESOCKETDATA: /* - * Set a linked list of telnet options + * socket callback data pointer. Might be NULL. */ - data->set.telnet_options = va_arg(param, struct curl_slist *); + data->set.closesocket_client = (void *)ptr; break; + case CURLOPT_TRAILERDATA: +#ifndef CURL_DISABLE_HTTP + data->set.trailer_data = (void *)ptr; #endif - case CURLOPT_BUFFERSIZE: - /* - * The application kindly asks for a differently sized receive buffer. - * If it seems reasonable, we'll use it. - */ - if(data->state.buffer) - return CURLE_BAD_FUNCTION_ARGUMENT; - - arg = va_arg(param, long); - - if(arg > READBUFFER_MAX) - arg = READBUFFER_MAX; - else if(arg < 1) - arg = READBUFFER_SIZE; - else if(arg < READBUFFER_MIN) - arg = READBUFFER_MIN; - - data->set.buffer_size = (unsigned int)arg; + break; + case CURLOPT_PREREQDATA: + data->set.prereq_userp = (void *)ptr; break; - case CURLOPT_UPLOAD_BUFFERSIZE: + case CURLOPT_ERRORBUFFER: /* - * The application kindly asks for a differently sized upload buffer. - * Cap it to sensible. + * Error buffer provided by the caller to get the human readable error + * string in. */ - arg = va_arg(param, long); - - if(arg > UPLOADBUFFER_MAX) - arg = UPLOADBUFFER_MAX; - else if(arg < UPLOADBUFFER_MIN) - arg = UPLOADBUFFER_MIN; - - data->set.upload_buffer_size = (unsigned int)arg; - Curl_safefree(data->state.ulbuf); /* force a realloc next opportunity */ + data->set.errorbuffer = ptr; break; - case CURLOPT_NOSIGNAL: +#ifndef CURL_DISABLE_FTP + case CURLOPT_FTPPORT: /* - * The application asks not to set any signal() or alarm() handlers, - * even when using a timeout. + * Use FTP PORT, this also specifies which IP address to use */ - data->set.no_signal = (0 != va_arg(param, long)) ? TRUE : FALSE; + result = Curl_setstropt(&data->set.str[STRING_FTPPORT], ptr); + data->set.ftp_use_port = !!(data->set.str[STRING_FTPPORT]); break; - case CURLOPT_SHARE: - { - struct Curl_share *set; - set = va_arg(param, struct Curl_share *); - - /* disconnect from old share, if any */ - if(data->share) { - Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); - - if(data->dns.hostcachetype == HCACHE_SHARED) { - data->dns.hostcache = NULL; - data->dns.hostcachetype = HCACHE_NONE; - } + case CURLOPT_FTP_ACCOUNT: + return Curl_setstropt(&data->set.str[STRING_FTP_ACCOUNT], ptr); -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) - if(data->share->cookies == data->cookies) - data->cookies = NULL; -#endif + case CURLOPT_FTP_ALTERNATIVE_TO_USER: + return Curl_setstropt(&data->set.str[STRING_FTP_ALTERNATIVE_TO_USER], ptr); -#ifndef CURL_DISABLE_HSTS - if(data->share->hsts == data->hsts) - data->hsts = NULL; -#endif -#ifdef USE_SSL - if(data->share->sslsession == data->state.session) - data->state.session = NULL; +#ifdef HAVE_GSSAPI + case CURLOPT_KRBLEVEL: + /* + * A string that defines the kerberos security level. + */ + result = Curl_setstropt(&data->set.str[STRING_KRB_LEVEL], ptr); + data->set.krb = !!(data->set.str[STRING_KRB_LEVEL]); + break; #endif -#ifdef USE_LIBPSL - if(data->psl == &data->share->psl) - data->psl = data->multi? &data->multi->psl: NULL; #endif - - data->share->dirty--; - - Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); - data->share = NULL; + case CURLOPT_URL: + /* + * The URL to fetch. + */ + if(data->state.url_alloc) { + /* the already set URL is allocated, free it first! */ + Curl_safefree(data->state.url); + data->state.url_alloc = FALSE; } + result = Curl_setstropt(&data->set.str[STRING_SET_URL], ptr); + data->state.url = data->set.str[STRING_SET_URL]; + break; - if(GOOD_SHARE_HANDLE(set)) - /* use new share if it set */ - data->share = set; - if(data->share) { - - Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); - - data->share->dirty++; - - if(data->share->specifier & (1<< CURL_LOCK_DATA_DNS)) { - /* use shared host cache */ - data->dns.hostcache = &data->share->hostcache; - data->dns.hostcachetype = HCACHE_SHARED; - } -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) - if(data->share->cookies) { - /* use shared cookie list, first free own one if any */ - Curl_cookie_cleanup(data->cookies); - /* enable cookies since we now use a share that uses cookies! */ - data->cookies = data->share->cookies; - } -#endif /* CURL_DISABLE_HTTP */ -#ifndef CURL_DISABLE_HSTS - if(data->share->hsts) { - /* first free the private one if any */ - Curl_hsts_cleanup(&data->hsts); - data->hsts = data->share->hsts; - } -#endif /* CURL_DISABLE_HTTP */ -#ifdef USE_SSL - if(data->share->sslsession) { - data->set.general_ssl.max_ssl_sessions = data->share->max_ssl_sessions; - data->state.session = data->share->sslsession; - } -#endif -#ifdef USE_LIBPSL - if(data->share->specifier & (1 << CURL_LOCK_DATA_PSL)) - data->psl = &data->share->psl; -#endif - - Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); - } - /* check for host cache not needed, - * it will be done by curl_easy_perform */ - } - break; + case CURLOPT_USERPWD: + /* + * user:password to use in the operation + */ + return setstropt_userpwd(ptr, &data->set.str[STRING_USERNAME], + &data->set.str[STRING_PASSWORD]); - case CURLOPT_PRIVATE: + case CURLOPT_USERNAME: /* - * Set private data pointer. + * authentication username to use in the operation */ - data->set.private_data = va_arg(param, void *); - break; + return Curl_setstropt(&data->set.str[STRING_USERNAME], ptr); - case CURLOPT_MAXFILESIZE: + case CURLOPT_PASSWORD: /* - * Set the maximum size of a file to download. + * authentication password to use in the operation */ - arg = va_arg(param, long); - if(arg < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.max_filesize = arg; - break; + return Curl_setstropt(&data->set.str[STRING_PASSWORD], ptr); -#ifdef USE_SSL - case CURLOPT_USE_SSL: + case CURLOPT_LOGIN_OPTIONS: /* - * Make transfers attempt to use SSL/TLS. + * authentication options to use in the operation */ - arg = va_arg(param, long); - if((arg < CURLUSESSL_NONE) || (arg >= CURLUSESSL_LAST)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.use_ssl = (unsigned char)arg; - break; + return Curl_setstropt(&data->set.str[STRING_OPTIONS], ptr); - case CURLOPT_SSL_OPTIONS: - arg = va_arg(param, long); - data->set.ssl.primary.ssl_options = (unsigned char)(arg & 0xff); - data->set.ssl.enable_beast = !!(arg & CURLSSLOPT_ALLOW_BEAST); - data->set.ssl.no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE); - data->set.ssl.no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN); - data->set.ssl.revoke_best_effort = !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT); - data->set.ssl.native_ca_store = !!(arg & CURLSSLOPT_NATIVE_CA); - data->set.ssl.auto_client_cert = !!(arg & CURLSSLOPT_AUTO_CLIENT_CERT); - /* If a setting is added here it should also be added in dohprobe() - which sets its own CURLOPT_SSL_OPTIONS based on these settings. */ - break; + case CURLOPT_XOAUTH2_BEARER: + /* + * OAuth 2.0 bearer token to use in the operation + */ + return Curl_setstropt(&data->set.str[STRING_BEARER], ptr); #ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXY_SSL_OPTIONS: - arg = va_arg(param, long); - data->set.proxy_ssl.primary.ssl_options = (unsigned char)(arg & 0xff); - data->set.proxy_ssl.enable_beast = !!(arg & CURLSSLOPT_ALLOW_BEAST); - data->set.proxy_ssl.no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE); - data->set.proxy_ssl.no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN); - data->set.proxy_ssl.revoke_best_effort = - !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT); - data->set.proxy_ssl.native_ca_store = !!(arg & CURLSSLOPT_NATIVE_CA); - data->set.proxy_ssl.auto_client_cert = - !!(arg & CURLSSLOPT_AUTO_CLIENT_CERT); + case CURLOPT_PROXYUSERPWD: { + /* + * user:password needed to use the proxy + */ + char *u = NULL; + char *p = NULL; + result = setstropt_userpwd(ptr, &u, &p); + + /* URL decode the components */ + if(!result && u) + result = Curl_urldecode(u, 0, &data->set.str[STRING_PROXYUSERNAME], NULL, + REJECT_ZERO); + if(!result && p) + result = Curl_urldecode(p, 0, &data->set.str[STRING_PROXYPASSWORD], NULL, + REJECT_ZERO); + free(u); + free(p); + } break; -#endif + case CURLOPT_PROXYUSERNAME: + /* + * authentication username to use in the operation + */ + return Curl_setstropt(&data->set.str[STRING_PROXYUSERNAME], ptr); - case CURLOPT_SSL_EC_CURVES: + case CURLOPT_PROXYPASSWORD: /* - * Set accepted curves in SSL connection setup. - * Specify colon-delimited list of curve algorithm names. + * authentication password to use in the operation */ - result = Curl_setstropt(&data->set.str[STRING_SSL_EC_CURVES], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_PROXYPASSWORD], ptr); + + case CURLOPT_NOPROXY: + /* + * proxy exception list + */ + return Curl_setstropt(&data->set.str[STRING_NOPROXY], ptr); #endif - case CURLOPT_IPRESOLVE: - arg = va_arg(param, long); - if((arg < CURL_IPRESOLVE_WHATEVER) || (arg > CURL_IPRESOLVE_V6)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.ipver = (unsigned char) arg; - break; - case CURLOPT_MAXFILESIZE_LARGE: + case CURLOPT_RANGE: /* - * Set the maximum size of a file to download. + * What range of the file you want to transfer */ - bigsize = va_arg(param, curl_off_t); - if(bigsize < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.max_filesize = bigsize; - break; + return Curl_setstropt(&data->set.str[STRING_SET_RANGE], ptr); - case CURLOPT_TCP_NODELAY: +#endif /* ! CURL_DISABLE_PROXY */ + case CURLOPT_CURLU: /* - * Enable or disable TCP_NODELAY, which will disable/enable the Nagle - * algorithm + * pass CURLU to set URL */ - data->set.tcp_nodelay = (0 != va_arg(param, long)) ? TRUE : FALSE; + data->set.uh = (CURLU *)ptr; break; + case CURLOPT_SSLCERT: + /* + * String that holds filename of the SSL certificate to use + */ + return Curl_setstropt(&data->set.str[STRING_CERT], ptr); - case CURLOPT_IGNORE_CONTENT_LENGTH: - data->set.ignorecl = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSLCERT: + /* + * String that holds filename of the SSL certificate to use for proxy + */ + return Curl_setstropt(&data->set.str[STRING_CERT_PROXY], ptr); - case CURLOPT_CONNECT_ONLY: +#endif + case CURLOPT_SSLCERTTYPE: /* - * No data transfer. - * (1) - only do connection - * (2) - do first get request but get no content + * String that holds file type of the SSL certificate to use */ - arg = va_arg(param, long); - if(arg > 2) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.connect_only = (unsigned char)arg; - break; + return Curl_setstropt(&data->set.str[STRING_CERT_TYPE], ptr); - case CURLOPT_SOCKOPTFUNCTION: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSLCERTTYPE: /* - * socket callback function: called after socket() but before connect() + * String that holds file type of the SSL certificate to use for proxy */ - data->set.fsockopt = va_arg(param, curl_sockopt_callback); - break; + return Curl_setstropt(&data->set.str[STRING_CERT_TYPE_PROXY], ptr); +#endif + case CURLOPT_SSLKEY: + /* + * String that holds filename of the SSL key to use + */ + return Curl_setstropt(&data->set.str[STRING_KEY], ptr); - case CURLOPT_SOCKOPTDATA: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSLKEY: /* - * socket callback data pointer. Might be NULL. + * String that holds filename of the SSL key to use for proxy */ - data->set.sockopt_client = va_arg(param, void *); - break; + return Curl_setstropt(&data->set.str[STRING_KEY_PROXY], ptr); - case CURLOPT_OPENSOCKETFUNCTION: +#endif + case CURLOPT_SSLKEYTYPE: /* - * open/create socket callback function: called instead of socket(), - * before connect() + * String that holds file type of the SSL key to use */ - data->set.fopensocket = va_arg(param, curl_opensocket_callback); + return Curl_setstropt(&data->set.str[STRING_KEY_TYPE], ptr); break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSLKEYTYPE: + /* + * String that holds file type of the SSL key to use for proxy + */ + return Curl_setstropt(&data->set.str[STRING_KEY_TYPE_PROXY], ptr); - case CURLOPT_OPENSOCKETDATA: +#endif + case CURLOPT_KEYPASSWD: /* - * socket callback data pointer. Might be NULL. + * String that holds the SSL or SSH private key password. */ - data->set.opensocket_client = va_arg(param, void *); - break; + return Curl_setstropt(&data->set.str[STRING_KEY_PASSWD], ptr); - case CURLOPT_CLOSESOCKETFUNCTION: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_KEYPASSWD: /* - * close socket callback function: called instead of close() - * when shutting down a connection + * String that holds the SSL private key password for proxy. + */ + return Curl_setstropt(&data->set.str[STRING_KEY_PASSWD_PROXY], ptr); +#endif + case CURLOPT_SSLENGINE: + /* + * String that holds the SSL crypto engine. */ - data->set.fclosesocket = va_arg(param, curl_closesocket_callback); + if(ptr && ptr[0]) { + result = Curl_setstropt(&data->set.str[STRING_SSL_ENGINE], ptr); + if(!result) { + result = Curl_ssl_set_engine(data, ptr); + } + } break; - case CURLOPT_RESOLVER_START_FUNCTION: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_HAPROXY_CLIENT_IP: /* - * resolver start callback function: called before a new resolver request - * is started + * Set the client IP to send through HAProxy PROXY protocol */ - data->set.resolver_start = va_arg(param, curl_resolver_start_callback); + result = Curl_setstropt(&data->set.str[STRING_HAPROXY_CLIENT_IP], ptr); + /* enable the HAProxy protocol */ + data->set.haproxyprotocol = TRUE; break; - - case CURLOPT_RESOLVER_START_DATA: +#endif + case CURLOPT_INTERFACE: /* - * resolver start callback data pointer. Might be NULL. + * Set what interface or address/hostname to bind the socket to when + * performing an operation and thus what from-IP your connection will use. */ - data->set.resolver_start_client = va_arg(param, void *); - break; + return setstropt_interface(ptr, + &data->set.str[STRING_DEVICE], + &data->set.str[STRING_INTERFACE], + &data->set.str[STRING_BINDHOST]); - case CURLOPT_CLOSESOCKETDATA: + case CURLOPT_PINNEDPUBLICKEY: /* - * socket callback data pointer. Might be NULL. + * Set pinned public key for SSL connection. + * Specify filename of the public key in DER format. */ - data->set.closesocket_client = va_arg(param, void *); - break; +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) + return Curl_setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY], ptr); +#endif + return CURLE_NOT_BUILT_IN; - case CURLOPT_SSL_SESSIONID_CACHE: - data->set.ssl.primary.sessionid = (0 != va_arg(param, long)) ? - TRUE : FALSE; #ifndef CURL_DISABLE_PROXY - data->set.proxy_ssl.primary.sessionid = data->set.ssl.primary.sessionid; + case CURLOPT_PROXY_PINNEDPUBLICKEY: + /* + * Set pinned public key for SSL connection. + * Specify filename of the public key in DER format. + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_PINNEDPUBKEY)) + return Curl_setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY], + ptr); #endif - break; - -#ifdef USE_SSH - /* we only include SSH options if explicitly built to support SSH */ - case CURLOPT_SSH_AUTH_TYPES: - data->set.ssh_auth_types = (unsigned int)va_arg(param, long); - break; - - case CURLOPT_SSH_PUBLIC_KEYFILE: + return CURLE_NOT_BUILT_IN; +#endif + case CURLOPT_CAINFO: /* - * Use this file instead of the $HOME/.ssh/id_dsa.pub file + * Set CA info for SSL connection. Specify filename of the CA certificate */ - result = Curl_setstropt(&data->set.str[STRING_SSH_PUBLIC_KEY], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_SSL_CAFILE], ptr); - case CURLOPT_SSH_PRIVATE_KEYFILE: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_CAINFO: /* - * Use this file instead of the $HOME/.ssh/id_dsa file + * Set CA info SSL connection for proxy. Specify filename of the + * CA certificate */ - result = Curl_setstropt(&data->set.str[STRING_SSH_PRIVATE_KEY], - va_arg(param, char *)); - break; - case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: + return Curl_setstropt(&data->set.str[STRING_SSL_CAFILE_PROXY], ptr); +#endif + + case CURLOPT_CAPATH: /* - * Option to allow for the MD5 of the host public key to be checked - * for validation purposes. + * Set CA path info for SSL connection. Specify directory name of the CA + * certificates which have been prepared using openssl c_rehash utility. */ - result = Curl_setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5], - va_arg(param, char *)); - break; - - case CURLOPT_SSH_KNOWNHOSTS: +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) + /* This does not work on Windows. */ + return Curl_setstropt(&data->set.str[STRING_SSL_CAPATH], ptr); +#endif + return CURLE_NOT_BUILT_IN; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_CAPATH: /* - * Store the file name to read known hosts from. + * Set CA path info for SSL connection proxy. Specify directory name of the + * CA certificates which have been prepared using openssl c_rehash utility. */ - result = Curl_setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS], - va_arg(param, char *)); - break; -#ifdef USE_LIBSSH2 - case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256: +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) + /* This does not work on Windows. */ + return Curl_setstropt(&data->set.str[STRING_SSL_CAPATH_PROXY], ptr); +#endif + return CURLE_NOT_BUILT_IN; +#endif + case CURLOPT_CRLFILE: /* - * Option to allow for the SHA256 of the host public key to be checked - * for validation purposes. + * Set CRL file info for SSL connection. Specify filename of the CRL + * to check certificates revocation */ - result = Curl_setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_SSL_CRLFILE], ptr); - case CURLOPT_SSH_HOSTKEYFUNCTION: - /* the callback to check the hostkey without the knownhost file */ - data->set.ssh_hostkeyfunc = va_arg(param, curl_sshhostkeycallback); - break; - - case CURLOPT_SSH_HOSTKEYDATA: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_CRLFILE: /* - * Custom client data to pass to the SSH keyfunc callback + * Set CRL file info for SSL connection for proxy. Specify filename of the + * CRL to check certificates revocation */ - data->set.ssh_hostkeyfunc_userp = va_arg(param, void *); - break; + return Curl_setstropt(&data->set.str[STRING_SSL_CRLFILE_PROXY], ptr); #endif - - case CURLOPT_SSH_KEYFUNCTION: - /* setting to NULL is fine since the ssh.c functions themselves will - then revert to use the internal default */ - data->set.ssh_keyfunc = va_arg(param, curl_sshkeycallback); - break; - - case CURLOPT_SSH_KEYDATA: + case CURLOPT_ISSUERCERT: /* - * Custom client data to pass to the SSH keyfunc callback + * Set Issuer certificate file + * to check certificates issuer */ - data->set.ssh_keyfunc_userp = va_arg(param, void *); - break; + return Curl_setstropt(&data->set.str[STRING_SSL_ISSUERCERT], ptr); - case CURLOPT_SSH_COMPRESSION: - data->set.ssh_compression = (0 != va_arg(param, long))?TRUE:FALSE; - break; -#endif /* USE_SSH */ - - case CURLOPT_HTTP_TRANSFER_DECODING: +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_ISSUERCERT: /* - * disable libcurl transfer encoding is used + * Set Issuer certificate file + * to check certificates issuer */ -#ifndef USE_HYPER - data->set.http_te_skip = (0 == va_arg(param, long)) ? TRUE : FALSE; - break; -#else - return CURLE_NOT_BUILT_IN; /* hyper doesn't support */ + return Curl_setstropt(&data->set.str[STRING_SSL_ISSUERCERT_PROXY], ptr); + #endif - case CURLOPT_HTTP_CONTENT_DECODING: + case CURLOPT_PRIVATE: /* - * raw data passed to the application when content encoding is used + * Set private data pointer. */ - data->set.http_ce_skip = (0 == va_arg(param, long)) ? TRUE : FALSE; + data->set.private_data = (void *)ptr; break; -#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) - case CURLOPT_NEW_FILE_PERMS: +#ifdef USE_SSL + case CURLOPT_SSL_EC_CURVES: /* - * Uses these permissions instead of 0644 + * Set accepted curves in SSL connection setup. + * Specify colon-delimited list of curve algorithm names. */ - arg = va_arg(param, long); - if((arg < 0) || (arg > 0777)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.new_file_perms = (unsigned int)arg; - break; + return Curl_setstropt(&data->set.str[STRING_SSL_EC_CURVES], ptr); #endif #ifdef USE_SSH - case CURLOPT_NEW_DIRECTORY_PERMS: + case CURLOPT_SSH_PUBLIC_KEYFILE: /* - * Uses these permissions instead of 0755 + * Use this file instead of the $HOME/.ssh/id_dsa.pub file */ - arg = va_arg(param, long); - if((arg < 0) || (arg > 0777)) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.new_directory_perms = (unsigned int)arg; - break; -#endif + return Curl_setstropt(&data->set.str[STRING_SSH_PUBLIC_KEY], ptr); -#ifdef ENABLE_IPV6 - case CURLOPT_ADDRESS_SCOPE: + case CURLOPT_SSH_PRIVATE_KEYFILE: /* - * Use this scope id when using IPv6 - * We always get longs when passed plain numericals so we should check - * that the value fits into an unsigned 32 bit integer. + * Use this file instead of the $HOME/.ssh/id_dsa file */ - uarg = va_arg(param, unsigned long); -#if SIZEOF_LONG > 4 - if(uarg > UINT_MAX) - return CURLE_BAD_FUNCTION_ARGUMENT; -#endif - data->set.scope_id = (unsigned int)uarg; - break; -#endif - - case CURLOPT_PROTOCOLS: - /* set the bitmask for the protocols that are allowed to be used for the - transfer, which thus helps the app which takes URLs from users or other - external inputs and want to restrict what protocol(s) to deal - with. Defaults to CURLPROTO_ALL. */ - data->set.allowed_protocols = (curl_prot_t)va_arg(param, long); - break; - - case CURLOPT_REDIR_PROTOCOLS: - /* set the bitmask for the protocols that libcurl is allowed to follow to, - as a subset of the CURLOPT_PROTOCOLS ones. That means the protocol needs - to be set in both bitmasks to be allowed to get redirected to. */ - data->set.redir_protocols = (curl_prot_t)va_arg(param, long); - break; - - case CURLOPT_PROTOCOLS_STR: { - curl_prot_t prot; - argptr = va_arg(param, char *); - result = protocol2num(argptr, &prot); - if(result) - return result; - data->set.allowed_protocols = prot; - break; - } - - case CURLOPT_REDIR_PROTOCOLS_STR: { - curl_prot_t prot; - argptr = va_arg(param, char *); - result = protocol2num(argptr, &prot); - if(result) - return result; - data->set.redir_protocols = prot; - break; - } - - case CURLOPT_DEFAULT_PROTOCOL: - /* Set the protocol to use when the URL doesn't include any protocol */ - result = Curl_setstropt(&data->set.str[STRING_DEFAULT_PROTOCOL], - va_arg(param, char *)); - break; -#ifndef CURL_DISABLE_SMTP - case CURLOPT_MAIL_FROM: - /* Set the SMTP mail originator */ - result = Curl_setstropt(&data->set.str[STRING_MAIL_FROM], - va_arg(param, char *)); - break; - - case CURLOPT_MAIL_AUTH: - /* Set the SMTP auth originator */ - result = Curl_setstropt(&data->set.str[STRING_MAIL_AUTH], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_SSH_PRIVATE_KEY], ptr); - case CURLOPT_MAIL_RCPT: - /* Set the list of mail recipients */ - data->set.mail_rcpt = va_arg(param, struct curl_slist *); - break; - case CURLOPT_MAIL_RCPT_ALLOWFAILS: - /* allow RCPT TO command to fail for some recipients */ - data->set.mail_rcpt_allowfails = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; -#endif + case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: + /* + * Option to allow for the MD5 of the host public key to be checked + * for validation purposes. + */ + return Curl_setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5], ptr); - case CURLOPT_SASL_AUTHZID: - /* Authorization identity (identity to act as) */ - result = Curl_setstropt(&data->set.str[STRING_SASL_AUTHZID], - va_arg(param, char *)); - break; + case CURLOPT_SSH_KNOWNHOSTS: + /* + * Store the filename to read known hosts from. + */ + return Curl_setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS], ptr); - case CURLOPT_SASL_IR: - /* Enable/disable SASL initial response */ - data->set.sasl_ir = (0 != va_arg(param, long)) ? TRUE : FALSE; + case CURLOPT_SSH_KEYDATA: + /* + * Custom client data to pass to the SSH keyfunc callback + */ + data->set.ssh_keyfunc_userp = (void *)ptr; break; -#ifndef CURL_DISABLE_RTSP - case CURLOPT_RTSP_REQUEST: - { +#ifdef USE_LIBSSH2 + case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256: /* - * Set the RTSP request method (OPTIONS, SETUP, PLAY, etc...) - * Would this be better if the RTSPREQ_* were just moved into here? + * Option to allow for the SHA256 of the host public key to be checked + * for validation purposes. */ - long in_rtspreq = va_arg(param, long); - Curl_RtspReq rtspreq = RTSPREQ_NONE; - switch(in_rtspreq) { - case CURL_RTSPREQ_OPTIONS: - rtspreq = RTSPREQ_OPTIONS; - break; - - case CURL_RTSPREQ_DESCRIBE: - rtspreq = RTSPREQ_DESCRIBE; - break; - - case CURL_RTSPREQ_ANNOUNCE: - rtspreq = RTSPREQ_ANNOUNCE; - break; - - case CURL_RTSPREQ_SETUP: - rtspreq = RTSPREQ_SETUP; - break; - - case CURL_RTSPREQ_PLAY: - rtspreq = RTSPREQ_PLAY; - break; - - case CURL_RTSPREQ_PAUSE: - rtspreq = RTSPREQ_PAUSE; - break; - - case CURL_RTSPREQ_TEARDOWN: - rtspreq = RTSPREQ_TEARDOWN; - break; + return Curl_setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256], + ptr); - case CURL_RTSPREQ_GET_PARAMETER: - rtspreq = RTSPREQ_GET_PARAMETER; - break; + case CURLOPT_SSH_HOSTKEYDATA: + /* + * Custom client data to pass to the SSH keyfunc callback + */ + data->set.ssh_hostkeyfunc_userp = (void *)ptr; + break; +#endif /* USE_LIBSSH2 */ +#endif /* USE_SSH */ + case CURLOPT_PROTOCOLS_STR: + if(ptr) + return protocol2num(ptr, &data->set.allowed_protocols); + /* make a NULL argument reset to default */ + data->set.allowed_protocols = (curl_prot_t) CURLPROTO_ALL; + break; - case CURL_RTSPREQ_SET_PARAMETER: - rtspreq = RTSPREQ_SET_PARAMETER; - break; + case CURLOPT_REDIR_PROTOCOLS_STR: + if(ptr) + return protocol2num(ptr, &data->set.redir_protocols); + /* make a NULL argument reset to default */ + data->set.redir_protocols = (curl_prot_t) CURLPROTO_REDIR; + break; - case CURL_RTSPREQ_RECORD: - rtspreq = RTSPREQ_RECORD; - break; + case CURLOPT_DEFAULT_PROTOCOL: + /* Set the protocol to use when the URL does not include any protocol */ + return Curl_setstropt(&data->set.str[STRING_DEFAULT_PROTOCOL], ptr); - case CURL_RTSPREQ_RECEIVE: - rtspreq = RTSPREQ_RECEIVE; - break; - default: - rtspreq = RTSPREQ_NONE; - } +#ifndef CURL_DISABLE_SMTP + case CURLOPT_MAIL_FROM: + /* Set the SMTP mail originator */ + return Curl_setstropt(&data->set.str[STRING_MAIL_FROM], ptr); - data->set.rtspreq = rtspreq; - break; - } + case CURLOPT_MAIL_AUTH: + /* Set the SMTP auth originator */ + return Curl_setstropt(&data->set.str[STRING_MAIL_AUTH], ptr); +#endif + case CURLOPT_SASL_AUTHZID: + /* Authorization identity (identity to act as) */ + return Curl_setstropt(&data->set.str[STRING_SASL_AUTHZID], ptr); +#ifndef CURL_DISABLE_RTSP case CURLOPT_RTSP_SESSION_ID: /* * Set the RTSP Session ID manually. Useful if the application is * resuming a previously established RTSP session */ - result = Curl_setstropt(&data->set.str[STRING_RTSP_SESSION_ID], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_RTSP_SESSION_ID], ptr); case CURLOPT_RTSP_STREAM_URI: /* * Set the Stream URI for the RTSP request. Unless the request is * for generic server options, the application will need to set this. */ - result = Curl_setstropt(&data->set.str[STRING_RTSP_STREAM_URI], - va_arg(param, char *)); + return Curl_setstropt(&data->set.str[STRING_RTSP_STREAM_URI], ptr); break; case CURLOPT_RTSP_TRANSPORT: /* * The content of the Transport: header for the RTSP request */ - result = Curl_setstropt(&data->set.str[STRING_RTSP_TRANSPORT], - va_arg(param, char *)); - break; - - case CURLOPT_RTSP_CLIENT_CSEQ: - /* - * Set the CSEQ number to issue for the next RTSP request. Useful if the - * application is resuming a previously broken connection. The CSEQ - * will increment from this new number henceforth. - */ - data->state.rtsp_next_client_CSeq = va_arg(param, long); - break; - - case CURLOPT_RTSP_SERVER_CSEQ: - /* Same as the above, but for server-initiated requests */ - data->state.rtsp_next_server_CSeq = va_arg(param, long); - break; + return Curl_setstropt(&data->set.str[STRING_RTSP_TRANSPORT], ptr); case CURLOPT_INTERLEAVEDATA: - data->set.rtp_out = va_arg(param, void *); - break; - case CURLOPT_INTERLEAVEFUNCTION: - /* Set the user defined RTP write function */ - data->set.fwrite_rtp = va_arg(param, curl_write_callback); + data->set.rtp_out = (void *)ptr; break; -#endif +#endif /* ! CURL_DISABLE_RTSP */ #ifndef CURL_DISABLE_FTP - case CURLOPT_WILDCARDMATCH: - data->set.wildcard_enabled = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - case CURLOPT_CHUNK_BGN_FUNCTION: - data->set.chunk_bgn = va_arg(param, curl_chunk_bgn_callback); - break; - case CURLOPT_CHUNK_END_FUNCTION: - data->set.chunk_end = va_arg(param, curl_chunk_end_callback); - break; - case CURLOPT_FNMATCH_FUNCTION: - data->set.fnmatch = va_arg(param, curl_fnmatch_callback); - break; case CURLOPT_CHUNK_DATA: - data->set.wildcardptr = va_arg(param, void *); + data->set.wildcardptr = (void *)ptr; break; case CURLOPT_FNMATCH_DATA: - data->set.fnmatch_data = va_arg(param, void *); + data->set.fnmatch_data = (void *)ptr; break; #endif #ifdef USE_TLS_SRP case CURLOPT_TLSAUTH_USERNAME: - result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME], ptr); + #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_TLSAUTH_USERNAME: - result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME_PROXY], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME_PROXY], ptr); + #endif case CURLOPT_TLSAUTH_PASSWORD: - result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD], ptr); + #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_TLSAUTH_PASSWORD: - result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD_PROXY], - va_arg(param, char *)); - break; + return Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD_PROXY], ptr); #endif case CURLOPT_TLSAUTH_TYPE: - argptr = va_arg(param, char *); - if(argptr && !strncasecompare(argptr, "SRP", strlen("SRP"))) + if(ptr && !strcasecompare(ptr, "SRP")) return CURLE_BAD_FUNCTION_ARGUMENT; break; #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_TLSAUTH_TYPE: - argptr = va_arg(param, char *); - if(argptr || !strncasecompare(argptr, "SRP", strlen("SRP"))) + if(ptr && !strcasecompare(ptr, "SRP")) return CURLE_BAD_FUNCTION_ARGUMENT; break; #endif #endif #ifdef USE_ARES case CURLOPT_DNS_SERVERS: - result = Curl_setstropt(&data->set.str[STRING_DNS_SERVERS], - va_arg(param, char *)); + result = Curl_setstropt(&data->set.str[STRING_DNS_SERVERS], ptr); if(result) return result; - result = Curl_set_dns_servers(data, data->set.str[STRING_DNS_SERVERS]); - break; + return Curl_set_dns_servers(data, data->set.str[STRING_DNS_SERVERS]); + case CURLOPT_DNS_INTERFACE: - result = Curl_setstropt(&data->set.str[STRING_DNS_INTERFACE], - va_arg(param, char *)); + result = Curl_setstropt(&data->set.str[STRING_DNS_INTERFACE], ptr); if(result) return result; - result = Curl_set_dns_interface(data, data->set.str[STRING_DNS_INTERFACE]); - break; + return Curl_set_dns_interface(data, data->set.str[STRING_DNS_INTERFACE]); + case CURLOPT_DNS_LOCAL_IP4: - result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP4], - va_arg(param, char *)); + result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP4], ptr); if(result) return result; - result = Curl_set_dns_local_ip4(data, data->set.str[STRING_DNS_LOCAL_IP4]); - break; + return Curl_set_dns_local_ip4(data, data->set.str[STRING_DNS_LOCAL_IP4]); + case CURLOPT_DNS_LOCAL_IP6: - result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP6], - va_arg(param, char *)); + result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP6], ptr); if(result) return result; - result = Curl_set_dns_local_ip6(data, data->set.str[STRING_DNS_LOCAL_IP6]); + return Curl_set_dns_local_ip6(data, data->set.str[STRING_DNS_LOCAL_IP6]); + +#endif +#ifdef USE_UNIX_SOCKETS + case CURLOPT_UNIX_SOCKET_PATH: + data->set.abstract_unix_socket = FALSE; + return Curl_setstropt(&data->set.str[STRING_UNIX_SOCKET_PATH], ptr); + + case CURLOPT_ABSTRACT_UNIX_SOCKET: + data->set.abstract_unix_socket = TRUE; + return Curl_setstropt(&data->set.str[STRING_UNIX_SOCKET_PATH], ptr); + +#endif + +#ifndef CURL_DISABLE_DOH + case CURLOPT_DOH_URL: + result = Curl_setstropt(&data->set.str[STRING_DOH], ptr); + data->set.doh = !!(data->set.str[STRING_DOH]); break; #endif - case CURLOPT_TCP_KEEPALIVE: - data->set.tcp_keepalive = (0 != va_arg(param, long)) ? TRUE : FALSE; +#ifndef CURL_DISABLE_HSTS + case CURLOPT_HSTSREADDATA: + data->set.hsts_read_userp = (void *)ptr; break; - case CURLOPT_TCP_KEEPIDLE: - arg = va_arg(param, long); - if(arg < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - else if(arg > INT_MAX) - arg = INT_MAX; - data->set.tcp_keepidle = (int)arg; + case CURLOPT_HSTSWRITEDATA: + data->set.hsts_write_userp = (void *)ptr; break; - case CURLOPT_TCP_KEEPINTVL: - arg = va_arg(param, long); - if(arg < 0) + case CURLOPT_HSTS: { + struct curl_slist *h; + if(!data->hsts) { + data->hsts = Curl_hsts_init(); + if(!data->hsts) + return CURLE_OUT_OF_MEMORY; + } + if(ptr) { + result = Curl_setstropt(&data->set.str[STRING_HSTS], ptr); + if(result) + return result; + /* this needs to build a list of filenames to read from, so that it can + read them later, as we might get a shared HSTS handle to load them + into */ + h = curl_slist_append(data->state.hstslist, ptr); + if(!h) { + curl_slist_free_all(data->state.hstslist); + data->state.hstslist = NULL; + return CURLE_OUT_OF_MEMORY; + } + data->state.hstslist = h; /* store the list for later use */ + } + else { + /* clear the list of HSTS files */ + curl_slist_free_all(data->state.hstslist); + data->state.hstslist = NULL; + if(!data->share || !data->share->hsts) + /* throw away the HSTS cache unless shared */ + Curl_hsts_cleanup(&data->hsts); + } + break; + } +#endif /* ! CURL_DISABLE_HSTS */ +#ifndef CURL_DISABLE_ALTSVC + case CURLOPT_ALTSVC: + if(!data->asi) { + data->asi = Curl_altsvc_init(); + if(!data->asi) + return CURLE_OUT_OF_MEMORY; + } + result = Curl_setstropt(&data->set.str[STRING_ALTSVC], ptr); + if(result) + return result; + if(ptr) + (void)Curl_altsvc_load(data->asi, ptr); + break; +#endif /* ! CURL_DISABLE_ALTSVC */ +#ifdef USE_ECH + case CURLOPT_ECH: { + size_t plen = 0; + + if(!ptr) { + data->set.tls_ech = CURLECH_DISABLE; + return CURLE_OK; + } + plen = strlen(ptr); + if(plen > CURL_MAX_INPUT_LENGTH) { + data->set.tls_ech = CURLECH_DISABLE; return CURLE_BAD_FUNCTION_ARGUMENT; - else if(arg > INT_MAX) - arg = INT_MAX; - data->set.tcp_keepintvl = (int)arg; + } + /* set tls_ech flag value, preserving CLA_CFG bit */ + if(!strcmp(ptr, "false")) + data->set.tls_ech = CURLECH_DISABLE | + (data->set.tls_ech & CURLECH_CLA_CFG); + else if(!strcmp(ptr, "grease")) + data->set.tls_ech = CURLECH_GREASE | + (data->set.tls_ech & CURLECH_CLA_CFG); + else if(!strcmp(ptr, "true")) + data->set.tls_ech = CURLECH_ENABLE | + (data->set.tls_ech & CURLECH_CLA_CFG); + else if(!strcmp(ptr, "hard")) + data->set.tls_ech = CURLECH_HARD | + (data->set.tls_ech & CURLECH_CLA_CFG); + else if(plen > 5 && !strncmp(ptr, "ecl:", 4)) { + result = Curl_setstropt(&data->set.str[STRING_ECH_CONFIG], ptr + 4); + if(result) + return result; + data->set.tls_ech |= CURLECH_CLA_CFG; + } + else if(plen > 4 && !strncmp(ptr, "pn:", 3)) { + result = Curl_setstropt(&data->set.str[STRING_ECH_PUBLIC], ptr + 3); + if(result) + return result; + } break; - case CURLOPT_TCP_FASTOPEN: -#if defined(CONNECT_DATA_IDEMPOTENT) || defined(MSG_FASTOPEN) || \ - defined(TCP_FASTOPEN_CONNECT) - data->set.tcp_fastopen = (0 != va_arg(param, long))?TRUE:FALSE; -#else - result = CURLE_NOT_BUILT_IN; + } #endif + default: + return CURLE_UNKNOWN_OPTION; + } + return result; +} + +static CURLcode setopt_func(struct Curl_easy *data, CURLoption option, + va_list param) +{ + switch(option) { + case CURLOPT_PROGRESSFUNCTION: + /* + * Progress callback function + */ + data->set.fprogress = va_arg(param, curl_progress_callback); + if(data->set.fprogress) + data->progress.callback = TRUE; /* no longer internal */ + else + data->progress.callback = FALSE; /* NULL enforces internal */ break; - case CURLOPT_SSL_ENABLE_NPN: + + case CURLOPT_XFERINFOFUNCTION: + /* + * Transfer info callback function + */ + data->set.fxferinfo = va_arg(param, curl_xferinfo_callback); + if(data->set.fxferinfo) + data->progress.callback = TRUE; /* no longer internal */ + else + data->progress.callback = FALSE; /* NULL enforces internal */ + break; - case CURLOPT_SSL_ENABLE_ALPN: - data->set.ssl_enable_alpn = (0 != va_arg(param, long)) ? TRUE : FALSE; + case CURLOPT_DEBUGFUNCTION: + /* + * stderr write callback. + */ + data->set.fdebug = va_arg(param, curl_debug_callback); + /* + * if the callback provided is NULL, it will use the default callback + */ break; -#ifdef USE_UNIX_SOCKETS - case CURLOPT_UNIX_SOCKET_PATH: - data->set.abstract_unix_socket = FALSE; - result = Curl_setstropt(&data->set.str[STRING_UNIX_SOCKET_PATH], - va_arg(param, char *)); + case CURLOPT_HEADERFUNCTION: + /* + * Set header write callback + */ + data->set.fwrite_header = va_arg(param, curl_write_callback); break; - case CURLOPT_ABSTRACT_UNIX_SOCKET: - data->set.abstract_unix_socket = TRUE; - result = Curl_setstropt(&data->set.str[STRING_UNIX_SOCKET_PATH], - va_arg(param, char *)); + case CURLOPT_WRITEFUNCTION: + /* + * Set data write callback + */ + data->set.fwrite_func = va_arg(param, curl_write_callback); + if(!data->set.fwrite_func) + /* When set to NULL, reset to our internal default function */ + data->set.fwrite_func = (curl_write_callback)fwrite; break; -#endif - - case CURLOPT_PATH_AS_IS: - data->set.path_as_is = (0 != va_arg(param, long)) ? TRUE : FALSE; + case CURLOPT_READFUNCTION: + /* + * Read data callback + */ + data->set.fread_func_set = va_arg(param, curl_read_callback); + if(!data->set.fread_func_set) { + data->set.is_fread_set = 0; + /* When set to NULL, reset to our internal default function */ + data->set.fread_func_set = (curl_read_callback)fread; + } + else + data->set.is_fread_set = 1; break; - case CURLOPT_PIPEWAIT: - data->set.pipewait = (0 != va_arg(param, long)) ? TRUE : FALSE; + case CURLOPT_SEEKFUNCTION: + /* + * Seek callback. Might be NULL. + */ + data->set.seek_func = va_arg(param, curl_seek_callback); break; - case CURLOPT_STREAM_WEIGHT: -#if defined(USE_HTTP2) || defined(USE_HTTP3) - arg = va_arg(param, long); - if((arg >= 1) && (arg <= 256)) - data->set.priority.weight = (int)arg; + case CURLOPT_IOCTLFUNCTION: + /* + * I/O control callback. Might be NULL. + */ + data->set.ioctl_func = va_arg(param, curl_ioctl_callback); break; -#else - return CURLE_NOT_BUILT_IN; + case CURLOPT_SSL_CTX_FUNCTION: + /* + * Set a SSL_CTX callback + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) + data->set.ssl.fsslctx = va_arg(param, curl_ssl_ctx_callback); + else #endif - case CURLOPT_STREAM_DEPENDS: - case CURLOPT_STREAM_DEPENDS_E: - { - struct Curl_easy *dep = va_arg(param, struct Curl_easy *); - if(!dep || GOOD_EASY_HANDLE(dep)) { - return Curl_data_priority_add_child(dep, data, - option == CURLOPT_STREAM_DEPENDS_E); - } + return CURLE_NOT_BUILT_IN; + break; + + case CURLOPT_SOCKOPTFUNCTION: + /* + * socket callback function: called after socket() but before connect() + */ + data->set.fsockopt = va_arg(param, curl_sockopt_callback); break; - } - case CURLOPT_CONNECT_TO: - data->set.connect_to = va_arg(param, struct curl_slist *); + + case CURLOPT_OPENSOCKETFUNCTION: + /* + * open/create socket callback function: called instead of socket(), + * before connect() + */ + data->set.fopensocket = va_arg(param, curl_opensocket_callback); break; - case CURLOPT_SUPPRESS_CONNECT_HEADERS: - data->set.suppress_connect_headers = (0 != va_arg(param, long))?TRUE:FALSE; + + case CURLOPT_CLOSESOCKETFUNCTION: + /* + * close socket callback function: called instead of close() + * when shutting down a connection + */ + data->set.fclosesocket = va_arg(param, curl_closesocket_callback); break; - case CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS: - uarg = va_arg(param, unsigned long); - if(uarg > UINT_MAX) - uarg = UINT_MAX; - data->set.happy_eyeballs_timeout = (unsigned int)uarg; + + case CURLOPT_RESOLVER_START_FUNCTION: + /* + * resolver start callback function: called before a new resolver request + * is started + */ + data->set.resolver_start = va_arg(param, curl_resolver_start_callback); break; -#ifndef CURL_DISABLE_SHUFFLE_DNS - case CURLOPT_DNS_SHUFFLE_ADDRESSES: - data->set.dns_shuffle_addresses = (0 != va_arg(param, long)) ? TRUE:FALSE; + + +#ifdef USE_SSH +#ifdef USE_LIBSSH2 + case CURLOPT_SSH_HOSTKEYFUNCTION: + /* the callback to check the hostkey without the knownhost file */ + data->set.ssh_hostkeyfunc = va_arg(param, curl_sshhostkeycallback); break; #endif - case CURLOPT_DISALLOW_USERNAME_IN_URL: - data->set.disallow_username_in_url = - (0 != va_arg(param, long)) ? TRUE : FALSE; + + case CURLOPT_SSH_KEYFUNCTION: + /* setting to NULL is fine since the ssh.c functions themselves will + then revert to use the internal default */ + data->set.ssh_keyfunc = va_arg(param, curl_sshkeycallback); break; -#ifndef CURL_DISABLE_DOH - case CURLOPT_DOH_URL: - result = Curl_setstropt(&data->set.str[STRING_DOH], - va_arg(param, char *)); - data->set.doh = data->set.str[STRING_DOH]?TRUE:FALSE; + +#endif /* USE_SSH */ + +#ifndef CURL_DISABLE_RTSP + case CURLOPT_INTERLEAVEFUNCTION: + /* Set the user defined RTP write function */ + data->set.fwrite_rtp = va_arg(param, curl_write_callback); break; #endif - case CURLOPT_UPKEEP_INTERVAL_MS: - arg = va_arg(param, long); - if(arg < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.upkeep_interval_ms = arg; +#ifndef CURL_DISABLE_FTP + case CURLOPT_CHUNK_BGN_FUNCTION: + data->set.chunk_bgn = va_arg(param, curl_chunk_bgn_callback); break; - case CURLOPT_MAXAGE_CONN: - arg = va_arg(param, long); - if(arg < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.maxage_conn = arg; + case CURLOPT_CHUNK_END_FUNCTION: + data->set.chunk_end = va_arg(param, curl_chunk_end_callback); break; - case CURLOPT_MAXLIFETIME_CONN: - arg = va_arg(param, long); - if(arg < 0) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.maxlifetime_conn = arg; + case CURLOPT_FNMATCH_FUNCTION: + data->set.fnmatch = va_arg(param, curl_fnmatch_callback); break; - case CURLOPT_TRAILERFUNCTION: +#endif #ifndef CURL_DISABLE_HTTP + case CURLOPT_TRAILERFUNCTION: data->set.trailer_callback = va_arg(param, curl_trailer_callback); -#endif break; - case CURLOPT_TRAILERDATA: -#ifndef CURL_DISABLE_HTTP - data->set.trailer_data = va_arg(param, void *); #endif - break; #ifndef CURL_DISABLE_HSTS case CURLOPT_HSTSREADFUNCTION: data->set.hsts_read = va_arg(param, curl_hstsread_callback); break; - case CURLOPT_HSTSREADDATA: - data->set.hsts_read_userp = va_arg(param, void *); - break; case CURLOPT_HSTSWRITEFUNCTION: data->set.hsts_write = va_arg(param, curl_hstswrite_callback); break; - case CURLOPT_HSTSWRITEDATA: - data->set.hsts_write_userp = va_arg(param, void *); - break; - case CURLOPT_HSTS: { - struct curl_slist *h; - if(!data->hsts) { - data->hsts = Curl_hsts_init(); - if(!data->hsts) - return CURLE_OUT_OF_MEMORY; - } - argptr = va_arg(param, char *); - if(argptr) { - result = Curl_setstropt(&data->set.str[STRING_HSTS], argptr); - if(result) - return result; - /* this needs to build a list of file names to read from, so that it can - read them later, as we might get a shared HSTS handle to load them - into */ - h = curl_slist_append(data->set.hstslist, argptr); - if(!h) { - curl_slist_free_all(data->set.hstslist); - data->set.hstslist = NULL; - return CURLE_OUT_OF_MEMORY; - } - data->set.hstslist = h; /* store the list for later use */ - } - else { - /* clear the list of HSTS files */ - curl_slist_free_all(data->set.hstslist); - data->set.hstslist = NULL; - if(!data->share || !data->share->hsts) - /* throw away the HSTS cache unless shared */ - Curl_hsts_cleanup(&data->hsts); - } +#endif + case CURLOPT_PREREQFUNCTION: + data->set.fprereq = va_arg(param, curl_prereq_callback); break; + default: + return CURLE_UNKNOWN_OPTION; } - case CURLOPT_HSTS_CTRL: - arg = va_arg(param, long); - if(arg & CURLHSTS_ENABLE) { - if(!data->hsts) { - data->hsts = Curl_hsts_init(); - if(!data->hsts) - return CURLE_OUT_OF_MEMORY; - } - } - else - Curl_hsts_cleanup(&data->hsts); + return CURLE_OK; +} + +static CURLcode setopt_offt(struct Curl_easy *data, CURLoption option, + curl_off_t offt) +{ + switch(option) { + case CURLOPT_TIMEVALUE_LARGE: + /* + * This is the value to compare with the remote document with the + * method set with CURLOPT_TIMECONDITION + */ + data->set.timevalue = (time_t)offt; break; -#endif -#ifndef CURL_DISABLE_ALTSVC - case CURLOPT_ALTSVC: - if(!data->asi) { - data->asi = Curl_altsvc_init(); - if(!data->asi) - return CURLE_OUT_OF_MEMORY; + + /* MQTT "borrows" some of the HTTP options */ + case CURLOPT_POSTFIELDSIZE_LARGE: + /* + * The size of the POSTFIELD data to prevent libcurl to do strlen() to + * figure it out. Enables binary posts. + */ + if(offt < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(data->set.postfieldsize < offt && + data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { + /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ + Curl_safefree(data->set.str[STRING_COPYPOSTFIELDS]); + data->set.postfields = NULL; } - argptr = va_arg(param, char *); - result = Curl_setstropt(&data->set.str[STRING_ALTSVC], argptr); - if(result) - return result; - if(argptr) - (void)Curl_altsvc_load(data->asi, argptr); + data->set.postfieldsize = offt; 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; + case CURLOPT_INFILESIZE_LARGE: + /* + * If known, this should inform curl about the file size of the + * to-be-uploaded file. + */ + if(offt < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.filesize = offt; break; -#endif - case CURLOPT_PREREQFUNCTION: - data->set.fprereq = va_arg(param, curl_prereq_callback); + case CURLOPT_MAX_SEND_SPEED_LARGE: + /* + * When transfer uploads are faster then CURLOPT_MAX_SEND_SPEED_LARGE + * bytes per second the transfer is throttled.. + */ + if(offt < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.max_send_speed = offt; break; - case CURLOPT_PREREQDATA: - data->set.prereq_userp = va_arg(param, void *); + case CURLOPT_MAX_RECV_SPEED_LARGE: + /* + * When receiving data faster than CURLOPT_MAX_RECV_SPEED_LARGE bytes per + * second the transfer is throttled.. + */ + if(offt < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.max_recv_speed = offt; + break; + case CURLOPT_RESUME_FROM_LARGE: + /* + * Resume transfer at the given file position + */ + if(offt < -1) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.set_resume_from = offt; break; -#ifdef USE_WEBSOCKETS - case CURLOPT_WS_OPTIONS: { - bool raw; - arg = va_arg(param, long); - raw = (arg & CURLWS_RAW_MODE); - data->set.ws_raw_mode = raw; + case CURLOPT_MAXFILESIZE_LARGE: + /* + * Set the maximum size of a file to download. + */ + if(offt < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.max_filesize = offt; break; + + default: + return CURLE_UNKNOWN_OPTION; } + return CURLE_OK; +} + +static CURLcode setopt_blob(struct Curl_easy *data, CURLoption option, + struct curl_blob *blob) +{ + switch(option) { + case CURLOPT_SSLCERT_BLOB: + /* + * Blob that holds file content of the SSL certificate to use + */ + return Curl_setblobopt(&data->set.blobs[BLOB_CERT], blob); +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSLCERT_BLOB: + /* + * Blob that holds file content of the SSL certificate to use for proxy + */ + return Curl_setblobopt(&data->set.blobs[BLOB_CERT_PROXY], blob); + case CURLOPT_PROXY_SSLKEY_BLOB: + /* + * Blob that holds file content of the SSL key to use for proxy + */ + return Curl_setblobopt(&data->set.blobs[BLOB_KEY_PROXY], blob); + case CURLOPT_PROXY_CAINFO_BLOB: + /* + * Blob that holds CA info for SSL connection proxy. + * Specify entire PEM of the CA certificate + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) + return Curl_setblobopt(&data->set.blobs[BLOB_CAINFO_PROXY], blob); #endif - case CURLOPT_QUICK_EXIT: - data->set.quick_exit = (0 != va_arg(param, long)) ? 1L:0L; - break; + return CURLE_NOT_BUILT_IN; + case CURLOPT_PROXY_ISSUERCERT_BLOB: + /* + * Blob that holds Issuer certificate to check certificates issuer + */ + return Curl_setblobopt(&data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY], + blob); +#endif + case CURLOPT_SSLKEY_BLOB: + /* + * Blob that holds file content of the SSL key to use + */ + return Curl_setblobopt(&data->set.blobs[BLOB_KEY], blob); + case CURLOPT_CAINFO_BLOB: + /* + * Blob that holds CA info for SSL connection. + * Specify entire PEM of the CA certificate + */ +#ifdef USE_SSL + if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) + return Curl_setblobopt(&data->set.blobs[BLOB_CAINFO], blob); +#endif + return CURLE_NOT_BUILT_IN; + case CURLOPT_ISSUERCERT_BLOB: + /* + * Blob that holds Issuer certificate to check certificates issuer + */ + return Curl_setblobopt(&data->set.blobs[BLOB_SSL_ISSUERCERT], blob); + default: - /* unknown tag and its companion, just ignore: */ - result = CURLE_UNKNOWN_OPTION; - break; + return CURLE_UNKNOWN_OPTION; } + /* unreachable */ +} - return result; +/* + * Do not make Curl_vsetopt() static: it is called from + * packages/OS400/ccsidcurl.c. + */ +CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) +{ + if(option < CURLOPTTYPE_OBJECTPOINT) + return setopt_long(data, option, va_arg(param, long)); + else if(option < CURLOPTTYPE_FUNCTIONPOINT) { + /* unfortunately, different pointer types cannot be identified any other + way than being listed explicitly */ + switch(option) { + case CURLOPT_HTTPHEADER: + case CURLOPT_QUOTE: + case CURLOPT_POSTQUOTE: + case CURLOPT_TELNETOPTIONS: + case CURLOPT_PREQUOTE: + case CURLOPT_HTTP200ALIASES: + case CURLOPT_MAIL_RCPT: + case CURLOPT_RESOLVE: + case CURLOPT_PROXYHEADER: + case CURLOPT_CONNECT_TO: + return setopt_slist(data, option, va_arg(param, struct curl_slist *)); + case CURLOPT_HTTPPOST: /* curl_httppost * */ + case CURLOPT_MIMEPOST: /* curl_mime * */ + case CURLOPT_STDERR: /* FILE * */ + case CURLOPT_SHARE: /* CURLSH * */ + case CURLOPT_STREAM_DEPENDS: /* CURL * */ + case CURLOPT_STREAM_DEPENDS_E: /* CURL * */ + return setopt_pointers(data, option, param); + default: + break; + } + /* the char pointer options */ + return setopt_cptr(data, option, va_arg(param, char *)); + } + else if(option < CURLOPTTYPE_OFF_T) + return setopt_func(data, option, param); + else if(option < CURLOPTTYPE_BLOB) + return setopt_offt(data, option, va_arg(param, curl_off_t)); + return setopt_blob(data, option, va_arg(param, struct curl_blob *)); } /* @@ -3188,10 +3065,11 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) */ #undef curl_easy_setopt -CURLcode curl_easy_setopt(struct Curl_easy *data, CURLoption tag, ...) +CURLcode curl_easy_setopt(CURL *d, CURLoption tag, ...) { va_list arg; CURLcode result; + struct Curl_easy *data = d; if(!data) return CURLE_BAD_FUNCTION_ARGUMENT; @@ -3201,5 +3079,9 @@ CURLcode curl_easy_setopt(struct Curl_easy *data, CURLoption tag, ...) result = Curl_vsetopt(data, tag, arg); va_end(arg); +#ifdef DEBUGBUILD + if(result == CURLE_BAD_FUNCTION_ARGUMENT) + infof(data, "setopt arg 0x%x returned CURLE_BAD_FUNCTION_ARGUMENT", tag); +#endif return result; } diff --git a/deps/curl/lib/setopt.h b/deps/curl/lib/setopt.h index 3c14a05e374..b0237467bd6 100644 --- a/deps/curl/lib/setopt.h +++ b/deps/curl/lib/setopt.h @@ -24,9 +24,10 @@ * ***************************************************************************/ -CURLcode Curl_setstropt(char **charp, const char *s); +CURLcode Curl_setstropt(char **charp, const char *s) WARN_UNUSED_RESULT; CURLcode Curl_setblobopt(struct curl_blob **blobp, - const struct curl_blob *blob); -CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list arg); + const struct curl_blob *blob) WARN_UNUSED_RESULT; +CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list arg) + WARN_UNUSED_RESULT; #endif /* HEADER_CURL_SETOPT_H */ diff --git a/deps/curl/lib/setup-os400.h b/deps/curl/lib/setup-os400.h index 53e91777eca..ef7baca67ef 100644 --- a/deps/curl/lib/setup-os400.h +++ b/deps/curl/lib/setup-os400.h @@ -38,6 +38,15 @@ typedef unsigned long u_int32_t; #define isatty(fd) 0 +/* Workaround bug in IBM QADRT runtime library: + * function puts() does not output the implicit trailing newline. + */ + +#include /* Be sure it is loaded. */ +#undef puts +#define puts(s) (fputs((s), stdout) == EOF? EOF: putchar('\n')) + + /* System API wrapper prototypes & definitions to support ASCII parameters. */ #include @@ -46,6 +55,8 @@ typedef unsigned long u_int32_t; #include #include +#ifdef BUILDING_LIBCURL + extern int Curl_getaddrinfo_a(const char *nodename, const char *servname, const struct addrinfo *hints, @@ -141,4 +152,6 @@ extern int Curl_os400_getsockname(int sd, struct sockaddr *addr, int *addrlen); #define inflateEnd Curl_os400_inflateEnd #endif +#endif /* BUILDING_LIBCURL */ + #endif /* HEADER_CURL_SETUP_OS400_H */ diff --git a/deps/curl/lib/setup-vms.h b/deps/curl/lib/setup-vms.h index 645cc1a9cda..33b74db358c 100644 --- a/deps/curl/lib/setup-vms.h +++ b/deps/curl/lib/setup-vms.h @@ -101,7 +101,7 @@ static char *vms_translate_path(const char *path) } } # else - /* VMS translate path is actually not needed on the current 64 bit */ + /* VMS translate path is actually not needed on the current 64-bit */ /* VMS platforms, so instead of figuring out the pointer settings */ /* Change it to a noop */ # define vms_translate_path(__path) __path @@ -144,7 +144,7 @@ static struct passwd *vms_getpwuid(uid_t uid) { struct passwd *my_passwd; -/* Hack needed to support 64 bit builds, decc_getpwnam is 32 bit only */ +/* Hack needed to support 64-bit builds, decc_getpwnam is 32-bit only */ #ifdef __DECC # if __INITIAL_POINTER_SIZE __char_ptr32 unix_path; @@ -374,8 +374,8 @@ static struct passwd *vms_getpwuid(uid_t uid) #ifdef HAVE_NETDB_H #include #ifndef AI_NUMERICHOST -#ifdef ENABLE_IPV6 -#undef ENABLE_IPV6 +#ifdef USE_IPV6 +#undef USE_IPV6 #endif #endif #endif diff --git a/deps/curl/lib/setup-win32.h b/deps/curl/lib/setup-win32.h index 13948389a41..a297bdcff46 100644 --- a/deps/curl/lib/setup-win32.h +++ b/deps/curl/lib/setup-win32.h @@ -24,18 +24,53 @@ * ***************************************************************************/ +#undef USE_WINSOCK +/* ---------------------------------------------------------------- */ +/* Watt-32 TCP/IP SPECIFIC */ +/* ---------------------------------------------------------------- */ +#ifdef USE_WATT32 +# include +# undef byte +# undef word +# define HAVE_SYS_IOCTL_H +# define HAVE_SYS_SOCKET_H +# define HAVE_NETINET_IN_H +# define HAVE_NETDB_H +# define HAVE_ARPA_INET_H +# define SOCKET int +/* ---------------------------------------------------------------- */ +/* BSD-style lwIP TCP/IP stack SPECIFIC */ +/* ---------------------------------------------------------------- */ +#elif defined(USE_LWIPSOCK) + /* Define to use BSD-style lwIP TCP/IP stack. */ + /* #define USE_LWIPSOCK 1 */ +# undef HAVE_GETHOSTNAME +# undef LWIP_POSIX_SOCKETS_IO_NAMES +# undef RECV_TYPE_ARG1 +# undef RECV_TYPE_ARG3 +# undef SEND_TYPE_ARG1 +# undef SEND_TYPE_ARG3 +# define HAVE_GETHOSTBYNAME_R +# define HAVE_GETHOSTBYNAME_R_6 +# define LWIP_POSIX_SOCKETS_IO_NAMES 0 +# define RECV_TYPE_ARG1 int +# define RECV_TYPE_ARG3 size_t +# define SEND_TYPE_ARG1 int +# define SEND_TYPE_ARG3 size_t +#elif defined(_WIN32) +# define USE_WINSOCK 2 +#endif + /* - * Include header files for windows builds before redefining anything. + * Include header files for Windows builds before redefining anything. * Use this preprocessor block only to include or exclude windows.h, - * winsock2.h or ws2tcpip.h. Any other windows thing belongs - * to any other further and independent block. Under Cygwin things work - * just as under linux (e.g. ) and the winsock headers should - * never be included when __CYGWIN__ is defined. configure script takes - * care of this, not defining HAVE_WINDOWS_H, HAVE_WINSOCK2_H, - * neither HAVE_WS2TCPIP_H when __CYGWIN__ is defined. + * winsock2.h or ws2tcpip.h. Any other Windows thing belongs + * to any other further and independent block. Under Cygwin things work + * just as under Linux (e.g. ) and the Winsock headers should + * never be included when __CYGWIN__ is defined. */ -#ifdef HAVE_WINDOWS_H +#ifdef _WIN32 # if defined(UNICODE) && !defined(_UNICODE) # error "UNICODE is defined but _UNICODE is not defined" # endif @@ -43,7 +78,7 @@ # error "_UNICODE is defined but UNICODE is not defined" # endif /* - * Don't include unneeded stuff in Windows headers to avoid compiler + * Do not include unneeded stuff in Windows headers to avoid compiler * warnings and macro clashes. * Make sure to define this macro before including any Windows headers. */ @@ -53,31 +88,16 @@ # ifndef NOGDI # define NOGDI # endif -# include +# include +# include # include -# ifdef HAVE_WINSOCK2_H -# include -# ifdef HAVE_WS2TCPIP_H -# include -# endif -# endif +# include # include # ifdef UNICODE typedef wchar_t *(*curl_wcsdup_callback)(const wchar_t *str); # endif #endif -/* - * Define USE_WINSOCK to 2 if we have and use WINSOCK2 API, else - * undefine USE_WINSOCK. - */ - -#undef USE_WINSOCK - -#ifdef HAVE_WINSOCK2_H -# define USE_WINSOCK 2 -#endif - /* * Define _WIN32_WINNT_[OS] symbols because not all Windows build systems have * those symbols to compare against, and even those that do may be missing @@ -96,18 +116,12 @@ #ifndef _WIN32_WINNT_WS03 #define _WIN32_WINNT_WS03 0x0502 /* Windows Server 2003 */ #endif -#ifndef _WIN32_WINNT_WIN6 -#define _WIN32_WINNT_WIN6 0x0600 /* Windows Vista */ -#endif #ifndef _WIN32_WINNT_VISTA #define _WIN32_WINNT_VISTA 0x0600 /* Windows Vista */ #endif #ifndef _WIN32_WINNT_WS08 #define _WIN32_WINNT_WS08 0x0600 /* Windows Server 2008 */ #endif -#ifndef _WIN32_WINNT_LONGHORN -#define _WIN32_WINNT_LONGHORN 0x0600 /* Windows Vista */ -#endif #ifndef _WIN32_WINNT_WIN7 #define _WIN32_WINNT_WIN7 0x0601 /* Windows 7 */ #endif @@ -117,9 +131,6 @@ #ifndef _WIN32_WINNT_WINBLUE #define _WIN32_WINNT_WINBLUE 0x0603 /* Windows 8.1 */ #endif -#ifndef _WIN32_WINNT_WINTHRESHOLD -#define _WIN32_WINNT_WINTHRESHOLD 0x0A00 /* Windows 10 */ -#endif #ifndef _WIN32_WINNT_WIN10 #define _WIN32_WINNT_WIN10 0x0A00 /* Windows 10 */ #endif diff --git a/deps/curl/lib/sha256.c b/deps/curl/lib/sha256.c index 4a02045d26e..c5bb921bb30 100644 --- a/deps/curl/lib/sha256.c +++ b/deps/curl/lib/sha256.c @@ -34,9 +34,6 @@ #ifdef USE_WOLFSSL #include -#ifndef NO_SHA256 -#define USE_OPENSSL_SHA256 -#endif #endif #if defined(USE_OPENSSL) @@ -100,13 +97,14 @@ #if defined(USE_OPENSSL_SHA256) -struct sha256_ctx { +struct ossl_sha256_ctx { EVP_MD_CTX *openssl_ctx; }; -typedef struct sha256_ctx my_sha256_ctx; +typedef struct ossl_sha256_ctx my_sha256_ctx; -static CURLcode my_sha256_init(my_sha256_ctx *ctx) +static CURLcode my_sha256_init(void *in) { + my_sha256_ctx *ctx = (my_sha256_ctx *)in; ctx->openssl_ctx = EVP_MD_CTX_create(); if(!ctx->openssl_ctx) return CURLE_OUT_OF_MEMORY; @@ -118,15 +116,17 @@ static CURLcode my_sha256_init(my_sha256_ctx *ctx) return CURLE_OK; } -static void my_sha256_update(my_sha256_ctx *ctx, +static void my_sha256_update(void *in, const unsigned char *data, unsigned int length) { + my_sha256_ctx *ctx = (my_sha256_ctx *)in; EVP_DigestUpdate(ctx->openssl_ctx, data, length); } -static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +static void my_sha256_final(unsigned char *digest, void *in) { + my_sha256_ctx *ctx = (my_sha256_ctx *)in; EVP_DigestFinal_ex(ctx->openssl_ctx, digest, NULL); EVP_MD_CTX_destroy(ctx->openssl_ctx); } @@ -135,20 +135,20 @@ static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) typedef struct sha256_ctx my_sha256_ctx; -static CURLcode my_sha256_init(my_sha256_ctx *ctx) +static CURLcode my_sha256_init(void *ctx) { sha256_init(ctx); return CURLE_OK; } -static void my_sha256_update(my_sha256_ctx *ctx, +static void my_sha256_update(void *ctx, const unsigned char *data, unsigned int length) { sha256_update(ctx, length, data); } -static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +static void my_sha256_final(unsigned char *digest, void *ctx) { sha256_digest(ctx, SHA256_DIGEST_SIZE, digest); } @@ -157,7 +157,7 @@ static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) typedef mbedtls_sha256_context my_sha256_ctx; -static CURLcode my_sha256_init(my_sha256_ctx *ctx) +static CURLcode my_sha256_init(void *ctx) { #if !defined(HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS) (void) mbedtls_sha256_starts(ctx, 0); @@ -167,7 +167,7 @@ static CURLcode my_sha256_init(my_sha256_ctx *ctx) return CURLE_OK; } -static void my_sha256_update(my_sha256_ctx *ctx, +static void my_sha256_update(void *ctx, const unsigned char *data, unsigned int length) { @@ -178,7 +178,7 @@ static void my_sha256_update(my_sha256_ctx *ctx, #endif } -static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +static void my_sha256_final(unsigned char *digest, void *ctx) { #if !defined(HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS) (void) mbedtls_sha256_finish(ctx, digest); @@ -190,20 +190,20 @@ static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) #elif defined(AN_APPLE_OS) typedef CC_SHA256_CTX my_sha256_ctx; -static CURLcode my_sha256_init(my_sha256_ctx *ctx) +static CURLcode my_sha256_init(void *ctx) { (void) CC_SHA256_Init(ctx); return CURLE_OK; } -static void my_sha256_update(my_sha256_ctx *ctx, +static void my_sha256_update(void *ctx, const unsigned char *data, unsigned int length) { (void) CC_SHA256_Update(ctx, data, length); } -static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +static void my_sha256_final(unsigned char *digest, void *ctx) { (void) CC_SHA256_Final(digest, ctx); } @@ -220,8 +220,9 @@ typedef struct sha256_ctx my_sha256_ctx; #define CALG_SHA_256 0x0000800c #endif -static CURLcode my_sha256_init(my_sha256_ctx *ctx) +static CURLcode my_sha256_init(void *in) { + my_sha256_ctx *ctx = (my_sha256_ctx *)in; if(!CryptAcquireContext(&ctx->hCryptProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) return CURLE_OUT_OF_MEMORY; @@ -235,19 +236,21 @@ static CURLcode my_sha256_init(my_sha256_ctx *ctx) return CURLE_OK; } -static void my_sha256_update(my_sha256_ctx *ctx, +static void my_sha256_update(void *in, const unsigned char *data, unsigned int length) { + my_sha256_ctx *ctx = (my_sha256_ctx *)in; CryptHashData(ctx->hHash, (unsigned char *) data, length, 0); } -static void my_sha256_final(unsigned char *digest, my_sha256_ctx *ctx) +static void my_sha256_final(unsigned char *digest, void *in) { + my_sha256_ctx *ctx = (my_sha256_ctx *)in; unsigned long length = 0; CryptGetHashParam(ctx->hHash, HP_HASHVAL, NULL, &length, 0); - if(length == SHA256_DIGEST_LENGTH) + if(length == CURL_SHA256_DIGEST_LENGTH) CryptGetHashParam(ctx->hHash, HP_HASHVAL, digest, &length, 0); if(ctx->hHash) @@ -334,14 +337,14 @@ static const unsigned long K[64] = { #define RORc(x, y) \ (((((unsigned long)(x) & 0xFFFFFFFFUL) >> (unsigned long)((y) & 31)) | \ ((unsigned long)(x) << (unsigned long)(32 - ((y) & 31)))) & 0xFFFFFFFFUL) -#define Ch(x,y,z) (z ^ (x & (y ^ z))) -#define Maj(x,y,z) (((x | y) & z) | (x & y)) -#define S(x, n) RORc((x), (n)) -#define R(x, n) (((x)&0xFFFFFFFFUL)>>(n)) -#define Sigma0(x) (S(x, 2) ^ S(x, 13) ^ S(x, 22)) -#define Sigma1(x) (S(x, 6) ^ S(x, 11) ^ S(x, 25)) -#define Gamma0(x) (S(x, 7) ^ S(x, 18) ^ R(x, 3)) -#define Gamma1(x) (S(x, 17) ^ S(x, 19) ^ R(x, 10)) +#define Sha256_Ch(x,y,z) (z ^ (x & (y ^ z))) +#define Sha256_Maj(x,y,z) (((x | y) & z) | (x & y)) +#define Sha256_S(x, n) RORc((x), (n)) +#define Sha256_R(x, n) (((x)&0xFFFFFFFFUL)>>(n)) +#define Sigma0(x) (Sha256_S(x, 2) ^ Sha256_S(x, 13) ^ Sha256_S(x, 22)) +#define Sigma1(x) (Sha256_S(x, 6) ^ Sha256_S(x, 11) ^ Sha256_S(x, 25)) +#define Gamma0(x) (Sha256_S(x, 7) ^ Sha256_S(x, 18) ^ Sha256_R(x, 3)) +#define Gamma1(x) (Sha256_S(x, 17) ^ Sha256_S(x, 19) ^ Sha256_R(x, 10)) /* Compress 512-bits */ static int sha256_compress(struct sha256_state *md, @@ -364,12 +367,12 @@ static int sha256_compress(struct sha256_state *md, } /* Compress */ -#define RND(a,b,c,d,e,f,g,h,i) \ - do { \ - unsigned long t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ - unsigned long t1 = Sigma0(a) + Maj(a, b, c); \ - d += t0; \ - h = t0 + t1; \ +#define RND(a,b,c,d,e,f,g,h,i) \ + do { \ + unsigned long t0 = h + Sigma1(e) + Sha256_Ch(e, f, g) + K[i] + W[i]; \ + unsigned long t1 = Sigma0(a) + Sha256_Maj(a, b, c); \ + d += t0; \ + h = t0 + t1; \ } while(0) for(i = 0; i < 64; ++i) { @@ -388,8 +391,9 @@ static int sha256_compress(struct sha256_state *md, } /* Initialize the hash state */ -static CURLcode my_sha256_init(struct sha256_state *md) +static CURLcode my_sha256_init(void *in) { + struct sha256_state *md = (struct sha256_state *)in; md->curlen = 0; md->length = 0; md->state[0] = 0x6A09E667UL; @@ -409,41 +413,39 @@ static CURLcode my_sha256_init(struct sha256_state *md) @param md The hash state @param in The data to hash @param inlen The length of the data (octets) - @return 0 if successful */ -static int my_sha256_update(struct sha256_state *md, - const unsigned char *in, - unsigned long inlen) +static void my_sha256_update(void *ctx, + const unsigned char *in, + unsigned int len) { + unsigned long inlen = len; unsigned long n; - -#define block_size 64 + struct sha256_state *md = (struct sha256_state *)ctx; +#define CURL_SHA256_BLOCK_SIZE 64 if(md->curlen > sizeof(md->buf)) - return -1; + return; while(inlen > 0) { - if(md->curlen == 0 && inlen >= block_size) { + if(md->curlen == 0 && inlen >= CURL_SHA256_BLOCK_SIZE) { if(sha256_compress(md, (unsigned char *)in) < 0) - return -1; - md->length += block_size * 8; - in += block_size; - inlen -= block_size; + return; + md->length += CURL_SHA256_BLOCK_SIZE * 8; + in += CURL_SHA256_BLOCK_SIZE; + inlen -= CURL_SHA256_BLOCK_SIZE; } else { - n = CURLMIN(inlen, (block_size - md->curlen)); + n = CURLMIN(inlen, (CURL_SHA256_BLOCK_SIZE - md->curlen)); memcpy(md->buf + md->curlen, in, n); md->curlen += n; in += n; inlen -= n; - if(md->curlen == block_size) { + if(md->curlen == CURL_SHA256_BLOCK_SIZE) { if(sha256_compress(md, md->buf) < 0) - return -1; - md->length += 8 * block_size; + return; + md->length += 8 * CURL_SHA256_BLOCK_SIZE; md->curlen = 0; } } } - - return 0; } /* @@ -452,13 +454,13 @@ static int my_sha256_update(struct sha256_state *md, @param out [out] The destination of the hash (32 bytes) @return 0 if successful */ -static int my_sha256_final(unsigned char *out, - struct sha256_state *md) +static void my_sha256_final(unsigned char *out, void *ctx) { + struct sha256_state *md = ctx; int i; if(md->curlen >= sizeof(md->buf)) - return -1; + return; /* Increase the length of the message */ md->length += md->curlen * 8; @@ -467,7 +469,7 @@ static int my_sha256_final(unsigned char *out, md->buf[md->curlen++] = (unsigned char)0x80; /* If the length is currently above 56 bytes we append zeros - * then compress. Then we can fall back to padding zeros and length + * then compress. Then we can fall back to padding zeros and length * encoding like normal. */ if(md->curlen > 56) { @@ -490,8 +492,6 @@ static int my_sha256_final(unsigned char *out, /* Copy output */ for(i = 0; i < 8; i++) WPA_PUT_BE32(out + (4 * i), md->state[i]); - - return 0; } #endif /* CRYPTO LIBS */ @@ -510,7 +510,7 @@ static int my_sha256_final(unsigned char *out, * Returns CURLE_OK on success. */ CURLcode Curl_sha256it(unsigned char *output, const unsigned char *input, - const size_t length) + const size_t length) { CURLcode result; my_sha256_ctx ctx; @@ -524,22 +524,14 @@ CURLcode Curl_sha256it(unsigned char *output, const unsigned char *input, } -const struct HMAC_params Curl_HMAC_SHA256[] = { - { - /* Hash initialization function. */ - CURLX_FUNCTION_CAST(HMAC_hinit_func, my_sha256_init), - /* Hash update function. */ - CURLX_FUNCTION_CAST(HMAC_hupdate_func, my_sha256_update), - /* Hash computation end function. */ - CURLX_FUNCTION_CAST(HMAC_hfinal_func, my_sha256_final), - /* Size of hash context structure. */ - sizeof(my_sha256_ctx), - /* Maximum key length. */ - 64, - /* Result size. */ - 32 - } +const struct HMAC_params Curl_HMAC_SHA256 = { + my_sha256_init, /* Hash initialization function. */ + my_sha256_update, /* Hash update function. */ + my_sha256_final, /* Hash computation end function. */ + sizeof(my_sha256_ctx), /* Size of hash context structure. */ + 64, /* Maximum key length. */ + 32 /* Result size. */ }; -#endif /* AWS, DIGEST, or libSSH2 */ +#endif /* AWS, DIGEST, or libssh2 */ diff --git a/deps/curl/lib/share.c b/deps/curl/lib/share.c index c0a8d806f34..6cdba18ec40 100644 --- a/deps/curl/lib/share.c +++ b/deps/curl/lib/share.c @@ -26,23 +26,25 @@ #include #include "urldata.h" +#include "connect.h" #include "share.h" #include "psl.h" #include "vtls/vtls.h" #include "hsts.h" +#include "url.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -struct Curl_share * +CURLSH * curl_share_init(void) { struct Curl_share *share = calloc(1, sizeof(struct Curl_share)); if(share) { share->magic = CURL_GOOD_SHARE; - share->specifier |= (1<specifier |= (1 << CURL_LOCK_DATA_SHARE); Curl_init_dnscache(&share->hostcache, 23); } @@ -51,7 +53,7 @@ curl_share_init(void) #undef curl_share_setopt CURLSHcode -curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) +curl_share_setopt(CURLSH *sh, CURLSHoption option, ...) { va_list param; int type; @@ -59,12 +61,13 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) curl_unlock_function unlockfunc; void *ptr; CURLSHcode res = CURLSHE_OK; + struct Curl_share *share = sh; if(!GOOD_SHARE_HANDLE(share)) return CURLSHE_INVALID; if(share->dirty) - /* don't allow setting options while one or more handles are already + /* do not allow setting options while one or more handles are already using this share */ return CURLSHE_IN_USE; @@ -119,8 +122,12 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) break; case CURL_LOCK_DATA_CONNECT: - if(Curl_conncache_init(&share->conn_cache, 103)) - res = CURLSHE_NOMEM; + /* It is safe to set this option several times on a share. */ + if(!share->cpool.idata) { + if(Curl_cpool_init(&share->cpool, Curl_on_disconnect, + NULL, share, 103)) + res = CURLSHE_NOMEM; + } break; case CURL_LOCK_DATA_PSL: @@ -133,13 +140,13 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) res = CURLSHE_BAD_OPTION; } if(!res) - share->specifier |= (1<specifier |= (unsigned int)(1 << type); break; case CURLSHOPT_UNSHARE: /* this is a type this share will no longer share */ type = va_arg(param, int); - share->specifier &= ~(1<specifier &= ~(unsigned int)(1 << type); switch(type) { case CURL_LOCK_DATA_DNS: break; @@ -208,8 +215,9 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) } CURLSHcode -curl_share_cleanup(struct Curl_share *share) +curl_share_cleanup(CURLSH *sh) { + struct Curl_share *share = sh; if(!GOOD_SHARE_HANDLE(share)) return CURLSHE_INVALID; @@ -223,8 +231,9 @@ curl_share_cleanup(struct Curl_share *share) return CURLSHE_IN_USE; } - Curl_conncache_close_all_connections(&share->conn_cache); - Curl_conncache_destroy(&share->conn_cache); + if(share->specifier & (1 << CURL_LOCK_DATA_CONNECT)) { + Curl_cpool_destroy(&share->cpool); + } Curl_hash_destroy(&share->hostcache); #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) @@ -264,11 +273,11 @@ Curl_share_lock(struct Curl_easy *data, curl_lock_data type, if(!share) return CURLSHE_INVALID; - if(share->specifier & (1<specifier & (unsigned int)(1 << type)) { if(share->lockfunc) /* only call this if set! */ share->lockfunc(data, type, accesstype, share->clientdata); } - /* else if we don't share this, pretend successful lock */ + /* else if we do not share this, pretend successful lock */ return CURLSHE_OK; } @@ -281,7 +290,7 @@ Curl_share_unlock(struct Curl_easy *data, curl_lock_data type) if(!share) return CURLSHE_INVALID; - if(share->specifier & (1<specifier & (unsigned int)(1 << type)) { if(share->unlockfunc) /* only call this if set! */ share->unlockfunc (data, type, share->clientdata); } diff --git a/deps/curl/lib/share.h b/deps/curl/lib/share.h index 7f55aac853d..124f7049f19 100644 --- a/deps/curl/lib/share.h +++ b/deps/curl/lib/share.h @@ -31,27 +31,22 @@ #include "urldata.h" #include "conncache.h" -/* SalfordC says "A structure member may not be volatile". Hence: - */ -#ifdef __SALFORDC__ -#define CURL_VOLATILE -#else -#define CURL_VOLATILE volatile -#endif - #define CURL_GOOD_SHARE 0x7e117a1e #define GOOD_SHARE_HANDLE(x) ((x) && (x)->magic == CURL_GOOD_SHARE) -/* this struct is libcurl-private, don't export details */ +#define CURL_SHARE_KEEP_CONNECT(s) \ + ((s) && ((s)->specifier & (1<< CURL_LOCK_DATA_CONNECT))) + +/* this struct is libcurl-private, do not export details */ struct Curl_share { unsigned int magic; /* CURL_GOOD_SHARE */ unsigned int specifier; - CURL_VOLATILE unsigned int dirty; + volatile unsigned int dirty; curl_lock_function lockfunc; curl_unlock_function unlockfunc; void *clientdata; - struct conncache conn_cache; + struct cpool cpool; struct Curl_hash hostcache; #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) struct CookieInfo *cookies; diff --git a/deps/curl/lib/sigpipe.h b/deps/curl/lib/sigpipe.h index 48761ad0fde..c57580f434a 100644 --- a/deps/curl/lib/sigpipe.h +++ b/deps/curl/lib/sigpipe.h @@ -25,7 +25,7 @@ ***************************************************************************/ #include "curl_setup.h" -#if defined(HAVE_SIGNAL_H) && defined(HAVE_SIGACTION) && \ +#if defined(HAVE_SIGACTION) && \ (defined(USE_OPENSSL) || defined(USE_MBEDTLS) || defined(USE_WOLFSSL)) #include @@ -35,6 +35,13 @@ struct sigpipe_ignore { }; #define SIGPIPE_VARIABLE(x) struct sigpipe_ignore x +#define SIGPIPE_MEMBER(x) struct sigpipe_ignore x + +static void sigpipe_init(struct sigpipe_ignore *ig) +{ + memset(ig, 0, sizeof(*ig)); + ig->no_signal = TRUE; +} /* * sigpipe_ignore() makes sure we ignore SIGPIPE while running libcurl @@ -70,11 +77,23 @@ static void sigpipe_restore(struct sigpipe_ignore *ig) sigaction(SIGPIPE, &ig->old_pipe_act, NULL); } +static void sigpipe_apply(struct Curl_easy *data, + struct sigpipe_ignore *ig) +{ + if(data->set.no_signal != ig->no_signal) { + sigpipe_restore(ig); + sigpipe_ignore(data, ig); + } +} + #else /* for systems without sigaction */ #define sigpipe_ignore(x,y) Curl_nop_stmt +#define sigpipe_apply(x,y) Curl_nop_stmt +#define sigpipe_init(x) Curl_nop_stmt #define sigpipe_restore(x) Curl_nop_stmt #define SIGPIPE_VARIABLE(x) +#define SIGPIPE_MEMBER(x) bool x #endif #endif /* HEADER_CURL_SIGPIPE_H */ diff --git a/deps/curl/lib/smb.c b/deps/curl/lib/smb.c index afcc99de245..a242fc5c2a2 100644 --- a/deps/curl/lib/smb.c +++ b/deps/curl/lib/smb.c @@ -27,8 +27,10 @@ #if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) -#ifdef WIN32 -#define getpid GetCurrentProcessId +#ifdef _WIN32 +#define Curl_getpid() ((unsigned int)GetCurrentProcessId()) +#else +#define Curl_getpid() ((unsigned int)getpid()) #endif #include "smb.h" @@ -44,7 +46,8 @@ #include "escape.h" #include "curl_endian.h" -/* The last #include files should be: */ +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" @@ -259,7 +262,7 @@ static CURLcode smb_parse_url_path(struct Curl_easy *data, * SMB handler interface */ const struct Curl_handler Curl_handler_smb = { - "SMB", /* scheme */ + "smb", /* scheme */ smb_setup_connection, /* setup_connection */ smb_do, /* do_it */ ZERO_NULL, /* done */ @@ -272,7 +275,8 @@ const struct Curl_handler Curl_handler_smb = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ smb_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMB, /* defport */ @@ -286,7 +290,7 @@ const struct Curl_handler Curl_handler_smb = { * SMBS handler interface */ const struct Curl_handler Curl_handler_smbs = { - "SMBS", /* scheme */ + "smbs", /* scheme */ smb_setup_connection, /* setup_connection */ smb_do, /* do_it */ ZERO_NULL, /* done */ @@ -299,7 +303,8 @@ const struct Curl_handler Curl_handler_smbs = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ smb_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMBS, /* defport */ @@ -314,20 +319,6 @@ const struct Curl_handler Curl_handler_smbs = { #define CLIENTNAME "curl" #define SERVICENAME "?????" -/* Append a string to an SMB message */ -#define MSGCAT(str) \ - do { \ - strcpy(p, (str)); \ - p += strlen(str); \ - } while(0) - -/* Append a null-terminated string to an SMB message */ -#define MSGCATNULL(str) \ - do { \ - strcpy(p, (str)); \ - p += strlen(str) + 1; \ - } while(0) - /* SMB is mostly little endian */ #if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \ defined(__OS400__) @@ -456,6 +447,9 @@ static CURLcode smb_connect(struct Curl_easy *data, bool *done) smbc->recv_buf = malloc(MAX_MESSAGE_SIZE); if(!smbc->recv_buf) return CURLE_OUT_OF_MEMORY; + smbc->send_buf = malloc(MAX_MESSAGE_SIZE); + if(!smbc->send_buf) + return CURLE_OUT_OF_MEMORY; /* Multiple requests are allowed with this connection */ connkeep(conn, "SMB default"); @@ -485,7 +479,6 @@ static CURLcode smb_connect(struct Curl_easy *data, bool *done) static CURLcode smb_recv_message(struct Curl_easy *data, void **msg) { struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; struct smb_conn *smbc = &conn->proto.smbc; char *buf = smbc->recv_buf; ssize_t bytes_read; @@ -494,7 +487,7 @@ static CURLcode smb_recv_message(struct Curl_easy *data, void **msg) size_t len = MAX_MESSAGE_SIZE - smbc->got; CURLcode result; - result = Curl_read(data, sockfd, buf + smbc->got, len, &bytes_read); + result = Curl_xfer_recv(data, buf + smbc->got, len, &bytes_read); if(result) return result; @@ -555,21 +548,20 @@ static void smb_format_message(struct Curl_easy *data, struct smb_header *h, h->flags2 = smb_swap16(SMB_FLAGS2_IS_LONG_NAME | SMB_FLAGS2_KNOWS_LONG_NAME); h->uid = smb_swap16(smbc->uid); h->tid = smb_swap16(req->tid); - pid = getpid(); + pid = Curl_getpid(); h->pid_high = smb_swap16((unsigned short)(pid >> 16)); h->pid = smb_swap16((unsigned short) pid); } -static CURLcode smb_send(struct Curl_easy *data, ssize_t len, +static CURLcode smb_send(struct Curl_easy *data, size_t len, size_t upload_size) { struct connectdata *conn = data->conn; struct smb_conn *smbc = &conn->proto.smbc; - ssize_t bytes_written; + size_t bytes_written; CURLcode result; - result = Curl_nwrite(data, FIRSTSOCKET, data->state.ulbuf, - len, &bytes_written); + result = Curl_xfer_send(data, smbc->send_buf, len, FALSE, &bytes_written); if(result) return result; @@ -587,16 +579,15 @@ static CURLcode smb_flush(struct Curl_easy *data) { struct connectdata *conn = data->conn; struct smb_conn *smbc = &conn->proto.smbc; - ssize_t bytes_written; - ssize_t len = smbc->send_size - smbc->sent; + size_t bytes_written; + size_t len = smbc->send_size - smbc->sent; CURLcode result; if(!smbc->send_size) return CURLE_OK; - result = Curl_nwrite(data, FIRSTSOCKET, - data->state.ulbuf + smbc->sent, - len, &bytes_written); + result = Curl_xfer_send(data, smbc->send_buf + smbc->sent, len, FALSE, + &bytes_written); if(result) return result; @@ -611,13 +602,13 @@ static CURLcode smb_flush(struct Curl_easy *data) static CURLcode smb_send_message(struct Curl_easy *data, unsigned char cmd, const void *msg, size_t msg_len) { - CURLcode result = Curl_get_upload_buffer(data); - if(result) - return result; - smb_format_message(data, (struct smb_header *)data->state.ulbuf, + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; + + smb_format_message(data, (struct smb_header *)smbc->send_buf, cmd, msg_len); - memcpy(data->state.ulbuf + sizeof(struct smb_header), - msg, msg_len); + DEBUGASSERT((sizeof(struct smb_header) + msg_len) <= MAX_MESSAGE_SIZE); + memcpy(smbc->send_buf + sizeof(struct smb_header), msg, msg_len); return smb_send(data, sizeof(struct smb_header) + msg_len, 0); } @@ -640,9 +631,9 @@ static CURLcode smb_send_setup(struct Curl_easy *data) unsigned char nt_hash[21]; unsigned char nt[24]; - size_t byte_count = sizeof(lm) + sizeof(nt); - byte_count += strlen(smbc->user) + strlen(smbc->domain); - byte_count += strlen(OS) + strlen(CLIENTNAME) + 4; /* 4 null chars */ + const size_t byte_count = sizeof(lm) + sizeof(nt) + + strlen(smbc->user) + strlen(smbc->domain) + + strlen(CURL_OS) + strlen(CLIENTNAME) + 4; /* 4 null chars */ if(byte_count > sizeof(msg.bytes)) return CURLE_FILESIZE_EXCEEDED; @@ -651,7 +642,7 @@ static CURLcode smb_send_setup(struct Curl_easy *data) Curl_ntlm_core_mk_nt_hash(conn->passwd, nt_hash); Curl_ntlm_core_lm_resp(nt_hash, smbc->challenge, nt); - memset(&msg, 0, sizeof(msg)); + memset(&msg, 0, sizeof(msg) - sizeof(msg.bytes)); msg.word_count = SMB_WC_SETUP_ANDX; msg.andx.command = SMB_COM_NO_ANDX_COMMAND; msg.max_buffer_size = smb_swap16(MAX_MESSAGE_SIZE); @@ -665,11 +656,14 @@ static CURLcode smb_send_setup(struct Curl_easy *data) p += sizeof(lm); memcpy(p, nt, sizeof(nt)); p += sizeof(nt); - MSGCATNULL(smbc->user); - MSGCATNULL(smbc->domain); - MSGCATNULL(OS); - MSGCATNULL(CLIENTNAME); - byte_count = p - msg.bytes; + p += msnprintf(p, byte_count - sizeof(nt) - sizeof(lm), + "%s%c" /* user */ + "%s%c" /* domain */ + "%s%c" /* OS */ + "%s", /* client name */ + smbc->user, 0, smbc->domain, 0, CURL_OS, 0, CLIENTNAME); + p++; /* count the final null termination */ + DEBUGASSERT(byte_count == (size_t)(p - msg.bytes)); msg.byte_count = smb_swap16((unsigned short)byte_count); return smb_send_message(data, SMB_COM_SETUP_ANDX, &msg, @@ -683,21 +677,23 @@ static CURLcode smb_send_tree_connect(struct Curl_easy *data) struct smb_conn *smbc = &conn->proto.smbc; char *p = msg.bytes; - size_t byte_count = strlen(conn->host.name) + strlen(smbc->share); - byte_count += strlen(SERVICENAME) + 5; /* 2 nulls and 3 backslashes */ + const size_t byte_count = strlen(conn->host.name) + strlen(smbc->share) + + strlen(SERVICENAME) + 5; /* 2 nulls and 3 backslashes */ if(byte_count > sizeof(msg.bytes)) return CURLE_FILESIZE_EXCEEDED; - memset(&msg, 0, sizeof(msg)); + memset(&msg, 0, sizeof(msg) - sizeof(msg.bytes)); msg.word_count = SMB_WC_TREE_CONNECT_ANDX; msg.andx.command = SMB_COM_NO_ANDX_COMMAND; msg.pw_len = 0; - MSGCAT("\\\\"); - MSGCAT(conn->host.name); - MSGCAT("\\"); - MSGCATNULL(smbc->share); - MSGCATNULL(SERVICENAME); /* Match any type of service */ - byte_count = p - msg.bytes; + + p += msnprintf(p, byte_count, + "\\\\%s\\" /* hostname */ + "%s%c" /* share */ + "%s", /* service */ + conn->host.name, smbc->share, 0, SERVICENAME); + p++; /* count the final null termination */ + DEBUGASSERT(byte_count == (size_t)(p - msg.bytes)); msg.byte_count = smb_swap16((unsigned short)byte_count); return smb_send_message(data, SMB_COM_TREE_CONNECT_ANDX, &msg, @@ -708,16 +704,15 @@ static CURLcode smb_send_open(struct Curl_easy *data) { struct smb_request *req = data->req.p.smb; struct smb_nt_create msg; - size_t byte_count; + const size_t byte_count = strlen(req->path) + 1; - if((strlen(req->path) + 1) > sizeof(msg.bytes)) + if(byte_count > sizeof(msg.bytes)) return CURLE_FILESIZE_EXCEEDED; - memset(&msg, 0, sizeof(msg)); + memset(&msg, 0, sizeof(msg) - sizeof(msg.bytes)); msg.word_count = SMB_WC_NT_CREATE_ANDX; msg.andx.command = SMB_COM_NO_ANDX_COMMAND; - byte_count = strlen(req->path); - msg.name_length = smb_swap16((unsigned short)byte_count); + msg.name_length = smb_swap16((unsigned short)(byte_count - 1)); msg.share_access = smb_swap32(SMB_FILE_SHARE_ALL); if(data->state.upload) { msg.access = smb_swap32(SMB_GENERIC_READ | SMB_GENERIC_WRITE); @@ -727,7 +722,7 @@ static CURLcode smb_send_open(struct Curl_easy *data) msg.access = smb_swap32(SMB_GENERIC_READ); msg.create_disposition = smb_swap32(SMB_FILE_OPEN); } - msg.byte_count = smb_swap16((unsigned short) ++byte_count); + msg.byte_count = smb_swap16((unsigned short) byte_count); strcpy(msg.bytes, req->path); return smb_send_message(data, SMB_COM_NT_CREATE_ANDX, &msg, @@ -775,15 +770,14 @@ static CURLcode smb_send_read(struct Curl_easy *data) static CURLcode smb_send_write(struct Curl_easy *data) { + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; struct smb_write *msg; struct smb_request *req = data->req.p.smb; curl_off_t offset = data->req.offset; curl_off_t upload_size = data->req.size - data->req.bytecount; - CURLcode result = Curl_get_upload_buffer(data); - if(result) - return result; - msg = (struct smb_write *)data->state.ulbuf; + msg = (struct smb_write *)smbc->send_buf; if(upload_size >= MAX_PAYLOAD_SIZE - 1) /* There is one byte of padding */ upload_size = MAX_PAYLOAD_SIZE - 1; @@ -812,10 +806,11 @@ static CURLcode smb_send_and_recv(struct Curl_easy *data, void **msg) /* Check if there is data in the transfer buffer */ if(!smbc->send_size && smbc->upload_size) { - size_t nread = smbc->upload_size > (size_t)data->set.upload_buffer_size ? - (size_t)data->set.upload_buffer_size : smbc->upload_size; - data->req.upload_fromhere = data->state.ulbuf; - result = Curl_fillreadbuffer(data, nread, &nread); + size_t nread = smbc->upload_size > (size_t)MAX_MESSAGE_SIZE ? + (size_t)MAX_MESSAGE_SIZE : smbc->upload_size; + bool eos; + + result = Curl_client_read(data, smbc->send_buf, nread, &nread, &eos); if(result && result != CURLE_AGAIN) return result; if(!nread) @@ -907,7 +902,7 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) } smbc->uid = smb_swap16(h->uid); conn_state(data, SMB_CONNECTED); - *done = true; + *done = TRUE; break; default: @@ -922,7 +917,7 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) /* * Convert a timestamp from the Windows world (100 nsec units from 1 Jan 1601) - * to Posix time. Cap the output to fit within a time_t. + * to POSIX time. Cap the output to fit within a time_t. */ static void get_posix_time(time_t *out, curl_off_t timestamp) { @@ -1047,9 +1042,7 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) break; } } - data->req.bytecount += len; data->req.offset += len; - Curl_pgrsSetDownloadCounter(data, data->req.bytecount); next_state = (len < MAX_PAYLOAD_SIZE) ? SMB_CLOSE : SMB_DOWNLOAD; break; @@ -1071,7 +1064,7 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) break; case SMB_CLOSE: - /* We don't care if the close failed, proceed to tree disconnect anyway */ + /* We do not care if the close failed, proceed to tree disconnect anyway */ next_state = SMB_TREE_DISCONNECT; break; @@ -1109,7 +1102,7 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) case SMB_DONE: result = req->result; - *done = true; + *done = TRUE; break; default: @@ -1135,6 +1128,7 @@ static CURLcode smb_disconnect(struct Curl_easy *data, Curl_safefree(smbc->share); Curl_safefree(smbc->domain); Curl_safefree(smbc->recv_buf); + Curl_safefree(smbc->send_buf); return CURLE_OK; } diff --git a/deps/curl/lib/smb.h b/deps/curl/lib/smb.h index 437f4a58a85..9ea2a8cc31c 100644 --- a/deps/curl/lib/smb.h +++ b/deps/curl/lib/smb.h @@ -42,6 +42,7 @@ struct smb_conn { unsigned int session_key; unsigned short uid; char *recv_buf; + char *send_buf; size_t upload_size; size_t send_size; size_t sent; diff --git a/deps/curl/lib/smtp.c b/deps/curl/lib/smtp.c index 81a17e38db4..d854d364f85 100644 --- a/deps/curl/lib/smtp.c +++ b/deps/curl/lib/smtp.c @@ -111,13 +111,14 @@ static CURLcode smtp_continue_auth(struct Curl_easy *data, const char *mech, const struct bufref *resp); static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech); static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out); +static CURLcode cr_eob_add(struct Curl_easy *data); /* * SMTP protocol handler. */ const struct Curl_handler Curl_handler_smtp = { - "SMTP", /* scheme */ + "smtp", /* scheme */ smtp_setup_connection, /* setup_connection */ smtp_do, /* do_it */ smtp_done, /* done */ @@ -130,7 +131,8 @@ const struct Curl_handler Curl_handler_smtp = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ smtp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMTP, /* defport */ @@ -146,7 +148,7 @@ const struct Curl_handler Curl_handler_smtp = { */ const struct Curl_handler Curl_handler_smtps = { - "SMTPS", /* scheme */ + "smtps", /* scheme */ smtp_setup_connection, /* setup_connection */ smtp_do, /* do_it */ smtp_done, /* done */ @@ -159,7 +161,8 @@ const struct Curl_handler Curl_handler_smtps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ smtp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMTPS, /* defport */ @@ -250,8 +253,8 @@ static bool smtp_endofresp(struct Curl_easy *data, struct connectdata *conn, */ static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out) { - char *message = data->state.buffer; - size_t len = strlen(message); + char *message = Curl_dyn_ptr(&data->conn->proto.smtpc.pp.recvbuf); + size_t len = data->conn->proto.smtpc.pp.nfinal; if(len > 4) { /* Find the start of the message */ @@ -285,7 +288,7 @@ static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out) static void smtp_state(struct Curl_easy *data, smtpstate newstate) { struct smtp_conn *smtpc = &data->conn->proto.smtpc; -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char * const names[] = { "STOP", @@ -305,8 +308,8 @@ static void smtp_state(struct Curl_easy *data, smtpstate newstate) }; if(smtpc->state != newstate) - infof(data, "SMTP %p state change from %s to %s", - (void *)smtpc, names[smtpc->state], names[newstate]); + CURL_TRC_SMTP(data, "state change from %s to %s", + names[smtpc->state], names[newstate]); #endif smtpc->state = newstate; @@ -531,16 +534,16 @@ static CURLcode smtp_perform_command(struct Curl_easy *data) if(smtp->rcpt) { /* We notify the server we are sending UTF-8 data if a) it supports the SMTPUTF8 extension and b) The mailbox contains UTF-8 characters, in - either the local address or host name parts. This is regardless of - whether the host name is encoded using IDN ACE */ + either the local address or hostname parts. This is regardless of + whether the hostname is encoded using IDN ACE */ bool utf8 = FALSE; if((!smtp->custom) || (!smtp->custom[0])) { char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; - /* Parse the mailbox to verify into the local address and host name - parts, converting the host name to an IDN A-label if necessary */ + /* Parse the mailbox to verify into the local address and hostname + parts, converting the hostname to an IDN A-label if necessary */ result = smtp_parse_address(smtp->rcpt->data, &address, &host); if(result) @@ -552,7 +555,7 @@ static CURLcode smtp_perform_command(struct Curl_easy *data) ((host.encalloc) || (!Curl_is_ASCII_name(address)) || (!Curl_is_ASCII_name(host.name))); - /* Send the VRFY command (Note: The host name part may be absent when the + /* Send the VRFY command (Note: The hostname part may be absent when the host is a local system) */ result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "VRFY %s%s%s%s", address, @@ -604,8 +607,8 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) /* We notify the server we are sending UTF-8 data if a) it supports the SMTPUTF8 extension and b) The mailbox contains UTF-8 characters, in - either the local address or host name parts. This is regardless of - whether the host name is encoded using IDN ACE */ + either the local address or hostname parts. This is regardless of + whether the hostname is encoded using IDN ACE */ bool utf8 = FALSE; /* Calculate the FROM parameter */ @@ -613,12 +616,12 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; - /* Parse the FROM mailbox into the local address and host name parts, - converting the host name to an IDN A-label if necessary */ + /* Parse the FROM mailbox into the local address and hostname parts, + converting the hostname to an IDN A-label if necessary */ result = smtp_parse_address(data->set.str[STRING_MAIL_FROM], &address, &host); if(result) - return result; + goto out; /* Establish whether we should report SMTPUTF8 to the server for this mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */ @@ -632,8 +635,8 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) Curl_free_idnconverted_hostname(&host); } else - /* An invalid mailbox was provided but we'll simply let the server worry - about that and reply with a 501 error */ + /* An invalid mailbox was provided but we will simply let the server + worry about that and reply with a 501 error */ from = aprintf("<%s>", address); free(address); @@ -642,8 +645,10 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) /* Null reverse-path, RFC-5321, sect. 3.6.3 */ from = strdup("<>"); - if(!from) - return CURLE_OUT_OF_MEMORY; + if(!from) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } /* Calculate the optional AUTH parameter */ if(data->set.str[STRING_MAIL_AUTH] && conn->proto.smtpc.sasl.authused) { @@ -651,14 +656,12 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; - /* Parse the AUTH mailbox into the local address and host name parts, - converting the host name to an IDN A-label if necessary */ + /* Parse the AUTH mailbox into the local address and hostname parts, + converting the hostname to an IDN A-label if necessary */ result = smtp_parse_address(data->set.str[STRING_MAIL_AUTH], &address, &host); - if(result) { - free(from); - return result; - } + if(result) + goto out; /* Establish whether we should report SMTPUTF8 to the server for this mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */ @@ -673,10 +676,9 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) Curl_free_idnconverted_hostname(&host); } else - /* An invalid mailbox was provided but we'll simply let the server + /* An invalid mailbox was provided but we will simply let the server worry about it */ auth = aprintf("<%s>", address); - free(address); } else @@ -684,16 +686,16 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) auth = strdup("<>"); if(!auth) { - free(from); - - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto out; } } +#ifndef CURL_DISABLE_MIME /* Prepare the mime data if some. */ if(data->set.mimepost.kind != MIMEKIND_NONE) { /* Use the whole structure as data. */ - data->set.mimepost.flags &= ~MIME_BODY_ONLY; + data->set.mimepost.flags &= ~(unsigned int)MIME_BODY_ONLY; /* Add external headers and mime version. */ curl_mime_headers(&data->set.mimepost, data->set.headers, 0); @@ -705,37 +707,31 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) result = Curl_mime_add_header(&data->set.mimepost.curlheaders, "Mime-Version: 1.0"); - /* Make sure we will read the entire mime structure. */ if(!result) - result = Curl_mime_rewind(&data->set.mimepost); - - if(result) { - free(from); - free(auth); - - return result; - } - - data->state.infilesize = Curl_mime_size(&data->set.mimepost); - - /* Read from mime structure. */ - data->state.fread_func = (curl_read_callback) Curl_mime_read; - data->state.in = (void *) &data->set.mimepost; + result = Curl_creader_set_mime(data, &data->set.mimepost); + if(result) + goto out; + data->state.infilesize = Curl_creader_total_length(data); + } + else +#endif + { + result = Curl_creader_set_fread(data, data->state.infilesize); + if(result) + goto out; } /* Calculate the optional SIZE parameter */ if(conn->proto.smtpc.size_supported && data->state.infilesize > 0) { - size = aprintf("%" CURL_FORMAT_CURL_OFF_T, data->state.infilesize); + size = aprintf("%" FMT_OFF_T, data->state.infilesize); if(!size) { - free(from); - free(auth); - - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto out; } } - /* If the mailboxes in the FROM and AUTH parameters don't include a UTF-8 + /* If the mailboxes in the FROM and AUTH parameters do not include a UTF-8 based address then quickly scan through the recipient list and check if any there do, as we need to correctly identify our support for SMTPUTF8 in the envelope, as per RFC-6531 sect. 3.4 */ @@ -744,7 +740,7 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) struct curl_slist *rcpt = smtp->rcpt; while(rcpt && !utf8) { - /* Does the host name contain non-ASCII characters? */ + /* Does the hostname contain non-ASCII characters? */ if(!Curl_is_ASCII_name(rcpt->data)) utf8 = TRUE; @@ -752,6 +748,11 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) } } + /* Add the client reader doing STMP EOB escaping */ + result = cr_eob_add(data); + if(result) + goto out; + /* Send the MAIL command */ result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "MAIL FROM:%s%s%s%s%s%s", @@ -763,6 +764,7 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) utf8 ? " SMTPUTF8" /* Internationalised mailbox */ : ""); /* included in our envelope */ +out: free(from); free(auth); free(size); @@ -788,8 +790,8 @@ static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data) char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; - /* Parse the recipient mailbox into the local address and host name parts, - converting the host name to an IDN A-label if necessary */ + /* Parse the recipient mailbox into the local address and hostname parts, + converting the hostname to an IDN A-label if necessary */ result = smtp_parse_address(smtp->rcpt->data, &address, &host); if(result) @@ -800,7 +802,7 @@ static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data) result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "RCPT TO:<%s@%s>", address, host.name); else - /* An invalid mailbox was provided but we'll simply let the server worry + /* An invalid mailbox was provided but we will simply let the server worry about that and reply with a 501 error */ result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "RCPT TO:<%s>", address); @@ -859,7 +861,7 @@ static CURLcode smtp_state_starttls_resp(struct Curl_easy *data, (void)instate; /* no use for this yet */ /* Pipelining in response is forbidden. */ - if(data->conn->proto.smtpc.pp.cache_size) + if(data->conn->proto.smtpc.pp.overflow) return CURLE_WEIRD_SERVER_REPLY; if(smtpcode != 220) { @@ -883,8 +885,8 @@ static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data, { CURLcode result = CURLE_OK; struct smtp_conn *smtpc = &conn->proto.smtpc; - const char *line = data->state.buffer; - size_t len = strlen(line); + const char *line = Curl_dyn_ptr(&smtpc->pp.recvbuf); + size_t len = smtpc->pp.nfinal; (void)instate; /* no use for this yet */ @@ -956,7 +958,7 @@ static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data, if(smtpcode != 1) { if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - /* We don't have a SSL/TLS connection yet, but SSL is requested */ + /* We do not have a SSL/TLS connection yet, but SSL is requested */ if(smtpc->tls_supported) /* Switch to TLS connection now */ result = smtp_perform_starttls(data, conn); @@ -1033,8 +1035,8 @@ static CURLcode smtp_state_command_resp(struct Curl_easy *data, int smtpcode, { CURLcode result = CURLE_OK; struct SMTP *smtp = data->req.p.smtp; - char *line = data->state.buffer; - size_t len = strlen(line); + char *line = Curl_dyn_ptr(&data->conn->proto.smtpc.pp.recvbuf); + size_t len = data->conn->proto.smtpc.pp.nfinal; (void)instate; /* no use for this yet */ @@ -1044,12 +1046,8 @@ static CURLcode smtp_state_command_resp(struct Curl_easy *data, int smtpcode, result = CURLE_WEIRD_SERVER_REPLY; } else { - /* Temporarily add the LF character back and send as body to the client */ - if(!data->req.no_body) { - line[len] = '\n'; - result = Curl_client_write(data, CLIENTWRITE_BODY, line, len + 1); - line[len] = '\0'; - } + if(!data->req.no_body) + result = Curl_client_write(data, CLIENTWRITE_BODY, line, len); if(smtpcode != 1) { if(smtp->rcpt) { @@ -1102,12 +1100,11 @@ static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, (void)instate; /* no use for this yet */ - is_smtp_err = (smtpcode/100 != 2) ? TRUE : FALSE; + is_smtp_err = (smtpcode/100 != 2); - /* If there's multiple RCPT TO to be issued, it's possible to ignore errors + /* If there is multiple RCPT TO to be issued, it is possible to ignore errors and proceed with only the valid addresses. */ - is_smtp_blocking_err = - (is_smtp_err && !data->set.mail_rcpt_allowfails) ? TRUE : FALSE; + is_smtp_blocking_err = (is_smtp_err && !data->set.mail_rcpt_allowfails); if(is_smtp_err) { /* Remembering the last failure which we can report if all "RCPT TO" have @@ -1131,7 +1128,7 @@ static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, /* Send the next RCPT TO command */ result = smtp_perform_rcpt_to(data); else { - /* We weren't able to issue a successful RCPT TO command while going + /* We were not able to issue a successful RCPT TO command while going over recipients (potentially multiple). Sending back last error. */ if(!smtp->rcpt_had_ok) { failf(data, "RCPT failed: %d (last error)", smtp->rcpt_last_error); @@ -1166,7 +1163,7 @@ static CURLcode smtp_state_data_resp(struct Curl_easy *data, int smtpcode, Curl_pgrsSetUploadSize(data, data->state.infilesize); /* SMTP upload */ - Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); /* End of DO phase */ smtp_state(data, SMTP_STOP); @@ -1198,13 +1195,13 @@ static CURLcode smtp_statemachine(struct Curl_easy *data, struct connectdata *conn) { CURLcode result = CURLE_OK; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; int smtpcode; struct smtp_conn *smtpc = &conn->proto.smtpc; struct pingpong *pp = &smtpc->pp; size_t nread = 0; /* Busy upgrading the connection; right now all I/O is SSL/TLS, not SMTP */ +upgrade_tls: if(smtpc->state == SMTP_UPGRADETLS) return smtp_perform_upgrade_tls(data); @@ -1214,7 +1211,7 @@ static CURLcode smtp_statemachine(struct Curl_easy *data, do { /* Read the response from the server */ - result = Curl_pp_readresp(data, sock, pp, &smtpcode, &nread); + result = Curl_pp_readresp(data, FIRSTSOCKET, pp, &smtpcode, &nread); if(result) return result; @@ -1241,6 +1238,10 @@ static CURLcode smtp_statemachine(struct Curl_easy *data, case SMTP_STARTTLS: result = smtp_state_starttls_resp(data, smtpcode, smtpc->state); + /* During UPGRADETLS, leave the read loop as we need to connect + * (e.g. TLS handshake) before we continue sending/receiving. */ + if(!result && (smtpc->state == SMTP_UPGRADETLS)) + goto upgrade_tls; break; case SMTP_AUTH: @@ -1268,7 +1269,6 @@ static CURLcode smtp_statemachine(struct Curl_easy *data, break; case SMTP_QUIT: - /* fallthrough, just stop! */ default: /* internal error */ smtp_state(data, SMTP_STOP); @@ -1295,7 +1295,7 @@ static CURLcode smtp_multi_statemach(struct Curl_easy *data, bool *done) } result = Curl_pp_statemach(data, &smtpc->pp, FALSE, FALSE); - *done = (smtpc->state == SMTP_STOP) ? TRUE : FALSE; + *done = (smtpc->state == SMTP_STOP); return result; } @@ -1320,7 +1320,7 @@ static CURLcode smtp_init(struct Curl_easy *data) CURLcode result = CURLE_OK; struct SMTP *smtp; - smtp = data->req.p.smtp = calloc(sizeof(struct SMTP), 1); + smtp = data->req.p.smtp = calloc(1, sizeof(struct SMTP)); if(!smtp) result = CURLE_OUT_OF_MEMORY; @@ -1362,8 +1362,7 @@ static CURLcode smtp_connect(struct Curl_easy *data, bool *done) Curl_sasl_init(&smtpc->sasl, data, &saslsmtp); /* Initialise the pingpong layer */ - Curl_pp_setup(pp); - Curl_pp_init(data, pp); + Curl_pp_init(pp); /* Parse the URL options */ result = smtp_parse_url_options(conn); @@ -1398,10 +1397,6 @@ static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; struct SMTP *smtp = data->req.p.smtp; - struct pingpong *pp = &conn->proto.smtpc.pp; - char *eob; - ssize_t len; - ssize_t bytes_written; (void)premature; @@ -1416,47 +1411,7 @@ static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, result = status; /* use the already set error code */ } else if(!data->set.connect_only && data->set.mail_rcpt && - (data->state.upload || data->set.mimepost.kind)) { - /* Calculate the EOB taking into account any terminating CRLF from the - previous line of the email or the CRLF of the DATA command when there - is "no mail data". RFC-5321, sect. 4.1.1.4. - - Note: As some SSL backends, such as OpenSSL, will cause Curl_write() to - fail when using a different pointer following a previous write, that - returned CURLE_AGAIN, we duplicate the EOB now rather than when the - bytes written doesn't equal len. */ - if(smtp->trailing_crlf || !data->state.infilesize) { - eob = strdup(&SMTP_EOB[2]); - len = SMTP_EOB_LEN - 2; - } - else { - eob = strdup(SMTP_EOB); - len = SMTP_EOB_LEN; - } - - if(!eob) - return CURLE_OUT_OF_MEMORY; - - /* Send the end of block data */ - result = Curl_write(data, conn->writesockfd, eob, len, &bytes_written); - if(result) { - free(eob); - return result; - } - - if(bytes_written != len) { - /* The whole chunk was not sent so keep it around and adjust the - pingpong structure accordingly */ - pp->sendthis = eob; - pp->sendsize = len; - pp->sendleft = len - bytes_written; - } - else { - /* Successfully sent so adjust the response timeout relative to now */ - pp->response = Curl_now(); - - free(eob); - } + (data->state.upload || IS_MIME_POST(data))) { smtp_state(data, SMTP_POSTDATA); @@ -1466,7 +1421,8 @@ static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, /* Clear the transfer mode for the next request */ smtp->transfer = PPTRANSFER_BODY; - + CURL_TRC_SMTP(data, "smtp_done(status=%d, premature=%d) -> %d", + status, premature, result); return result; } @@ -1484,7 +1440,7 @@ static CURLcode smtp_perform(struct Curl_easy *data, bool *connected, CURLcode result = CURLE_OK; struct SMTP *smtp = data->req.p.smtp; - DEBUGF(infof(data, "DO phase starts")); + CURL_TRC_SMTP(data, "smtp_perform(), start"); if(data->req.no_body) { /* Requested no body means no transfer */ @@ -1496,10 +1452,10 @@ static CURLcode smtp_perform(struct Curl_easy *data, bool *connected, /* Store the first recipient (or NULL if not specified) */ smtp->rcpt = data->set.mail_rcpt; - /* Track of whether we've successfully sent at least one RCPT TO command */ + /* Track of whether we have successfully sent at least one RCPT TO command */ smtp->rcpt_had_ok = FALSE; - /* Track of the last error we've received by sending RCPT TO command */ + /* Track of the last error we have received by sending RCPT TO command */ smtp->rcpt_last_error = 0; /* Initial data character is the first character in line: it is implicitly @@ -1508,7 +1464,7 @@ static CURLcode smtp_perform(struct Curl_easy *data, bool *connected, smtp->eob = 2; /* Start the first command in the DO phase */ - if((data->state.upload || data->set.mimepost.kind) && data->set.mail_rcpt) + if((data->state.upload || IS_MIME_POST(data)) && data->set.mail_rcpt) /* MAIL transfer */ result = smtp_perform_mail(data); else @@ -1516,16 +1472,16 @@ static CURLcode smtp_perform(struct Curl_easy *data, bool *connected, result = smtp_perform_command(data); if(result) - return result; + goto out; /* Run the state-machine */ result = smtp_multi_statemach(data, dophase_done); *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET); - if(*dophase_done) - DEBUGF(infof(data, "DO phase is complete")); - +out: + CURL_TRC_SMTP(data, "smtp_perform() -> %d, connected=%d, done=%d", + result, *connected, *dophase_done); return result; } @@ -1541,6 +1497,8 @@ static CURLcode smtp_perform(struct Curl_easy *data, bool *connected, static CURLcode smtp_do(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; + DEBUGASSERT(data); + DEBUGASSERT(data->conn); *done = FALSE; /* default to false */ /* Parse the custom request */ @@ -1549,7 +1507,7 @@ static CURLcode smtp_do(struct Curl_easy *data, bool *done) return result; result = smtp_regular_transfer(data, done); - + CURL_TRC_SMTP(data, "smtp_do() -> %d, done=%d", result, *done); return result; } @@ -1584,6 +1542,7 @@ static CURLcode smtp_disconnect(struct Curl_easy *data, /* Cleanup our connection based variables */ Curl_safefree(smtpc->domain); + CURL_TRC_SMTP(data, "smtp_disconnect(), finished"); return CURLE_OK; } @@ -1597,7 +1556,7 @@ static CURLcode smtp_dophase_done(struct Curl_easy *data, bool connected) if(smtp->transfer != PPTRANSFER_BODY) /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); return CURLE_OK; } @@ -1615,6 +1574,7 @@ static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done) DEBUGF(infof(data, "DO phase is complete")); } + CURL_TRC_SMTP(data, "smtp_doing() -> %d, done=%d", result, *dophase_done); return result; } @@ -1649,6 +1609,8 @@ static CURLcode smtp_regular_transfer(struct Curl_easy *data, if(!result && *dophase_done) result = smtp_dophase_done(data, connected); + CURL_TRC_SMTP(data, "smtp_regular_transfer() -> %d, done=%d", + result, *dophase_done); return result; } @@ -1662,10 +1624,8 @@ static CURLcode smtp_setup_connection(struct Curl_easy *data, /* Initialise the SMTP layer */ result = smtp_init(data); - if(result) - return result; - - return CURLE_OK; + CURL_TRC_SMTP(data, "smtp_setup_connection() -> %d", result); + return result; } /*********************************************************************** @@ -1755,7 +1715,7 @@ static CURLcode smtp_parse_custom_request(struct Curl_easy *data) * smtp_parse_address() * * Parse the fully qualified mailbox address into a local address part and the - * host name, converting the host name to an IDN A-label, as per RFC-5890, if + * hostname, converting the hostname to an IDN A-label, as per RFC-5890, if * necessary. * * Parameters: @@ -1766,8 +1726,8 @@ static CURLcode smtp_parse_custom_request(struct Curl_easy *data) * address [in/out] - A new allocated buffer which holds the local * address part of the mailbox. This buffer must be * free'ed by the caller. - * host [in/out] - The host name structure that holds the original, - * and optionally encoded, host name. + * host [in/out] - The hostname structure that holds the original, + * and optionally encoded, hostname. * Curl_free_idnconverted_hostname() must be called * once the caller has finished with the structure. * @@ -1775,14 +1735,14 @@ static CURLcode smtp_parse_custom_request(struct Curl_easy *data) * * Notes: * - * Should a UTF-8 host name require conversion to IDN ACE and we cannot honor + * Should a UTF-8 hostname require conversion to IDN ACE and we cannot honor * that conversion then we shall return success. This allow the caller to send * the data to the server as a U-label (as per RFC-6531 sect. 3.2). * * If an mailbox '@' separator cannot be located then the mailbox is considered * to be either a local mailbox or an invalid mailbox (depending on what the * calling function deems it to be) then the input will simply be returned in - * the address part with the host name being NULL. + * the address part with the hostname being NULL. */ static CURLcode smtp_parse_address(const char *fqma, char **address, struct hostname *host) @@ -1791,7 +1751,7 @@ static CURLcode smtp_parse_address(const char *fqma, char **address, size_t length; /* Duplicate the fully qualified email address so we can manipulate it, - ensuring it doesn't contain the delimiters if specified */ + ensuring it does not contain the delimiters if specified */ char *dup = strdup(fqma[0] == '<' ? fqma + 1 : fqma); if(!dup) return CURLE_OUT_OF_MEMORY; @@ -1802,17 +1762,17 @@ static CURLcode smtp_parse_address(const char *fqma, char **address, dup[length - 1] = '\0'; } - /* Extract the host name from the address (if we can) */ + /* Extract the hostname from the address (if we can) */ host->name = strpbrk(dup, "@"); if(host->name) { *host->name = '\0'; host->name = host->name + 1; - /* Attempt to convert the host name to IDN ACE */ + /* Attempt to convert the hostname to IDN ACE */ (void) Curl_idnconvert_hostname(host); /* If Curl_idnconvert_hostname() fails then we shall attempt to continue - and send the host name using UTF-8 rather than as 7-bit ACE (which is + and send the hostname using UTF-8 rather than as 7-bit ACE (which is our preference) */ } @@ -1822,108 +1782,174 @@ static CURLcode smtp_parse_address(const char *fqma, char **address, return result; } -CURLcode Curl_smtp_escape_eob(struct Curl_easy *data, - const ssize_t nread, - const ssize_t offset) +struct cr_eob_ctx { + struct Curl_creader super; + struct bufq buf; + size_t n_eob; /* how many EOB bytes we matched so far */ + size_t eob; /* Number of bytes of the EOB (End Of Body) that + have been received so far */ + BIT(read_eos); /* we read an EOS from the next reader */ + BIT(eos); /* we have returned an EOS */ +}; + +static CURLcode cr_eob_init(struct Curl_easy *data, + struct Curl_creader *reader) { - /* When sending a SMTP payload we must detect CRLF. sequences making sure - they are sent as CRLF.. instead, as a . on the beginning of a line will - be deleted by the server when not part of an EOB terminator and a - genuine CRLF.CRLF which isn't escaped will wrongly be detected as end of - data by the server - */ - ssize_t i; - ssize_t si; - struct SMTP *smtp = data->req.p.smtp; - char *scratch = data->state.scratch; - char *newscratch = NULL; - char *oldscratch = NULL; - size_t eob_sent; + struct cr_eob_ctx *ctx = reader->ctx; + (void)data; + /* The first char we read is the first on a line, as if we had + * read CRLF just before */ + ctx->n_eob = 2; + Curl_bufq_init2(&ctx->buf, (16 * 1024), 1, BUFQ_OPT_SOFT_LIMIT); + return CURLE_OK; +} - /* Do we need to allocate a scratch buffer? */ - if(!scratch || data->set.crlf) { - oldscratch = scratch; +static void cr_eob_close(struct Curl_easy *data, struct Curl_creader *reader) +{ + struct cr_eob_ctx *ctx = reader->ctx; + (void)data; + Curl_bufq_free(&ctx->buf); +} - scratch = newscratch = malloc(2 * data->set.upload_buffer_size); - if(!newscratch) { - failf(data, "Failed to alloc scratch buffer"); +/* this is the 5-bytes End-Of-Body marker for SMTP */ +#define SMTP_EOB "\r\n.\r\n" +#define SMTP_EOB_FIND_LEN 3 - return CURLE_OUT_OF_MEMORY; - } - } - DEBUGASSERT((size_t)data->set.upload_buffer_size >= (size_t)nread); - - /* Have we already sent part of the EOB? */ - eob_sent = smtp->eob; - - /* This loop can be improved by some kind of Boyer-Moore style of - approach but that is saved for later... */ - if(offset) - memcpy(scratch, data->req.upload_fromhere, offset); - for(i = offset, si = offset; i < nread; i++) { - if(SMTP_EOB[smtp->eob] == data->req.upload_fromhere[i]) { - smtp->eob++; - - /* Is the EOB potentially the terminating CRLF? */ - if(2 == smtp->eob || SMTP_EOB_LEN == smtp->eob) - smtp->trailing_crlf = TRUE; - else - smtp->trailing_crlf = FALSE; - } - else if(smtp->eob) { - /* A previous substring matched so output that first */ - memcpy(&scratch[si], &SMTP_EOB[eob_sent], smtp->eob - eob_sent); - si += smtp->eob - eob_sent; - - /* Then compare the first byte */ - if(SMTP_EOB[0] == data->req.upload_fromhere[i]) - smtp->eob = 1; - else - smtp->eob = 0; +/* client reader doing SMTP End-Of-Body escaping. */ +static CURLcode cr_eob_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_eob_ctx *ctx = reader->ctx; + CURLcode result = CURLE_OK; + size_t nread, i, start, n; + bool eos; + + if(!ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { + /* Get more and convert it when needed */ + result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos); + if(result) + return result; + + ctx->read_eos = eos; + if(nread) { + if(!ctx->n_eob && !memchr(buf, SMTP_EOB[0], nread)) { + /* not in the middle of a match, no EOB start found, just pass */ + *pnread = nread; + *peos = FALSE; + return CURLE_OK; + } + /* scan for EOB (continuation) and convert */ + for(i = start = 0; i < nread; ++i) { + if(ctx->n_eob >= SMTP_EOB_FIND_LEN) { + /* matched the EOB prefix and seeing additional char, add '.' */ + result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n); + if(result) + return result; + result = Curl_bufq_cwrite(&ctx->buf, ".", 1, &n); + if(result) + return result; + ctx->n_eob = 0; + start = i; + if(data->state.infilesize > 0) + data->state.infilesize++; + } + + if(buf[i] != SMTP_EOB[ctx->n_eob]) + ctx->n_eob = 0; - eob_sent = 0; + if(buf[i] == SMTP_EOB[ctx->n_eob]) { + /* matching another char of the EOB */ + ++ctx->n_eob; + } + } - /* Reset the trailing CRLF flag as there was more data */ - smtp->trailing_crlf = FALSE; + /* add any remainder to buf */ + if(start < nread) { + result = Curl_bufq_cwrite(&ctx->buf, buf + start, nread - start, &n); + if(result) + return result; + } } - /* Do we have a match for CRLF. as per RFC-5321, sect. 4.5.2 */ - if(SMTP_EOB_FIND_LEN == smtp->eob) { - /* Copy the replacement data to the target buffer */ - memcpy(&scratch[si], &SMTP_EOB_REPL[eob_sent], - SMTP_EOB_REPL_LEN - eob_sent); - si += SMTP_EOB_REPL_LEN - eob_sent; - smtp->eob = 0; - eob_sent = 0; + if(ctx->read_eos) { + /* if we last matched a CRLF or if the data was empty, add ".\r\n" + * to end the body. If we sent something and it did not end with "\r\n", + * add "\r\n.\r\n" to end the body */ + const char *eob = SMTP_EOB; + switch(ctx->n_eob) { + case 2: + /* seen a CRLF at the end, just add the remainder */ + eob = &SMTP_EOB[2]; + break; + case 3: + /* ended with '\r\n.', we should escpe the last '.' */ + eob = "." SMTP_EOB; + break; + default: + break; + } + result = Curl_bufq_cwrite(&ctx->buf, eob, strlen(eob), &n); + if(result) + return result; } - else if(!smtp->eob) - scratch[si++] = data->req.upload_fromhere[i]; } - if(smtp->eob - eob_sent) { - /* A substring matched before processing ended so output that now */ - memcpy(&scratch[si], &SMTP_EOB[eob_sent], smtp->eob - eob_sent); - si += smtp->eob - eob_sent; + *peos = FALSE; + if(!Curl_bufq_is_empty(&ctx->buf)) { + result = Curl_bufq_cread(&ctx->buf, buf, blen, pnread); } + else + *pnread = 0; - /* Only use the new buffer if we replaced something */ - if(si != nread) { - /* Upload from the new (replaced) buffer instead */ - data->req.upload_fromhere = scratch; + if(ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { + /* no more data, read all, done. */ + ctx->eos = TRUE; + } + *peos = ctx->eos; + DEBUGF(infof(data, "cr_eob_read(%zu) -> %d, %zd, %d", + blen, result, *pnread, *peos)); + return result; +} - /* Save the buffer so it can be freed later */ - data->state.scratch = scratch; +static curl_off_t cr_eob_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + /* this reader changes length depending on input */ + (void)data; + (void)reader; + return -1; +} - /* Free the old scratch buffer */ - free(oldscratch); +static const struct Curl_crtype cr_eob = { + "cr-smtp-eob", + cr_eob_init, + cr_eob_read, + cr_eob_close, + Curl_creader_def_needs_rewind, + cr_eob_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct cr_eob_ctx) +}; - /* Set the new amount too */ - data->req.upload_present = si; - } - else - free(newscratch); +static CURLcode cr_eob_add(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; - return CURLE_OK; + result = Curl_creader_create(&reader, data, &cr_eob, + CURL_CR_CONTENT_ENCODE); + if(!result) + result = Curl_creader_add(data, reader); + + if(result && reader) + Curl_creader_free(data, reader); + return result; } #endif /* CURL_DISABLE_SMTP */ diff --git a/deps/curl/lib/smtp.h b/deps/curl/lib/smtp.h index 7a04c215499..7c2af680739 100644 --- a/deps/curl/lib/smtp.h +++ b/deps/curl/lib/smtp.h @@ -84,17 +84,4 @@ struct smtp_conn { extern const struct Curl_handler Curl_handler_smtp; extern const struct Curl_handler Curl_handler_smtps; -/* this is the 5-bytes End-Of-Body marker for SMTP */ -#define SMTP_EOB "\x0d\x0a\x2e\x0d\x0a" -#define SMTP_EOB_LEN 5 -#define SMTP_EOB_FIND_LEN 3 - -/* if found in data, replace it with this string instead */ -#define SMTP_EOB_REPL "\x0d\x0a\x2e\x2e" -#define SMTP_EOB_REPL_LEN 4 - -CURLcode Curl_smtp_escape_eob(struct Curl_easy *data, - const ssize_t nread, - const ssize_t offset); - #endif /* HEADER_CURL_SMTP_H */ diff --git a/deps/curl/lib/sockaddr.h b/deps/curl/lib/sockaddr.h index 5a6bb207dc5..2e2d375e063 100644 --- a/deps/curl/lib/sockaddr.h +++ b/deps/curl/lib/sockaddr.h @@ -30,7 +30,7 @@ struct Curl_sockaddr_storage { union { struct sockaddr sa; struct sockaddr_in sa_in; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 sa_in6; #endif #ifdef HAVE_STRUCT_SOCKADDR_STORAGE diff --git a/deps/curl/lib/socketpair.c b/deps/curl/lib/socketpair.c index 963e1406f60..b14f5a5f145 100644 --- a/deps/curl/lib/socketpair.c +++ b/deps/curl/lib/socketpair.c @@ -27,15 +27,82 @@ #include "urldata.h" #include "rand.h" -#if !defined(HAVE_SOCKETPAIR) && !defined(CURL_DISABLE_SOCKETPAIR) -#ifdef WIN32 +#if defined(USE_EVENTFD) +#ifdef HAVE_SYS_EVENTFD_H +#include +#endif + +int Curl_eventfd(curl_socket_t socks[2], bool nonblocking) +{ + int efd = eventfd(0, nonblocking ? EFD_CLOEXEC | EFD_NONBLOCK : EFD_CLOEXEC); + if(efd == -1) { + socks[0] = socks[1] = CURL_SOCKET_BAD; + return -1; + } + socks[0] = socks[1] = efd; + return 0; +} +#elif defined(HAVE_PIPE) +#ifdef HAVE_FCNTL +#include +#endif + +int Curl_pipe(curl_socket_t socks[2], bool nonblocking) +{ + if(pipe(socks)) + return -1; +#ifdef HAVE_FCNTL + if(fcntl(socks[0], F_SETFD, FD_CLOEXEC) || + fcntl(socks[1], F_SETFD, FD_CLOEXEC) ) { + close(socks[0]); + close(socks[1]); + socks[0] = socks[1] = CURL_SOCKET_BAD; + return -1; + } +#endif + if(nonblocking) { + if(curlx_nonblock(socks[0], TRUE) < 0 || + curlx_nonblock(socks[1], TRUE) < 0) { + close(socks[0]); + close(socks[1]); + socks[0] = socks[1] = CURL_SOCKET_BAD; + return -1; + } + } + + return 0; +} +#endif + + +#ifndef CURL_DISABLE_SOCKETPAIR +#ifdef HAVE_SOCKETPAIR +int Curl_socketpair(int domain, int type, int protocol, + curl_socket_t socks[2], bool nonblocking) +{ +#ifdef SOCK_NONBLOCK + type = nonblocking ? type | SOCK_NONBLOCK : type; +#endif + if(socketpair(domain, type, protocol, socks)) + return -1; +#ifndef SOCK_NONBLOCK + if(nonblocking) { + if(curlx_nonblock(socks[0], TRUE) < 0 || + curlx_nonblock(socks[1], TRUE) < 0) { + close(socks[0]); + close(socks[1]); + return -1; + } + } +#endif + return 0; +} +#else /* !HAVE_SOCKETPAIR */ +#ifdef _WIN32 /* * This is a socketpair() implementation for Windows. */ #include -#include -#include -#include #include #else #ifdef HAVE_NETDB_H @@ -50,7 +117,7 @@ #ifndef INADDR_LOOPBACK #define INADDR_LOOPBACK 0x7f000001 #endif /* !INADDR_LOOPBACK */ -#endif /* !WIN32 */ +#endif /* !_WIN32 */ #include "nonblock.h" /* for curlx_nonblock */ #include "timeval.h" /* needed before select.h */ @@ -62,7 +129,7 @@ #include "memdebug.h" int Curl_socketpair(int domain, int type, int protocol, - curl_socket_t socks[2]) + curl_socket_t socks[2], bool nonblocking) { union { struct sockaddr_in inaddr; @@ -87,8 +154,8 @@ int Curl_socketpair(int domain, int type, int protocol, socks[0] = socks[1] = CURL_SOCKET_BAD; -#if defined(WIN32) || defined(__CYGWIN__) - /* don't set SO_REUSEADDR on Windows */ +#if defined(_WIN32) || defined(__CYGWIN__) + /* do not set SO_REUSEADDR on Windows */ (void)reuse; #ifdef SO_EXCLUSIVEADDRUSE { @@ -116,7 +183,7 @@ int Curl_socketpair(int domain, int type, int protocol, if(connect(socks[0], &a.addr, sizeof(a.inaddr)) == -1) goto error; - /* use non-blocking accept to make sure we don't block forever */ + /* use non-blocking accept to make sure we do not block forever */ if(curlx_nonblock(listener, TRUE) < 0) goto error; pfd[0].fd = listener; @@ -150,7 +217,7 @@ int Curl_socketpair(int domain, int type, int protocol, nread = sread(socks[1], p, s); if(nread == -1) { int sockerr = SOCKERRNO; - /* Don't block forever */ + /* Do not block forever */ if(Curl_timediff(Curl_now(), start) > (60 * 1000)) goto error; if( @@ -180,6 +247,10 @@ int Curl_socketpair(int domain, int type, int protocol, } while(1); } + if(nonblocking) + if(curlx_nonblock(socks[0], TRUE) < 0 || + curlx_nonblock(socks[1], TRUE) < 0) + goto error; sclose(listener); return 0; @@ -189,5 +260,5 @@ int Curl_socketpair(int domain, int type, int protocol, sclose(socks[1]); return -1; } - -#endif /* ! HAVE_SOCKETPAIR */ +#endif +#endif /* !CURL_DISABLE_SOCKETPAIR */ diff --git a/deps/curl/lib/socketpair.h b/deps/curl/lib/socketpair.h index 306ab5dc4c4..3044f1122ee 100644 --- a/deps/curl/lib/socketpair.h +++ b/deps/curl/lib/socketpair.h @@ -25,13 +25,74 @@ ***************************************************************************/ #include "curl_setup.h" -#ifndef HAVE_SOCKETPAIR + +#if defined(HAVE_EVENTFD) && \ + defined(__x86_64__) && \ + defined(__aarch64__) && \ + defined(__ia64__) && \ + defined(__ppc64__) && \ + defined(__mips64) && \ + defined(__sparc64__) && \ + defined(__riscv_64e) && \ + defined(__s390x__) + +/* Use eventfd only with 64-bit CPU architectures because eventfd has a + * stringent rule of requiring the 8-byte buffer when calling read(2) and + * write(2) on it. In some rare cases, the C standard library implementation + * on a 32-bit system might choose to define uint64_t as a 32-bit type for + * various reasons (memory limitations, compatibility with older code), + * which makes eventfd broken. + */ +#define USE_EVENTFD 1 + +#define wakeup_write write +#define wakeup_read read +#define wakeup_close close +#define wakeup_create(p,nb) Curl_eventfd(p,nb) + #include +int Curl_eventfd(curl_socket_t socks[2], bool nonblocking); -int Curl_socketpair(int domain, int type, int protocol, - curl_socket_t socks[2]); +#elif defined(HAVE_PIPE) + +#define wakeup_write write +#define wakeup_read read +#define wakeup_close close +#define wakeup_create(p,nb) Curl_pipe(p,nb) + +#include +int Curl_pipe(curl_socket_t socks[2], bool nonblocking); + +#else /* !USE_EVENTFD && !HAVE_PIPE */ + +#define wakeup_write swrite +#define wakeup_read sread +#define wakeup_close sclose + +#if defined(USE_UNIX_SOCKETS) && defined(HAVE_SOCKETPAIR) +#define SOCKETPAIR_FAMILY AF_UNIX +#elif !defined(HAVE_SOCKETPAIR) +#define SOCKETPAIR_FAMILY 0 /* not used */ #else -#define Curl_socketpair(a,b,c,d) socketpair(a,b,c,d) +#error "unsupported Unix domain and socketpair build combo" +#endif + +#ifdef SOCK_CLOEXEC +#define SOCKETPAIR_TYPE (SOCK_STREAM | SOCK_CLOEXEC) +#else +#define SOCKETPAIR_TYPE SOCK_STREAM +#endif + +#define wakeup_create(p,nb)\ +Curl_socketpair(SOCKETPAIR_FAMILY, SOCKETPAIR_TYPE, 0, p, nb) + +#endif /* USE_EVENTFD */ + +#ifndef CURL_DISABLE_SOCKETPAIR +#include + +int Curl_socketpair(int domain, int type, int protocol, + curl_socket_t socks[2], bool nonblocking); #endif #endif /* HEADER_CURL_SOCKETPAIR_H */ diff --git a/deps/curl/lib/socks.c b/deps/curl/lib/socks.c index c492d663c47..d16a30b90ad 100644 --- a/deps/curl/lib/socks.c +++ b/deps/curl/lib/socks.c @@ -71,9 +71,18 @@ enum connect_t { CONNECT_DONE /* 17 connected fine to the remote or the SOCKS proxy */ }; +#define CURL_SOCKS_BUF_SIZE 600 + +/* make sure we configure it not too low */ +#if CURL_SOCKS_BUF_SIZE < 600 +#error CURL_SOCKS_BUF_SIZE must be at least 600 +#endif + + struct socks_state { enum connect_t state; ssize_t outstanding; /* send this many bytes more */ + unsigned char buffer[CURL_SOCKS_BUF_SIZE]; unsigned char *outp; /* send from this pointer */ const char *hostname; @@ -116,7 +125,7 @@ int Curl_blockread_all(struct Curl_cfilter *cf, } nread = Curl_conn_cf_recv(cf->next, data, buf, buffersize, &err); if(nread <= 0) { - result = err; + result = (int)err; if(CURLE_AGAIN == err) continue; if(err) { @@ -185,7 +194,7 @@ static void socksstate(struct socks_state *sx, struct Curl_easy *data, (void)data; if(oldstate == state) - /* don't bother when the new state is the same as the old state */ + /* do not bother when the new state is the same as the old state */ return; sx->state = state; @@ -208,7 +217,7 @@ static CURLproxycode socks_state_send(struct Curl_cfilter *cf, CURLcode result; nwritten = Curl_conn_cf_send(cf->next, data, (char *)sx->outp, - sx->outstanding, &result); + sx->outstanding, FALSE, &result); if(nwritten <= 0) { if(CURLE_AGAIN == result) { return CURLPX_OK; @@ -249,7 +258,7 @@ static CURLproxycode socks_state_recv(struct Curl_cfilter *cf, failf(data, "connection to proxy closed"); return CURLPX_CLOSED; } - failf(data, "SOCKS4: Failed receiving %s: %s", description, + failf(data, "SOCKS: Failed receiving %s: %s", description, curl_easy_strerror(result)); return failcode; } @@ -277,15 +286,12 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, { struct connectdata *conn = cf->conn; const bool protocol4a = - (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A) ? TRUE : FALSE; - unsigned char *socksreq = (unsigned char *)data->state.buffer; + (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A); + unsigned char *socksreq = sx->buffer; CURLcode result; CURLproxycode presult; struct Curl_dns_entry *dns = NULL; - /* make sure that the buffer is at least 600 bytes */ - DEBUGASSERT(READBUFFER_MIN >= 600); - switch(sx->state) { case CONNECT_SOCKS_INIT: /* SOCKS4 can only do IPv4, insist! */ @@ -329,13 +335,13 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, goto CONNECT_RESOLVED; } - /* socks4a doesn't resolve anything locally */ + /* socks4a does not resolve anything locally */ sxstate(sx, data, CONNECT_REQ_INIT); goto CONNECT_REQ_INIT; case CONNECT_RESOLVING: /* check if we have the name resolved by now */ - dns = Curl_fetch_addr(data, sx->hostname, (int)conn->port); + dns = Curl_fetch_addr(data, sx->hostname, conn->primary.remote_port); if(dns) { #ifdef CURLRES_ASYNCH @@ -353,12 +359,13 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, return CURLPX_OK; } } - /* FALLTHROUGH */ + FALLTHROUGH(); + case CONNECT_RESOLVED: CONNECT_RESOLVED: - case CONNECT_RESOLVED: { + { struct Curl_addrinfo *hp = NULL; /* - * We cannot use 'hostent' as a struct that Curl_resolv() returns. It + * We cannot use 'hostent' as a struct that Curl_resolv() returns. It * returns a Curl_addrinfo pointer that may not always look the same. */ if(dns) { @@ -381,7 +388,7 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, infof(data, "SOCKS4 connect to IPv4 %s (locally resolved)", buf); - Curl_resolv_unlock(data, dns); /* not used anymore from now on */ + Curl_resolv_unlink(data, &dns); /* not used anymore from now on */ } else failf(data, "SOCKS4 connection to %s not supported", sx->hostname); @@ -393,17 +400,20 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, if(!hp) return CURLPX_RESOLVE_HOST; } - /* FALLTHROUGH */ -CONNECT_REQ_INIT: + FALLTHROUGH(); case CONNECT_REQ_INIT: +CONNECT_REQ_INIT: /* * This is currently not supporting "Identification Protocol (RFC1413)". */ socksreq[8] = 0; /* ensure empty userid is NUL-terminated */ if(sx->proxy_user) { size_t plen = strlen(sx->proxy_user); - if(plen >= (size_t)data->set.buffer_size - 8) { - failf(data, "Too long SOCKS proxy user name, can't use"); + if(plen > 255) { + /* there is no real size limit to this field in the protocol, but + SOCKS5 limits the proxy user field to 255 bytes and it seems likely + that a longer field is either a mistake or malicious input */ + failf(data, "Too long SOCKS proxy username"); return CURLPX_LONG_USER; } /* copy the proxy name WITH trailing zero */ @@ -426,19 +436,21 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, socksreq[7] = 1; /* append hostname */ hostnamelen = strlen(sx->hostname) + 1; /* length including NUL */ - if(hostnamelen <= 255) + if((hostnamelen <= 255) && + (packetsize + hostnamelen < sizeof(sx->buffer))) strcpy((char *)socksreq + packetsize, sx->hostname); else { - failf(data, "SOCKS4: too long host name"); + failf(data, "SOCKS4: too long hostname"); return CURLPX_LONG_HOSTNAME; } packetsize += hostnamelen; } sx->outp = socksreq; + DEBUGASSERT(packetsize <= sizeof(sx->buffer)); sx->outstanding = packetsize; sxstate(sx, data, CONNECT_REQ_SENDING); } - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_REQ_SENDING: /* Send request */ presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, @@ -454,7 +466,7 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, sx->outp = socksreq; sxstate(sx, data, CONNECT_SOCKS_READ); - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_SOCKS_READ: /* Receive response */ presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT, @@ -500,11 +512,11 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, /* Result */ switch(socksreq[1]) { case 90: - infof(data, "SOCKS4%s request granted.", protocol4a?"a":""); + infof(data, "SOCKS4%s request granted.", protocol4a ? "a" : ""); break; case 91: failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + "cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" ", request rejected or failed.", socksreq[4], socksreq[5], socksreq[6], socksreq[7], (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), @@ -512,7 +524,7 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, return CURLPX_REQUEST_FAILED; case 92: failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + "cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" ", request rejected because SOCKS server cannot connect to " "identd on the client.", socksreq[4], socksreq[5], socksreq[6], socksreq[7], @@ -521,7 +533,7 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, return CURLPX_IDENTD; case 93: failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + "cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" ", request rejected because the client program and identd " "report different user-ids.", socksreq[4], socksreq[5], socksreq[6], socksreq[7], @@ -530,7 +542,7 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, return CURLPX_IDENTD_DIFFER; default: failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + "cannot complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" ", Unknown.", socksreq[4], socksreq[5], socksreq[6], socksreq[7], (((unsigned char)socksreq[2] << 8) | (unsigned char)socksreq[3]), @@ -550,7 +562,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, struct Curl_easy *data) { /* - According to the RFC1928, section "6. Replies". This is what a SOCK5 + According to the RFC1928, section "6. Replies". This is what a SOCK5 replies: +----+-----+-------+------+----------+----------+ @@ -566,14 +578,14 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, o X'00' succeeded */ struct connectdata *conn = cf->conn; - unsigned char *socksreq = (unsigned char *)data->state.buffer; - int idx; + unsigned char *socksreq = sx->buffer; + size_t idx; CURLcode result; CURLproxycode presult; bool socks5_resolve_local = - (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE; + (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5); const size_t hostname_len = strlen(sx->hostname); - ssize_t len = 0; + size_t len = 0; const unsigned char auth = data->set.socks5auth; bool allow_gssapi = FALSE; struct Curl_dns_entry *dns = NULL; @@ -587,9 +599,9 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */ if(!socks5_resolve_local && hostname_len > 255) { - infof(data, "SOCKS5: server resolving disabled for hostnames of " - "length > 255 [actual len=%zu]", hostname_len); - socks5_resolve_local = TRUE; + failf(data, "SOCKS5: the destination hostname is too long to be " + "resolved remotely by the proxy."); + return CURLPX_LONG_HOSTNAME; } if(auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) @@ -616,6 +628,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, socksreq[1] = (unsigned char) (idx - 2); sx->outp = socksreq; + DEBUGASSERT(idx <= sizeof(sx->buffer)); sx->outstanding = idx; presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, "initial SOCKS5 request"); @@ -636,12 +649,12 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, /* remain in sending state */ return CURLPX_OK; } - /* FALLTHROUGH */ -CONNECT_SOCKS_READ_INIT: + FALLTHROUGH(); case CONNECT_SOCKS_READ_INIT: +CONNECT_SOCKS_READ_INIT: sx->outstanding = 2; /* expect two bytes */ sx->outp = socksreq; /* store it here */ - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_SOCKS_READ: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT, "initial SOCKS5 response"); @@ -701,7 +714,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, CONNECT_AUTH_INIT: case CONNECT_AUTH_INIT: { - /* Needs user name and password */ + /* Needs username and password */ size_t proxy_user_len, proxy_password_len; if(sx->proxy_user && sx->proxy_password) { proxy_user_len = strlen(sx->proxy_user); @@ -725,7 +738,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, if(sx->proxy_user && proxy_user_len) { /* the length must fit in a single byte */ if(proxy_user_len > 255) { - failf(data, "Excessive user name length for proxy auth"); + failf(data, "Excessive username length for proxy auth"); return CURLPX_LONG_USER; } memcpy(socksreq + len, sx->proxy_user, proxy_user_len); @@ -742,10 +755,11 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, } len += proxy_password_len; sxstate(sx, data, CONNECT_AUTH_SEND); + DEBUGASSERT(len <= sizeof(sx->buffer)); sx->outstanding = len; sx->outp = socksreq; } - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_AUTH_SEND: presult = socks_state_send(cf, sx, data, CURLPX_SEND_AUTH, "SOCKS5 sub-negotiation request"); @@ -758,7 +772,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, sx->outp = socksreq; sx->outstanding = 2; sxstate(sx, data, CONNECT_AUTH_READ); - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_AUTH_READ: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_AUTH, "SOCKS5 sub-negotiation response"); @@ -777,9 +791,9 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, /* Everything is good so far, user was authenticated! */ sxstate(sx, data, CONNECT_REQ_INIT); - /* FALLTHROUGH */ -CONNECT_REQ_INIT: + FALLTHROUGH(); case CONNECT_REQ_INIT: +CONNECT_REQ_INIT: if(socks5_resolve_local) { enum resolve_t rc = Curl_resolv(data, sx->hostname, sx->remote_port, TRUE, &dns); @@ -816,13 +830,23 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, return CURLPX_OK; } } - /* FALLTHROUGH */ + FALLTHROUGH(); + case CONNECT_RESOLVED: CONNECT_RESOLVED: - case CONNECT_RESOLVED: { - char dest[MAX_IPADR_LEN] = "unknown"; /* printable address */ + { + char dest[MAX_IPADR_LEN]; /* printable address */ struct Curl_addrinfo *hp = NULL; if(dns) hp = dns->addr; +#ifdef USE_IPV6 + if(data->set.ipver != CURL_IPRESOLVE_WHATEVER) { + int wanted_family = data->set.ipver == CURL_IPRESOLVE_V4 ? + AF_INET : AF_INET6; + /* scan for the first proper address */ + while(hp && (hp->ai_family != wanted_family)) + hp = hp->ai_next; + } +#endif if(!hp) { failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", sx->hostname); @@ -848,7 +872,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, infof(data, "SOCKS5 connect to %s:%d (locally resolved)", dest, sx->remote_port); } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else if(hp->ai_family == AF_INET6) { int i; struct sockaddr_in6 *saddr_in6; @@ -869,7 +893,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, failf(data, "SOCKS5 connection to %s not supported", dest); } - Curl_resolv_unlock(data, dns); /* not used anymore from now on */ + Curl_resolv_unlink(data, &dns); /* not used anymore from now on */ goto CONNECT_REQ_SEND; } CONNECT_RESOLVE_REMOTE: @@ -885,7 +909,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, IPv6 == 4, IPv4 == 1 */ unsigned char ip4[4]; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(conn->bits.ipv6_ip) { char ip6[16]; if(1 != Curl_inet_pton(AF_INET6, sx->hostname, ip6)) @@ -903,17 +927,17 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, } else { socksreq[len++] = 3; - socksreq[len++] = (char) hostname_len; /* one byte address length */ + socksreq[len++] = (unsigned char) hostname_len; /* one byte length */ memcpy(&socksreq[len], sx->hostname, hostname_len); /* w/o NULL */ len += hostname_len; } infof(data, "SOCKS5 connect to %s:%d (remotely resolved)", sx->hostname, sx->remote_port); } - /* FALLTHROUGH */ + FALLTHROUGH(); -CONNECT_REQ_SEND: case CONNECT_REQ_SEND: +CONNECT_REQ_SEND: /* PORT MSB */ socksreq[len++] = (unsigned char)((sx->remote_port >> 8) & 0xff); /* PORT LSB */ @@ -926,9 +950,10 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, } #endif sx->outp = socksreq; + DEBUGASSERT(len <= sizeof(sx->buffer)); sx->outstanding = len; sxstate(sx, data, CONNECT_REQ_SENDING); - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_REQ_SENDING: presult = socks_state_send(cf, sx, data, CURLPX_SEND_REQUEST, "SOCKS5 connect request"); @@ -947,7 +972,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, sx->outstanding = 10; /* minimum packet size is 10 */ sx->outp = socksreq; sxstate(sx, data, CONNECT_REQ_READ); - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_REQ_READ: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_REQACK, "SOCKS5 connect request ack"); @@ -965,7 +990,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, else if(socksreq[1]) { /* Anything besides 0 is an error */ CURLproxycode rc = CURLPX_REPLY_UNASSIGNED; int code = socksreq[1]; - failf(data, "Can't complete SOCKS5 connection to %s. (%d)", + failf(data, "cannot complete SOCKS5 connection to %s. (%d)", sx->hostname, (unsigned char)socksreq[1]); if(code < 9) { /* RFC 1928 section 6 lists: */ @@ -1025,6 +1050,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, /* decrypt_gssapi_blockread already read the whole packet */ #endif if(len > 10) { + DEBUGASSERT(len <= sizeof(sx->buffer)); sx->outstanding = len - 10; /* get the rest */ sx->outp = &socksreq[10]; sxstate(sx, data, CONNECT_REQ_READ_MORE); @@ -1036,7 +1062,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) } #endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_REQ_READ_MORE: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_ADDRESS, "SOCKS5 connect request address"); @@ -1094,7 +1120,7 @@ static void socks_proxy_cf_free(struct Curl_cfilter *cf) } /* After a TCP connection to the proxy has been verified, this function does - the next magic steps. If 'done' isn't set TRUE, it is not done yet and + the next magic steps. If 'done' is not set TRUE, it is not done yet and must be called again. Note: this function's sub-functions call failf() @@ -1119,7 +1145,7 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, return result; if(!sx) { - sx = calloc(sizeof(*sx), 1); + sx = calloc(1, sizeof(*sx)); if(!sx) return CURLE_OUT_OF_MEMORY; cf->ctx = sx; @@ -1149,7 +1175,7 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, result = connect_SOCKS(cf, sx, data); if(!result && sx->state == CONNECT_DONE) { cf->connected = TRUE; - Curl_verboseconnect(data, conn); + Curl_verboseconnect(data, conn, cf->sockindex); socks_proxy_cf_free(cf); } @@ -1157,32 +1183,29 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, return result; } -static int socks_cf_get_select_socks(struct Curl_cfilter *cf, +static void socks_cf_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) + struct easy_pollset *ps) { struct socks_state *sx = cf->ctx; - int fds; - fds = cf->next->cft->get_select_socks(cf->next, data, socks); - if(!fds && cf->next->connected && !cf->connected && sx) { + if(!cf->connected && sx) { /* If we are not connected, the filter below is and has nothing * to wait on, we determine what to wait for. */ - socks[0] = Curl_conn_cf_get_socket(cf, data); + curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); switch(sx->state) { case CONNECT_RESOLVING: case CONNECT_SOCKS_READ: case CONNECT_AUTH_READ: case CONNECT_REQ_READ: case CONNECT_REQ_READ_MORE: - fds = GETSOCK_READSOCK(0); + Curl_pollset_set_in_only(data, ps, sock); break; default: - fds = GETSOCK_WRITESOCK(0); + Curl_pollset_set_out_only(data, ps, sock); break; } } - return fds; } static void socks_proxy_cf_close(struct Curl_cfilter *cf, @@ -1221,13 +1244,14 @@ static void socks_cf_get_host(struct Curl_cfilter *cf, struct Curl_cftype Curl_cft_socks_proxy = { "SOCKS-PROXYY", - CF_TYPE_IP_CONNECT, + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, 0, socks_proxy_cf_destroy, socks_proxy_cf_connect, socks_proxy_cf_close, + Curl_cf_def_shutdown, socks_cf_get_host, - socks_cf_get_select_socks, + socks_cf_adjust_pollset, Curl_cf_def_data_pending, Curl_cf_def_send, Curl_cf_def_recv, diff --git a/deps/curl/lib/socks_gssapi.c b/deps/curl/lib/socks_gssapi.c index 2ede8c7c29a..f6fd55dcec9 100644 --- a/deps/curl/lib/socks_gssapi.c +++ b/deps/curl/lib/socks_gssapi.c @@ -35,12 +35,15 @@ #include "timeval.h" #include "socks.h" #include "warnless.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" +#define MAX_GSS_LEN 1024 + static gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT; /* @@ -55,10 +58,9 @@ static int check_gss_err(struct Curl_easy *data, OM_uint32 maj_stat, min_stat; OM_uint32 msg_ctx = 0; gss_buffer_desc status_string = GSS_C_EMPTY_BUFFER; - char buf[1024]; - size_t len; + struct dynbuf dbuf; - len = 0; + Curl_dyn_init(&dbuf, MAX_GSS_LEN); msg_ctx = 0; while(!msg_ctx) { /* convert major status code (GSS-API error) to text */ @@ -67,19 +69,16 @@ static int check_gss_err(struct Curl_easy *data, GSS_C_NULL_OID, &msg_ctx, &status_string); if(maj_stat == GSS_S_COMPLETE) { - if(sizeof(buf) > len + status_string.length + 1) { - strcpy(buf + len, (char *) status_string.value); - len += status_string.length; - } + if(Curl_dyn_addn(&dbuf, status_string.value, + status_string.length)) + return 1; /* error */ gss_release_buffer(&min_stat, &status_string); break; } gss_release_buffer(&min_stat, &status_string); } - if(sizeof(buf) > len + 3) { - strcpy(buf + len, ".\n"); - len += 2; - } + if(Curl_dyn_addn(&dbuf, ".\n", 2)) + return 1; /* error */ msg_ctx = 0; while(!msg_ctx) { /* convert minor status code (underlying routine error) to text */ @@ -88,14 +87,16 @@ static int check_gss_err(struct Curl_easy *data, GSS_C_NULL_OID, &msg_ctx, &status_string); if(maj_stat == GSS_S_COMPLETE) { - if(sizeof(buf) > len + status_string.length) - strcpy(buf + len, (char *) status_string.value); + if(Curl_dyn_addn(&dbuf, status_string.value, + status_string.length)) + return 1; /* error */ gss_release_buffer(&min_stat, &status_string); break; } gss_release_buffer(&min_stat, &status_string); } - failf(data, "GSS-API error: %s failed: %s", function, buf); + failf(data, "GSS-API error: %s failed: %s", function, Curl_dyn_ptr(&dbuf)); + Curl_dyn_free(&dbuf); return 1; } @@ -139,10 +140,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, /* prepare service name */ if(strchr(serviceptr, '/')) { service.length = serviceptr_length; - service.value = malloc(service.length); + service.value = Curl_memdup(serviceptr, service.length); if(!service.value) return CURLE_OUT_OF_MEMORY; - memcpy(service.value, serviceptr, service.length); gss_major_status = gss_import_name(&gss_minor_status, &service, (gss_OID) GSS_C_NULL_OID, &server); @@ -172,7 +172,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, (void)curlx_nonblock(sock, FALSE); - /* As long as we need to keep sending some context info, and there's no */ + /* As long as we need to keep sending some context info, and there is no */ /* errors, keep sending it... */ for(;;) { gss_major_status = Curl_gss_init_sec_context(data, @@ -201,10 +201,11 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(gss_send_token.length) { socksreq[0] = 1; /* GSS-API subnegotiation version */ socksreq[1] = 1; /* authentication message type */ - us_length = htons((short)gss_send_token.length); + us_length = htons((unsigned short)gss_send_token.length); memcpy(socksreq + 2, &us_length, sizeof(short)); - nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, + FALSE, &code); if(code || (4 != nwritten)) { failf(data, "Failed to send GSS-API authentication request."); gss_release_name(&gss_status, &server); @@ -216,7 +217,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, nwritten = Curl_conn_cf_send(cf->next, data, (char *)gss_send_token.value, - gss_send_token.length, &code); + gss_send_token.length, FALSE, &code); if(code || ((ssize_t)gss_send_token.length != nwritten)) { failf(data, "Failed to send GSS-API authentication token."); gss_release_name(&gss_status, &server); @@ -306,7 +307,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_minor_status, "gss_inquire_context")) { gss_delete_sec_context(&gss_status, &gss_context, NULL); gss_release_name(&gss_status, &gss_client_name); - failf(data, "Failed to determine user name."); + failf(data, "Failed to determine username."); return CURLE_COULDNT_CONNECT; } gss_major_status = gss_display_name(&gss_minor_status, gss_client_name, @@ -316,7 +317,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_delete_sec_context(&gss_status, &gss_context, NULL); gss_release_name(&gss_status, &gss_client_name); gss_release_buffer(&gss_status, &gss_send_token); - failf(data, "Failed to determine user name."); + failf(data, "Failed to determine username."); return CURLE_COULDNT_CONNECT; } user = malloc(gss_send_token.length + 1); @@ -348,7 +349,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_enc = 1; infof(data, "SOCKS5 server supports GSS-API %s data protection.", - (gss_enc == 0)?"no":((gss_enc==1)?"integrity":"confidentiality")); + (gss_enc == 0) ? "no" : + ((gss_enc == 1) ? "integrity" : "confidentiality")); /* force for the moment to no data protection */ gss_enc = 0; /* @@ -377,7 +379,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, * * The token is produced by encapsulating an octet containing the * required protection level using gss_seal()/gss_wrap() with conf_req - * set to FALSE. The token is verified using gss_unseal()/ + * set to FALSE. The token is verified using gss_unseal()/ * gss_unwrap(). * */ @@ -387,12 +389,11 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, } else { gss_send_token.length = 1; - gss_send_token.value = malloc(1); + gss_send_token.value = Curl_memdup(&gss_enc, 1); if(!gss_send_token.value) { gss_delete_sec_context(&gss_status, &gss_context, NULL); return CURLE_OUT_OF_MEMORY; } - memcpy(gss_send_token.value, &gss_enc, 1); gss_major_status = gss_wrap(&gss_minor_status, gss_context, 0, GSS_C_QOP_DEFAULT, &gss_send_token, @@ -407,11 +408,12 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, } gss_release_buffer(&gss_status, &gss_send_token); - us_length = htons((short)gss_w_token.length); + us_length = htons((unsigned short)gss_w_token.length); memcpy(socksreq + 2, &us_length, sizeof(short)); } - nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, FALSE, + &code); if(code || (4 != nwritten)) { failf(data, "Failed to send GSS-API encryption request."); gss_release_buffer(&gss_status, &gss_w_token); @@ -421,7 +423,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(data->set.socks5_gssapi_nec) { memcpy(socksreq, &gss_enc, 1); - nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, &code); + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, FALSE, + &code); if(code || ( 1 != nwritten)) { failf(data, "Failed to send GSS-API encryption type."); gss_delete_sec_context(&gss_status, &gss_context, NULL); @@ -431,7 +434,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, else { nwritten = Curl_conn_cf_send(cf->next, data, (char *)gss_w_token.value, - gss_w_token.length, &code); + gss_w_token.length, FALSE, &code); if(code || ((ssize_t)gss_w_token.length != nwritten)) { failf(data, "Failed to send GSS-API encryption type."); gss_release_buffer(&gss_status, &gss_w_token); @@ -476,7 +479,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_recv_token.length, &actualread); if(result || (actualread != us_length)) { - failf(data, "Failed to receive GSS-API encryptrion type."); + failf(data, "Failed to receive GSS-API encryption type."); gss_release_buffer(&gss_status, &gss_recv_token); gss_delete_sec_context(&gss_status, &gss_context, NULL); return CURLE_COULDNT_CONNECT; @@ -523,8 +526,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, (void)curlx_nonblock(sock, TRUE); infof(data, "SOCKS5 access with%s protection granted.", - (socksreq[0] == 0)?"out GSS-API data": - ((socksreq[0] == 1)?" GSS-API integrity":" GSS-API confidentiality")); + (socksreq[0] == 0) ? "out GSS-API data": + ((socksreq[0] == 1) ? " GSS-API integrity" : + " GSS-API confidentiality")); conn->socks5_gssapi_enctype = socksreq[0]; if(socksreq[0] == 0) diff --git a/deps/curl/lib/socks_sspi.c b/deps/curl/lib/socks_sspi.c index d1200ea037b..6d8e6ef730d 100644 --- a/deps/curl/lib/socks_sspi.c +++ b/deps/curl/lib/socks_sspi.c @@ -139,7 +139,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, cred_handle.dwLower = 0; cred_handle.dwUpper = 0; - status = s_pSecFn->AcquireCredentialsHandle(NULL, + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT("Kerberos"), SECPKG_CRED_OUTBOUND, NULL, @@ -152,13 +152,13 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(check_sspi_err(data, status, "AcquireCredentialsHandle")) { failf(data, "Failed to acquire credentials."); free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); return CURLE_COULDNT_CONNECT; } (void)curlx_nonblock(sock, FALSE); - /* As long as we need to keep sending some context info, and there's no */ + /* As long as we need to keep sending some context info, and there is no */ /* errors, keep sending it... */ for(;;) { TCHAR *sname; @@ -167,7 +167,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(!sname) return CURLE_OUT_OF_MEMORY; - status = s_pSecFn->InitializeSecurityContext(&cred_handle, + status = Curl_pSecFn->InitializeSecurityContext(&cred_handle, context_handle, sname, ISC_REQ_MUTUAL_AUTH | @@ -186,17 +186,17 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, curlx_unicodefree(sname); if(sspi_recv_token.pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); sspi_recv_token.pvBuffer = NULL; sspi_recv_token.cbBuffer = 0; } if(check_sspi_err(data, status, "InitializeSecurityContext")) { free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); if(sspi_recv_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); failf(data, "Failed to initialise security context."); return CURLE_COULDNT_CONNECT; } @@ -204,47 +204,48 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(sspi_send_token.cbBuffer) { socksreq[0] = 1; /* GSS-API subnegotiation version */ socksreq[1] = 1; /* authentication message type */ - us_length = htons((short)sspi_send_token.cbBuffer); + us_length = htons((unsigned short)sspi_send_token.cbBuffer); memcpy(socksreq + 2, &us_length, sizeof(short)); - written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, FALSE, + &code); if(code || (4 != written)) { failf(data, "Failed to send SSPI authentication request."); free(service_name); if(sspi_send_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); if(sspi_recv_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } written = Curl_conn_cf_send(cf->next, data, (char *)sspi_send_token.pvBuffer, - sspi_send_token.cbBuffer, &code); + sspi_send_token.cbBuffer, FALSE, &code); if(code || (sspi_send_token.cbBuffer != (size_t)written)) { failf(data, "Failed to send SSPI authentication token."); free(service_name); if(sspi_send_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); if(sspi_recv_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } } if(sspi_send_token.pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); sspi_send_token.pvBuffer = NULL; } sspi_send_token.cbBuffer = 0; if(sspi_recv_token.pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); sspi_recv_token.pvBuffer = NULL; } sspi_recv_token.cbBuffer = 0; @@ -266,8 +267,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(result || (actualread != 4)) { failf(data, "Failed to receive SSPI authentication response."); free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -276,8 +277,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, failf(data, "User was rejected by the SOCKS5 server (%u %u).", (unsigned int)socksreq[0], (unsigned int)socksreq[1]); free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -285,8 +286,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, failf(data, "Invalid SSPI authentication response type (%u %u).", (unsigned int)socksreq[0], (unsigned int)socksreq[1]); free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -298,8 +299,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(!sspi_recv_token.pvBuffer) { free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } result = Curl_blockread_all(cf, data, (char *)sspi_recv_token.pvBuffer, @@ -309,9 +310,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, failf(data, "Failed to receive SSPI authentication token."); free(service_name); if(sspi_recv_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -321,19 +322,25 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, free(service_name); /* Everything is good so far, user was authenticated! */ - status = s_pSecFn->QueryCredentialsAttributes(&cred_handle, + status = Curl_pSecFn->QueryCredentialsAttributes(&cred_handle, SECPKG_CRED_ATTR_NAMES, &names); - s_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); if(check_sspi_err(data, status, "QueryCredentialAttributes")) { - s_pSecFn->DeleteSecurityContext(&sspi_context); - s_pSecFn->FreeContextBuffer(names.sUserName); - failf(data, "Failed to determine user name."); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(names.sUserName); + failf(data, "Failed to determine username."); return CURLE_COULDNT_CONNECT; } - infof(data, "SOCKS5 server authenticated user %s with GSS-API.", - names.sUserName); - s_pSecFn->FreeContextBuffer(names.sUserName); + else { +#ifndef CURL_DISABLE_VERBOSE_STRINGS + char *user_utf8 = curlx_convert_tchar_to_UTF8(names.sUserName); + infof(data, "SOCKS5 server authenticated user %s with GSS-API.", + (user_utf8 ? user_utf8 : "(unknown)")); + curlx_unicodefree(user_utf8); +#endif + Curl_pSecFn->FreeContextBuffer(names.sUserName); + } /* Do encryption */ socksreq[0] = 1; /* GSS-API subnegotiation version */ @@ -348,7 +355,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, gss_enc = 1; infof(data, "SOCKS5 server supports GSS-API %s data protection.", - (gss_enc == 0)?"no":((gss_enc == 1)?"integrity":"confidentiality") ); + (gss_enc == 0) ? "no" : + ((gss_enc == 1) ? "integrity":"confidentiality") ); /* force to no data protection, avoid encryption/decryption for now */ gss_enc = 0; /* @@ -377,21 +385,21 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, * * The token is produced by encapsulating an octet containing the * required protection level using gss_seal()/gss_wrap() with conf_req - * set to FALSE. The token is verified using gss_unseal()/ + * set to FALSE. The token is verified using gss_unseal()/ * gss_unwrap(). * */ if(data->set.socks5_gssapi_nec) { - us_length = htons((short)1); + us_length = htons((unsigned short)1); memcpy(socksreq + 2, &us_length, sizeof(short)); } else { - status = s_pSecFn->QueryContextAttributes(&sspi_context, + status = Curl_pSecFn->QueryContextAttributes(&sspi_context, SECPKG_ATTR_SIZES, &sspi_sizes); if(check_sspi_err(data, status, "QueryContextAttributes")) { - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); failf(data, "Failed to query security context attributes."); return CURLE_COULDNT_CONNECT; } @@ -401,15 +409,15 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_w_token[0].pvBuffer = malloc(sspi_sizes.cbSecurityTrailer); if(!sspi_w_token[0].pvBuffer) { - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } sspi_w_token[1].cbBuffer = 1; sspi_w_token[1].pvBuffer = malloc(1); if(!sspi_w_token[1].pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } @@ -418,20 +426,20 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_w_token[2].cbBuffer = sspi_sizes.cbBlockSize; sspi_w_token[2].pvBuffer = malloc(sspi_sizes.cbBlockSize); if(!sspi_w_token[2].pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } - status = s_pSecFn->EncryptMessage(&sspi_context, + status = Curl_pSecFn->EncryptMessage(&sspi_context, KERB_WRAP_NO_ENCRYPT, &wrap_desc, 0); if(check_sspi_err(data, status, "EncryptMessage")) { - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); failf(data, "Failed to query security context attributes."); return CURLE_COULDNT_CONNECT; } @@ -440,10 +448,10 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, + sspi_w_token[2].cbBuffer; sspi_send_token.pvBuffer = malloc(sspi_send_token.cbBuffer); if(!sspi_send_token.pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } @@ -456,57 +464,59 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, + sspi_w_token[1].cbBuffer, sspi_w_token[2].pvBuffer, sspi_w_token[2].cbBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); sspi_w_token[0].pvBuffer = NULL; sspi_w_token[0].cbBuffer = 0; - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); sspi_w_token[1].pvBuffer = NULL; sspi_w_token[1].cbBuffer = 0; - s_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); sspi_w_token[2].pvBuffer = NULL; sspi_w_token[2].cbBuffer = 0; - us_length = htons((short)sspi_send_token.cbBuffer); + us_length = htons((unsigned short)sspi_send_token.cbBuffer); memcpy(socksreq + 2, &us_length, sizeof(short)); } - written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, FALSE, + &code); if(code || (4 != written)) { failf(data, "Failed to send SSPI encryption request."); if(sspi_send_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } if(data->set.socks5_gssapi_nec) { memcpy(socksreq, &gss_enc, 1); - written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, &code); + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, FALSE, + &code); if(code || (1 != written)) { failf(data, "Failed to send SSPI encryption type."); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } } else { written = Curl_conn_cf_send(cf->next, data, (char *)sspi_send_token.pvBuffer, - sspi_send_token.cbBuffer, &code); + sspi_send_token.cbBuffer, FALSE, &code); if(code || (sspi_send_token.cbBuffer != (size_t)written)) { failf(data, "Failed to send SSPI encryption type."); if(sspi_send_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } if(sspi_send_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); } result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread); if(result || (actualread != 4)) { failf(data, "Failed to receive SSPI encryption response."); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -514,14 +524,14 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(socksreq[1] == 255) { /* status / message type */ failf(data, "User was rejected by the SOCKS5 server (%u %u).", (unsigned int)socksreq[0], (unsigned int)socksreq[1]); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } if(socksreq[1] != 2) { /* status / message type */ failf(data, "Invalid SSPI encryption response type (%u %u).", (unsigned int)socksreq[0], (unsigned int)socksreq[1]); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -531,7 +541,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_w_token[0].cbBuffer = us_length; sspi_w_token[0].pvBuffer = malloc(us_length); if(!sspi_w_token[0].pvBuffer) { - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } @@ -540,8 +550,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, if(result || (actualread != us_length)) { failf(data, "Failed to receive SSPI encryption type."); - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -553,17 +563,17 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, sspi_w_token[1].cbBuffer = 0; sspi_w_token[1].pvBuffer = NULL; - status = s_pSecFn->DecryptMessage(&sspi_context, + status = Curl_pSecFn->DecryptMessage(&sspi_context, &wrap_desc, 0, &qop); if(check_sspi_err(data, status, "DecryptMessage")) { if(sspi_w_token[0].pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); if(sspi_w_token[1].pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); failf(data, "Failed to query security context attributes."); return CURLE_COULDNT_CONNECT; } @@ -572,40 +582,41 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, failf(data, "Invalid SSPI encryption response length (%lu).", (unsigned long)sspi_w_token[1].cbBuffer); if(sspi_w_token[0].pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); if(sspi_w_token[1].pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } memcpy(socksreq, sspi_w_token[1].pvBuffer, sspi_w_token[1].cbBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); } else { if(sspi_w_token[0].cbBuffer != 1) { failf(data, "Invalid SSPI encryption response length (%lu).", (unsigned long)sspi_w_token[0].cbBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } memcpy(socksreq, sspi_w_token[0].pvBuffer, sspi_w_token[0].cbBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); } (void)curlx_nonblock(sock, TRUE); infof(data, "SOCKS5 access with%s protection granted.", - (socksreq[0] == 0)?"out GSS-API data": - ((socksreq[0] == 1)?" GSS-API integrity":" GSS-API confidentiality")); + (socksreq[0] == 0) ? "out GSS-API data": + ((socksreq[0] == 1) ? " GSS-API integrity" : + " GSS-API confidentiality")); /* For later use if encryption is required conn->socks5_gssapi_enctype = socksreq[0]; if(socksreq[0] != 0) conn->socks5_sspi_context = sspi_context; else { - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); conn->socks5_sspi_context = sspi_context; } */ diff --git a/deps/curl/lib/speedcheck.h b/deps/curl/lib/speedcheck.h index bff2f32b774..8b116f15289 100644 --- a/deps/curl/lib/speedcheck.h +++ b/deps/curl/lib/speedcheck.h @@ -27,7 +27,7 @@ #include "curl_setup.h" #include "timeval.h" - +struct Curl_easy; void Curl_speedinit(struct Curl_easy *data); CURLcode Curl_speedcheck(struct Curl_easy *data, struct curltime now); diff --git a/deps/curl/lib/splay.c b/deps/curl/lib/splay.c index 48e079b32aa..3f2bae02387 100644 --- a/deps/curl/lib/splay.c +++ b/deps/curl/lib/splay.c @@ -24,6 +24,7 @@ #include "curl_setup.h" +#include "timeval.h" #include "splay.h" /* @@ -33,7 +34,7 @@ * zero : when i is equal to j * positive when : when i is larger than j */ -#define compare(i,j) Curl_splaycomparekeys((i),(j)) +#define compare(i,j) Curl_timediff_us(i,j) /* * Splay using the key i (which may or may not be in the tree.) The starting @@ -45,12 +46,12 @@ struct Curl_tree *Curl_splay(struct curltime i, struct Curl_tree N, *l, *r, *y; if(!t) - return t; + return NULL; N.smaller = N.larger = NULL; l = r = &N; for(;;) { - long comp = compare(i, t->key); + timediff_t comp = compare(i, t->key); if(comp < 0) { if(!t->smaller) break; @@ -93,7 +94,7 @@ struct Curl_tree *Curl_splay(struct curltime i, return t; } -/* Insert key i into the tree t. Return a pointer to the resulting tree or +/* Insert key i into the tree t. Return a pointer to the resulting tree or * NULL if something went wrong. * * @unittest: 1309 @@ -106,15 +107,15 @@ struct Curl_tree *Curl_splayinsert(struct curltime i, ~0, -1 }; /* will *NEVER* appear */ - if(!node) - return t; + DEBUGASSERT(node); if(t) { t = Curl_splay(i, t); + DEBUGASSERT(t); if(compare(i, t->key) == 0) { - /* There already exists a node in the tree with the very same key. Build - a doubly-linked circular list of nodes. We add the new 'node' struct - to the end of this list. */ + /* There already exists a node in the tree with the same key. Build a + doubly-linked circular list of nodes. We add the new 'node' struct to + the end of this list. */ node->key = KEY_NOTUSED; /* we set the key in the sub node to NOTUSED to quickly identify this node as a subnode */ @@ -150,7 +151,7 @@ struct Curl_tree *Curl_splayinsert(struct curltime i, } /* Finds and deletes the best-fit node from the tree. Return a pointer to the - resulting tree. best-fit means the smallest node if it is not larger than + resulting tree. best-fit means the smallest node if it is not larger than the key */ struct Curl_tree *Curl_splaygetbest(struct curltime i, struct Curl_tree *t, @@ -166,6 +167,7 @@ struct Curl_tree *Curl_splaygetbest(struct curltime i, /* find smallest */ t = Curl_splay(tv_zero, t); + DEBUGASSERT(t); if(compare(i, t->key) < 0) { /* even the smallest is too big */ *removed = NULL; @@ -197,13 +199,13 @@ struct Curl_tree *Curl_splaygetbest(struct curltime i, } -/* Deletes the very node we point out from the tree if it's there. Stores a +/* Deletes the node we point out from the tree if it is there. Stores a * pointer to the new resulting tree in 'newroot'. * * Returns zero on success and non-zero on errors! * When returning error, it does not touch the 'newroot' pointer. * - * NOTE: when the last node of the tree is removed, there's no tree left so + * NOTE: when the last node of the tree is removed, there is no tree left so * 'newroot' will be made to point to NULL. * * @unittest: 1309 @@ -217,9 +219,11 @@ int Curl_splayremove(struct Curl_tree *t, }; /* will *NEVER* appear */ struct Curl_tree *x; - if(!t || !removenode) + if(!t) return 1; + DEBUGASSERT(removenode); + if(compare(KEY_NOTUSED, removenode->key) == 0) { /* Key set to NOTUSED means it is a subnode within a 'same' linked list and thus we can unlink it easily. */ @@ -238,10 +242,11 @@ int Curl_splayremove(struct Curl_tree *t, } t = Curl_splay(removenode->key, t); + DEBUGASSERT(t); /* First make sure that we got the same root node as the one we want to remove, as otherwise we might be trying to remove a node that - isn't actually in the tree. + is not actually in the tree. We cannot just compare the keys here as a double remove in quick succession of a node with key != KEY_NOTUSED && same != NULL @@ -249,7 +254,7 @@ int Curl_splayremove(struct Curl_tree *t, if(t != removenode) return 2; - /* Check if there is a list with identical sizes, as then we're trying to + /* Check if there is a list with identical sizes, as then we are trying to remove the root node of a list of nodes with identical keys. */ x = t->samen; if(x != t) { @@ -268,6 +273,7 @@ int Curl_splayremove(struct Curl_tree *t, x = t->larger; else { x = Curl_splay(removenode->key, t->smaller); + DEBUGASSERT(x); x->larger = t->larger; } } @@ -276,3 +282,16 @@ int Curl_splayremove(struct Curl_tree *t, return 0; } + +/* set and get the custom payload for this tree node */ +void Curl_splayset(struct Curl_tree *node, void *payload) +{ + DEBUGASSERT(node); + node->ptr = payload; +} + +void *Curl_splayget(struct Curl_tree *node) +{ + DEBUGASSERT(node); + return node->ptr; +} diff --git a/deps/curl/lib/splay.h b/deps/curl/lib/splay.h index dd1d07ac2e0..b8c9360e573 100644 --- a/deps/curl/lib/splay.h +++ b/deps/curl/lib/splay.h @@ -26,13 +26,14 @@ #include "curl_setup.h" #include "timeval.h" +/* only use function calls to access this struct */ struct Curl_tree { struct Curl_tree *smaller; /* smaller node */ struct Curl_tree *larger; /* larger node */ struct Curl_tree *samen; /* points to the next node with identical key */ struct Curl_tree *samep; /* points to the prev node with identical key */ - struct curltime key; /* this node's "sort" key */ - void *payload; /* data the splay code doesn't care about */ + struct curltime key; /* this node's "sort" key */ + void *ptr; /* data the splay code does not care about */ }; struct Curl_tree *Curl_splay(struct curltime i, @@ -50,9 +51,8 @@ int Curl_splayremove(struct Curl_tree *t, struct Curl_tree *removenode, struct Curl_tree **newroot); -#define Curl_splaycomparekeys(i,j) ( ((i.tv_sec) < (j.tv_sec)) ? -1 : \ - ( ((i.tv_sec) > (j.tv_sec)) ? 1 : \ - ( ((i.tv_usec) < (j.tv_usec)) ? -1 : \ - ( ((i.tv_usec) > (j.tv_usec)) ? 1 : 0)))) +/* set and get the custom payload for this tree node */ +void Curl_splayset(struct Curl_tree *node, void *payload); +void *Curl_splayget(struct Curl_tree *node); #endif /* HEADER_CURL_SPLAY_H */ diff --git a/deps/curl/lib/strcase.c b/deps/curl/lib/strcase.c index 7c0b4ef9095..b22dd31fc86 100644 --- a/deps/curl/lib/strcase.c +++ b/deps/curl/lib/strcase.c @@ -71,7 +71,7 @@ static const unsigned char tolowermap[256] = { altered by the current locale. */ char Curl_raw_toupper(char in) { - return touppermap[(unsigned char) in]; + return (char)touppermap[(unsigned char) in]; } @@ -79,7 +79,7 @@ char Curl_raw_toupper(char in) altered by the current locale. */ char Curl_raw_tolower(char in) { - return tolowermap[(unsigned char) in]; + return (char)tolowermap[(unsigned char) in]; } /* @@ -93,12 +93,12 @@ static int casecompare(const char *first, const char *second) { while(*first && *second) { if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) - /* get out of the loop as soon as they don't match */ + /* get out of the loop as soon as they do not match */ return 0; first++; second++; } - /* If we're here either the strings are the same or the length is different. + /* If we are here either the strings are the same or the length is different. We can just test if the "current" character is non-zero for one and zero for the other. Note that the characters may not be exactly the same even if they match, we only want to compare zero-ness. */ @@ -141,8 +141,8 @@ int curl_strnequal(const char *first, const char *second, size_t max) /* if both pointers are NULL then treat them as equal if max is non-zero */ return (NULL == first && NULL == second && max); } -/* Copy an upper case version of the string from src to dest. The - * strings may overlap. No more than n characters of the string are copied +/* Copy an upper case version of the string from src to dest. The + * strings may overlap. No more than n characters of the string are copied * (including any NUL) and the destination string will NOT be * NUL-terminated if that limit is reached. */ @@ -156,8 +156,8 @@ void Curl_strntoupper(char *dest, const char *src, size_t n) } while(*src++ && --n); } -/* Copy a lower case version of the string from src to dest. The - * strings may overlap. No more than n characters of the string are copied +/* Copy a lower case version of the string from src to dest. The + * strings may overlap. No more than n characters of the string are copied * (including any NUL) and the destination string will NOT be * NUL-terminated if that limit is reached. */ diff --git a/deps/curl/lib/strdup.c b/deps/curl/lib/strdup.c index 07a61391ae5..299c9cc36b7 100644 --- a/deps/curl/lib/strdup.c +++ b/deps/curl/lib/strdup.c @@ -26,7 +26,7 @@ #include -#ifdef WIN32 +#ifdef _WIN32 #include #endif @@ -56,7 +56,7 @@ char *Curl_strdup(const char *str) } #endif -#ifdef WIN32 +#ifdef _WIN32 /*************************************************************************** * * Curl_wcsdup(source) @@ -99,6 +99,26 @@ void *Curl_memdup(const void *src, size_t length) return buffer; } +/*************************************************************************** + * + * Curl_memdup0(source, length) + * + * Copies the 'source' string to a newly allocated buffer (that is returned). + * Copies 'length' bytes then adds a null terminator. + * + * Returns the new pointer or NULL on failure. + * + ***************************************************************************/ +void *Curl_memdup0(const char *src, size_t length) +{ + char *buf = malloc(length + 1); + if(!buf) + return NULL; + memcpy(buf, src, length); + buf[length] = 0; + return buf; +} + /*************************************************************************** * * Curl_saferealloc(ptr, size) diff --git a/deps/curl/lib/strdup.h b/deps/curl/lib/strdup.h index c3430b54d56..238a2611f64 100644 --- a/deps/curl/lib/strdup.h +++ b/deps/curl/lib/strdup.h @@ -28,10 +28,11 @@ #ifndef HAVE_STRDUP char *Curl_strdup(const char *str); #endif -#ifdef WIN32 +#ifdef _WIN32 wchar_t* Curl_wcsdup(const wchar_t* src); #endif void *Curl_memdup(const void *src, size_t buffer_length); void *Curl_saferealloc(void *ptr, size_t size); +void *Curl_memdup0(const char *src, size_t length); #endif /* HEADER_CURL_STRDUP_H */ diff --git a/deps/curl/lib/strerror.c b/deps/curl/lib/strerror.c index be419141404..7d326e16f82 100644 --- a/deps/curl/lib/strerror.c +++ b/deps/curl/lib/strerror.c @@ -48,7 +48,7 @@ #include "curl_memory.h" #include "memdebug.h" -#if defined(WIN32) || defined(_WIN32_WCE) +#if defined(_WIN32) || defined(_WIN32_WCE) #define PRESERVE_WINDOWS_ERROR_CODE #endif @@ -74,13 +74,13 @@ curl_easy_strerror(CURLcode error) " this libcurl due to a build-time decision."; case CURLE_COULDNT_RESOLVE_PROXY: - return "Couldn't resolve proxy name"; + return "Could not resolve proxy name"; case CURLE_COULDNT_RESOLVE_HOST: - return "Couldn't resolve host name"; + return "Could not resolve hostname"; case CURLE_COULDNT_CONNECT: - return "Couldn't connect to server"; + return "Could not connect to server"; case CURLE_WEIRD_SERVER_REPLY: return "Weird server reply"; @@ -107,19 +107,19 @@ curl_easy_strerror(CURLcode error) return "FTP: unknown 227 response format"; case CURLE_FTP_CANT_GET_HOST: - return "FTP: can't figure out the host in the PASV response"; + return "FTP: cannot figure out the host in the PASV response"; case CURLE_HTTP2: return "Error in the HTTP2 framing layer"; case CURLE_FTP_COULDNT_SET_TYPE: - return "FTP: couldn't set file type"; + return "FTP: could not set file type"; case CURLE_PARTIAL_FILE: return "Transferred a partial file"; case CURLE_FTP_COULDNT_RETR_FILE: - return "FTP: couldn't retrieve (RETR failed) the specified file"; + return "FTP: could not retrieve (RETR failed) the specified file"; case CURLE_QUOTE_ERROR: return "Quote command returned error"; @@ -158,10 +158,10 @@ curl_easy_strerror(CURLcode error) return "SSL connect error"; case CURLE_BAD_DOWNLOAD_RESUME: - return "Couldn't resume download"; + return "Could not resume download"; case CURLE_FILE_COULDNT_READ_FILE: - return "Couldn't read a file:// file"; + return "Could not read a file:// file"; case CURLE_LDAP_CANNOT_BIND: return "LDAP: cannot bind"; @@ -212,7 +212,7 @@ curl_easy_strerror(CURLcode error) return "Problem with the local SSL certificate"; case CURLE_SSL_CIPHER: - return "Couldn't use specified SSL cipher"; + return "Could not use specified SSL cipher"; case CURLE_PEER_FAILED_VERIFICATION: return "SSL peer certificate or SSH remote key was not OK"; @@ -319,6 +319,12 @@ curl_easy_strerror(CURLcode error) case CURLE_UNRECOVERABLE_POLL: return "Unrecoverable error in select/poll"; + case CURLE_TOO_LARGE: + return "A value or data field grew larger than allowed"; + + case CURLE_ECH_REQUIRED: + return "ECH attempted but failed"; + /* error codes not used by current libcurl */ case CURLE_OBSOLETE20: case CURLE_OBSOLETE24: @@ -339,16 +345,15 @@ curl_easy_strerror(CURLcode error) /* * By using a switch, gcc -Wall will complain about enum values * which do not appear, helping keep this function up-to-date. - * By using gcc -Wall -Werror, you can't forget. + * By using gcc -Wall -Werror, you cannot forget. * - * A table would not have the same benefit. Most compilers will - * generate code very similar to a table in any case, so there - * is little performance gain from a table. And something is broken - * for the user's application, anyways, so does it matter how fast - * it _doesn't_ work? + * A table would not have the same benefit. Most compilers will generate + * code similar to a table in any case, so there is little performance gain + * from a table. Something is broken for the user's application, anyways, so + * does it matter how fast it _does not_ work? * - * The line number for the error will be near this comment, which - * is why it is here, and not at the start of the switch. + * The line number for the error will be near this comment, which is why it + * is here, and not at the start of the switch. */ return "Unknown error"; #else @@ -553,6 +558,9 @@ curl_url_strerror(CURLUcode error) case CURLUE_LACKS_IDN: return "libcurl lacks IDN support"; + case CURLUE_TOO_LARGE: + return "A value or data field is larger than allowed"; + case CURLUE_LAST: break; } @@ -572,10 +580,11 @@ curl_url_strerror(CURLUcode error) * Returns NULL if no error message was found for error code. */ static const char * -get_winsock_error (int err, char *buf, size_t len) +get_winsock_error(int err, char *buf, size_t len) { #ifndef CURL_DISABLE_VERBOSE_STRINGS const char *p; + size_t alen; #endif if(!len) @@ -755,14 +764,15 @@ get_winsock_error (int err, char *buf, size_t len) default: return NULL; } - strncpy(buf, p, len); - buf [len-1] = '\0'; + alen = strlen(p); + if(alen < len) + strcpy(buf, p); return buf; #endif } #endif /* USE_WINSOCK */ -#if defined(WIN32) || defined(_WIN32_WCE) +#if defined(_WIN32) || defined(_WIN32_WCE) /* This is a helper function for Curl_strerror that converts Windows API error * codes (GetLastError) to error messages. * Returns NULL if no error message was found for error code. @@ -784,7 +794,7 @@ get_winapi_error(int err, char *buf, size_t buflen) expect the local codepage (eg fprintf, failf, infof). FormatMessageW -> wcstombs is used for Windows CE compatibility. */ if(FormatMessageW((FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS), NULL, err, + FORMAT_MESSAGE_IGNORE_INSERTS), NULL, (DWORD)err, LANG_NEUTRAL, wbuf, sizeof(wbuf)/sizeof(wchar_t), NULL)) { size_t written = wcstombs(buf, wbuf, buflen - 1); if(written != (size_t)-1) @@ -804,7 +814,7 @@ get_winapi_error(int err, char *buf, size_t buflen) return (*buf ? buf : NULL); } -#endif /* WIN32 || _WIN32_WCE */ +#endif /* _WIN32 || _WIN32_WCE */ /* * Our thread-safe and smart strerror() replacement. @@ -812,9 +822,9 @@ get_winapi_error(int err, char *buf, size_t buflen) * The 'err' argument passed in to this function MUST be a true errno number * as reported on this system. We do no range checking on the number before * we pass it to the "number-to-message" conversion function and there might - * be systems that don't do proper range checking in there themselves. + * be systems that do not do proper range checking in there themselves. * - * We don't do range checking (on systems other than Windows) since there is + * We do not do range checking (on systems other than Windows) since there is * no good reliable and portable way to do it. * * On Windows different types of error codes overlap. This function has an @@ -832,32 +842,30 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) #endif int old_errno = errno; char *p; - size_t max; if(!buflen) return NULL; -#ifndef WIN32 +#ifndef _WIN32 DEBUGASSERT(err >= 0); #endif - max = buflen - 1; *buf = '\0'; -#if defined(WIN32) || defined(_WIN32_WCE) -#if defined(WIN32) +#if defined(_WIN32) || defined(_WIN32_WCE) +#if defined(_WIN32) /* 'sys_nerr' is the maximum errno number, it is not widely portable */ if(err >= 0 && err < sys_nerr) - strncpy(buf, sys_errlist[err], max); + msnprintf(buf, buflen, "%s", sys_errlist[err]); else #endif { if( #ifdef USE_WINSOCK - !get_winsock_error(err, buf, max) && + !get_winsock_error(err, buf, buflen) && #endif - !get_winapi_error((DWORD)err, buf, max)) - msnprintf(buf, max, "Unknown error %d (%#x)", err, err); + !get_winapi_error(err, buf, buflen)) + msnprintf(buf, buflen, "Unknown error %d (%#x)", err, err); } #else /* not Windows coming up */ @@ -867,9 +875,9 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) * storage is supplied via 'strerrbuf' and 'buflen' to hold the generated * message string, or EINVAL if 'errnum' is not a valid error number. */ - if(0 != strerror_r(err, buf, max)) { + if(0 != strerror_r(err, buf, buflen)) { if('\0' == buf[0]) - msnprintf(buf, max, "Unknown error %d", err); + msnprintf(buf, buflen, "Unknown error %d", err); } #elif defined(HAVE_STRERROR_R) && defined(HAVE_GLIBC_STRERROR_R) /* @@ -881,25 +889,23 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) char buffer[256]; char *msg = strerror_r(err, buffer, sizeof(buffer)); if(msg) - strncpy(buf, msg, max); + msnprintf(buf, buflen, "%s", msg); else - msnprintf(buf, max, "Unknown error %d", err); + msnprintf(buf, buflen, "Unknown error %d", err); } #else { /* !checksrc! disable STRERROR 1 */ const char *msg = strerror(err); if(msg) - strncpy(buf, msg, max); + msnprintf(buf, buflen, "%s", msg); else - msnprintf(buf, max, "Unknown error %d", err); + msnprintf(buf, buflen, "Unknown error %d", err); } #endif #endif /* end of not Windows */ - buf[max] = '\0'; /* make sure the string is null-terminated */ - /* strip trailing '\r\n' or '\n'. */ p = strrchr(buf, '\n'); if(p && (p - buf) >= 2) @@ -923,7 +929,7 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) * Curl_winapi_strerror: * Variant of Curl_strerror if the error code is definitely Windows API. */ -#if defined(WIN32) || defined(_WIN32_WCE) +#if defined(_WIN32) || defined(_WIN32_WCE) const char *Curl_winapi_strerror(DWORD err, char *buf, size_t buflen) { #ifdef PRESERVE_WINDOWS_ERROR_CODE @@ -937,14 +943,14 @@ const char *Curl_winapi_strerror(DWORD err, char *buf, size_t buflen) *buf = '\0'; #ifndef CURL_DISABLE_VERBOSE_STRINGS - if(!get_winapi_error(err, buf, buflen)) { + if(!get_winapi_error((int)err, buf, buflen)) { msnprintf(buf, buflen, "Unknown error %lu (0x%08lX)", err, err); } #else { const char *txt = (err == ERROR_SUCCESS) ? "No error" : "Error"; - strncpy(buf, txt, buflen); - buf[buflen - 1] = '\0'; + if(strlen(txt) < buflen) + strcpy(buf, txt); } #endif @@ -958,7 +964,7 @@ const char *Curl_winapi_strerror(DWORD err, char *buf, size_t buflen) return buf; } -#endif /* WIN32 || _WIN32_WCE */ +#endif /* _WIN32 || _WIN32_WCE */ #ifdef USE_WINDOWS_SSPI /* @@ -986,6 +992,10 @@ const char *Curl_sspi_strerror(int err, char *buf, size_t buflen) break; #define SEC2TXT(sec) case sec: txt = #sec; break SEC2TXT(CRYPT_E_REVOKED); + SEC2TXT(CRYPT_E_NO_REVOCATION_DLL); + SEC2TXT(CRYPT_E_NO_REVOCATION_CHECK); + SEC2TXT(CRYPT_E_REVOCATION_OFFLINE); + SEC2TXT(CRYPT_E_NOT_IN_REVOCATION_DATABASE); SEC2TXT(SEC_E_ALGORITHM_MISMATCH); SEC2TXT(SEC_E_BAD_BINDINGS); SEC2TXT(SEC_E_BAD_PKGID); @@ -1077,17 +1087,11 @@ const char *Curl_sspi_strerror(int err, char *buf, size_t buflen) err); } else { - char txtbuf[80]; char msgbuf[256]; - - msnprintf(txtbuf, sizeof(txtbuf), "%s (0x%08X)", txt, err); - if(get_winapi_error(err, msgbuf, sizeof(msgbuf))) - msnprintf(buf, buflen, "%s - %s", txtbuf, msgbuf); - else { - strncpy(buf, txtbuf, buflen); - buf[buflen - 1] = '\0'; - } + msnprintf(buf, buflen, "%s (0x%08X) - %s", txt, err, msgbuf); + else + msnprintf(buf, buflen, "%s (0x%08X)", txt, err); } #else @@ -1095,8 +1099,8 @@ const char *Curl_sspi_strerror(int err, char *buf, size_t buflen) txt = "No error"; else txt = "Error"; - strncpy(buf, txt, buflen); - buf[buflen - 1] = '\0'; + if(buflen > strlen(txt)) + strcpy(buf, txt); #endif if(errno != old_errno) diff --git a/deps/curl/lib/strerror.h b/deps/curl/lib/strerror.h index 399712f8ebb..68068673453 100644 --- a/deps/curl/lib/strerror.h +++ b/deps/curl/lib/strerror.h @@ -29,7 +29,7 @@ #define STRERROR_LEN 256 /* a suitable length */ const char *Curl_strerror(int err, char *buf, size_t buflen); -#if defined(WIN32) || defined(_WIN32_WCE) +#if defined(_WIN32) || defined(_WIN32_WCE) const char *Curl_winapi_strerror(DWORD err, char *buf, size_t buflen); #endif #ifdef USE_WINDOWS_SSPI diff --git a/deps/curl/lib/strtok.c b/deps/curl/lib/strtok.c index d8e1e8183f4..d2cc71c47dc 100644 --- a/deps/curl/lib/strtok.c +++ b/deps/curl/lib/strtok.c @@ -65,4 +65,4 @@ Curl_strtok_r(char *ptr, const char *sep, char **end) return NULL; } -#endif /* this was only compiled if strtok_r wasn't present */ +#endif /* this was only compiled if strtok_r was not present */ diff --git a/deps/curl/lib/strtoofft.c b/deps/curl/lib/strtoofft.c index 077b25792e0..f1c7ba27110 100644 --- a/deps/curl/lib/strtoofft.c +++ b/deps/curl/lib/strtoofft.c @@ -31,7 +31,7 @@ * NOTE: * * In the ISO C standard (IEEE Std 1003.1), there is a strtoimax() function we - * could use in case strtoll() doesn't exist... See + * could use in case strtoll() does not exist... See * https://www.opengroup.org/onlinepubs/009695399/functions/strtoimax.html */ @@ -73,17 +73,16 @@ static const char valchars[] = static int get_char(char c, int base); /** - * Custom version of the strtooff function. This extracts a curl_off_t + * Custom version of the strtooff function. This extracts a curl_off_t * value from the given input string and returns it. */ static curl_off_t strtooff(const char *nptr, char **endptr, int base) { char *end; - int is_negative = 0; - int overflow; + bool is_negative = FALSE; + bool overflow = FALSE; int i; curl_off_t value = 0; - curl_off_t newval; /* Skip leading whitespace. */ end = (char *)nptr; @@ -93,7 +92,7 @@ static curl_off_t strtooff(const char *nptr, char **endptr, int base) /* Handle the sign, if any. */ if(end[0] == '-') { - is_negative = 1; + is_negative = TRUE; end++; } else if(end[0] == '+') { @@ -121,27 +120,23 @@ static curl_off_t strtooff(const char *nptr, char **endptr, int base) } } - /* Matching strtol, if the base is 0 and it doesn't look like - * the number is octal or hex, we assume it's base 10. + /* Matching strtol, if the base is 0 and it does not look like + * the number is octal or hex, we assume it is base 10. */ if(base == 0) { base = 10; } /* Loop handling digits. */ - value = 0; - overflow = 0; for(i = get_char(end[0], base); i != -1; end++, i = get_char(end[0], base)) { - newval = base * value + i; - if(newval < value) { - /* We've overflowed. */ - overflow = 1; + + if(value > (CURL_OFF_T_MAX - i) / base) { + overflow = TRUE; break; } - else - value = newval; + value = base * value + i; } if(!overflow) { @@ -173,7 +168,7 @@ static curl_off_t strtooff(const char *nptr, char **endptr, int base) * @param c the character to interpret according to base * @param base the base in which to interpret c * - * @return the value of c in base, or -1 if c isn't in range + * @return the value of c in base, or -1 if c is not in range */ static int get_char(char c, int base) { @@ -209,15 +204,15 @@ static int get_char(char c, int base) return value; } -#endif /* Only present if we need strtoll, but don't have it. */ +#endif /* Only present if we need strtoll, but do not have it. */ /* - * Parse a *positive* up to 64 bit number written in ascii. + * Parse a *positive* up to 64-bit number written in ASCII. */ CURLofft curlx_strtoofft(const char *str, char **endp, int base, curl_off_t *num) { - char *end; + char *end = NULL; curl_off_t number; errno = 0; *num = 0; /* clear by default */ @@ -227,7 +222,7 @@ CURLofft curlx_strtoofft(const char *str, char **endp, int base, str++; if(('-' == *str) || (ISSPACE(*str))) { if(endp) - *endp = (char *)str; /* didn't actually move */ + *endp = (char *)str; /* did not actually move */ return CURL_OFFT_INVAL; /* nothing parsed */ } number = strtooff(str, &end, base); diff --git a/deps/curl/lib/strtoofft.h b/deps/curl/lib/strtoofft.h index 34d293ba382..71808b719c9 100644 --- a/deps/curl/lib/strtoofft.h +++ b/deps/curl/lib/strtoofft.h @@ -30,7 +30,7 @@ * Determine which string to integral data type conversion function we use * to implement string conversion to our curl_off_t integral data type. * - * Notice that curl_off_t might be 64 or 32 bit wide, and that it might use + * Notice that curl_off_t might be 64 or 32 bits wide, and that it might use * an underlying data type which might be 'long', 'int64_t', 'long long' or * '__int64' and more remotely other data types. * diff --git a/deps/curl/lib/system_win32.c b/deps/curl/lib/system_win32.c index 0cdaf3b2fe7..5ab711871e5 100644 --- a/deps/curl/lib/system_win32.c +++ b/deps/curl/lib/system_win32.c @@ -24,7 +24,7 @@ #include "curl_setup.h" -#if defined(WIN32) +#if defined(_WIN32) #include #include "system_win32.h" @@ -45,11 +45,11 @@ static HMODULE s_hIpHlpApiDll = NULL; /* Pointer to the if_nametoindex function */ IF_NAMETOINDEX_FN Curl_if_nametoindex = NULL; -/* Curl_win32_init() performs win32 global initialization */ +/* Curl_win32_init() performs Win32 global initialization */ CURLcode Curl_win32_init(long flags) { /* CURL_GLOBAL_WIN32 controls the *optional* part of the initialization which - is just for Winsock at the moment. Any required win32 initialization + is just for Winsock at the moment. Any required Win32 initialization should take place after this block. */ if(flags & CURL_GLOBAL_WIN32) { #ifdef USE_WINSOCK @@ -61,7 +61,7 @@ CURLcode Curl_win32_init(long flags) res = WSAStartup(wVersionRequested, &wsaData); if(res) - /* Tell the user that we couldn't find a usable */ + /* Tell the user that we could not find a usable */ /* winsock.dll. */ return CURLE_FAILED_INIT; @@ -73,7 +73,7 @@ CURLcode Curl_win32_init(long flags) if(LOBYTE(wsaData.wVersion) != LOBYTE(wVersionRequested) || HIBYTE(wsaData.wVersion) != HIBYTE(wVersionRequested) ) { - /* Tell the user that we couldn't find a usable */ + /* Tell the user that we could not find a usable */ /* winsock.dll. */ WSACleanup(); @@ -175,11 +175,11 @@ typedef HMODULE (APIENTRY *LOADLIBRARYEX_FN)(LPCTSTR, HANDLE, DWORD); */ HMODULE Curl_load_library(LPCTSTR filename) { -#ifndef CURL_WINDOWS_APP +#ifndef CURL_WINDOWS_UWP HMODULE hModule = NULL; LOADLIBRARYEX_FN pLoadLibraryEx = NULL; - /* Get a handle to kernel32 so we can access it's functions at runtime */ + /* Get a handle to kernel32 so we can access its functions at runtime */ HMODULE hKernel32 = GetModuleHandle(TEXT("kernel32")); if(!hKernel32) return NULL; @@ -190,7 +190,7 @@ HMODULE Curl_load_library(LPCTSTR filename) CURLX_FUNCTION_CAST(LOADLIBRARYEX_FN, (GetProcAddress(hKernel32, LOADLIBARYEX))); - /* Detect if there's already a path in the filename and load the library if + /* Detect if there is already a path in the filename and load the library if there is. Note: Both back slashes and forward slashes have been supported since the earlier days of DOS at an API level although they are not supported by command prompt */ @@ -232,10 +232,10 @@ HMODULE Curl_load_library(LPCTSTR filename) } return hModule; #else - /* the Universal Windows Platform (UWP) can't do this */ + /* the Universal Windows Platform (UWP) cannot do this */ (void)filename; return NULL; #endif } -#endif /* WIN32 */ +#endif /* _WIN32 */ diff --git a/deps/curl/lib/system_win32.h b/deps/curl/lib/system_win32.h index 6482643fab9..024d959f327 100644 --- a/deps/curl/lib/system_win32.h +++ b/deps/curl/lib/system_win32.h @@ -26,7 +26,9 @@ #include "curl_setup.h" -#if defined(WIN32) +#if defined(_WIN32) + +#include extern LARGE_INTEGER Curl_freq; extern bool Curl_isVistaOrGreater; @@ -42,8 +44,8 @@ extern IF_NAMETOINDEX_FN Curl_if_nametoindex; /* This is used to dynamically load DLLs */ HMODULE Curl_load_library(LPCTSTR filename); -#else /* WIN32 */ +#else /* _WIN32 */ #define Curl_win32_init(x) CURLE_OK -#endif /* !WIN32 */ +#endif /* !_WIN32 */ #endif /* HEADER_CURL_SYSTEM_WIN32_H */ diff --git a/deps/curl/lib/telnet.c b/deps/curl/lib/telnet.c index 850f88c1ecc..64d552d1592 100644 --- a/deps/curl/lib/telnet.c +++ b/deps/curl/lib/telnet.c @@ -154,12 +154,13 @@ struct TELNET { int himq[256]; int him_preferred[256]; int subnegotiation[256]; - char subopt_ttype[32]; /* Set with suboption TTYPE */ - char subopt_xdisploc[128]; /* Set with suboption XDISPLOC */ + char *subopt_ttype; /* Set with suboption TTYPE */ + char *subopt_xdisploc; /* Set with suboption XDISPLOC */ unsigned short subopt_wsx; /* Set with suboption NAWS */ unsigned short subopt_wsy; /* Set with suboption NAWS */ TelnetReceive telrcv_state; struct curl_slist *telnet_vars; /* Environment variables */ + struct dynbuf out; /* output buffer */ /* suboptions */ unsigned char subbuffer[SUBBUFSIZE]; @@ -172,7 +173,7 @@ struct TELNET { */ const struct Curl_handler Curl_handler_telnet = { - "TELNET", /* scheme */ + "telnet", /* scheme */ ZERO_NULL, /* setup_connection */ telnet_do, /* do_it */ telnet_done, /* done */ @@ -185,7 +186,8 @@ const struct Curl_handler Curl_handler_telnet = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_TELNET, /* defport */ @@ -204,6 +206,7 @@ CURLcode init_telnet(struct Curl_easy *data) if(!tn) return CURLE_OUT_OF_MEMORY; + Curl_dyn_init(&tn->out, 0xffff); data->req.p.telnet = tn; /* make us known */ tn->telrcv_state = CURL_TS_DATA; @@ -668,7 +671,7 @@ static void printsub(struct Curl_easy *data, if(data->set.verbose) { unsigned int i = 0; if(direction) { - infof(data, "%s IAC SB ", (direction == '<')? "RCVD":"SENT"); + infof(data, "%s IAC SB ", (direction == '<') ? "RCVD" : "SENT"); if(length >= 3) { int j; @@ -718,8 +721,8 @@ static void printsub(struct Curl_easy *data, switch(pointer[0]) { case CURL_TELOPT_NAWS: if(length > 4) - infof(data, "Width: %d ; Height: %d", (pointer[1]<<8) | pointer[2], - (pointer[3]<<8) | pointer[4]); + infof(data, "Width: %d ; Height: %d", (pointer[1] << 8) | pointer[2], + (pointer[3] << 8) | pointer[4]); break; default: switch(pointer[1]) { @@ -795,12 +798,14 @@ static CURLcode check_telnet_options(struct Curl_easy *data) struct TELNET *tn = data->req.p.telnet; CURLcode result = CURLE_OK; - /* Add the user name as an environment variable if it + /* Add the username as an environment variable if it was given on the command line */ if(data->state.aptr.user) { char buffer[256]; - if(str_is_nonascii(data->conn->user)) + if(str_is_nonascii(data->conn->user)) { + DEBUGF(infof(data, "set a non ASCII username in telnet")); return CURLE_BAD_FUNCTION_ARGUMENT; + } msnprintf(buffer, sizeof(buffer), "USER,%s", data->conn->user); beg = curl_slist_append(tn->telnet_vars, buffer); if(!beg) { @@ -826,23 +831,21 @@ static CURLcode check_telnet_options(struct Curl_easy *data) case 5: /* Terminal type */ if(strncasecompare(option, "TTYPE", 5)) { - strncpy(tn->subopt_ttype, arg, 31); - tn->subopt_ttype[31] = 0; /* String termination */ + tn->subopt_ttype = arg; tn->us_preferred[CURL_TELOPT_TTYPE] = CURL_YES; + break; } - else - result = CURLE_UNKNOWN_OPTION; + result = CURLE_UNKNOWN_OPTION; break; case 8: /* Display variable */ if(strncasecompare(option, "XDISPLOC", 8)) { - strncpy(tn->subopt_xdisploc, arg, 127); - tn->subopt_xdisploc[127] = 0; /* String termination */ + tn->subopt_xdisploc = arg; tn->us_preferred[CURL_TELOPT_XDISPLOC] = CURL_YES; + break; } - else - result = CURLE_UNKNOWN_OPTION; + result = CURLE_UNKNOWN_OPTION; break; case 7: @@ -1182,12 +1185,12 @@ CURLcode telrcv(struct Curl_easy *data, if(c != CURL_SE) { if(c != CURL_IAC) { /* - * This is an error. We only expect to get "IAC IAC" or "IAC SE". - * Several things may have happened. An IAC was not doubled, the + * This is an error. We only expect to get "IAC IAC" or "IAC SE". + * Several things may have happened. An IAC was not doubled, the * IAC SE was left off, or another option got inserted into the - * suboption are all possibilities. If we assume that the IAC was + * suboption are all possibilities. If we assume that the IAC was * not doubled, and really the IAC SE was left off, we could get - * into an infinite loop here. So, instead, we terminate the + * into an infinite loop here. So, instead, we terminate the * suboption, and process the partial suboption if we can. */ CURL_SB_ACCUM(tn, CURL_IAC); @@ -1223,37 +1226,37 @@ CURLcode telrcv(struct Curl_easy *data, static CURLcode send_telnet_data(struct Curl_easy *data, char *buffer, ssize_t nread) { - ssize_t escapes, i, outlen; - unsigned char *outbuf = NULL; + size_t i, outlen; + unsigned char *outbuf; CURLcode result = CURLE_OK; - ssize_t bytes_written, total_written; + size_t bytes_written; + size_t total_written = 0; struct connectdata *conn = data->conn; + struct TELNET *tn = data->req.p.telnet; - /* Determine size of new buffer after escaping */ - escapes = 0; - for(i = 0; i < nread; i++) - if((unsigned char)buffer[i] == CURL_IAC) - escapes++; - outlen = nread + escapes; + DEBUGASSERT(tn); + DEBUGASSERT(nread > 0); + if(nread < 0) + return CURLE_TOO_LARGE; - if(outlen == nread) - outbuf = (unsigned char *)buffer; - else { - ssize_t j; - outbuf = malloc(nread + escapes + 1); - if(!outbuf) - return CURLE_OUT_OF_MEMORY; + if(memchr(buffer, CURL_IAC, nread)) { + /* only use the escape buffer when necessary */ + Curl_dyn_reset(&tn->out); - j = 0; - for(i = 0; i < nread; i++) { - outbuf[j++] = (unsigned char)buffer[i]; - if((unsigned char)buffer[i] == CURL_IAC) - outbuf[j++] = CURL_IAC; + for(i = 0; i < (size_t)nread && !result; i++) { + result = Curl_dyn_addn(&tn->out, &buffer[i], 1); + if(!result && ((unsigned char)buffer[i] == CURL_IAC)) + /* IAC is FF in hex */ + result = Curl_dyn_addn(&tn->out, "\xff", 1); } - outbuf[j] = '\0'; - } - total_written = 0; + outlen = Curl_dyn_len(&tn->out); + outbuf = Curl_dyn_uptr(&tn->out); + } + else { + outlen = (size_t)nread; + outbuf = (unsigned char *)buffer; + } while(!result && total_written < outlen) { /* Make sure socket is writable to avoid EWOULDBLOCK condition */ struct pollfd pfd[1]; @@ -1266,19 +1269,13 @@ static CURLcode send_telnet_data(struct Curl_easy *data, break; default: /* write! */ bytes_written = 0; - result = Curl_nwrite(data, FIRSTSOCKET, - outbuf + total_written, - outlen - total_written, - &bytes_written); + result = Curl_xfer_send(data, outbuf + total_written, + outlen - total_written, FALSE, &bytes_written); total_written += bytes_written; break; } } - /* Free malloc copy if escaped */ - if(outbuf != (unsigned char *)buffer) - free(outbuf); - return result; } @@ -1294,6 +1291,7 @@ static CURLcode telnet_done(struct Curl_easy *data, curl_slist_free_all(tn->telnet_vars); tn->telnet_vars = NULL; + Curl_dyn_free(&tn->out); return CURLE_OK; } @@ -1321,7 +1319,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) ssize_t nread; struct curltime now; bool keepon = TRUE; - char *buf = data->state.buffer; + char buffer[4*1024]; struct TELNET *tn; *done = TRUE; /* unconditionally */ @@ -1338,7 +1336,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) #ifdef USE_WINSOCK /* We want to wait for both stdin and the socket. Since - ** the select() function in winsock only works on sockets + ** the select() function in Winsock only works on sockets ** we have to use the WaitForMultipleObjects() call. */ @@ -1349,7 +1347,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) return CURLE_FAILED_INIT; } - /* Tell winsock what events we want to listen to */ + /* Tell Winsock what events we want to listen to */ if(WSAEventSelect(sockfd, event_handle, FD_READ|FD_CLOSE) == SOCKET_ERROR) { WSACloseEvent(event_handle); return CURLE_OK; @@ -1366,7 +1364,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) else use the old WaitForMultipleObjects() way */ if(GetFileType(stdin_handle) == FILE_TYPE_PIPE || data->set.is_fread_set) { - /* Don't wait for stdin_handle, just wait for event_handle */ + /* Do not wait for stdin_handle, just wait for event_handle */ obj_count = 1; /* Check stdin_handle per 100 milliseconds */ wait_timeout = 100; @@ -1378,7 +1376,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) /* Keep on listening and act on events */ while(keepon) { - const DWORD buf_size = (DWORD)data->set.buffer_size; + const DWORD buf_size = (DWORD)sizeof(buffer); DWORD waitret = WaitForMultipleObjects(obj_count, objs, FALSE, wait_timeout); switch(waitret) { @@ -1389,7 +1387,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) if(data->set.is_fread_set) { size_t n; /* read from user-supplied method */ - n = data->state.fread_func(buf, 1, buf_size, data->state.in); + n = data->state.fread_func(buffer, 1, buf_size, data->state.in); if(n == CURL_READFUNC_ABORT) { keepon = FALSE; result = CURLE_READ_ERROR; @@ -1417,7 +1415,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) if(!readfile_read) break; - if(!ReadFile(stdin_handle, buf, buf_size, + if(!ReadFile(stdin_handle, buffer, buf_size, &readfile_read, NULL)) { keepon = FALSE; result = CURLE_READ_ERROR; @@ -1425,7 +1423,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } } - result = send_telnet_data(data, buf, readfile_read); + result = send_telnet_data(data, buffer, readfile_read); if(result) { keepon = FALSE; break; @@ -1436,14 +1434,14 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) case WAIT_OBJECT_0 + 1: { - if(!ReadFile(stdin_handle, buf, buf_size, + if(!ReadFile(stdin_handle, buffer, buf_size, &readfile_read, NULL)) { keepon = FALSE; result = CURLE_READ_ERROR; break; } - result = send_telnet_data(data, buf, readfile_read); + result = send_telnet_data(data, buffer, readfile_read); if(result) { keepon = FALSE; break; @@ -1465,8 +1463,8 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } if(events.lNetworkEvents & FD_READ) { /* read data from network */ - result = Curl_read(data, sockfd, buf, data->set.buffer_size, &nread); - /* read would've blocked. Loop again */ + result = Curl_xfer_recv(data, buffer, sizeof(buffer), &nread); + /* read would have blocked. Loop again */ if(result == CURLE_AGAIN) break; /* returned not-zero, this an error */ @@ -1481,14 +1479,14 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) break; } - result = telrcv(data, (unsigned char *) buf, nread); + result = telrcv(data, (unsigned char *) buffer, nread); if(result) { keepon = FALSE; break; } /* Negotiate if the peer has started negotiating, - otherwise don't. We don't want to speak telnet with + otherwise do not. We do not want to speak telnet with non-telnet servers, like POP or SMTP. */ if(tn->please_negotiate && !tn->already_negotiated) { negotiate(data); @@ -1531,23 +1529,28 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) pfd[1].events = POLLIN; poll_cnt = 2; interval_ms = 1 * 1000; + if(pfd[1].fd < 0) { + failf(data, "cannot read input"); + result = CURLE_RECV_ERROR; + keepon = FALSE; + } } while(keepon) { DEBUGF(infof(data, "telnet_do, poll %d fds", poll_cnt)); - switch(Curl_poll(pfd, poll_cnt, interval_ms)) { + switch(Curl_poll(pfd, (unsigned int)poll_cnt, interval_ms)) { case -1: /* error, stop reading */ keepon = FALSE; continue; case 0: /* timeout */ pfd[0].revents = 0; pfd[1].revents = 0; - /* FALLTHROUGH */ + FALLTHROUGH(); default: /* read! */ if(pfd[0].revents & POLLIN) { /* read data from network */ - result = Curl_read(data, sockfd, buf, data->set.buffer_size, &nread); - /* read would've blocked. Loop again */ + result = Curl_xfer_recv(data, buffer, sizeof(buffer), &nread); + /* read would have blocked. Loop again */ if(result == CURLE_AGAIN) break; /* returned not-zero, this an error */ @@ -1570,15 +1573,16 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } total_dl += nread; - Curl_pgrsSetDownloadCounter(data, total_dl); - result = telrcv(data, (unsigned char *)buf, nread); + result = Curl_pgrsSetDownloadCounter(data, total_dl); + if(!result) + result = telrcv(data, (unsigned char *)buffer, nread); if(result) { keepon = FALSE; break; } /* Negotiate if the peer has started negotiating, - otherwise don't. We don't want to speak telnet with + otherwise do not. We do not want to speak telnet with non-telnet servers, like POP or SMTP. */ if(tn->please_negotiate && !tn->already_negotiated) { negotiate(data); @@ -1589,12 +1593,12 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) nread = 0; if(poll_cnt == 2) { if(pfd[1].revents & POLLIN) { /* read from in file */ - nread = read(pfd[1].fd, buf, data->set.buffer_size); + nread = read(pfd[1].fd, buffer, sizeof(buffer)); } } else { /* read from user-supplied method */ - nread = (int)data->state.fread_func(buf, 1, data->set.buffer_size, + nread = (int)data->state.fread_func(buffer, 1, sizeof(buffer), data->state.in); if(nread == CURL_READFUNC_ABORT) { keepon = FALSE; @@ -1605,7 +1609,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } if(nread > 0) { - result = send_telnet_data(data, buf, nread); + result = send_telnet_data(data, buffer, nread); if(result) { keepon = FALSE; break; @@ -1635,7 +1639,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } #endif /* mark this as "no further transfer wanted" */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); return result; } diff --git a/deps/curl/lib/tftp.c b/deps/curl/lib/tftp.c index 8ed1b887b4d..e92e5127a56 100644 --- a/deps/curl/lib/tftp.c +++ b/deps/curl/lib/tftp.c @@ -70,8 +70,6 @@ /* RFC2348 allows the block size to be negotiated */ #define TFTP_BLKSIZE_DEFAULT 512 -#define TFTP_BLKSIZE_MIN 8 -#define TFTP_BLKSIZE_MAX 65464 #define TFTP_OPTION_BLKSIZE "blksize" /* from RFC2349: */ @@ -170,7 +168,7 @@ static CURLcode tftp_translate_code(tftp_error_t error); */ const struct Curl_handler Curl_handler_tftp = { - "TFTP", /* scheme */ + "tftp", /* scheme */ tftp_setup_connection, /* setup_connection */ tftp_do, /* do_it */ tftp_done, /* done */ @@ -183,7 +181,8 @@ const struct Curl_handler Curl_handler_tftp = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ tftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_TFTP, /* defport */ @@ -206,7 +205,7 @@ static CURLcode tftp_set_timeouts(struct tftp_state_data *state) { time_t maxtime, timeout; timediff_t timeout_ms; - bool start = (state->state == TFTP_STATE_START) ? TRUE : FALSE; + bool start = (state->state == TFTP_STATE_START); /* Compute drop-dead time */ timeout_ms = Curl_timeleft(state->data, NULL, start); @@ -229,24 +228,23 @@ static CURLcode tftp_set_timeouts(struct tftp_state_data *state) state->retry_max = (int)timeout/5; /* But bound the total number */ - if(state->retry_max<3) + if(state->retry_max < 3) state->retry_max = 3; - if(state->retry_max>50) + if(state->retry_max > 50) state->retry_max = 50; /* Compute the re-ACK interval to suit the timeout */ state->retry_time = (int)(timeout/state->retry_max); - if(state->retry_time<1) + if(state->retry_time < 1) state->retry_time = 1; infof(state->data, - "set timeouts for state %d; Total % " CURL_FORMAT_CURL_OFF_T - ", retry %d maxtry %d", + "set timeouts for state %d; Total % " FMT_OFF_T ", retry %d maxtry %d", (int)state->state, timeout_ms, state->retry_time, state->retry_max); /* init RX time */ - time(&state->rx_time); + state->rx_time = time(NULL); return CURLE_OK; } @@ -316,7 +314,7 @@ static CURLcode tftp_parse_option_ack(struct tftp_state_data *state, const char *tmp = ptr; struct Curl_easy *data = state->data; - /* if OACK doesn't contain blksize option, the default (512) must be used */ + /* if OACK does not contain blksize option, the default (512) must be used */ state->blksize = TFTP_BLKSIZE_DEFAULT; while(tmp < ptr + len) { @@ -350,7 +348,7 @@ static CURLcode tftp_parse_option_ack(struct tftp_state_data *state, return CURLE_TFTP_ILLEGAL; } else if(blksize > state->requested_blksize) { - /* could realloc pkt buffers here, but the spec doesn't call out + /* could realloc pkt buffers here, but the spec does not call out * support for the server requesting a bigger blksize than the client * requests */ failf(data, "%s (%ld)", @@ -435,7 +433,7 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, struct Curl_easy *data = state->data; CURLcode result = CURLE_OK; - /* Set ascii mode if -B flag was used */ + /* Set ASCII mode if -B flag was used */ if(data->state.prefer_ascii) mode = "netascii"; @@ -445,7 +443,7 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, case TFTP_EVENT_TIMEOUT: /* Resend the first packet out */ /* Increment the retry counter, quit if over the limit */ state->retries++; - if(state->retries>state->retry_max) { + if(state->retries > state->retry_max) { state->error = TFTP_ERR_NORESPONSE; state->state = TFTP_STATE_FIN; return result; @@ -454,8 +452,6 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, if(data->state.upload) { /* If we are uploading, send an WRQ */ setpacketevent(&state->spacket, TFTP_EVENT_WRQ); - state->data->req.upload_fromhere = - (char *)state->spacket.data + 4; if(data->state.infilesize != -1) Curl_pgrsSetUploadSize(data, data->state.infilesize); } @@ -464,7 +460,7 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, setpacketevent(&state->spacket, TFTP_EVENT_RRQ); } /* As RFC3617 describes the separator slash is not actually part of the - file name so we skip the always-present first letter of the path + filename so we skip the always-present first letter of the path string. */ result = Curl_urldecode(&state->data->state.up.path[1], 0, &filename, NULL, REJECT_ZERO); @@ -472,9 +468,9 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, return result; if(strlen(filename) > (state->blksize - strlen(mode) - 4)) { - failf(data, "TFTP file name too long"); + failf(data, "TFTP filename too long"); free(filename); - return CURLE_TFTP_ILLEGAL; /* too long file name field */ + return CURLE_TFTP_ILLEGAL; /* too long filename field */ } msnprintf((char *)state->spacket.data + 2, @@ -486,11 +482,9 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, if(!data->set.tftp_no_options) { char buf[64]; /* add tsize option */ - if(data->state.upload && (data->state.infilesize != -1)) - msnprintf(buf, sizeof(buf), "%" CURL_FORMAT_CURL_OFF_T, - data->state.infilesize); - else - strcpy(buf, "0"); /* the destination is large enough */ + msnprintf(buf, sizeof(buf), "%" FMT_OFF_T, + data->state.upload && (data->state.infilesize != -1) ? + data->state.infilesize : 0); result = tftp_option_add(state, &sbytes, (char *)state->spacket.data + sbytes, @@ -530,8 +524,8 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, not have a size_t argument, like older unixes that want an 'int' */ senddata = sendto(state->sockfd, (void *)state->spacket.data, (SEND_TYPE_ARG3)sbytes, 0, - &data->conn->remote_addr->sa_addr, - data->conn->remote_addr->addrlen); + &data->conn->remote_addr->curl_sa_addr, + (curl_socklen_t)data->conn->remote_addr->addrlen); if(senddata != (ssize_t)sbytes) { char buffer[STRERROR_LEN]; failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); @@ -593,7 +587,7 @@ static CURLcode tftp_rx(struct tftp_state_data *state, /* Is this the block we expect? */ rblock = getrpacketblock(&state->rpacket); if(NEXT_BLOCKNUM(state->block) == rblock) { - /* This is the expected block. Reset counters and ACK it. */ + /* This is the expected block. Reset counters and ACK it. */ state->retries = 0; } else if(state->block == rblock) { @@ -629,7 +623,7 @@ static CURLcode tftp_rx(struct tftp_state_data *state, else { state->state = TFTP_STATE_RX; } - time(&state->rx_time); + state->rx_time = time(NULL); break; case TFTP_EVENT_OACK: @@ -647,16 +641,16 @@ static CURLcode tftp_rx(struct tftp_state_data *state, return CURLE_SEND_ERROR; } - /* we're ready to RX data */ + /* we are ready to RX data */ state->state = TFTP_STATE_RX; - time(&state->rx_time); + state->rx_time = time(NULL); break; case TFTP_EVENT_TIMEOUT: /* Increment the retry count and fail if over the limit */ state->retries++; infof(data, - "Timeout waiting for block %d ACK. Retries = %d", + "Timeout waiting for block %d ACK. Retries = %d", NEXT_BLOCKNUM(state->block), state->retries); if(state->retries > state->retry_max) { state->error = TFTP_ERR_TIMEOUT; @@ -668,7 +662,7 @@ static CURLcode tftp_rx(struct tftp_state_data *state, 4, SEND_4TH_ARG, (struct sockaddr *)&state->remote_addr, state->remote_addrlen); - if(sbytes<0) { + if(sbytes < 0) { failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); return CURLE_SEND_ERROR; } @@ -682,8 +676,8 @@ static CURLcode tftp_rx(struct tftp_state_data *state, 4, SEND_4TH_ARG, (struct sockaddr *)&state->remote_addr, state->remote_addrlen); - /* don't bother with the return code, but if the socket is still up we - * should be a good TFTP client and let the server know we're done */ + /* do not bother with the return code, but if the socket is still up we + * should be a good TFTP client and let the server know we are done */ state->state = TFTP_STATE_FIN; break; @@ -710,6 +704,8 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) struct SingleRequest *k = &data->req; size_t cb; /* Bytes currently read */ char buffer[STRERROR_LEN]; + char *bufptr; + bool eos; switch(event) { @@ -720,18 +716,18 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) int rblock = getrpacketblock(&state->rpacket); if(rblock != state->block && - /* There's a bug in tftpd-hpa that causes it to send us an ack for - * 65535 when the block number wraps to 0. So when we're expecting + /* There is a bug in tftpd-hpa that causes it to send us an ack for + * 65535 when the block number wraps to 0. So when we are expecting * 0, also accept 65535. See * https://www.syslinux.org/archives/2010-September/015612.html * */ !(state->block == 0 && rblock == 65535)) { - /* This isn't the expected block. Log it and up the retry counter */ + /* This is not the expected block. Log it and up the retry counter */ infof(data, "Received ACK for block %d, expecting %d", rblock, state->block); state->retries++; /* Bail out if over the maximum */ - if(state->retries>state->retry_max) { + if(state->retries > state->retry_max) { failf(data, "tftp_tx: giving up waiting for block %d ack", state->block); result = CURLE_SEND_ERROR; @@ -739,11 +735,11 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) else { /* Re-send the data packet */ sbytes = sendto(state->sockfd, (void *)state->spacket.data, - 4 + state->sbytes, SEND_4TH_ARG, + 4 + (SEND_TYPE_ARG3)state->sbytes, SEND_4TH_ARG, (struct sockaddr *)&state->remote_addr, state->remote_addrlen); /* Check all sbytes were sent */ - if(sbytes<0) { + if(sbytes < 0) { failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); result = CURLE_SEND_ERROR; @@ -752,9 +748,9 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) return result; } - /* This is the expected packet. Reset the counters and send the next + /* This is the expected packet. Reset the counters and send the next block */ - time(&state->rx_time); + state->rx_time = time(NULL); state->block++; } else @@ -773,21 +769,22 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) * data block. * */ state->sbytes = 0; - state->data->req.upload_fromhere = (char *)state->spacket.data + 4; + bufptr = (char *)state->spacket.data + 4; do { - result = Curl_fillreadbuffer(data, state->blksize - state->sbytes, &cb); + result = Curl_client_read(data, bufptr, state->blksize - state->sbytes, + &cb, &eos); if(result) return result; state->sbytes += (int)cb; - state->data->req.upload_fromhere += cb; + bufptr += cb; } while(state->sbytes < state->blksize && cb); sbytes = sendto(state->sockfd, (void *) state->spacket.data, - 4 + state->sbytes, SEND_4TH_ARG, + 4 + (SEND_TYPE_ARG3)state->sbytes, SEND_4TH_ARG, (struct sockaddr *)&state->remote_addr, state->remote_addrlen); /* Check all sbytes were sent */ - if(sbytes<0) { + if(sbytes < 0) { failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); return CURLE_SEND_ERROR; } @@ -801,7 +798,7 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) state->retries++; infof(data, "Timeout waiting for block %d ACK. " " Retries = %d", NEXT_BLOCKNUM(state->block), state->retries); - /* Decide if we've had enough */ + /* Decide if we have had enough */ if(state->retries > state->retry_max) { state->error = TFTP_ERR_TIMEOUT; state->state = TFTP_STATE_FIN; @@ -809,11 +806,11 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) else { /* Re-send the data packet */ sbytes = sendto(state->sockfd, (void *)state->spacket.data, - 4 + state->sbytes, SEND_4TH_ARG, + 4 + (SEND_TYPE_ARG3)state->sbytes, SEND_4TH_ARG, (struct sockaddr *)&state->remote_addr, state->remote_addrlen); /* Check all sbytes were sent */ - if(sbytes<0) { + if(sbytes < 0) { failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); return CURLE_SEND_ERROR; } @@ -829,8 +826,8 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) (void)sendto(state->sockfd, (void *)state->spacket.data, 4, SEND_4TH_ARG, (struct sockaddr *)&state->remote_addr, state->remote_addrlen); - /* don't bother with the return code, but if the socket is still up we - * should be a good TFTP client and let the server know we're done */ + /* do not bother with the return code, but if the socket is still up we + * should be a good TFTP client and let the server know we are done */ state->state = TFTP_STATE_FIN; break; @@ -978,11 +975,9 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) return CURLE_OUT_OF_MEMORY; /* alloc pkt buffers based on specified blksize */ - if(data->set.tftp_blksize) { + if(data->set.tftp_blksize) + /* range checked when set */ blksize = (int)data->set.tftp_blksize; - if(blksize > TFTP_BLKSIZE_MAX || blksize < TFTP_BLKSIZE_MIN) - return CURLE_TFTP_ILLEGAL; - } need_blksize = blksize; /* default size is the fallback when no OACK is received */ @@ -1003,7 +998,7 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) return CURLE_OUT_OF_MEMORY; } - /* we don't keep TFTP connections up basically because there's none or very + /* we do not keep TFTP connections up basically because there is none or * little gain for UDP */ connclose(conn, "TFTP"); @@ -1034,7 +1029,7 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) * IPv4 and IPv6... */ int rc = bind(state->sockfd, (struct sockaddr *)&state->local_addr, - conn->remote_addr->addrlen); + (curl_socklen_t)conn->remote_addr->addrlen); if(rc) { char buffer[STRERROR_LEN]; failf(data, "bind() failed; %s", @@ -1102,25 +1097,20 @@ static int tftp_getsock(struct Curl_easy *data, **********************************************************/ static CURLcode tftp_receive_packet(struct Curl_easy *data) { - struct Curl_sockaddr_storage fromaddr; curl_socklen_t fromlen; CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; struct tftp_state_data *state = conn->proto.tftpc; - struct SingleRequest *k = &data->req; /* Receive the packet */ - fromlen = sizeof(fromaddr); + fromlen = sizeof(state->remote_addr); state->rbytes = (int)recvfrom(state->sockfd, (void *)state->rpacket.data, - state->blksize + 4, + (RECV_TYPE_ARG3)state->blksize + 4, 0, - (struct sockaddr *)&fromaddr, + (struct sockaddr *)&state->remote_addr, &fromlen); - if(state->remote_addrlen == 0) { - memcpy(&state->remote_addr, &fromaddr, fromlen); - state->remote_addrlen = fromlen; - } + state->remote_addrlen = fromlen; /* Sanity check packet length */ if(state->rbytes < 4) { @@ -1135,7 +1125,7 @@ static CURLcode tftp_receive_packet(struct Curl_easy *data) switch(state->event) { case TFTP_EVENT_DATA: - /* Don't pass to the client empty or retransmitted packets */ + /* Do not pass to the client empty or retransmitted packets */ if(state->rbytes > 4 && (NEXT_BLOCKNUM(state->block) == getrpacketblock(&state->rpacket))) { result = Curl_client_write(data, CLIENTWRITE_BODY, @@ -1145,8 +1135,6 @@ static CURLcode tftp_receive_packet(struct Curl_easy *data) tftp_state_machine(state, TFTP_EVENT_ERROR); return result; } - k->bytecount += state->rbytes-4; - Curl_pgrsSetDownloadCounter(data, (curl_off_t) k->bytecount); } break; case TFTP_EVENT_ERROR: @@ -1209,11 +1197,11 @@ static timediff_t tftp_state_timeout(struct Curl_easy *data, state->state = TFTP_STATE_FIN; return 0; } - time(¤t); + current = time(NULL); if(current > state->rx_time + state->retry_time) { if(event) *event = TFTP_EVENT_TIMEOUT; - time(&state->rx_time); /* update even though we received nothing */ + state->rx_time = time(NULL); /* update even though we received nothing */ } return timeout_ms; @@ -1244,10 +1232,10 @@ static CURLcode tftp_multi_statemach(struct Curl_easy *data, bool *done) result = tftp_state_machine(state, event); if(result) return result; - *done = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE; + *done = (state->state == TFTP_STATE_FIN); if(*done) - /* Tell curl we're done */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + /* Tell curl we are done */ + Curl_xfer_setup_nop(data); } else { /* no timeouts to handle, check our socket */ @@ -1267,10 +1255,10 @@ static CURLcode tftp_multi_statemach(struct Curl_easy *data, bool *done) result = tftp_state_machine(state, state->event); if(result) return result; - *done = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE; + *done = (state->state == TFTP_STATE_FIN); if(*done) - /* Tell curl we're done */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + /* Tell curl we are done */ + Curl_xfer_setup_nop(data); } /* if rc == 0, then select() timed out */ } @@ -1294,7 +1282,7 @@ static CURLcode tftp_doing(struct Curl_easy *data, bool *dophase_done) DEBUGF(infof(data, "DO phase is complete")); } else if(!result) { - /* The multi code doesn't have this logic for the DOING state so we + /* The multi code does not have this logic for the DOING state so we provide it for TFTP since it may do the entire transfer in this state. */ if(Curl_pgrsUpdate(data)) @@ -1381,7 +1369,7 @@ static CURLcode tftp_setup_connection(struct Curl_easy *data, conn->transport = TRNSPRT_UDP; /* TFTP URLs support an extension like ";mode=" that - * we'll try to get now! */ + * we will try to get now! */ type = strstr(data->state.up.path, ";mode="); if(!type) diff --git a/deps/curl/lib/tftp.h b/deps/curl/lib/tftp.h index 5d2d5da6151..12404bf6d20 100644 --- a/deps/curl/lib/tftp.h +++ b/deps/curl/lib/tftp.h @@ -25,6 +25,9 @@ ***************************************************************************/ #ifndef CURL_DISABLE_TFTP extern const struct Curl_handler Curl_handler_tftp; + +#define TFTP_BLKSIZE_MIN 8 +#define TFTP_BLKSIZE_MAX 65464 #endif #endif /* HEADER_CURL_TFTP_H */ diff --git a/deps/curl/lib/timediff.c b/deps/curl/lib/timediff.c index 1b762bbd3ed..d0824d1448b 100644 --- a/deps/curl/lib/timediff.c +++ b/deps/curl/lib/timediff.c @@ -53,7 +53,7 @@ struct timeval *curlx_mstotv(struct timeval *tv, timediff_t ms) #endif tv->tv_sec = (time_t)tv_sec; tv->tv_usec = (suseconds_t)tv_usec; -#elif defined(WIN32) /* maybe also others in the future */ +#elif defined(_WIN32) /* maybe also others in the future */ #if TIMEDIFF_T_MAX > LONG_MAX /* tv_sec overflow check on Windows there we know it is long */ if(tv_sec > LONG_MAX) diff --git a/deps/curl/lib/timediff.h b/deps/curl/lib/timediff.h index fb318d4f2b0..75f996c55cf 100644 --- a/deps/curl/lib/timediff.h +++ b/deps/curl/lib/timediff.h @@ -26,10 +26,10 @@ #include "curl_setup.h" -/* Use a larger type even for 32 bit time_t systems so that we can keep +/* Use a larger type even for 32-bit time_t systems so that we can keep microsecond accuracy in it */ typedef curl_off_t timediff_t; -#define CURL_FORMAT_TIMEDIFF_T CURL_FORMAT_CURL_OFF_T +#define FMT_TIMEDIFF_T FMT_OFF_T #define TIMEDIFF_T_MAX CURL_OFF_T_MAX #define TIMEDIFF_T_MIN CURL_OFF_T_MIN diff --git a/deps/curl/lib/timeval.c b/deps/curl/lib/timeval.c index 2de79be46de..bb29bfdfee6 100644 --- a/deps/curl/lib/timeval.c +++ b/deps/curl/lib/timeval.c @@ -24,11 +24,10 @@ #include "timeval.h" -#if defined(WIN32) && !defined(MSDOS) +#if defined(_WIN32) -/* set in win32_init() */ -extern LARGE_INTEGER Curl_freq; -extern bool Curl_isVistaOrGreater; +#include +#include "system_win32.h" /* In case of bug fix this function has a counterpart in tool_util.c */ struct curltime Curl_now(void) @@ -52,8 +51,8 @@ struct curltime Curl_now(void) #pragma warning(pop) #endif - now.tv_sec = milliseconds / 1000; - now.tv_usec = (milliseconds % 1000) * 1000; + now.tv_sec = (time_t)(milliseconds / 1000); + now.tv_usec = (int)((milliseconds % 1000) * 1000); } return now; } @@ -78,7 +77,7 @@ struct curltime Curl_now(void) /* ** clock_gettime() may be defined by Apple's SDK as weak symbol thus - ** code compiles but fails during run-time if clock_gettime() is + ** code compiles but fails during runtime if clock_gettime() is ** called on unsupported OS version. */ #if defined(__APPLE__) && defined(HAVE_BUILTIN_AVAILABLE) && \ @@ -96,7 +95,7 @@ struct curltime Curl_now(void) #endif (0 == clock_gettime(CLOCK_MONOTONIC_RAW, &tsnow))) { cnow.tv_sec = tsnow.tv_sec; - cnow.tv_usec = (unsigned int)(tsnow.tv_nsec / 1000); + cnow.tv_usec = (int)(tsnow.tv_nsec / 1000); } else #endif @@ -108,18 +107,18 @@ struct curltime Curl_now(void) #endif (0 == clock_gettime(CLOCK_MONOTONIC, &tsnow))) { cnow.tv_sec = tsnow.tv_sec; - cnow.tv_usec = (unsigned int)(tsnow.tv_nsec / 1000); + cnow.tv_usec = (int)(tsnow.tv_nsec / 1000); } /* ** Even when the configure process has truly detected monotonic clock ** availability, it might happen that it is not actually available at - ** run-time. When this occurs simply fallback to other time source. + ** runtime. When this occurs simply fallback to other time source. */ #ifdef HAVE_GETTIMEOFDAY else { (void)gettimeofday(&now, NULL); cnow.tv_sec = now.tv_sec; - cnow.tv_usec = (unsigned int)now.tv_usec; + cnow.tv_usec = (int)now.tv_usec; } #else else { @@ -138,7 +137,7 @@ struct curltime Curl_now(void) struct curltime Curl_now(void) { /* - ** Monotonic timer on Mac OS is provided by mach_absolute_time(), which + ** Monotonic timer on macOS is provided by mach_absolute_time(), which ** returns time in Mach "absolute time units," which are platform-dependent. ** To convert to nanoseconds, one must use conversion factors specified by ** mach_timebase_info(). @@ -209,6 +208,20 @@ timediff_t Curl_timediff(struct curltime newer, struct curltime older) return diff * 1000 + (newer.tv_usec-older.tv_usec)/1000; } +/* + * Returns: time difference in number of milliseconds, rounded up. + * For too large diffs it returns max value. + */ +timediff_t Curl_timediff_ceil(struct curltime newer, struct curltime older) +{ + timediff_t diff = (timediff_t)newer.tv_sec-older.tv_sec; + if(diff >= (TIMEDIFF_T_MAX/1000)) + return TIMEDIFF_T_MAX; + else if(diff <= (TIMEDIFF_T_MIN/1000)) + return TIMEDIFF_T_MIN; + return diff * 1000 + (newer.tv_usec - older.tv_usec + 999)/1000; +} + /* * Returns: time difference in number of microseconds. For too large diffs it * returns max value. diff --git a/deps/curl/lib/timeval.h b/deps/curl/lib/timeval.h index 92e484ad1db..33dfb5b10dd 100644 --- a/deps/curl/lib/timeval.h +++ b/deps/curl/lib/timeval.h @@ -36,16 +36,24 @@ struct curltime { struct curltime Curl_now(void); /* - * Make sure that the first argument (t1) is the more recent time and t2 is - * the older time, as otherwise you get a weird negative time-diff back... + * Make sure that the first argument (newer) is the more recent time and older + * is the older time, as otherwise you get a weird negative time-diff back... * * Returns: the time difference in number of milliseconds. */ -timediff_t Curl_timediff(struct curltime t1, struct curltime t2); +timediff_t Curl_timediff(struct curltime newer, struct curltime older); /* - * Make sure that the first argument (t1) is the more recent time and t2 is - * the older time, as otherwise you get a weird negative time-diff back... + * Make sure that the first argument (newer) is the more recent time and older + * is the older time, as otherwise you get a weird negative time-diff back... + * + * Returns: the time difference in number of milliseconds, rounded up. + */ +timediff_t Curl_timediff_ceil(struct curltime newer, struct curltime older); + +/* + * Make sure that the first argument (newer) is the more recent time and older + * is the older time, as otherwise you get a weird negative time-diff back... * * Returns: the time difference in number of microseconds. */ diff --git a/deps/curl/lib/transfer.c b/deps/curl/lib/transfer.c index d0602b8753b..d7d3d16f3ee 100644 --- a/deps/curl/lib/transfer.c +++ b/deps/curl/lib/transfer.c @@ -40,9 +40,7 @@ #ifdef HAVE_SYS_IOCTL_H #include #endif -#ifdef HAVE_SIGNAL_H #include -#endif #ifdef HAVE_SYS_PARAM_H #include @@ -55,7 +53,7 @@ #endif #ifndef HAVE_SOCKET -#error "We can't compile without socket() support!" +#error "We cannot compile without socket() support!" #endif #include "urldata.h" @@ -65,6 +63,7 @@ #include "content_encoding.h" #include "hostip.h" #include "cfilters.h" +#include "cw-out.h" #include "transfer.h" #include "sendf.h" #include "speedcheck.h" @@ -80,7 +79,6 @@ #include "http2.h" #include "mime.h" #include "strcase.h" -#include "urlapi-int.h" #include "hsts.h" #include "setopt.h" #include "headers.h" @@ -116,254 +114,6 @@ char *Curl_checkheaders(const struct Curl_easy *data, } #endif -CURLcode Curl_get_upload_buffer(struct Curl_easy *data) -{ - if(!data->state.ulbuf) { - data->state.ulbuf = malloc(data->set.upload_buffer_size); - if(!data->state.ulbuf) - return CURLE_OUT_OF_MEMORY; - } - return CURLE_OK; -} - -#ifndef CURL_DISABLE_HTTP -/* - * This function will be called to loop through the trailers buffer - * until no more data is available for sending. - */ -static size_t trailers_read(char *buffer, size_t size, size_t nitems, - void *raw) -{ - struct Curl_easy *data = (struct Curl_easy *)raw; - struct dynbuf *trailers_buf = &data->state.trailers_buf; - size_t bytes_left = Curl_dyn_len(trailers_buf) - - data->state.trailers_bytes_sent; - size_t to_copy = (size*nitems < bytes_left) ? size*nitems : bytes_left; - if(to_copy) { - memcpy(buffer, - Curl_dyn_ptr(trailers_buf) + data->state.trailers_bytes_sent, - to_copy); - data->state.trailers_bytes_sent += to_copy; - } - return to_copy; -} - -static size_t trailers_left(void *raw) -{ - struct Curl_easy *data = (struct Curl_easy *)raw; - struct dynbuf *trailers_buf = &data->state.trailers_buf; - return Curl_dyn_len(trailers_buf) - data->state.trailers_bytes_sent; -} -#endif - -/* - * This function will call the read callback to fill our buffer with data - * to upload. - */ -CURLcode Curl_fillreadbuffer(struct Curl_easy *data, size_t bytes, - size_t *nreadp) -{ - size_t buffersize = bytes; - size_t nread; - - curl_read_callback readfunc = NULL; - void *extra_data = NULL; - -#ifndef CURL_DISABLE_HTTP - if(data->state.trailers_state == TRAILERS_INITIALIZED) { - struct curl_slist *trailers = NULL; - CURLcode result; - int trailers_ret_code; - - /* at this point we already verified that the callback exists - so we compile and store the trailers buffer, then proceed */ - infof(data, - "Moving trailers state machine from initialized to sending."); - data->state.trailers_state = TRAILERS_SENDING; - Curl_dyn_init(&data->state.trailers_buf, DYN_TRAILERS); - - data->state.trailers_bytes_sent = 0; - Curl_set_in_callback(data, true); - trailers_ret_code = data->set.trailer_callback(&trailers, - data->set.trailer_data); - Curl_set_in_callback(data, false); - if(trailers_ret_code == CURL_TRAILERFUNC_OK) { - result = Curl_http_compile_trailers(trailers, &data->state.trailers_buf, - data); - } - else { - failf(data, "operation aborted by trailing headers callback"); - *nreadp = 0; - result = CURLE_ABORTED_BY_CALLBACK; - } - if(result) { - Curl_dyn_free(&data->state.trailers_buf); - curl_slist_free_all(trailers); - return result; - } - infof(data, "Successfully compiled trailers."); - curl_slist_free_all(trailers); - } -#endif - -#ifndef CURL_DISABLE_HTTP - /* if we are transmitting trailing data, we don't need to write - a chunk size so we skip this */ - if(data->req.upload_chunky && - data->state.trailers_state == TRAILERS_NONE) { - /* if chunked Transfer-Encoding */ - buffersize -= (8 + 2 + 2); /* 32bit hex + CRLF + CRLF */ - data->req.upload_fromhere += (8 + 2); /* 32bit hex + CRLF */ - } - - if(data->state.trailers_state == TRAILERS_SENDING) { - /* if we're here then that means that we already sent the last empty chunk - but we didn't send a final CR LF, so we sent 0 CR LF. We then start - pulling trailing data until we have no more at which point we - simply return to the previous point in the state machine as if - nothing happened. - */ - readfunc = trailers_read; - extra_data = (void *)data; - } - else -#endif - { - readfunc = data->state.fread_func; - extra_data = data->state.in; - } - - Curl_set_in_callback(data, true); - nread = readfunc(data->req.upload_fromhere, 1, - buffersize, extra_data); - Curl_set_in_callback(data, false); - - if(nread == CURL_READFUNC_ABORT) { - failf(data, "operation aborted by callback"); - *nreadp = 0; - return CURLE_ABORTED_BY_CALLBACK; - } - if(nread == CURL_READFUNC_PAUSE) { - struct SingleRequest *k = &data->req; - - if(data->conn->handler->flags & PROTOPT_NONETWORK) { - /* protocols that work without network cannot be paused. This is - actually only FILE:// just now, and it can't pause since the transfer - isn't done using the "normal" procedure. */ - failf(data, "Read callback asked for PAUSE when not supported"); - return CURLE_READ_ERROR; - } - - /* CURL_READFUNC_PAUSE pauses read callbacks that feed socket writes */ - k->keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */ - if(data->req.upload_chunky) { - /* Back out the preallocation done above */ - data->req.upload_fromhere -= (8 + 2); - } - *nreadp = 0; - - return CURLE_OK; /* nothing was read */ - } - else if(nread > buffersize) { - /* the read function returned a too large value */ - *nreadp = 0; - failf(data, "read function returned funny value"); - return CURLE_READ_ERROR; - } - -#ifndef CURL_DISABLE_HTTP - if(!data->req.forbidchunk && data->req.upload_chunky) { - /* if chunked Transfer-Encoding - * build chunk: - * - * CRLF - * CRLF - */ - /* On non-ASCII platforms the may or may not be - translated based on state.prefer_ascii while the protocol - portion must always be translated to the network encoding. - To further complicate matters, line end conversion might be - done later on, so we need to prevent CRLFs from becoming - CRCRLFs if that's the case. To do this we use bare LFs - here, knowing they'll become CRLFs later on. - */ - - bool added_crlf = FALSE; - int hexlen = 0; - const char *endofline_native; - const char *endofline_network; - - if( -#ifdef CURL_DO_LINEEND_CONV - (data->state.prefer_ascii) || -#endif - (data->set.crlf)) { - /* \n will become \r\n later on */ - endofline_native = "\n"; - endofline_network = "\x0a"; - } - else { - endofline_native = "\r\n"; - endofline_network = "\x0d\x0a"; - } - - /* if we're not handling trailing data, proceed as usual */ - if(data->state.trailers_state != TRAILERS_SENDING) { - char hexbuffer[11] = ""; - hexlen = msnprintf(hexbuffer, sizeof(hexbuffer), - "%zx%s", nread, endofline_native); - - /* move buffer pointer */ - data->req.upload_fromhere -= hexlen; - nread += hexlen; - - /* copy the prefix to the buffer, leaving out the NUL */ - memcpy(data->req.upload_fromhere, hexbuffer, hexlen); - - /* always append ASCII CRLF to the data unless - we have a valid trailer callback */ - if((nread-hexlen) == 0 && - data->set.trailer_callback != NULL && - data->state.trailers_state == TRAILERS_NONE) { - data->state.trailers_state = TRAILERS_INITIALIZED; - } - else { - memcpy(data->req.upload_fromhere + nread, - endofline_network, - strlen(endofline_network)); - added_crlf = TRUE; - } - } - - if(data->state.trailers_state == TRAILERS_SENDING && - !trailers_left(data)) { - Curl_dyn_free(&data->state.trailers_buf); - data->state.trailers_state = TRAILERS_DONE; - data->set.trailer_data = NULL; - data->set.trailer_callback = NULL; - /* mark the transfer as done */ - data->req.upload_done = TRUE; - infof(data, "Signaling end of chunked upload after trailers."); - } - else - if((nread - hexlen) == 0 && - data->state.trailers_state != TRAILERS_INITIALIZED) { - /* mark this as done once this chunk is transferred */ - data->req.upload_done = TRUE; - infof(data, - "Signaling end of chunked upload via terminating chunk."); - } - - if(added_crlf) - nread += strlen(endofline_network); /* for the added end of line */ - } -#endif - - *nreadp = nread; - - return CURLE_OK; -} - static int data_pending(struct Curl_easy *data) { struct connectdata *conn = data->conn; @@ -409,720 +159,301 @@ bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc) return TRUE; } +static CURLcode xfer_recv_shutdown(struct Curl_easy *data, bool *done) +{ + int sockindex; + + if(!data || !data->conn) + return CURLE_FAILED_INIT; + if(data->conn->sockfd == CURL_SOCKET_BAD) + return CURLE_FAILED_INIT; + sockindex = (data->conn->sockfd == data->conn->sock[SECONDARYSOCKET]); + return Curl_conn_shutdown(data, sockindex, done); +} + +static bool xfer_recv_shutdown_started(struct Curl_easy *data) +{ + int sockindex; + + if(!data || !data->conn) + return CURLE_FAILED_INIT; + if(data->conn->sockfd == CURL_SOCKET_BAD) + return CURLE_FAILED_INIT; + sockindex = (data->conn->sockfd == data->conn->sock[SECONDARYSOCKET]); + return Curl_shutdown_started(data, sockindex); +} + +CURLcode Curl_xfer_send_shutdown(struct Curl_easy *data, bool *done) +{ + int sockindex; + + if(!data || !data->conn) + return CURLE_FAILED_INIT; + if(data->conn->writesockfd == CURL_SOCKET_BAD) + return CURLE_FAILED_INIT; + sockindex = (data->conn->writesockfd == data->conn->sock[SECONDARYSOCKET]); + return Curl_conn_shutdown(data, sockindex, done); +} + +/** + * Receive raw response data for the transfer. + * @param data the transfer + * @param buf buffer to keep response data received + * @param blen length of `buf` + * @param eos_reliable if EOS detection in underlying connection is reliable + * @param err error code in case of -1 return + * @return number of bytes read or -1 for error + */ +static ssize_t xfer_recv_resp(struct Curl_easy *data, + char *buf, size_t blen, + bool eos_reliable, + CURLcode *err) +{ + ssize_t nread; + + DEBUGASSERT(blen > 0); + /* If we are reading BODY data and the connection does NOT handle EOF + * and we know the size of the BODY data, limit the read amount */ + if(!eos_reliable && !data->req.header && data->req.size != -1) { + curl_off_t totalleft = data->req.size - data->req.bytecount; + if(totalleft <= 0) + blen = 0; + else if(totalleft < (curl_off_t)blen) + blen = (size_t)totalleft; + } + else if(xfer_recv_shutdown_started(data)) { + /* we already received everything. Do not try more. */ + blen = 0; + } + + if(!blen) { + /* want nothing more */ + *err = CURLE_OK; + nread = 0; + } + else { + *err = Curl_xfer_recv(data, buf, blen, &nread); + } + + if(*err) + return -1; + if(nread == 0) { + if(data->req.shutdown) { + bool done; + *err = xfer_recv_shutdown(data, &done); + if(*err) + return -1; + if(!done) { + *err = CURLE_AGAIN; + return -1; + } + } + DEBUGF(infof(data, "sendrecv_dl: we are done")); + } + DEBUGASSERT(nread >= 0); + return nread; +} + /* * Go ahead and do a read if we have a readable socket or if * the stream was rewound (in which case we have data in a * buffer) - * - * return '*comeback' TRUE if we didn't properly drain the socket so this - * function should get called again without select() or similar in between! */ -static CURLcode readwrite_data(struct Curl_easy *data, - struct connectdata *conn, - struct SingleRequest *k, - int *didwhat, bool *done, - bool *comeback) +static CURLcode sendrecv_dl(struct Curl_easy *data, + struct SingleRequest *k, + int *didwhat) { + struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; - ssize_t nread; /* number of bytes read */ - size_t excess = 0; /* excess bytes read */ - bool readmore = FALSE; /* used by RTP to signal for more data */ - int maxloops = 100; - curl_off_t max_recv = data->set.max_recv_speed? - data->set.max_recv_speed : CURL_OFF_T_MAX; - char *buf = data->state.buffer; - bool data_eof_handled = FALSE; - DEBUGASSERT(buf); - - *done = FALSE; - *comeback = FALSE; + char *buf, *xfer_buf; + size_t blen, xfer_blen; + int maxloops = 10; + curl_off_t total_received = 0; + bool is_multiplex = FALSE; + + result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen); + if(result) + goto out; /* This is where we loop until we have read everything there is to read or we get a CURLE_AGAIN */ do { - bool is_empty_data = FALSE; - size_t buffersize = data->set.buffer_size; - size_t bytestoread = buffersize; - /* For HTTP/2 and HTTP/3, read data without caring about the content - length. This is safe because body in HTTP/2 is always segmented - thanks to its framing layer. Meanwhile, we have to call Curl_read - to ensure that http2_handle_stream_close is called when we read all - incoming bytes for a particular stream. */ - bool is_http3 = Curl_conn_is_http3(data, conn, FIRSTSOCKET); - data_eof_handled = is_http3 || Curl_conn_is_http2(data, conn, FIRSTSOCKET); - - if(!data_eof_handled && k->size != -1 && !k->header) { - /* make sure we don't read too much */ - curl_off_t totalleft = k->size - k->bytecount; - if(totalleft < (curl_off_t)bytestoread) - bytestoread = (size_t)totalleft; + bool is_eos = FALSE; + size_t bytestoread; + ssize_t nread; + + if(!is_multiplex) { + /* Multiplexed connection have inherent handling of EOF and we do not + * have to carefully restrict the amount we try to read. + * Multiplexed changes only in one direction. */ + is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET); } - if(bytestoread) { - /* receive data from the network! */ - result = Curl_read(data, conn->sockfd, buf, bytestoread, &nread); + buf = xfer_buf; + bytestoread = xfer_blen; - /* read would've blocked */ - if(CURLE_AGAIN == result) { - result = CURLE_OK; - break; /* get out of loop */ + if(bytestoread && data->set.max_recv_speed > 0) { + /* In case of speed limit on receiving: if this loop already got + * data, break out. If not, limit the amount of bytes to receive. + * The overall, timed, speed limiting is done in multi.c */ + if(total_received) + break; + if(data->set.max_recv_speed < (curl_off_t)bytestoread) + bytestoread = (size_t)data->set.max_recv_speed; + } + + nread = xfer_recv_resp(data, buf, bytestoread, is_multiplex, &result); + if(nread < 0) { + if(CURLE_AGAIN != result) + goto out; /* real error */ + result = CURLE_OK; + if(data->req.download_done && data->req.no_body && + !data->req.resp_trailer) { + DEBUGF(infof(data, "EAGAIN, download done, no trailer announced, " + "not waiting for EOS")); + nread = 0; + /* continue as if we read the EOS */ } - - if(result>0) - goto out; - } - else { - /* read nothing but since we wanted nothing we consider this an OK - situation to proceed from */ - DEBUGF(infof(data, "readwrite_data: we're done")); - nread = 0; - } - - if(!k->bytecount) { - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - if(k->exp100 > EXP100_SEND_DATA) - /* set time stamp to compare with when waiting for the 100 */ - k->start100 = Curl_now(); + else + break; /* get out of loop */ } + /* We only get a 0-length read on EndOfStream */ + blen = (size_t)nread; + is_eos = (blen == 0); *didwhat |= KEEP_RECV; - /* indicates data of zero size, i.e. empty file */ - is_empty_data = ((nread == 0) && (k->bodywrites == 0)) ? TRUE : FALSE; - if(0 < nread || is_empty_data) { - buf[nread] = 0; - } - if(!nread) { + if(!blen) { /* if we receive 0 or less here, either the data transfer is done or the server closed the connection and we bail out from this! */ - if(data_eof_handled) + if(is_multiplex) DEBUGF(infof(data, "nread == 0, stream closed, bailing")); else DEBUGF(infof(data, "nread <= 0, server closed connection, bailing")); - k->keepon = 0; /* stop sending as well */ - if(!is_empty_data) - break; - } - - /* Default buffer to use when we write the buffer, it may be changed - in the flow below before the actual storing is done. */ - k->str = buf; - - if(conn->handler->readwrite) { - result = conn->handler->readwrite(data, conn, &nread, &readmore); + result = Curl_req_stop_send_recv(data); if(result) goto out; - if(readmore) + if(k->eos_written) /* already did write this to client, leave */ break; } + total_received += blen; -#ifndef CURL_DISABLE_HTTP - /* Since this is a two-state thing, we check if we are parsing - headers at the moment or not. */ - if(k->header) { - /* we are in parse-the-header-mode */ - bool stop_reading = FALSE; - result = Curl_http_readwrite_headers(data, conn, &nread, &stop_reading); - if(result) - goto out; - - if(conn->handler->readwrite && - (k->maxdownload <= 0 && nread > 0)) { - result = conn->handler->readwrite(data, conn, &nread, &readmore); - if(result) - goto out; - if(readmore) - break; - } - - if(stop_reading) { - /* We've stopped dealing with input, get out of the do-while loop */ - - if(nread > 0) { - infof(data, - "Excess found:" - " excess = %zd" - " url = %s (zero-length body)", - nread, data->state.up.path); - } - - break; - } - } -#endif /* CURL_DISABLE_HTTP */ - - - /* This is not an 'else if' since it may be a rest from the header - parsing, where the beginning of the buffer is headers and the end - is non-headers. */ - if(!k->header && (nread > 0 || is_empty_data)) { - - if(data->req.no_body) { - /* data arrives although we want none, bail out */ - streamclose(conn, "ignoring body"); - *done = TRUE; - result = CURLE_WEIRD_SERVER_REPLY; - goto out; - } - -#ifndef CURL_DISABLE_HTTP - if(0 == k->bodywrites && !is_empty_data) { - /* These checks are only made the first time we are about to - write a piece of the body */ - if(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)) { - /* HTTP-only checks */ - result = Curl_http_firstwrite(data, conn, done); - if(result || *done) - goto out; - } - } /* this is the first time we write a body part */ -#endif /* CURL_DISABLE_HTTP */ - - k->bodywrites++; - - /* pass data to the debug function before it gets "dechunked" */ - if(data->set.verbose) { - if(k->badheader) { - Curl_debug(data, CURLINFO_DATA_IN, - Curl_dyn_ptr(&data->state.headerb), - Curl_dyn_len(&data->state.headerb)); - if(k->badheader == HEADER_PARTHEADER) - Curl_debug(data, CURLINFO_DATA_IN, - k->str, (size_t)nread); - } - else - Curl_debug(data, CURLINFO_DATA_IN, - k->str, (size_t)nread); - } - -#ifndef CURL_DISABLE_HTTP - if(k->chunk) { - /* - * Here comes a chunked transfer flying and we need to decode this - * properly. While the name says read, this function both reads - * and writes away the data. The returned 'nread' holds the number - * of actual data it wrote to the client. - */ - CURLcode extra; - CHUNKcode res = - Curl_httpchunk_read(data, k->str, nread, &nread, &extra); - - if(CHUNKE_OK < res) { - if(CHUNKE_PASSTHRU_ERROR == res) { - failf(data, "Failed reading the chunked-encoded stream"); - result = extra; - goto out; - } - failf(data, "%s in chunked-encoding", Curl_chunked_strerror(res)); - result = CURLE_RECV_ERROR; - goto out; - } - if(CHUNKE_STOP == res) { - /* we're done reading chunks! */ - k->keepon &= ~KEEP_RECV; /* read no more */ - - /* N number of bytes at the end of the str buffer that weren't - written to the client. */ - if(conn->chunk.datasize) { - infof(data, "Leftovers after chunking: % " - CURL_FORMAT_CURL_OFF_T "u bytes", - conn->chunk.datasize); - } - } - /* If it returned OK, we just keep going */ - } -#endif /* CURL_DISABLE_HTTP */ - - /* Account for body content stored in the header buffer */ - if((k->badheader == HEADER_PARTHEADER) && !k->ignorebody) { - size_t headlen = Curl_dyn_len(&data->state.headerb); - DEBUGF(infof(data, "Increasing bytecount by %zu", headlen)); - k->bytecount += headlen; - } - - if((-1 != k->maxdownload) && - (k->bytecount + nread >= k->maxdownload)) { - - excess = (size_t)(k->bytecount + nread - k->maxdownload); - if(excess > 0 && !k->ignorebody) { - infof(data, - "Excess found in a read:" - " excess = %zu" - ", size = %" CURL_FORMAT_CURL_OFF_T - ", maxdownload = %" CURL_FORMAT_CURL_OFF_T - ", bytecount = %" CURL_FORMAT_CURL_OFF_T, - excess, k->size, k->maxdownload, k->bytecount); - connclose(conn, "excess found in a read"); - } - - nread = (ssize_t) (k->maxdownload - k->bytecount); - if(nread < 0) /* this should be unusual */ - nread = 0; - - /* HTTP/3 over QUIC should keep reading until QUIC connection - is closed. In contrast to HTTP/2 which can stop reading - from TCP connection, HTTP/3 over QUIC needs ACK from server - to ensure stream closure. It should keep reading. */ - if(!is_http3) { - k->keepon &= ~KEEP_RECV; /* we're done reading */ - } - } - - k->bytecount += nread; - max_recv -= nread; - - Curl_pgrsSetDownloadCounter(data, k->bytecount); - - if(!k->chunk && (nread || k->badheader || is_empty_data)) { - /* If this is chunky transfer, it was already written */ - - if(k->badheader && !k->ignorebody) { - /* we parsed a piece of data wrongly assuming it was a header - and now we output it as body instead */ - size_t headlen = Curl_dyn_len(&data->state.headerb); - - /* Don't let excess data pollute body writes */ - if(k->maxdownload == -1 || (curl_off_t)headlen <= k->maxdownload) - result = Curl_client_write(data, CLIENTWRITE_BODY, - Curl_dyn_ptr(&data->state.headerb), - headlen); - else - result = Curl_client_write(data, CLIENTWRITE_BODY, - Curl_dyn_ptr(&data->state.headerb), - (size_t)k->maxdownload); - - if(result) - goto out; - } - if(k->badheader < HEADER_ALLBAD) { - /* This switch handles various content encodings. If there's an - error here, be sure to check over the almost identical code - in http_chunks.c. - Make sure that ALL_CONTENT_ENCODINGS contains all the - encodings handled here. */ - if(data->set.http_ce_skip || !k->writer_stack) { - if(!k->ignorebody && nread) { -#ifndef CURL_DISABLE_POP3 - if(conn->handler->protocol & PROTO_FAMILY_POP3) - result = Curl_pop3_write(data, k->str, nread); - else -#endif /* CURL_DISABLE_POP3 */ - result = Curl_client_write(data, CLIENTWRITE_BODY, k->str, - nread); - } - } - else if(!k->ignorebody && nread) - result = Curl_unencode_write(data, k->writer_stack, k->str, nread); - } - k->badheader = HEADER_NORMAL; /* taken care of now */ - - if(result) - goto out; - } - - } /* if(!header and data to read) */ - - if(conn->handler->readwrite && excess) { - /* Parse the excess data */ - k->str += nread; - - if(&k->str[excess] > &buf[data->set.buffer_size]) { - /* the excess amount was too excessive(!), make sure - it doesn't read out of buffer */ - excess = &buf[data->set.buffer_size] - k->str; - } - nread = (ssize_t)excess; - - result = conn->handler->readwrite(data, conn, &nread, &readmore); - if(result) - goto out; - - if(readmore) - k->keepon |= KEEP_RECV; /* we're not done reading */ - break; - } + result = Curl_xfer_write_resp(data, buf, blen, is_eos); + if(result || data->req.done) + goto out; - if(is_empty_data) { - /* if we received nothing, the server closed the connection and we - are done */ - k->keepon &= ~KEEP_RECV; + /* if we are done, we stop receiving. On multiplexed connections, + * we should read the EOS. Which may arrive as meta data after + * the bytes. Not taking it in might lead to RST of streams. */ + if((!is_multiplex && data->req.download_done) || is_eos) { + data->req.keepon &= ~KEEP_RECV; } - - if((k->keepon & KEEP_RECV_PAUSE) || !(k->keepon & KEEP_RECV)) { - /* this is a paused or stopped transfer */ + /* if we are PAUSEd or stopped receiving, leave the loop */ + if((k->keepon & KEEP_RECV_PAUSE) || !(k->keepon & KEEP_RECV)) break; - } - } while((max_recv > 0) && data_pending(data) && maxloops--); + } while(maxloops--); - if(maxloops <= 0 || max_recv <= 0) { - /* we mark it as read-again-please */ - data->state.dselect_bits = CURL_CSELECT_IN; - *comeback = TRUE; + if((maxloops <= 0) || data_pending(data)) { + /* did not read until EAGAIN or there is still pending data, mark as + read-again-please */ + data->state.select_bits = CURL_CSELECT_IN; + if((k->keepon & KEEP_SENDBITS) == KEEP_SEND) + data->state.select_bits |= CURL_CSELECT_OUT; } if(((k->keepon & (KEEP_RECV|KEEP_SEND)) == KEEP_SEND) && - (conn->bits.close || data_eof_handled)) { - /* When we've read the entire thing and the close bit is set, the server - may now close the connection. If there's now any kind of sending going + (conn->bits.close || is_multiplex)) { + /* When we have read the entire thing and the close bit is set, the server + may now close the connection. If there is now any kind of sending going on from our side, we need to stop that immediately. */ infof(data, "we are done reading and this is set to close, stop send"); - k->keepon &= ~KEEP_SEND; /* no writing anymore either */ + Curl_req_abort_sending(data); } out: + Curl_multi_xfer_buf_release(data, xfer_buf); if(result) - DEBUGF(infof(data, "readwrite_data() -> %d", result)); + DEBUGF(infof(data, "sendrecv_dl() -> %d", result)); return result; } -CURLcode Curl_done_sending(struct Curl_easy *data, - struct SingleRequest *k) -{ - k->keepon &= ~KEEP_SEND; /* we're done writing */ - - /* These functions should be moved into the handler struct! */ - Curl_conn_ev_data_done_send(data); - - return CURLE_OK; -} - -#if defined(WIN32) && defined(USE_WINSOCK) -#ifndef SIO_IDEAL_SEND_BACKLOG_QUERY -#define SIO_IDEAL_SEND_BACKLOG_QUERY 0x4004747B -#endif - -static void win_update_buffer_size(curl_socket_t sockfd) -{ - int result; - ULONG ideal; - DWORD ideallen; - result = WSAIoctl(sockfd, SIO_IDEAL_SEND_BACKLOG_QUERY, 0, 0, - &ideal, sizeof(ideal), &ideallen, 0, 0); - if(result == 0) { - setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, - (const char *)&ideal, sizeof(ideal)); - } -} -#else -#define win_update_buffer_size(x) -#endif - -#define curl_upload_refill_watermark(data) \ - ((ssize_t)((data)->set.upload_buffer_size >> 5)) - /* * Send data to upload to the server, when the socket is writable. */ -static CURLcode readwrite_upload(struct Curl_easy *data, - struct connectdata *conn, - int *didwhat) +static CURLcode sendrecv_ul(struct Curl_easy *data, int *didwhat) { - ssize_t i, si; - ssize_t bytes_written; - CURLcode result; - ssize_t nread; /* number of bytes read */ - bool sending_http_headers = FALSE; - struct SingleRequest *k = &data->req; - - *didwhat |= KEEP_SEND; - - do { - curl_off_t nbody; - ssize_t offset = 0; - - if(0 != k->upload_present && - k->upload_present < curl_upload_refill_watermark(data) && - !k->upload_chunky &&/*(variable sized chunked header; append not safe)*/ - !k->upload_done && /*!(k->upload_done once k->upload_present sent)*/ - !(k->writebytecount + k->upload_present - k->pendingheader == - data->state.infilesize)) { - offset = k->upload_present; - } - - /* only read more data if there's no upload data already - present in the upload buffer, or if appending to upload buffer */ - if(0 == k->upload_present || offset) { - result = Curl_get_upload_buffer(data); - if(result) - return result; - if(offset && k->upload_fromhere != data->state.ulbuf) - memmove(data->state.ulbuf, k->upload_fromhere, offset); - /* init the "upload from here" pointer */ - k->upload_fromhere = data->state.ulbuf; - - if(!k->upload_done) { - /* HTTP pollution, this should be written nicer to become more - protocol agnostic. */ - size_t fillcount; - struct HTTP *http = k->p.http; - - if((k->exp100 == EXP100_SENDING_REQUEST) && - (http->sending == HTTPSEND_BODY)) { - /* If this call is to send body data, we must take some action: - We have sent off the full HTTP 1.1 request, and we shall now - go into the Expect: 100 state and await such a header */ - k->exp100 = EXP100_AWAITING_CONTINUE; /* wait for the header */ - k->keepon &= ~KEEP_SEND; /* disable writing */ - k->start100 = Curl_now(); /* timeout count starts now */ - *didwhat &= ~KEEP_SEND; /* we didn't write anything actually */ - /* set a timeout for the multi interface */ - Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT); - break; - } - - if(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)) { - if(http->sending == HTTPSEND_REQUEST) - /* We're sending the HTTP request headers, not the data. - Remember that so we don't change the line endings. */ - sending_http_headers = TRUE; - else - sending_http_headers = FALSE; - } - - k->upload_fromhere += offset; - result = Curl_fillreadbuffer(data, data->set.upload_buffer_size-offset, - &fillcount); - k->upload_fromhere -= offset; - if(result) - return result; - - nread = offset + fillcount; - } - else - nread = 0; /* we're done uploading/reading */ - - if(!nread && (k->keepon & KEEP_SEND_PAUSE)) { - /* this is a paused transfer */ - break; - } - if(nread <= 0) { - result = Curl_done_sending(data, k); - if(result) - return result; - break; - } - - /* store number of bytes available for upload */ - k->upload_present = nread; - - /* convert LF to CRLF if so asked */ - if((!sending_http_headers) && ( -#ifdef CURL_DO_LINEEND_CONV - /* always convert if we're FTPing in ASCII mode */ - (data->state.prefer_ascii) || -#endif - (data->set.crlf))) { - /* Do we need to allocate a scratch buffer? */ - if(!data->state.scratch) { - data->state.scratch = malloc(2 * data->set.upload_buffer_size); - if(!data->state.scratch) { - failf(data, "Failed to alloc scratch buffer"); - - return CURLE_OUT_OF_MEMORY; - } - } - - /* - * ASCII/EBCDIC Note: This is presumably a text (not binary) - * transfer so the data should already be in ASCII. - * That means the hex values for ASCII CR (0x0d) & LF (0x0a) - * must be used instead of the escape sequences \r & \n. - */ - if(offset) - memcpy(data->state.scratch, k->upload_fromhere, offset); - for(i = offset, si = offset; i < nread; i++, si++) { - if(k->upload_fromhere[i] == 0x0a) { - data->state.scratch[si++] = 0x0d; - data->state.scratch[si] = 0x0a; - if(!data->set.crlf) { - /* we're here only because FTP is in ASCII mode... - bump infilesize for the LF we just added */ - if(data->state.infilesize != -1) - data->state.infilesize++; - } - } - else - data->state.scratch[si] = k->upload_fromhere[i]; - } - - if(si != nread) { - /* only perform the special operation if we really did replace - anything */ - nread = si; - - /* upload from the new (replaced) buffer instead */ - k->upload_fromhere = data->state.scratch; - - /* set the new amount too */ - k->upload_present = nread; - } - } - -#ifndef CURL_DISABLE_SMTP - if(conn->handler->protocol & PROTO_FAMILY_SMTP) { - result = Curl_smtp_escape_eob(data, nread, offset); - if(result) - return result; - } -#endif /* CURL_DISABLE_SMTP */ - } /* if 0 == k->upload_present or appended to upload buffer */ - else { - /* We have a partial buffer left from a previous "round". Use - that instead of reading more data */ - } - - /* write to socket (send away data) */ - result = Curl_write(data, - conn->writesockfd, /* socket to send to */ - k->upload_fromhere, /* buffer pointer */ - k->upload_present, /* buffer size */ - &bytes_written); /* actually sent */ - if(result) - return result; - -#if defined(WIN32) && defined(USE_WINSOCK) - { - struct curltime n = Curl_now(); - if(Curl_timediff(n, k->last_sndbuf_update) > 1000) { - win_update_buffer_size(conn->writesockfd); - k->last_sndbuf_update = n; - } - } -#endif - - if(k->pendingheader) { - /* parts of what was sent was header */ - curl_off_t n = CURLMIN(k->pendingheader, bytes_written); - /* show the data before we change the pointer upload_fromhere */ - Curl_debug(data, CURLINFO_HEADER_OUT, k->upload_fromhere, (size_t)n); - k->pendingheader -= n; - nbody = bytes_written - n; /* size of the written body part */ - } - else - nbody = bytes_written; - - if(nbody) { - /* show the data before we change the pointer upload_fromhere */ - Curl_debug(data, CURLINFO_DATA_OUT, - &k->upload_fromhere[bytes_written - nbody], - (size_t)nbody); - - k->writebytecount += nbody; - Curl_pgrsSetUploadCounter(data, k->writebytecount); - } - - if((!k->upload_chunky || k->forbidchunk) && - (k->writebytecount == data->state.infilesize)) { - /* we have sent all data we were supposed to */ - k->upload_done = TRUE; - infof(data, "We are completely uploaded and fine"); - } - - if(k->upload_present != bytes_written) { - /* we only wrote a part of the buffer (if anything), deal with it! */ - - /* store the amount of bytes left in the buffer to write */ - k->upload_present -= bytes_written; - - /* advance the pointer where to find the buffer when the next send - is to happen */ - k->upload_fromhere += bytes_written; - } - else { - /* we've uploaded that buffer now */ - result = Curl_get_upload_buffer(data); - if(result) - return result; - k->upload_fromhere = data->state.ulbuf; - k->upload_present = 0; /* no more bytes left */ - - if(k->upload_done) { - result = Curl_done_sending(data, k); - if(result) - return result; - } - } - - - } while(0); /* just to break out from! */ + /* We should not get here when the sending is already done. It + * probably means that someone set `data-req.keepon |= KEEP_SEND` + * when it should not. */ + DEBUGASSERT(!Curl_req_done_sending(data)); + if(!Curl_req_done_sending(data)) { + *didwhat |= KEEP_SEND; + return Curl_req_send_more(data); + } return CURLE_OK; } +static int select_bits_paused(struct Curl_easy *data, int select_bits) +{ + /* See issue #11982: we really need to be careful not to progress + * a transfer direction when that direction is paused. Not all parts + * of our state machine are handling PAUSED transfers correctly. So, we + * do not want to go there. + * NOTE: we are only interested in PAUSE, not HOLD. */ + + /* if there is data in a direction not paused, return false */ + if(((select_bits & CURL_CSELECT_IN) && + !(data->req.keepon & KEEP_RECV_PAUSE)) || + ((select_bits & CURL_CSELECT_OUT) && + !(data->req.keepon & KEEP_SEND_PAUSE))) + return FALSE; + + return (data->req.keepon & (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)); +} + /* - * Curl_readwrite() is the low-level function to be called when data is to + * Curl_sendrecv() is the low-level function to be called when data is to * be read and written to/from the connection. - * - * return '*comeback' TRUE if we didn't properly drain the socket so this - * function should get called again without select() or similar in between! */ -CURLcode Curl_readwrite(struct connectdata *conn, - struct Curl_easy *data, - bool *done, - bool *comeback) +CURLcode Curl_sendrecv(struct Curl_easy *data, struct curltime *nowp) { struct SingleRequest *k = &data->req; - CURLcode result; - struct curltime now; + CURLcode result = CURLE_OK; int didwhat = 0; - int select_bits; - - if(data->state.dselect_bits) { - select_bits = data->state.dselect_bits; - data->state.dselect_bits = 0; - } - else if(conn->cselect_bits) { - select_bits = conn->cselect_bits; - conn->cselect_bits = 0; - } - else { - curl_socket_t fd_read; - curl_socket_t fd_write; - /* only use the proper socket if the *_HOLD bit is not set simultaneously - as then we are in rate limiting state in that transfer direction */ - if((k->keepon & KEEP_RECVBITS) == KEEP_RECV) - fd_read = conn->sockfd; - else - fd_read = CURL_SOCKET_BAD; - - if((k->keepon & KEEP_SENDBITS) == KEEP_SEND) - fd_write = conn->writesockfd; - else - fd_write = CURL_SOCKET_BAD; - - select_bits = Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write, 0); - } - - if(select_bits == CURL_CSELECT_ERR) { - failf(data, "select/poll returned error"); - result = CURLE_SEND_ERROR; - goto out; + DEBUGASSERT(nowp); + if(data->state.select_bits) { + if(select_bits_paused(data, data->state.select_bits)) { + /* leave the bits unchanged, so they'll tell us what to do when + * this transfer gets unpaused. */ + result = CURLE_OK; + goto out; + } + data->state.select_bits = 0; } #ifdef USE_HYPER - if(conn->datastream) { - result = conn->datastream(data, conn, &didwhat, done, select_bits); - if(result || *done) + if(data->conn->datastream) { + result = data->conn->datastream(data, data->conn, &didwhat, + CURL_CSELECT_OUT|CURL_CSELECT_IN); + if(result || data->req.done) goto out; } else { #endif - /* We go ahead and do a read if we have a readable socket or if - the stream was rewound (in which case we have data in a - buffer) */ - if((k->keepon & KEEP_RECV) && (select_bits & CURL_CSELECT_IN)) { - result = readwrite_data(data, conn, k, &didwhat, done, comeback); - if(result || *done) + /* We go ahead and do a read if we have a readable socket or if the stream + was rewound (in which case we have data in a buffer) */ + if(k->keepon & KEEP_RECV) { + result = sendrecv_dl(data, k, &didwhat); + if(result || data->req.done) goto out; } /* If we still have writing to do, we check if we have a writable socket. */ - if((k->keepon & KEEP_SEND) && (select_bits & CURL_CSELECT_OUT)) { - /* write */ - - result = readwrite_upload(data, conn, &didwhat); + if(Curl_req_want_send(data) || (data->req.keepon & KEEP_SEND_TIMED)) { + result = sendrecv_ul(data, &didwhat); if(result) goto out; } @@ -1130,33 +461,8 @@ CURLcode Curl_readwrite(struct connectdata *conn, } #endif - now = Curl_now(); if(!didwhat) { - /* no read no write, this is a timeout? */ - if(k->exp100 == EXP100_AWAITING_CONTINUE) { - /* This should allow some time for the header to arrive, but only a - very short time as otherwise it'll be too much wasted time too - often. */ - - /* Quoting RFC2616, section "8.2.3 Use of the 100 (Continue) Status": - - Therefore, when a client sends this header field to an origin server - (possibly via a proxy) from which it has never seen a 100 (Continue) - status, the client SHOULD NOT wait for an indefinite period before - sending the request body. - - */ - - timediff_t ms = Curl_timediff(now, k->start100); - if(ms >= data->set.expect_100_timeout) { - /* we've waited long enough, continue anyway */ - k->exp100 = EXP100_SEND_DATA; - k->keepon |= KEEP_SEND; - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - infof(data, "Done waiting for 100-continue"); - } - } - + /* Transfer wanted to send/recv, but nothing was possible. */ result = Curl_conn_ev_data_idle(data); if(result) goto out; @@ -1165,23 +471,23 @@ CURLcode Curl_readwrite(struct connectdata *conn, if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(data, now); + result = Curl_speedcheck(data, *nowp); if(result) goto out; if(k->keepon) { - if(0 > Curl_timeleft(data, &now, FALSE)) { + if(0 > Curl_timeleft(data, nowp, FALSE)) { if(k->size != -1) { - failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds with %" CURL_FORMAT_CURL_OFF_T " out of %" - CURL_FORMAT_CURL_OFF_T " bytes received", - Curl_timediff(now, data->progress.t_startsingle), + failf(data, "Operation timed out after %" FMT_TIMEDIFF_T + " milliseconds with %" FMT_OFF_T " out of %" + FMT_OFF_T " bytes received", + Curl_timediff(*nowp, data->progress.t_startsingle), k->bytecount, k->size); } else { - failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds with %" CURL_FORMAT_CURL_OFF_T " bytes received", - Curl_timediff(now, data->progress.t_startsingle), + failf(data, "Operation timed out after %" FMT_TIMEDIFF_T + " milliseconds with %" FMT_OFF_T " bytes received", + Curl_timediff(*nowp, data->progress.t_startsingle), k->bytecount); } result = CURLE_OPERATION_TIMEDOUT; @@ -1193,97 +499,29 @@ CURLcode Curl_readwrite(struct connectdata *conn, * The transfer has been performed. Just make some general checks before * returning. */ - if(!(data->req.no_body) && (k->size != -1) && - (k->bytecount != k->size) && -#ifdef CURL_DO_LINEEND_CONV - /* Most FTP servers don't adjust their file SIZE response for CRLFs, - so we'll check to see if the discrepancy can be explained - by the number of CRLFs we've changed to LFs. - */ - (k->bytecount != (k->size + data->state.crlf_conversions)) && -#endif /* CURL_DO_LINEEND_CONV */ - !k->newurl) { - failf(data, "transfer closed with %" CURL_FORMAT_CURL_OFF_T + (k->bytecount != k->size) && !k->newurl) { + failf(data, "transfer closed with %" FMT_OFF_T " bytes remaining to read", k->size - k->bytecount); result = CURLE_PARTIAL_FILE; goto out; } - if(!(data->req.no_body) && k->chunk && - (conn->chunk.state != CHUNK_STOP)) { - /* - * In chunked mode, return an error if the connection is closed prior to - * the empty (terminating) chunk is read. - * - * The condition above used to check for - * conn->proto.http->chunk.datasize != 0 which is true after reading - * *any* chunk, not just the empty chunk. - * - */ - failf(data, "transfer closed with outstanding read data remaining"); - result = CURLE_PARTIAL_FILE; - goto out; - } if(Curl_pgrsUpdate(data)) { result = CURLE_ABORTED_BY_CALLBACK; goto out; } } - /* Now update the "done" boolean we return */ - *done = (0 == (k->keepon&(KEEP_RECVBITS|KEEP_SENDBITS))) ? TRUE : FALSE; + /* If there is nothing more to send/recv, the request is done */ + if(0 == (k->keepon&(KEEP_RECVBITS|KEEP_SENDBITS))) + data->req.done = TRUE; + out: if(result) - DEBUGF(infof(data, "Curl_readwrite() -> %d", result)); + DEBUGF(infof(data, "Curl_sendrecv() -> %d", result)); return result; } -/* - * Curl_single_getsock() gets called by the multi interface code when the app - * has requested to get the sockets for the current connection. This function - * will then be called once for every connection that the multi interface - * keeps track of. This function will only be called for connections that are - * in the proper state to have this information available. - */ -int Curl_single_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *sock) -{ - int bitmap = GETSOCK_BLANK; - unsigned sockindex = 0; - - if(conn->handler->perform_getsock) - return conn->handler->perform_getsock(data, conn, sock); - - /* don't include HOLD and PAUSE connections */ - if((data->req.keepon & KEEP_RECVBITS) == KEEP_RECV) { - - DEBUGASSERT(conn->sockfd != CURL_SOCKET_BAD); - - bitmap |= GETSOCK_READSOCK(sockindex); - sock[sockindex] = conn->sockfd; - } - - /* don't include HOLD and PAUSE connections */ - if((data->req.keepon & KEEP_SENDBITS) == KEEP_SEND) { - if((conn->sockfd != conn->writesockfd) || - bitmap == GETSOCK_BLANK) { - /* only if they are not the same socket and we have a readable - one, we increase index */ - if(bitmap != GETSOCK_BLANK) - sockindex++; /* increase index if we need two entries */ - - DEBUGASSERT(conn->writesockfd != CURL_SOCKET_BAD); - - sock[sockindex] = conn->writesockfd; - } - - bitmap |= GETSOCK_WRITESOCK(sockindex); - } - - return bitmap; -} - /* Curl_init_CONNECT() gets called each time the handle switches to CONNECT which means this gets called once for each subsequent redirect etc */ void Curl_init_CONNECT(struct Curl_easy *data) @@ -1303,7 +541,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) CURLcode result; if(!data->state.url && !data->set.uh) { - /* we can't do anything without URL */ + /* we cannot do anything without URL */ failf(data, "No URL set"); return CURLE_URL_MALFORMAT; } @@ -1327,7 +565,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) } if(data->set.postfields && data->set.set_resume_from) { - /* we can't */ + /* we cannot */ failf(data, "cannot mix POSTFIELDS with RESUME_FROM"); return CURLE_BAD_FUNCTION_ARGUMENT; } @@ -1413,8 +651,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) return CURLE_OUT_OF_MEMORY; } wc = data->wildcard; - if((wc->state < CURLWC_INIT) || - (wc->state >= CURLWC_CLEAN)) { + if(wc->state < CURLWC_INIT) { if(wc->ftpwc) wc->dtor(wc->ftpwc); Curl_safefree(wc->pattern); @@ -1430,7 +667,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) /* * Set user-agent. Used for HTTP, but since we can attempt to tunnel - * basically anything through an HTTP proxy we can't limit this based on + * basically anything through an HTTP proxy we cannot limit this based on * protocol. */ if(data->set.str[STRING_USERAGENT]) { @@ -1441,330 +678,29 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) return CURLE_OUT_OF_MEMORY; } + if(data->set.str[STRING_USERNAME] || + data->set.str[STRING_PASSWORD]) + data->state.creds_from = CREDS_OPTION; if(!result) result = Curl_setstropt(&data->state.aptr.user, data->set.str[STRING_USERNAME]); if(!result) result = Curl_setstropt(&data->state.aptr.passwd, data->set.str[STRING_PASSWORD]); +#ifndef CURL_DISABLE_PROXY if(!result) result = Curl_setstropt(&data->state.aptr.proxyuser, data->set.str[STRING_PROXYUSERNAME]); if(!result) result = Curl_setstropt(&data->state.aptr.proxypasswd, data->set.str[STRING_PROXYPASSWORD]); +#endif data->req.headerbytecount = 0; Curl_headers_cleanup(data); return result; } -/* - * Curl_posttransfer() is called immediately after a transfer ends - */ -CURLcode Curl_posttransfer(struct Curl_easy *data) -{ -#if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) - /* restore the signal handler for SIGPIPE before we get back */ - if(!data->set.no_signal) - signal(SIGPIPE, data->state.prev_signal); -#else - (void)data; /* unused parameter */ -#endif - - return CURLE_OK; -} - -/* - * Curl_follow() handles the URL redirect magic. Pass in the 'newurl' string - * as given by the remote server and set up the new URL to request. - * - * This function DOES NOT FREE the given url. - */ -CURLcode Curl_follow(struct Curl_easy *data, - char *newurl, /* the Location: string */ - followtype type) /* see transfer.h */ -{ -#ifdef CURL_DISABLE_HTTP - (void)data; - (void)newurl; - (void)type; - /* Location: following will not happen when HTTP is disabled */ - return CURLE_TOO_MANY_REDIRECTS; -#else - - /* Location: redirect */ - bool disallowport = FALSE; - bool reachedmax = FALSE; - CURLUcode uc; - - DEBUGASSERT(type != FOLLOW_NONE); - - if(type != FOLLOW_FAKE) - data->state.requests++; /* count all real follows */ - if(type == FOLLOW_REDIR) { - if((data->set.maxredirs != -1) && - (data->state.followlocation >= data->set.maxredirs)) { - reachedmax = TRUE; - type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected - to URL */ - } - else { - data->state.followlocation++; /* count redirect-followings, including - auth reloads */ - - if(data->set.http_auto_referer) { - CURLU *u; - char *referer = NULL; - - /* We are asked to automatically set the previous URL as the referer - when we get the next URL. We pick the ->url field, which may or may - not be 100% correct */ - - if(data->state.referer_alloc) { - Curl_safefree(data->state.referer); - data->state.referer_alloc = FALSE; - } - - /* Make a copy of the URL without credentials and fragment */ - u = curl_url(); - if(!u) - return CURLE_OUT_OF_MEMORY; - - uc = curl_url_set(u, CURLUPART_URL, data->state.url, 0); - if(!uc) - uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0); - if(!uc) - uc = curl_url_set(u, CURLUPART_USER, NULL, 0); - if(!uc) - uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0); - if(!uc) - uc = curl_url_get(u, CURLUPART_URL, &referer, 0); - - curl_url_cleanup(u); - - if(uc || !referer) - return CURLE_OUT_OF_MEMORY; - - data->state.referer = referer; - data->state.referer_alloc = TRUE; /* yes, free this later */ - } - } - } - - if((type != FOLLOW_RETRY) && - (data->req.httpcode != 401) && (data->req.httpcode != 407) && - Curl_is_absolute_url(newurl, NULL, 0, FALSE)) { - /* If this is not redirect due to a 401 or 407 response and an absolute - URL: don't allow a custom port number */ - disallowport = TRUE; - } - - DEBUGASSERT(data->state.uh); - uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl, - (type == FOLLOW_FAKE) ? CURLU_NON_SUPPORT_SCHEME : - ((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) | - CURLU_ALLOW_SPACE | - (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)); - if(uc) { - if(type != FOLLOW_FAKE) { - failf(data, "The redirect target URL could not be parsed: %s", - curl_url_strerror(uc)); - return Curl_uc_to_curlcode(uc); - } - - /* the URL could not be parsed for some reason, but since this is FAKE - mode, just duplicate the field as-is */ - newurl = strdup(newurl); - if(!newurl) - return CURLE_OUT_OF_MEMORY; - } - else { - uc = curl_url_get(data->state.uh, CURLUPART_URL, &newurl, 0); - if(uc) - return Curl_uc_to_curlcode(uc); - - /* Clear auth if this redirects to a different port number or protocol, - unless permitted */ - if(!data->set.allow_auth_to_other_hosts && (type != FOLLOW_FAKE)) { - char *portnum; - int port; - bool clear = FALSE; - - if(data->set.use_port && data->state.allow_port) - /* a custom port is used */ - port = (int)data->set.use_port; - else { - uc = curl_url_get(data->state.uh, CURLUPART_PORT, &portnum, - CURLU_DEFAULT_PORT); - if(uc) { - free(newurl); - return Curl_uc_to_curlcode(uc); - } - port = atoi(portnum); - free(portnum); - } - if(port != data->info.conn_remote_port) { - infof(data, "Clear auth, redirects to port from %u to %u", - data->info.conn_remote_port, port); - clear = TRUE; - } - else { - char *scheme; - const struct Curl_handler *p; - uc = curl_url_get(data->state.uh, CURLUPART_SCHEME, &scheme, 0); - if(uc) { - free(newurl); - return Curl_uc_to_curlcode(uc); - } - - p = Curl_builtin_scheme(scheme, CURL_ZERO_TERMINATED); - if(p && (p->protocol != data->info.conn_protocol)) { - infof(data, "Clear auth, redirects scheme from %s to %s", - data->info.conn_scheme, scheme); - clear = TRUE; - } - free(scheme); - } - if(clear) { - Curl_safefree(data->state.aptr.user); - Curl_safefree(data->state.aptr.passwd); - } - } - } - - if(type == FOLLOW_FAKE) { - /* we're only figuring out the new url if we would've followed locations - but now we're done so we can get out! */ - data->info.wouldredirect = newurl; - - if(reachedmax) { - failf(data, "Maximum (%ld) redirects followed", data->set.maxredirs); - return CURLE_TOO_MANY_REDIRECTS; - } - return CURLE_OK; - } - - if(disallowport) - data->state.allow_port = FALSE; - - if(data->state.url_alloc) - Curl_safefree(data->state.url); - - data->state.url = newurl; - data->state.url_alloc = TRUE; - - infof(data, "Issue another request to this URL: '%s'", data->state.url); - - /* - * We get here when the HTTP code is 300-399 (and 401). We need to perform - * differently based on exactly what return code there was. - * - * News from 7.10.6: we can also get here on a 401 or 407, in case we act on - * an HTTP (proxy-) authentication scheme other than Basic. - */ - switch(data->info.httpcode) { - /* 401 - Act on a WWW-Authenticate, we keep on moving and do the - Authorization: XXXX header in the HTTP request code snippet */ - /* 407 - Act on a Proxy-Authenticate, we keep on moving and do the - Proxy-Authorization: XXXX header in the HTTP request code snippet */ - /* 300 - Multiple Choices */ - /* 306 - Not used */ - /* 307 - Temporary Redirect */ - default: /* for all above (and the unknown ones) */ - /* Some codes are explicitly mentioned since I've checked RFC2616 and they - * seem to be OK to POST to. - */ - break; - case 301: /* Moved Permanently */ - /* (quote from RFC7231, section 6.4.2) - * - * Note: For historical reasons, a user agent MAY change the request - * method from POST to GET for the subsequent request. If this - * behavior is undesired, the 307 (Temporary Redirect) status code - * can be used instead. - * - * ---- - * - * Many webservers expect this, so these servers often answers to a POST - * request with an error page. To be sure that libcurl gets the page that - * most user agents would get, libcurl has to force GET. - * - * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and - * can be overridden with CURLOPT_POSTREDIR. - */ - if((data->state.httpreq == HTTPREQ_POST - || data->state.httpreq == HTTPREQ_POST_FORM - || data->state.httpreq == HTTPREQ_POST_MIME) - && !(data->set.keep_post & CURL_REDIR_POST_301)) { - infof(data, "Switch from POST to GET"); - data->state.httpreq = HTTPREQ_GET; - } - break; - case 302: /* Found */ - /* (quote from RFC7231, section 6.4.3) - * - * Note: For historical reasons, a user agent MAY change the request - * method from POST to GET for the subsequent request. If this - * behavior is undesired, the 307 (Temporary Redirect) status code - * can be used instead. - * - * ---- - * - * Many webservers expect this, so these servers often answers to a POST - * request with an error page. To be sure that libcurl gets the page that - * most user agents would get, libcurl has to force GET. - * - * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and - * can be overridden with CURLOPT_POSTREDIR. - */ - if((data->state.httpreq == HTTPREQ_POST - || data->state.httpreq == HTTPREQ_POST_FORM - || data->state.httpreq == HTTPREQ_POST_MIME) - && !(data->set.keep_post & CURL_REDIR_POST_302)) { - infof(data, "Switch from POST to GET"); - data->state.httpreq = HTTPREQ_GET; - } - break; - - case 303: /* See Other */ - /* 'See Other' location is not the resource but a substitute for the - * resource. In this case we switch the method to GET/HEAD, unless the - * method is POST and the user specified to keep it as POST. - * https://github.com/curl/curl/issues/5237#issuecomment-614641049 - */ - if(data->state.httpreq != HTTPREQ_GET && - ((data->state.httpreq != HTTPREQ_POST && - data->state.httpreq != HTTPREQ_POST_FORM && - data->state.httpreq != HTTPREQ_POST_MIME) || - !(data->set.keep_post & CURL_REDIR_POST_303))) { - data->state.httpreq = HTTPREQ_GET; - infof(data, "Switch to %s", - data->req.no_body?"HEAD":"GET"); - } - break; - case 304: /* Not Modified */ - /* 304 means we did a conditional request and it was "Not modified". - * We shouldn't get any Location: header in this response! - */ - break; - case 305: /* Use Proxy */ - /* (quote from RFC2616, section 10.3.6): - * "The requested resource MUST be accessed through the proxy given - * by the Location field. The Location field gives the URI of the - * proxy. The recipient is expected to repeat this single request - * via the proxy. 305 responses MUST only be generated by origin - * servers." - */ - break; - } - Curl_pgrsTime(data, TIMER_REDIRECT); - Curl_pgrsResetTransferSizes(data); - - return CURLE_OK; -#endif /* CURL_DISABLE_HTTP */ -} - /* Returns CURLE_OK *and* sets '*url' if a request retry is wanted. NOTE: that the *url is malloc()ed. */ @@ -1774,8 +710,9 @@ CURLcode Curl_retry_request(struct Curl_easy *data, char **url) bool retry = FALSE; *url = NULL; - /* if we're talking upload, we can't do the checks below, unless the protocol - is HTTP as when uploading over HTTP we will still get a response */ + /* if we are talking upload, we cannot do the checks below, unless the + protocol is HTTP as when uploading over HTTP we will still get a + response */ if(data->state.upload && !(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP))) return CURLE_OK; @@ -1821,66 +758,62 @@ CURLcode Curl_retry_request(struct Curl_easy *data, char **url) return CURLE_OUT_OF_MEMORY; connclose(conn, "retry"); /* close this connection */ - conn->bits.retry = TRUE; /* mark this as a connection we're about + conn->bits.retry = TRUE; /* mark this as a connection we are about to retry. Marking it this way should prevent i.e HTTP transfers to return error just because nothing has been transferred! */ - - - if((conn->handler->protocol&PROTO_FAMILY_HTTP) && - data->req.writebytecount) { - data->state.rewindbeforesend = TRUE; - infof(data, "state.rewindbeforesend = TRUE"); - } + Curl_creader_set_rewind(data, TRUE); } return CURLE_OK; } /* - * Curl_setup_transfer() is called to setup some basic properties for the - * upcoming transfer. + * xfer_setup() is called to setup basic properties for the transfer. */ -void -Curl_setup_transfer( +static void xfer_setup( struct Curl_easy *data, /* transfer */ int sockindex, /* socket index to read from or -1 */ curl_off_t size, /* -1 if unknown at this point */ bool getheader, /* TRUE if header parsing is wanted */ - int writesockindex /* socket index to write to, it may very well be - the same we read from. -1 disables */ + int writesockindex, /* socket index to write to, it may be the same we + read from. -1 disables */ + bool shutdown, /* shutdown connection at transfer end. Only + * supported when sending OR receiving. */ + bool shutdown_err_ignore /* errors during shutdown do not fail the + * transfer */ ) { struct SingleRequest *k = &data->req; struct connectdata *conn = data->conn; - struct HTTP *http = data->req.p.http; - bool httpsending; + bool want_send = Curl_req_want_send(data); DEBUGASSERT(conn != NULL); DEBUGASSERT((sockindex <= 1) && (sockindex >= -1)); + DEBUGASSERT((writesockindex <= 1) && (writesockindex >= -1)); + DEBUGASSERT(!shutdown || (sockindex == -1) || (writesockindex == -1)); - httpsending = ((conn->handler->protocol&PROTO_FAMILY_HTTP) && - (http->sending == HTTPSEND_REQUEST)); - - if(conn->bits.multiplex || conn->httpversion >= 20 || httpsending) { + if(conn->bits.multiplex || conn->httpversion >= 20 || want_send) { /* when multiplexing, the read/write sockets need to be the same! */ conn->sockfd = sockindex == -1 ? ((writesockindex == -1 ? CURL_SOCKET_BAD : conn->sock[writesockindex])) : conn->sock[sockindex]; conn->writesockfd = conn->sockfd; - if(httpsending) - /* special and very HTTP-specific */ + if(want_send) + /* special and HTTP-specific */ writesockindex = FIRSTSOCKET; } else { conn->sockfd = sockindex == -1 ? CURL_SOCKET_BAD : conn->sock[sockindex]; conn->writesockfd = writesockindex == -1 ? - CURL_SOCKET_BAD:conn->sock[writesockindex]; + CURL_SOCKET_BAD : conn->sock[writesockindex]; } - k->getheader = getheader; + k->getheader = getheader; k->size = size; + k->shutdown = shutdown; + k->shutdown_err_ignore = shutdown_err_ignore; /* The code sequence below is placed in this function just because all necessary input is not always known in do_complete() as this function may @@ -1891,43 +824,169 @@ Curl_setup_transfer( if(size > 0) Curl_pgrsSetDownloadSize(data, size); } - /* we want header and/or body, if neither then don't do this! */ + /* we want header and/or body, if neither then do not do this! */ if(k->getheader || !data->req.no_body) { if(sockindex != -1) k->keepon |= KEEP_RECV; - if(writesockindex != -1) { - /* HTTP 1.1 magic: - - Even if we require a 100-return code before uploading data, we might - need to write data before that since the REQUEST may not have been - finished sent off just yet. - - Thus, we must check if the request has been sent before we set the - state info where we wait for the 100-return code - */ - if((data->state.expect100header) && - (conn->handler->protocol&PROTO_FAMILY_HTTP) && - (http->sending == HTTPSEND_BODY)) { - /* wait with write until we either got 100-continue or a timeout */ - k->exp100 = EXP100_AWAITING_CONTINUE; - k->start100 = Curl_now(); - - /* Set a timeout for the multi interface. Add the inaccuracy margin so - that we don't fire slightly too early and get denied to run. */ - Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT); - } - else { - if(data->state.expect100header) - /* when we've sent off the rest of the headers, we must await a - 100-continue but first finish sending the request */ - k->exp100 = EXP100_SENDING_REQUEST; - - /* enable the write bit when we're not waiting for continue */ - k->keepon |= KEEP_SEND; - } - } /* if(writesockindex != -1) */ + if(writesockindex != -1) + k->keepon |= KEEP_SEND; } /* if(k->getheader || !data->req.no_body) */ } + +void Curl_xfer_setup_nop(struct Curl_easy *data) +{ + xfer_setup(data, -1, -1, FALSE, -1, FALSE, FALSE); +} + +void Curl_xfer_setup1(struct Curl_easy *data, + int send_recv, + curl_off_t recv_size, + bool getheader) +{ + int recv_index = (send_recv & CURL_XFER_RECV) ? FIRSTSOCKET : -1; + int send_index = (send_recv & CURL_XFER_SEND) ? FIRSTSOCKET : -1; + DEBUGASSERT((recv_index >= 0) || (recv_size == -1)); + xfer_setup(data, recv_index, recv_size, getheader, send_index, FALSE, FALSE); +} + +void Curl_xfer_setup2(struct Curl_easy *data, + int send_recv, + curl_off_t recv_size, + bool shutdown, + bool shutdown_err_ignore) +{ + int recv_index = (send_recv & CURL_XFER_RECV) ? SECONDARYSOCKET : -1; + int send_index = (send_recv & CURL_XFER_SEND) ? SECONDARYSOCKET : -1; + DEBUGASSERT((recv_index >= 0) || (recv_size == -1)); + xfer_setup(data, recv_index, recv_size, FALSE, send_index, + shutdown, shutdown_err_ignore); +} + +CURLcode Curl_xfer_write_resp(struct Curl_easy *data, + const char *buf, size_t blen, + bool is_eos) +{ + CURLcode result = CURLE_OK; + + if(data->conn->handler->write_resp) { + /* protocol handlers offering this function take full responsibility + * for writing all received download data to the client. */ + result = data->conn->handler->write_resp(data, buf, blen, is_eos); + } + else { + /* No special handling by protocol handler, write all received data + * as BODY to the client. */ + if(blen || is_eos) { + int cwtype = CLIENTWRITE_BODY; + if(is_eos) + cwtype |= CLIENTWRITE_EOS; + result = Curl_client_write(data, cwtype, buf, blen); + } + } + + if(!result && is_eos) { + /* If we wrote the EOS, we are definitely done */ + data->req.eos_written = TRUE; + data->req.download_done = TRUE; + } + CURL_TRC_WRITE(data, "xfer_write_resp(len=%zu, eos=%d) -> %d", + blen, is_eos, result); + return result; +} + +CURLcode Curl_xfer_write_resp_hd(struct Curl_easy *data, + const char *hd0, size_t hdlen, bool is_eos) +{ + if(data->conn->handler->write_resp_hd) { + /* protocol handlers offering this function take full responsibility + * for writing all received download data to the client. */ + return data->conn->handler->write_resp_hd(data, hd0, hdlen, is_eos); + } + /* No special handling by protocol handler, write as response bytes */ + return Curl_xfer_write_resp(data, hd0, hdlen, is_eos); +} + +CURLcode Curl_xfer_write_done(struct Curl_easy *data, bool premature) +{ + (void)premature; + return Curl_cw_out_done(data); +} + +bool Curl_xfer_needs_flush(struct Curl_easy *data) +{ + int sockindex; + sockindex = ((data->conn->writesockfd != CURL_SOCKET_BAD) && + (data->conn->writesockfd == data->conn->sock[SECONDARYSOCKET])); + return Curl_conn_needs_flush(data, sockindex); +} + +CURLcode Curl_xfer_flush(struct Curl_easy *data) +{ + int sockindex; + sockindex = ((data->conn->writesockfd != CURL_SOCKET_BAD) && + (data->conn->writesockfd == data->conn->sock[SECONDARYSOCKET])); + return Curl_conn_flush(data, sockindex); +} + +CURLcode Curl_xfer_send(struct Curl_easy *data, + const void *buf, size_t blen, bool eos, + size_t *pnwritten) +{ + CURLcode result; + int sockindex; + + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + + sockindex = ((data->conn->writesockfd != CURL_SOCKET_BAD) && + (data->conn->writesockfd == data->conn->sock[SECONDARYSOCKET])); + result = Curl_conn_send(data, sockindex, buf, blen, eos, pnwritten); + if(result == CURLE_AGAIN) { + result = CURLE_OK; + *pnwritten = 0; + } + else if(!result && *pnwritten) + data->info.request_size += *pnwritten; + + DEBUGF(infof(data, "Curl_xfer_send(len=%zu, eos=%d) -> %d, %zu", + blen, eos, result, *pnwritten)); + return result; +} + +CURLcode Curl_xfer_recv(struct Curl_easy *data, + char *buf, size_t blen, + ssize_t *pnrcvd) +{ + int sockindex; + + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + DEBUGASSERT(data->set.buffer_size > 0); + + sockindex = ((data->conn->sockfd != CURL_SOCKET_BAD) && + (data->conn->sockfd == data->conn->sock[SECONDARYSOCKET])); + if((size_t)data->set.buffer_size < blen) + blen = (size_t)data->set.buffer_size; + return Curl_conn_recv(data, sockindex, buf, blen, pnrcvd); +} + +CURLcode Curl_xfer_send_close(struct Curl_easy *data) +{ + Curl_conn_ev_data_done_send(data); + return CURLE_OK; +} + +bool Curl_xfer_is_blocked(struct Curl_easy *data) +{ + bool want_send = ((data)->req.keepon & KEEP_SEND); + bool want_recv = ((data)->req.keepon & KEEP_RECV); + if(!want_send) + return (want_recv && Curl_cwriter_is_paused(data)); + else if(!want_recv) + return (want_send && Curl_creader_is_paused(data)); + else + return Curl_creader_is_paused(data) && Curl_cwriter_is_paused(data); +} diff --git a/deps/curl/lib/transfer.h b/deps/curl/lib/transfer.h index 536ac249b7c..8c9b88c1785 100644 --- a/deps/curl/lib/transfer.h +++ b/deps/curl/lib/transfer.h @@ -32,7 +32,6 @@ char *Curl_checkheaders(const struct Curl_easy *data, void Curl_init_CONNECT(struct Curl_easy *data); CURLcode Curl_pretransfer(struct Curl_easy *data); -CURLcode Curl_posttransfer(struct Curl_easy *data); typedef enum { FOLLOW_NONE, /* not used within the function, just a placeholder to @@ -43,31 +42,113 @@ typedef enum { FOLLOW_REDIR /* a full true redirect */ } followtype; -CURLcode Curl_follow(struct Curl_easy *data, char *newurl, - followtype type); -CURLcode Curl_readwrite(struct connectdata *conn, - struct Curl_easy *data, bool *done, - bool *comeback); +CURLcode Curl_sendrecv(struct Curl_easy *data, struct curltime *nowp); int Curl_single_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks); -CURLcode Curl_fillreadbuffer(struct Curl_easy *data, size_t bytes, - size_t *nreadp); CURLcode Curl_retry_request(struct Curl_easy *data, char **url); bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc); -CURLcode Curl_get_upload_buffer(struct Curl_easy *data); - -CURLcode Curl_done_sending(struct Curl_easy *data, - struct SingleRequest *k); - -/* This sets up a forthcoming transfer */ -void -Curl_setup_transfer (struct Curl_easy *data, - int sockindex, /* socket index to read from or -1 */ - curl_off_t size, /* -1 if unknown at this point */ - bool getheader, /* TRUE if header parsing is wanted */ - int writesockindex /* socket index to write to. May be - the same we read from. -1 - disables */ - ); + +/** + * Write the transfer raw response bytes, as received from the connection. + * Will handle all passed bytes or return an error. By default, this will + * write the bytes as BODY to the client. Protocols may provide a + * "write_resp" callback in their handler to add specific treatment. E.g. + * HTTP parses response headers and passes them differently to the client. + * @param data the transfer + * @param buf the raw response bytes + * @param blen the amount of bytes in `buf` + * @param is_eos TRUE iff the connection indicates this to be the last + * bytes of the response + */ +CURLcode Curl_xfer_write_resp(struct Curl_easy *data, + const char *buf, size_t blen, + bool is_eos); + +/** + * Write a single "header" line from a server response. + * @param hd0 the 0-terminated, single header line + * @param hdlen the length of the header line + * @param is_eos TRUE iff this is the end of the response + */ +CURLcode Curl_xfer_write_resp_hd(struct Curl_easy *data, + const char *hd0, size_t hdlen, bool is_eos); + +#define CURL_XFER_NOP (0) +#define CURL_XFER_RECV (1<<(0)) +#define CURL_XFER_SEND (1<<(1)) +#define CURL_XFER_SENDRECV (CURL_XFER_RECV|CURL_XFER_SEND) + +/** + * The transfer is neither receiving nor sending now. + */ +void Curl_xfer_setup_nop(struct Curl_easy *data); + +/** + * The transfer will use socket 1 to send/recv. `recv_size` is + * the amount to receive or -1 if unknown. `getheader` indicates + * response header processing is expected. + */ +void Curl_xfer_setup1(struct Curl_easy *data, + int send_recv, + curl_off_t recv_size, + bool getheader); + +/** + * The transfer will use socket 2 to send/recv. `recv_size` is + * the amount to receive or -1 if unknown. With `shutdown` being + * set, the transfer is only allowed to either send OR receive + * and the socket 2 connection will be shutdown at the end of + * the transfer. An unclean shutdown will fail the transfer + * unless `shutdown_err_ignore` is TRUE. + */ +void Curl_xfer_setup2(struct Curl_easy *data, + int send_recv, + curl_off_t recv_size, + bool shutdown, bool shutdown_err_ignore); + +/** + * Multi has set transfer to DONE. Last chance to trigger + * missing response things like writing an EOS to the client. + */ +CURLcode Curl_xfer_write_done(struct Curl_easy *data, bool premature); + +/** + * Return TRUE iff transfer has pending data to send. Checks involved + * connection filters. + */ +bool Curl_xfer_needs_flush(struct Curl_easy *data); + +/** + * Flush any pending send data on the transfer connection. + */ +CURLcode Curl_xfer_flush(struct Curl_easy *data); + +/** + * Send data on the socket/connection filter designated + * for transfer's outgoing data. + * Will return CURLE_OK on blocking with (*pnwritten == 0). + */ +CURLcode Curl_xfer_send(struct Curl_easy *data, + const void *buf, size_t blen, bool eos, + size_t *pnwritten); + +/** + * Receive data on the socket/connection filter designated + * for transfer's incoming data. + * Will return CURLE_AGAIN on blocking with (*pnrcvd == 0). + */ +CURLcode Curl_xfer_recv(struct Curl_easy *data, + char *buf, size_t blen, + ssize_t *pnrcvd); + +CURLcode Curl_xfer_send_close(struct Curl_easy *data); +CURLcode Curl_xfer_send_shutdown(struct Curl_easy *data, bool *done); + +/** + * Return TRUE iff the transfer is not done, but further progress + * is blocked. For example when it is only receiving and its writer + * is PAUSED. + */ +bool Curl_xfer_is_blocked(struct Curl_easy *data); #endif /* HEADER_CURL_TRANSFER_H */ diff --git a/deps/curl/lib/url.c b/deps/curl/lib/url.c index 4f5673ed0d9..f9bb05f793e 100644 --- a/deps/curl/lib/url.c +++ b/deps/curl/lib/url.c @@ -56,7 +56,7 @@ #endif #ifndef HAVE_SOCKET -#error "We can't compile without socket() support!" +#error "We cannot compile without socket() support!" #endif #include @@ -136,7 +136,7 @@ static void data_priority_cleanup(struct Curl_easy *data); #endif /* Some parts of the code (e.g. chunked encoding) assume this buffer has at - * more than just a few bytes to play with. Don't let it become too small or + * more than just a few bytes to play with. Do not let it become too small or * bad things will happen. */ #if READBUFFER_SIZE < READBUFFER_MIN @@ -168,130 +168,6 @@ static curl_prot_t get_protocol_family(const struct Curl_handler *h) return h->family; } - -/* - * Protocol table. Schemes (roughly) in 2019 popularity order: - * - * HTTPS, HTTP, FTP, FTPS, SFTP, FILE, SCP, SMTP, LDAP, IMAPS, TELNET, IMAP, - * LDAPS, SMTPS, TFTP, SMB, POP3, GOPHER POP3S, RTSP, RTMP, SMBS, DICT - */ -static const struct Curl_handler * const protocols[] = { - -#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) - &Curl_handler_https, -#endif - -#ifndef CURL_DISABLE_HTTP - &Curl_handler_http, -#endif - -#ifdef USE_WEBSOCKETS -#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) - &Curl_handler_wss, -#endif - -#ifndef CURL_DISABLE_HTTP - &Curl_handler_ws, -#endif -#endif - -#ifndef CURL_DISABLE_FTP - &Curl_handler_ftp, -#endif - -#if defined(USE_SSL) && !defined(CURL_DISABLE_FTP) - &Curl_handler_ftps, -#endif - -#if defined(USE_SSH) - &Curl_handler_sftp, -#endif - -#ifndef CURL_DISABLE_FILE - &Curl_handler_file, -#endif - -#if defined(USE_SSH) && !defined(USE_WOLFSSH) - &Curl_handler_scp, -#endif - -#ifndef CURL_DISABLE_SMTP - &Curl_handler_smtp, -#ifdef USE_SSL - &Curl_handler_smtps, -#endif -#endif - -#ifndef CURL_DISABLE_LDAP - &Curl_handler_ldap, -#if !defined(CURL_DISABLE_LDAPS) && \ - ((defined(USE_OPENLDAP) && defined(USE_SSL)) || \ - (!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL))) - &Curl_handler_ldaps, -#endif -#endif - -#ifndef CURL_DISABLE_IMAP - &Curl_handler_imap, -#ifdef USE_SSL - &Curl_handler_imaps, -#endif -#endif - -#ifndef CURL_DISABLE_TELNET - &Curl_handler_telnet, -#endif - -#ifndef CURL_DISABLE_TFTP - &Curl_handler_tftp, -#endif - -#ifndef CURL_DISABLE_POP3 - &Curl_handler_pop3, -#ifdef USE_SSL - &Curl_handler_pop3s, -#endif -#endif - -#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) && \ - (SIZEOF_CURL_OFF_T > 4) - &Curl_handler_smb, -#ifdef USE_SSL - &Curl_handler_smbs, -#endif -#endif - -#ifndef CURL_DISABLE_RTSP - &Curl_handler_rtsp, -#endif - -#ifndef CURL_DISABLE_MQTT - &Curl_handler_mqtt, -#endif - -#ifndef CURL_DISABLE_GOPHER - &Curl_handler_gopher, -#ifdef USE_SSL - &Curl_handler_gophers, -#endif -#endif - -#ifdef USE_LIBRTMP - &Curl_handler_rtmp, - &Curl_handler_rtmpt, - &Curl_handler_rtmpe, - &Curl_handler_rtmpte, - &Curl_handler_rtmps, - &Curl_handler_rtmpts, -#endif - -#ifndef CURL_DISABLE_DICT - &Curl_handler_dict, -#endif - - (struct Curl_handler *) NULL -}; - void Curl_freeset(struct Curl_easy *data) { /* Free all dynamic strings stored in the data->set substructure. */ @@ -320,8 +196,8 @@ void Curl_freeset(struct Curl_easy *data) Curl_mime_cleanpart(&data->set.mimepost); #ifndef CURL_DISABLE_COOKIES - curl_slist_free_all(data->set.cookielist); - data->set.cookielist = NULL; + curl_slist_free_all(data->state.cookielist); + data->state.cookielist = NULL; #endif } @@ -358,23 +234,25 @@ CURLcode Curl_close(struct Curl_easy **datap) data = *datap; *datap = NULL; - Curl_expire_clear(data); /* shut off timers */ - /* Detach connection if any is left. This should not be normal, but can be the case for example with CONNECT_ONLY + recv/send (test 556) */ Curl_detach_connection(data); - if(data->multi) - /* This handle is still part of a multi handle, take care of this first - and detach this handle from there. */ - curl_multi_remove_handle(data->multi, data); + if(!data->state.internal) { + if(data->multi) + /* This handle is still part of a multi handle, take care of this first + and detach this handle from there. */ + curl_multi_remove_handle(data->multi, data); - if(data->multi_easy) { - /* when curl_easy_perform() is used, it creates its own multi handle to - use and this is the one */ - curl_multi_cleanup(data->multi_easy); - data->multi_easy = NULL; + if(data->multi_easy) { + /* when curl_easy_perform() is used, it creates its own multi handle to + use and this is the one */ + curl_multi_cleanup(data->multi_easy); + data->multi_easy = NULL; + } } + Curl_expire_clear(data); /* shut off any timers left */ + data->magic = 0; /* force a clear AFTER the possibly enforced removal from the multi handle, since that function uses the magic field! */ @@ -382,19 +260,14 @@ CURLcode Curl_close(struct Curl_easy **datap) if(data->state.rangestringalloc) free(data->state.range); - /* freed here just in case DONE wasn't called */ - Curl_free_request_state(data); + /* freed here just in case DONE was not called */ + Curl_req_free(&data->req, data); /* Close down all open SSL info and sessions */ Curl_ssl_close_all(data); Curl_safefree(data->state.first_host); - Curl_safefree(data->state.scratch); Curl_ssl_free_certinfo(data); - /* Cleanup possible redirect junk */ - free(data->req.newurl); - data->req.newurl = NULL; - if(data->state.referer_alloc) { Curl_safefree(data->state.referer); data->state.referer_alloc = FALSE; @@ -402,17 +275,17 @@ CURLcode Curl_close(struct Curl_easy **datap) data->state.referer = NULL; up_free(data); - Curl_safefree(data->state.buffer); Curl_dyn_free(&data->state.headerb); - Curl_safefree(data->state.ulbuf); Curl_flush_cookies(data, TRUE); +#ifndef CURL_DISABLE_ALTSVC Curl_altsvc_save(data, data->asi, data->set.str[STRING_ALTSVC]); Curl_altsvc_cleanup(&data->asi); - Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]); +#endif #ifndef CURL_DISABLE_HSTS + Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]); if(!data->share || !data->share->hsts) Curl_hsts_cleanup(&data->hsts); - curl_slist_free_all(data->set.hstslist); /* clean up list */ + curl_slist_free_all(data->state.hstslist); /* clean up list */ #endif #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH) Curl_http_auth_cleanup_digest(data); @@ -433,7 +306,9 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); } +#ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuserpwd); +#endif Curl_safefree(data->state.aptr.uagent); Curl_safefree(data->state.aptr.userpwd); Curl_safefree(data->state.aptr.accept_encoding); @@ -441,24 +316,21 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_safefree(data->state.aptr.rangeline); Curl_safefree(data->state.aptr.ref); Curl_safefree(data->state.aptr.host); +#ifndef CURL_DISABLE_COOKIES Curl_safefree(data->state.aptr.cookiehost); +#endif +#ifndef CURL_DISABLE_RTSP Curl_safefree(data->state.aptr.rtsp_transport); +#endif Curl_safefree(data->state.aptr.user); Curl_safefree(data->state.aptr.passwd); +#ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuser); Curl_safefree(data->state.aptr.proxypasswd); - -#ifndef CURL_DISABLE_DOH - if(data->req.doh) { - Curl_dyn_free(&data->req.doh->probe[0].serverdoh); - Curl_dyn_free(&data->req.doh->probe[1].serverdoh); - curl_slist_free_all(data->req.doh->headers); - Curl_safefree(data->req.doh); - } #endif +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_FORM_API) Curl_mime_cleanpart(data->state.formp); -#ifndef CURL_DISABLE_HTTP Curl_safefree(data->state.formp); #endif @@ -466,6 +338,7 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_wildcard_dtor(&data->wildcard); Curl_freeset(data); Curl_headers_cleanup(data); + Curl_netrc_cleanup(&data->state.netrc); free(data); return CURLE_OK; } @@ -490,10 +363,9 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) set->fread_func_set = (curl_read_callback)fread; set->is_fread_set = 0; - set->seek_func = ZERO_NULL; set->seek_client = ZERO_NULL; - set->filesize = -1; /* we don't know the size */ + set->filesize = -1; /* we do not know the size */ set->postfieldsize = -1; /* unknown size */ set->maxredirs = 30; /* sensible default */ @@ -530,31 +402,20 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) Curl_mime_initpart(&set->mimepost); - /* - * libcurl 7.10 introduced SSL verification *by default*! This needs to be - * switched off unless wanted. - */ + Curl_ssl_easy_config_init(data); #ifndef CURL_DISABLE_DOH set->doh_verifyhost = TRUE; set->doh_verifypeer = TRUE; #endif - set->ssl.primary.verifypeer = TRUE; - set->ssl.primary.verifyhost = TRUE; #ifdef USE_SSH /* defaults to any auth type */ set->ssh_auth_types = CURLSSH_AUTH_DEFAULT; set->new_directory_perms = 0755; /* Default permissions */ #endif - set->ssl.primary.sessionid = TRUE; /* session ID caching enabled by - default */ -#ifndef CURL_DISABLE_PROXY - set->proxy_ssl = set->ssl; -#endif set->new_file_perms = 0644; /* Default permissions */ set->allowed_protocols = (curl_prot_t) CURLPROTO_ALL; - set->redir_protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | - CURLPROTO_FTPS; + set->redir_protocols = CURLPROTO_REDIR; #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) /* @@ -566,29 +427,33 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) /* Set the default CA cert bundle/path detected/specified at build time. * - * If Schannel is the selected SSL backend then these locations are - * ignored. We allow setting CA location for schannel only when explicitly - * specified by the user via CURLOPT_CAINFO / --cacert. + * If Schannel or Secure Transport is the selected SSL backend then these + * locations are ignored. We allow setting CA location for Schannel and + * Secure Transport when explicitly specified by the user via + * CURLOPT_CAINFO / --cacert. */ - if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { + if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL && + Curl_ssl_backend() != CURLSSLBACKEND_SECURETRANSPORT) { #if defined(CURL_CA_BUNDLE) result = Curl_setstropt(&set->str[STRING_SSL_CAFILE], CURL_CA_BUNDLE); if(result) return result; - +#ifndef CURL_DISABLE_PROXY result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY], CURL_CA_BUNDLE); if(result) return result; #endif +#endif #if defined(CURL_CA_PATH) result = Curl_setstropt(&set->str[STRING_SSL_CAPATH], CURL_CA_PATH); if(result) return result; - +#ifndef CURL_DISABLE_PROXY result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], CURL_CA_PATH); if(result) return result; +#endif #endif } @@ -601,6 +466,7 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) set->tcp_keepalive = FALSE; set->tcp_keepintvl = 60; set->tcp_keepidle = 60; + set->tcp_keepcnt = 9; set->tcp_fastopen = FALSE; set->tcp_nodelay = TRUE; set->ssl_enable_alpn = TRUE; @@ -640,19 +506,22 @@ CURLcode Curl_open(struct Curl_easy **curl) CURLcode result; struct Curl_easy *data; - /* Very simple start-up: alloc the struct, init it with zeroes and return */ + /* simple start-up: alloc the struct, init it with zeroes and return */ data = calloc(1, sizeof(struct Curl_easy)); if(!data) { - /* this is a very serious error */ + /* this is a serious error */ DEBUGF(fprintf(stderr, "Error: calloc of Curl_easy failed\n")); return CURLE_OUT_OF_MEMORY; } data->magic = CURLEASY_MAGIC_NUMBER; + Curl_req_init(&data->req); + result = Curl_resolver_init(data, &data->state.async.resolver); if(result) { DEBUGF(fprintf(stderr, "Error: resolver_init failed\n")); + Curl_req_free(&data->req, data); free(data); return result; } @@ -667,37 +536,33 @@ CURLcode Curl_open(struct Curl_easy **curl) data->state.recent_conn_id = -1; /* and not assigned an id yet */ data->id = -1; + data->mid = -1; +#ifndef CURL_DISABLE_DOH + data->set.dohfor_mid = -1; +#endif data->progress.flags |= PGRS_HIDE; data->state.current_speed = -1; /* init to negative == impossible */ +#ifndef CURL_DISABLE_HTTP + Curl_llist_init(&data->state.httphdrs, NULL); +#endif + Curl_netrc_init(&data->state.netrc); } if(result) { Curl_resolver_cleanup(data->state.async.resolver); Curl_dyn_free(&data->state.headerb); Curl_freeset(data); + Curl_req_free(&data->req, data); free(data); data = NULL; } else *curl = data; - return result; } -static void conn_shutdown(struct Curl_easy *data) -{ - DEBUGASSERT(data); - infof(data, "Closing connection"); - - /* possible left-overs from the async name resolvers */ - Curl_resolver_cancel(data); - - Curl_conn_close(data, SECONDARYSOCKET); - Curl_conn_close(data, FIRSTSOCKET); -} - -static void conn_free(struct Curl_easy *data, struct connectdata *conn) +void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn) { size_t i; @@ -718,33 +583,30 @@ static void conn_free(struct Curl_easy *data, struct connectdata *conn) Curl_safefree(conn->socks_proxy.passwd); Curl_safefree(conn->http_proxy.host.rawalloc); /* http proxy name buffer */ Curl_safefree(conn->socks_proxy.host.rawalloc); /* socks proxy name buffer */ - Curl_free_primary_ssl_config(&conn->proxy_ssl_config); #endif Curl_safefree(conn->user); Curl_safefree(conn->passwd); Curl_safefree(conn->sasl_authzid); Curl_safefree(conn->options); Curl_safefree(conn->oauth_bearer); -#ifndef CURL_DISABLE_HTTP - Curl_dyn_free(&conn->trailer); -#endif - Curl_safefree(conn->host.rawalloc); /* host name buffer */ - Curl_safefree(conn->conn_to_host.rawalloc); /* host name buffer */ + Curl_safefree(conn->host.rawalloc); /* hostname buffer */ + Curl_safefree(conn->conn_to_host.rawalloc); /* hostname buffer */ Curl_safefree(conn->hostname_resolve); Curl_safefree(conn->secondaryhostname); Curl_safefree(conn->localdev); - Curl_free_primary_ssl_config(&conn->ssl_config); + Curl_ssl_conn_config_cleanup(conn); #ifdef USE_UNIX_SOCKETS Curl_safefree(conn->unix_domain_socket); #endif + Curl_safefree(conn->destination); free(conn); /* free all the connection oriented data */ } /* * Disconnects the given connection. Note the connection may not be the - * primary connection, like when freeing room in the connection cache or + * primary connection, like when freeing room in the connection pool or * killing of a dead old connection. * * A connection needs an easy handle when closing down. We support this passed @@ -752,18 +614,16 @@ static void conn_free(struct Curl_easy *data, struct connectdata *conn) * disassociated from an easy handle. * * This function MUST NOT reset state in the Curl_easy struct if that - * isn't strictly bound to the life-time of *this* particular connection. - * + * is not strictly bound to the life-time of *this* particular connection. */ - -void Curl_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead_connection) +bool Curl_on_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool aborted) { /* there must be a connection to close */ DEBUGASSERT(conn); - /* it must be removed from the connection cache */ - DEBUGASSERT(!conn->bundle); + /* it must be removed from the connection pool */ + DEBUGASSERT(!conn->bits.in_cpool); /* there must be an associated transfer */ DEBUGASSERT(data); @@ -771,22 +631,11 @@ void Curl_disconnect(struct Curl_easy *data, /* the transfer must be detached from the connection */ DEBUGASSERT(!data->conn); - DEBUGF(infof(data, "Curl_disconnect(conn #%" - CURL_FORMAT_CURL_OFF_T ", dead=%d)", - conn->connection_id, dead_connection)); - /* - * If this connection isn't marked to force-close, leave it open if there - * are other users of it - */ - if(CONN_INUSE(conn) && !dead_connection) { - DEBUGF(infof(data, "Curl_disconnect when inuse: %zu", CONN_INUSE(conn))); - return; - } + DEBUGF(infof(data, "Curl_disconnect(conn #%" FMT_OFF_T ", aborted=%d)", + conn->connection_id, aborted)); - if(conn->dns_entry) { - Curl_resolv_unlock(data, conn->dns_entry); - conn->dns_entry = NULL; - } + if(conn->dns_entry) + Curl_resolv_unlink(data, &conn->dns_entry); /* Cleanup NTLM connection-related data */ Curl_http_auth_cleanup_ntlm(conn); @@ -795,46 +644,31 @@ void Curl_disconnect(struct Curl_easy *data, Curl_http_auth_cleanup_negotiate(conn); if(conn->connect_only) - /* treat the connection as dead in CONNECT_ONLY situations */ - dead_connection = TRUE; + /* treat the connection as aborted in CONNECT_ONLY situations */ + aborted = TRUE; - /* temporarily attach the connection to this transfer handle for the - disconnect and shutdown */ - Curl_attach_connection(data, conn); - - if(conn->handler && conn->handler->disconnect) - /* This is set if protocol-specific cleanups should be made */ - conn->handler->disconnect(data, conn, dead_connection); - - conn_shutdown(data); - - /* detach it again */ - Curl_detach_connection(data); - - conn_free(data, conn); + return aborted; } /* - * IsMultiplexingPossible() + * xfer_may_multiplex() * - * Return a bitmask with the available multiplexing options for the given - * requested connection. + * Return a TRUE, iff the transfer can be done over an (appropriate) + * multiplexed connection. */ -static int IsMultiplexingPossible(const struct Curl_easy *handle, - const struct connectdata *conn) +static bool xfer_may_multiplex(const struct Curl_easy *data, + const struct connectdata *conn) { - int avail = 0; - /* If an HTTP protocol and multiplexing is enabled */ if((conn->handler->protocol & PROTO_FAMILY_HTTP) && (!conn->bits.protoconnstart || !conn->bits.close)) { - if(Curl_multiplex_wanted(handle->multi) && - (handle->state.httpwant >= CURL_HTTP_VERSION_2)) - /* allows HTTP/2 */ - avail |= CURLPIPE_MULTIPLEX; + if(Curl_multiplex_wanted(data->multi) && + (data->state.httpwant >= CURL_HTTP_VERSION_2)) + /* allows HTTP/2 or newer */ + return TRUE; } - return avail; + return FALSE; } #ifndef CURL_DISABLE_PROXY @@ -869,7 +703,7 @@ socks_proxy_info_matches(const struct proxy_info *data, return TRUE; } #else -/* disabled, won't get called */ +/* disabled, will not get called */ #define proxy_info_matches(x,y) FALSE #define socks_proxy_info_matches(x,y) FALSE #endif @@ -888,8 +722,8 @@ static bool conn_maxage(struct Curl_easy *data, idletime /= 1000; /* integer seconds is fine */ if(idletime > data->set.maxage_conn) { - infof(data, "Too old connection (%ld seconds idle), disconnect it", - idletime); + infof(data, "Too old connection (%" FMT_TIMEDIFF_T + " seconds idle), disconnect it", idletime); return TRUE; } @@ -898,8 +732,8 @@ static bool conn_maxage(struct Curl_easy *data, if(data->set.maxlifetime_conn && lifetime > data->set.maxlifetime_conn) { infof(data, - "Too old connection (%ld seconds since creation), disconnect it", - lifetime); + "Too old connection (%" FMT_TIMEDIFF_T + " seconds since creation), disconnect it", lifetime); return TRUE; } @@ -908,23 +742,24 @@ static bool conn_maxage(struct Curl_easy *data, } /* - * This function checks if the given connection is dead and extracts it from - * the connection cache if so. - * - * When this is called as a Curl_conncache_foreach() callback, the connection - * cache lock is held! - * - * Returns TRUE if the connection was dead and extracted. + * Return TRUE iff the given connection is considered dead. */ -static bool extract_if_dead(struct connectdata *conn, - struct Curl_easy *data) +bool Curl_conn_seems_dead(struct connectdata *conn, + struct Curl_easy *data, + struct curltime *pnow) { + DEBUGASSERT(!data->conn); if(!CONN_INUSE(conn)) { - /* The check for a dead socket makes sense only if the connection isn't in + /* The check for a dead socket makes sense only if the connection is not in use */ bool dead; - struct curltime now = Curl_now(); - if(conn_maxage(data, conn, now)) { + struct curltime now; + if(!pnow) { + now = Curl_now(); + pnow = &now; + } + + if(conn_maxage(data, conn, *pnow)) { /* avoid check if already too old */ dead = TRUE; } @@ -944,7 +779,7 @@ static bool extract_if_dead(struct connectdata *conn, } else { - bool input_pending; + bool input_pending = FALSE; Curl_attach_connection(data, conn); dead = !Curl_conn_is_alive(data, conn, &input_pending); @@ -957,77 +792,47 @@ static bool extract_if_dead(struct connectdata *conn, * any time (HTTP/2 PING for example), the protocol handler needs * to install its own `connection_check` callback. */ + DEBUGF(infof(data, "connection has input pending, not reusable")); dead = TRUE; } Curl_detach_connection(data); } if(dead) { - infof(data, "Connection %" CURL_FORMAT_CURL_OFF_T " seems to be dead", + /* remove connection from cpool */ + infof(data, "Connection %" FMT_OFF_T " seems to be dead", conn->connection_id); - Curl_conncache_remove_conn(data, conn, FALSE); return TRUE; } } return FALSE; } -struct prunedead { - struct Curl_easy *data; - struct connectdata *extracted; -}; - -/* - * Wrapper to use extract_if_dead() function in Curl_conncache_foreach() - * - */ -static int call_extract_if_dead(struct Curl_easy *data, - struct connectdata *conn, void *param) +CURLcode Curl_conn_upkeep(struct Curl_easy *data, + struct connectdata *conn, + struct curltime *now) { - struct prunedead *p = (struct prunedead *)param; - if(extract_if_dead(conn, data)) { - /* stop the iteration here, pass back the connection that was extracted */ - p->extracted = conn; - return 1; - } - return 0; /* continue iteration */ -} - -/* - * This function scans the connection cache for half-open/dead connections, - * closes and removes them. The cleanup is done at most once per second. - * - * When called, this transfer has no connection attached. - */ -static void prune_dead_connections(struct Curl_easy *data) -{ - struct curltime now = Curl_now(); - timediff_t elapsed; - - DEBUGASSERT(!data->conn); /* no connection */ - CONNCACHE_LOCK(data); - elapsed = - Curl_timediff(now, data->state.conn_cache->last_cleanup); - CONNCACHE_UNLOCK(data); - - if(elapsed >= 1000L) { - struct prunedead prune; - prune.data = data; - prune.extracted = NULL; - while(Curl_conncache_foreach(data, data->state.conn_cache, &prune, - call_extract_if_dead)) { - /* unlocked */ - - /* remove connection from cache */ - Curl_conncache_remove_conn(data, prune.extracted, TRUE); + CURLcode result = CURLE_OK; + if(Curl_timediff(*now, conn->keepalive) <= data->set.upkeep_interval_ms) + return result; - /* disconnect it */ - Curl_disconnect(data, prune.extracted, TRUE); - } - CONNCACHE_LOCK(data); - data->state.conn_cache->last_cleanup = now; - CONNCACHE_UNLOCK(data); + /* briefly attach for action */ + Curl_attach_connection(data, conn); + if(conn->handler->connection_check) { + /* Do a protocol-specific keepalive check on the connection. */ + unsigned int rc; + rc = conn->handler->connection_check(data, conn, CONNCHECK_KEEPALIVE); + if(rc & CONNRESULT_DEAD) + result = CURLE_RECV_ERROR; } + else { + /* Do the generic action on the FIRSTSOCKET filter chain */ + result = Curl_conn_keep_alive(data, conn, FIRSTSOCKET); + } + Curl_detach_connection(data); + + conn->keepalive = *now; + return result; } #ifdef USE_SSH @@ -1041,453 +846,432 @@ static bool ssh_config_matches(struct connectdata *one, #define ssh_config_matches(x,y) FALSE #endif -/* - * Given one filled in connection struct (named needle), this function should - * detect if there already is one that has all the significant details - * exactly the same and thus should be used instead. - * - * If there is a match, this function returns TRUE - and has marked the - * connection as 'in-use'. It must later be called with ConnectionDone() to - * return back to 'idle' (unused) state. - * - * The force_reuse flag is set if the connection must be used. - */ -static bool -ConnectionExists(struct Curl_easy *data, - struct connectdata *needle, - struct connectdata **usethis, - bool *force_reuse, - bool *waitpipe) -{ - struct connectdata *check; - struct connectdata *chosen = 0; - bool foundPendingCandidate = FALSE; - bool canmultiplex = IsMultiplexingPossible(data, needle); - struct connectbundle *bundle; - -#ifdef USE_NTLM - bool wantNTLMhttp = ((data->state.authhost.want & - (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && - (needle->handler->protocol & PROTO_FAMILY_HTTP)); -#ifndef CURL_DISABLE_PROXY - bool wantProxyNTLMhttp = (needle->bits.proxy_user_passwd && - ((data->state.authproxy.want & - (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && - (needle->handler->protocol & PROTO_FAMILY_HTTP))); -#else - bool wantProxyNTLMhttp = FALSE; -#endif -#endif - /* plain HTTP with upgrade */ - bool h2upgrade = (data->state.httpwant == CURL_HTTP_VERSION_2_0) && - (needle->handler->protocol & CURLPROTO_HTTP); - - *force_reuse = FALSE; - *waitpipe = FALSE; - - /* Look up the bundle with all the connections to this particular host. - Locks the connection cache, beware of early returns! */ - bundle = Curl_conncache_find_bundle(data, needle, data->state.conn_cache); - if(bundle) { - /* Max pipe length is zero (unlimited) for multiplexed connections */ - struct Curl_llist_element *curr; - - infof(data, "Found bundle for host: %p [%s]", - (void *)bundle, (bundle->multiuse == BUNDLE_MULTIPLEX ? - "can multiplex" : "serially")); - - /* We can't multiplex if we don't know anything about the server */ - if(canmultiplex) { - if(bundle->multiuse == BUNDLE_UNKNOWN) { - if(data->set.pipewait) { - infof(data, "Server doesn't support multiplex yet, wait"); - *waitpipe = TRUE; - CONNCACHE_UNLOCK(data); - return FALSE; /* no reuse */ - } - - infof(data, "Server doesn't support multiplex (yet)"); - canmultiplex = FALSE; - } - if((bundle->multiuse == BUNDLE_MULTIPLEX) && - !Curl_multiplex_wanted(data->multi)) { - infof(data, "Could multiplex, but not asked to"); - canmultiplex = FALSE; - } - if(bundle->multiuse == BUNDLE_NO_MULTIUSE) { - infof(data, "Can not multiplex, even if we wanted to"); - canmultiplex = FALSE; - } - } - - curr = bundle->conn_list.head; - while(curr) { - bool match = FALSE; - size_t multiplexed = 0; +struct url_conn_match { + struct connectdata *found; + struct Curl_easy *data; + struct connectdata *needle; + BIT(may_multiplex); + BIT(want_ntlm_http); + BIT(want_proxy_ntlm_http); + + BIT(wait_pipe); + BIT(force_reuse); + BIT(seen_pending_conn); + BIT(seen_single_use_conn); + BIT(seen_multiplex_conn); +}; - /* - * Note that if we use an HTTP proxy in normal mode (no tunneling), we - * check connections to that proxy and not to the actual remote server. - */ - check = curr->ptr; - curr = curr->next; +static bool url_match_conn(struct connectdata *conn, void *userdata) +{ + struct url_conn_match *match = userdata; + struct Curl_easy *data = match->data; + struct connectdata *needle = match->needle; - if(check->connect_only || check->bits.close) - /* connect-only or to-be-closed connections will not be reused */ - continue; + /* Check if `conn` can be used for transfer `data` */ - if(extract_if_dead(check, data)) { - /* disconnect it */ - Curl_disconnect(data, check, TRUE); - continue; - } + if(conn->connect_only || conn->bits.close) + /* connect-only or to-be-closed connections will not be reused */ + return FALSE; - if(data->set.ipver != CURL_IPRESOLVE_WHATEVER - && data->set.ipver != check->ip_version) { - /* skip because the connection is not via the requested IP version */ - continue; - } + if(data->set.ipver != CURL_IPRESOLVE_WHATEVER + && data->set.ipver != conn->ip_version) { + /* skip because the connection is not via the requested IP version */ + return FALSE; + } - if(bundle->multiuse == BUNDLE_MULTIPLEX) - multiplexed = CONN_INUSE(check); + if(needle->localdev || needle->localport) { + /* If we are bound to a specific local end (IP+port), we must not reuse a + random other one, although if we did not ask for a particular one we + can reuse one that was bound. + + This comparison is a bit rough and too strict. Since the input + parameters can be specified in numerous ways and still end up the same + it would take a lot of processing to make it really accurate. Instead, + this matching will assume that reuses of bound connections will most + likely also reuse the exact same binding parameters and missing out a + few edge cases should not hurt anyone much. + */ + if((conn->localport != needle->localport) || + (conn->localportrange != needle->localportrange) || + (needle->localdev && + (!conn->localdev || strcmp(conn->localdev, needle->localdev)))) + return FALSE; + } + + if(needle->bits.conn_to_host != conn->bits.conn_to_host) + /* do not mix connections that use the "connect to host" feature and + * connections that do not use this feature */ + return FALSE; - if(!canmultiplex) { - if(multiplexed) { - /* can only happen within multi handles, and means that another easy - handle is using this connection */ - continue; - } + if(needle->bits.conn_to_port != conn->bits.conn_to_port) + /* do not mix connections that use the "connect to port" feature and + * connections that do not use this feature */ + return FALSE; - if(Curl_resolver_asynch() && - /* primary_ip[0] is NUL only if the resolving of the name hasn't - completed yet and until then we don't reuse this connection */ - !check->primary_ip[0]) - continue; - } + if(!Curl_conn_is_connected(conn, FIRSTSOCKET) || + conn->bits.asks_multiplex) { + /* Not yet connected, or not yet decided if it multiplexes. The later + * happens for HTTP/2 Upgrade: requests that need a response. */ + if(match->may_multiplex) { + match->seen_pending_conn = TRUE; + /* Do not pick a connection that has not connected yet */ + infof(data, "Connection #%" FMT_OFF_T + " is not open enough, cannot reuse", conn->connection_id); + } + /* Do not pick a connection that has not connected yet */ + return FALSE; + } + /* `conn` is connected. If it has transfers, can we add ours to it? */ - if(!Curl_conn_is_connected(check, FIRSTSOCKET)) { - foundPendingCandidate = TRUE; - /* Don't pick a connection that hasn't connected yet */ - infof(data, "Connection #%" CURL_FORMAT_CURL_OFF_T - "isn't open enough, can't reuse", check->connection_id); - continue; - } + if(CONN_INUSE(conn)) { + if(!conn->bits.multiplex) { + /* conn busy and conn cannot take more transfers */ + match->seen_single_use_conn = TRUE; + return FALSE; + } + match->seen_multiplex_conn = TRUE; + if(!match->may_multiplex) + /* conn busy and transfer cannot be multiplexed */ + return FALSE; + else { + /* transfer and conn multiplex. Are they on the same multi? */ + struct Curl_llist_node *e = Curl_llist_head(&conn->easyq); + struct Curl_easy *entry = Curl_node_elem(e); + if(entry->multi != data->multi) + return FALSE; + } + } + /* `conn` is connected and we could add the transfer to it, if + * all the other criteria do match. */ + /* Does `conn` use the correct protocol? */ #ifdef USE_UNIX_SOCKETS - if(needle->unix_domain_socket) { - if(!check->unix_domain_socket) - continue; - if(strcmp(needle->unix_domain_socket, check->unix_domain_socket)) - continue; - if(needle->bits.abstract_unix_socket != - check->bits.abstract_unix_socket) - continue; - } - else if(check->unix_domain_socket) - continue; + if(needle->unix_domain_socket) { + if(!conn->unix_domain_socket) + return FALSE; + if(strcmp(needle->unix_domain_socket, conn->unix_domain_socket)) + return FALSE; + if(needle->bits.abstract_unix_socket != conn->bits.abstract_unix_socket) + return FALSE; + } + else if(conn->unix_domain_socket) + return FALSE; #endif - if((needle->handler->flags&PROTOPT_SSL) != - (check->handler->flags&PROTOPT_SSL)) - /* don't do mixed SSL and non-SSL connections */ - if(get_protocol_family(check->handler) != - needle->handler->protocol || !check->bits.tls_upgraded) - /* except protocols that have been upgraded via TLS */ - continue; + if((needle->handler->flags&PROTOPT_SSL) != + (conn->handler->flags&PROTOPT_SSL)) + /* do not do mixed SSL and non-SSL connections */ + if(get_protocol_family(conn->handler) != + needle->handler->protocol || !conn->bits.tls_upgraded) + /* except protocols that have been upgraded via TLS */ + return FALSE; #ifndef CURL_DISABLE_PROXY - if(needle->bits.httpproxy != check->bits.httpproxy || - needle->bits.socksproxy != check->bits.socksproxy) - continue; - - if(needle->bits.socksproxy && - !socks_proxy_info_matches(&needle->socks_proxy, - &check->socks_proxy)) - continue; -#endif - if(needle->bits.conn_to_host != check->bits.conn_to_host) - /* don't mix connections that use the "connect to host" feature and - * connections that don't use this feature */ - continue; + if(needle->bits.httpproxy != conn->bits.httpproxy || + needle->bits.socksproxy != conn->bits.socksproxy) + return FALSE; - if(needle->bits.conn_to_port != check->bits.conn_to_port) - /* don't mix connections that use the "connect to port" feature and - * connections that don't use this feature */ - continue; + if(needle->bits.socksproxy && + !socks_proxy_info_matches(&needle->socks_proxy, + &conn->socks_proxy)) + return FALSE; -#ifndef CURL_DISABLE_PROXY - if(needle->bits.httpproxy) { - if(!proxy_info_matches(&needle->http_proxy, &check->http_proxy)) - continue; - - if(needle->bits.tunnel_proxy != check->bits.tunnel_proxy) - continue; - - if(IS_HTTPS_PROXY(needle->http_proxy.proxytype)) { - /* use https proxy */ - if(needle->http_proxy.proxytype != - check->http_proxy.proxytype) - continue; - else if(needle->handler->flags&PROTOPT_SSL) { - /* use double layer ssl */ - if(!Curl_ssl_config_matches(&needle->proxy_ssl_config, - &check->proxy_ssl_config)) - continue; - } - else if(!Curl_ssl_config_matches(&needle->ssl_config, - &check->ssl_config)) - continue; - } + if(needle->bits.httpproxy) { + if(needle->bits.tunnel_proxy != conn->bits.tunnel_proxy) + return FALSE; + + if(!proxy_info_matches(&needle->http_proxy, &conn->http_proxy)) + return FALSE; + + if(IS_HTTPS_PROXY(needle->http_proxy.proxytype)) { + /* https proxies come in different types, http/1.1, h2, ... */ + if(needle->http_proxy.proxytype != conn->http_proxy.proxytype) + return FALSE; + /* match SSL config to proxy */ + if(!Curl_ssl_conn_config_match(data, conn, TRUE)) { + DEBUGF(infof(data, + "Connection #%" FMT_OFF_T + " has different SSL proxy parameters, cannot reuse", + conn->connection_id)); + return FALSE; } + /* the SSL config to the server, which may apply here is checked + * further below */ + } + } #endif - if(h2upgrade && !check->httpversion && canmultiplex) { - if(data->set.pipewait) { - infof(data, "Server upgrade doesn't support multiplex yet, wait"); - *waitpipe = TRUE; - CONNCACHE_UNLOCK(data); - return FALSE; /* no reuse */ - } - infof(data, "Server upgrade cannot be used"); - continue; /* can't be used atm */ - } - - if(!canmultiplex && CONN_INUSE(check)) - /* this request can't be multiplexed but the checked connection is - already in use so we skip it */ - continue; - - if(CONN_INUSE(check)) { - /* Subject for multiplex use if 'checks' belongs to the same multi - handle as 'data' is. */ - struct Curl_llist_element *e = check->easyq.head; - struct Curl_easy *entry = e->ptr; - if(entry->multi != data->multi) - continue; - } + if(match->may_multiplex && + (data->state.httpwant == CURL_HTTP_VERSION_2_0) && + (needle->handler->protocol & CURLPROTO_HTTP) && + !conn->httpversion) { + if(data->set.pipewait) { + infof(data, "Server upgrade does not support multiplex yet, wait"); + match->found = NULL; + match->wait_pipe = TRUE; + return TRUE; /* stop searching, we want to wait */ + } + infof(data, "Server upgrade cannot be used"); + return FALSE; + } - if(needle->localdev || needle->localport) { - /* If we are bound to a specific local end (IP+port), we must not - reuse a random other one, although if we didn't ask for a - particular one we can reuse one that was bound. - - This comparison is a bit rough and too strict. Since the input - parameters can be specified in numerous ways and still end up the - same it would take a lot of processing to make it really accurate. - Instead, this matching will assume that reuses of bound connections - will most likely also reuse the exact same binding parameters and - missing out a few edge cases shouldn't hurt anyone very much. - */ - if((check->localport != needle->localport) || - (check->localportrange != needle->localportrange) || - (needle->localdev && - (!check->localdev || strcmp(check->localdev, needle->localdev)))) - continue; - } + if(!(needle->handler->flags & PROTOPT_CREDSPERREQUEST)) { + /* This protocol requires credentials per connection, + so verify that we are using the same name and password as well */ + if(Curl_timestrcmp(needle->user, conn->user) || + Curl_timestrcmp(needle->passwd, conn->passwd) || + Curl_timestrcmp(needle->sasl_authzid, conn->sasl_authzid) || + Curl_timestrcmp(needle->oauth_bearer, conn->oauth_bearer)) { + /* one of them was different */ + return FALSE; + } + } - if(!(needle->handler->flags & PROTOPT_CREDSPERREQUEST)) { - /* This protocol requires credentials per connection, - so verify that we're using the same name and password as well */ - if(Curl_timestrcmp(needle->user, check->user) || - Curl_timestrcmp(needle->passwd, check->passwd) || - Curl_timestrcmp(needle->sasl_authzid, check->sasl_authzid) || - Curl_timestrcmp(needle->oauth_bearer, check->oauth_bearer)) { - /* one of them was different */ - continue; - } - } + /* GSS delegation differences do not actually affect every connection + and auth method, but this check takes precaution before efficiency */ + if(needle->gssapi_delegation != conn->gssapi_delegation) + return FALSE; - /* GSS delegation differences do not actually affect every connection - and auth method, but this check takes precaution before efficiency */ - if(needle->gssapi_delegation != check->gssapi_delegation) - continue; - - /* If multiplexing isn't enabled on the h2 connection and h1 is - explicitly requested, handle it: */ - if((needle->handler->protocol & PROTO_FAMILY_HTTP) && - (((check->httpversion >= 20) && - (data->state.httpwant < CURL_HTTP_VERSION_2_0)) - || ((check->httpversion >= 30) && - (data->state.httpwant < CURL_HTTP_VERSION_3)))) - continue; + /* If looking for HTTP and the HTTP version we want is less + * than the HTTP version of conn, continue looking. + * CURL_HTTP_VERSION_2TLS is default which indicates no preference, + * so we take any existing connection. */ + if((needle->handler->protocol & PROTO_FAMILY_HTTP) && + (data->state.httpwant != CURL_HTTP_VERSION_2TLS)) { + if((conn->httpversion >= 20) && + (data->state.httpwant < CURL_HTTP_VERSION_2_0)) { + DEBUGF(infof(data, "nor reusing conn #%" CURL_FORMAT_CURL_OFF_T + " with httpversion=%d, we want a version less than h2", + conn->connection_id, conn->httpversion)); + } + if((conn->httpversion >= 30) && + (data->state.httpwant < CURL_HTTP_VERSION_3)) { + DEBUGF(infof(data, "nor reusing conn #%" CURL_FORMAT_CURL_OFF_T + " with httpversion=%d, we want a version less than h3", + conn->connection_id, conn->httpversion)); + return FALSE; + } + } #ifdef USE_SSH - else if(get_protocol_family(needle->handler) & PROTO_FAMILY_SSH) { - if(!ssh_config_matches(needle, check)) - continue; - } + else if(get_protocol_family(needle->handler) & PROTO_FAMILY_SSH) { + if(!ssh_config_matches(needle, conn)) + return FALSE; + } #endif #ifndef CURL_DISABLE_FTP - else if(get_protocol_family(needle->handler) & PROTO_FAMILY_FTP) { - /* Also match ACCOUNT, ALTERNATIVE-TO-USER, USE_SSL and CCC options */ - if(Curl_timestrcmp(needle->proto.ftpc.account, - check->proto.ftpc.account) || - Curl_timestrcmp(needle->proto.ftpc.alternative_to_user, - check->proto.ftpc.alternative_to_user) || - (needle->proto.ftpc.use_ssl != check->proto.ftpc.use_ssl) || - (needle->proto.ftpc.ccc != check->proto.ftpc.ccc)) - continue; - } + else if(get_protocol_family(needle->handler) & PROTO_FAMILY_FTP) { + /* Also match ACCOUNT, ALTERNATIVE-TO-USER, USE_SSL and CCC options */ + if(Curl_timestrcmp(needle->proto.ftpc.account, + conn->proto.ftpc.account) || + Curl_timestrcmp(needle->proto.ftpc.alternative_to_user, + conn->proto.ftpc.alternative_to_user) || + (needle->proto.ftpc.use_ssl != conn->proto.ftpc.use_ssl) || + (needle->proto.ftpc.ccc != conn->proto.ftpc.ccc)) + return FALSE; + } #endif - if((needle->handler->flags&PROTOPT_SSL) + /* Additional match requirements if talking TLS OR + * not talking to an HTTP proxy OR using a tunnel through a proxy */ + if((needle->handler->flags&PROTOPT_SSL) #ifndef CURL_DISABLE_PROXY - || !needle->bits.httpproxy || needle->bits.tunnel_proxy -#endif - ) { - /* The requested connection does not use an HTTP proxy or it uses SSL - or it is a non-SSL protocol tunneled or it is a non-SSL protocol - which is allowed to be upgraded via TLS */ - - if((strcasecompare(needle->handler->scheme, check->handler->scheme) || - (get_protocol_family(check->handler) == - needle->handler->protocol && check->bits.tls_upgraded)) && - (!needle->bits.conn_to_host || strcasecompare( - needle->conn_to_host.name, check->conn_to_host.name)) && - (!needle->bits.conn_to_port || - needle->conn_to_port == check->conn_to_port) && - strcasecompare(needle->host.name, check->host.name) && - needle->remote_port == check->remote_port) { - /* The schemes match or the protocol family is the same and the - previous connection was TLS upgraded, and the hostname and host - port match */ - if(needle->handler->flags & PROTOPT_SSL) { - /* This is a SSL connection so verify that we're using the same - SSL options as well */ - if(!Curl_ssl_config_matches(&needle->ssl_config, - &check->ssl_config)) { - DEBUGF(infof(data, - "Connection #%" CURL_FORMAT_CURL_OFF_T - " has different SSL parameters, can't reuse", - check->connection_id)); - continue; - } - } - match = TRUE; - } - } - else { - /* The requested connection is using the same HTTP proxy in normal - mode (no tunneling) */ - match = TRUE; - } + || !needle->bits.httpproxy || needle->bits.tunnel_proxy +#endif + ) { + /* Talking the same protocol scheme or a TLS upgraded protocol in the + * same protocol family? */ + if(!strcasecompare(needle->handler->scheme, conn->handler->scheme) && + (get_protocol_family(conn->handler) != + needle->handler->protocol || !conn->bits.tls_upgraded)) + return FALSE; + + /* If needle has "conn_to_*" set, conn must match this */ + if((needle->bits.conn_to_host && !strcasecompare( + needle->conn_to_host.name, conn->conn_to_host.name)) || + (needle->bits.conn_to_port && + needle->conn_to_port != conn->conn_to_port)) + return FALSE; + + /* hostname and port must match */ + if(!strcasecompare(needle->host.name, conn->host.name) || + needle->remote_port != conn->remote_port) + return FALSE; + + /* If talking TLS, conn needs to use the same SSL options. */ + if((needle->handler->flags & PROTOPT_SSL) && + !Curl_ssl_conn_config_match(data, conn, FALSE)) { + DEBUGF(infof(data, + "Connection #%" FMT_OFF_T + " has different SSL parameters, cannot reuse", + conn->connection_id)); + return FALSE; + } + } - if(match) { #if defined(USE_NTLM) - /* If we are looking for an HTTP+NTLM connection, check if this is - already authenticating with the right credentials. If not, keep - looking so that we can reuse NTLM connections if - possible. (Especially we must not reuse the same connection if - partway through a handshake!) */ - if(wantNTLMhttp) { - if(Curl_timestrcmp(needle->user, check->user) || - Curl_timestrcmp(needle->passwd, check->passwd)) { - - /* we prefer a credential match, but this is at least a connection - that can be reused and "upgraded" to NTLM */ - if(check->http_ntlm_state == NTLMSTATE_NONE) - chosen = check; - continue; - } - } - else if(check->http_ntlm_state != NTLMSTATE_NONE) { - /* Connection is using NTLM auth but we don't want NTLM */ - continue; - } + /* If we are looking for an HTTP+NTLM connection, check if this is + already authenticating with the right credentials. If not, keep + looking so that we can reuse NTLM connections if + possible. (Especially we must not reuse the same connection if + partway through a handshake!) */ + if(match->want_ntlm_http) { + if(Curl_timestrcmp(needle->user, conn->user) || + Curl_timestrcmp(needle->passwd, conn->passwd)) { + + /* we prefer a credential match, but this is at least a connection + that can be reused and "upgraded" to NTLM */ + if(conn->http_ntlm_state == NTLMSTATE_NONE) + match->found = conn; + return FALSE; + } + } + else if(conn->http_ntlm_state != NTLMSTATE_NONE) { + /* Connection is using NTLM auth but we do not want NTLM */ + return FALSE; + } #ifndef CURL_DISABLE_PROXY - /* Same for Proxy NTLM authentication */ - if(wantProxyNTLMhttp) { - /* Both check->http_proxy.user and check->http_proxy.passwd can be - * NULL */ - if(!check->http_proxy.user || !check->http_proxy.passwd) - continue; - - if(Curl_timestrcmp(needle->http_proxy.user, - check->http_proxy.user) || - Curl_timestrcmp(needle->http_proxy.passwd, - check->http_proxy.passwd)) - continue; - } - else if(check->proxy_ntlm_state != NTLMSTATE_NONE) { - /* Proxy connection is using NTLM auth but we don't want NTLM */ - continue; - } -#endif - if(wantNTLMhttp || wantProxyNTLMhttp) { - /* Credentials are already checked, we can use this connection */ - chosen = check; - - if((wantNTLMhttp && - (check->http_ntlm_state != NTLMSTATE_NONE)) || - (wantProxyNTLMhttp && - (check->proxy_ntlm_state != NTLMSTATE_NONE))) { - /* We must use this connection, no other */ - *force_reuse = TRUE; - break; - } - - /* Continue look up for a better connection */ - continue; - } + /* Same for Proxy NTLM authentication */ + if(match->want_proxy_ntlm_http) { + /* Both conn->http_proxy.user and conn->http_proxy.passwd can be + * NULL */ + if(!conn->http_proxy.user || !conn->http_proxy.passwd) + return FALSE; + + if(Curl_timestrcmp(needle->http_proxy.user, + conn->http_proxy.user) || + Curl_timestrcmp(needle->http_proxy.passwd, + conn->http_proxy.passwd)) + return FALSE; + } + else if(conn->proxy_ntlm_state != NTLMSTATE_NONE) { + /* Proxy connection is using NTLM auth but we do not want NTLM */ + return FALSE; + } #endif - if(canmultiplex) { - /* We can multiplex if we want to. Let's continue looking for - the optimal connection to use. */ + if(match->want_ntlm_http || match->want_proxy_ntlm_http) { + /* Credentials are already checked, we may use this connection. + * With NTLM being weird as it is, we MUST use a + * connection where it has already been fully negotiated. + * If it has not, we keep on looking for a better one. */ + match->found = conn; - if(!multiplexed) { - /* We have the optimal connection. Let's stop looking. */ - chosen = check; - break; - } - -#ifdef USE_NGHTTP2 - /* If multiplexed, make sure we don't go over concurrency limit */ - if(check->bits.multiplex) { - if(multiplexed >= Curl_conn_get_max_concurrent(data, check, - FIRSTSOCKET)) { - infof(data, "MAX_CONCURRENT_STREAMS reached, skip (%zu)", - multiplexed); - continue; - } - else if(multiplexed >= - Curl_multi_max_concurrent_streams(data->multi)) { - infof(data, "client side MAX_CONCURRENT_STREAMS reached" - ", skip (%zu)", - multiplexed); - continue; - } - } -#endif - /* When not multiplexed, we have a match here! */ - chosen = check; - infof(data, "Multiplexed connection found"); - break; - } - else { - /* We have found a connection. Let's stop searching. */ - chosen = check; - break; - } - } + if((match->want_ntlm_http && + (conn->http_ntlm_state != NTLMSTATE_NONE)) || + (match->want_proxy_ntlm_http && + (conn->proxy_ntlm_state != NTLMSTATE_NONE))) { + /* We must use this connection, no other */ + match->force_reuse = TRUE; + return TRUE; } + /* Continue look up for a better connection */ + return FALSE; } +#endif - if(chosen) { - /* mark it as used before releasing the lock */ - Curl_attach_connection(data, chosen); - CONNCACHE_UNLOCK(data); - *usethis = chosen; - return TRUE; /* yes, we found one to use! */ + if(CONN_INUSE(conn)) { + DEBUGASSERT(match->may_multiplex); + DEBUGASSERT(conn->bits.multiplex); + /* If multiplexed, make sure we do not go over concurrency limit */ + if(CONN_INUSE(conn) >= + Curl_multi_max_concurrent_streams(data->multi)) { + infof(data, "client side MAX_CONCURRENT_STREAMS reached" + ", skip (%zu)", CONN_INUSE(conn)); + return FALSE; + } + if(CONN_INUSE(conn) >= + Curl_conn_get_max_concurrent(data, conn, FIRSTSOCKET)) { + infof(data, "MAX_CONCURRENT_STREAMS reached, skip (%zu)", + CONN_INUSE(conn)); + return FALSE; + } + /* When not multiplexed, we have a match here! */ + infof(data, "Multiplexed connection found"); + } + else if(Curl_conn_seems_dead(conn, data, NULL)) { + /* removed and disconnect. Do not treat as aborted. */ + Curl_cpool_disconnect(data, conn, FALSE); + return FALSE; } - CONNCACHE_UNLOCK(data); - if(foundPendingCandidate && data->set.pipewait) { - infof(data, + /* We have found a connection. Let's stop searching. */ + match->found = conn; + return TRUE; +} + +static bool url_match_result(bool result, void *userdata) +{ + struct url_conn_match *match = userdata; + (void)result; + if(match->found) { + /* Attach it now while still under lock, so the connection does + * no longer appear idle and can be reaped. */ + Curl_attach_connection(match->data, match->found); + return TRUE; + } + else if(match->seen_single_use_conn && !match->seen_multiplex_conn) { + /* We've seen a single-use, existing connection to the destination and + * no multiplexed one. It seems safe to assume that the server does + * not support multiplexing. */ + match->wait_pipe = FALSE; + } + else if(match->seen_pending_conn && match->data->set.pipewait) { + infof(match->data, "Found pending candidate for reuse and CURLOPT_PIPEWAIT is set"); - *waitpipe = TRUE; + match->wait_pipe = TRUE; } + match->force_reuse = FALSE; + return FALSE; +} - return FALSE; /* no matching connecting exists */ +/* + * Given one filled in connection struct (named needle), this function should + * detect if there already is one that has all the significant details + * exactly the same and thus should be used instead. + * + * If there is a match, this function returns TRUE - and has marked the + * connection as 'in-use'. It must later be called with ConnectionDone() to + * return back to 'idle' (unused) state. + * + * The force_reuse flag is set if the connection must be used. + */ +static bool +ConnectionExists(struct Curl_easy *data, + struct connectdata *needle, + struct connectdata **usethis, + bool *force_reuse, + bool *waitpipe) +{ + struct url_conn_match match; + bool result; + + memset(&match, 0, sizeof(match)); + match.data = data; + match.needle = needle; + match.may_multiplex = xfer_may_multiplex(data, needle); + +#ifdef USE_NTLM + match.want_ntlm_http = ((data->state.authhost.want & CURLAUTH_NTLM) && + (needle->handler->protocol & PROTO_FAMILY_HTTP)); +#ifndef CURL_DISABLE_PROXY + match.want_proxy_ntlm_http = + (needle->bits.proxy_user_passwd && + (data->state.authproxy.want & CURLAUTH_NTLM) && + (needle->handler->protocol & PROTO_FAMILY_HTTP)); +#endif +#endif + + /* Find a connection in the pool that matches what "data + needle" + * requires. If a suitable candidate is found, it is attached to "data". */ + result = Curl_cpool_find(data, needle->destination, needle->destination_len, + url_match_conn, url_match_result, &match); + + /* wait_pipe is TRUE if we encounter a bundle that is undecided. There + * is no matching connection then, yet. */ + *usethis = match.found; + *force_reuse = match.force_reuse; + *waitpipe = match.wait_pipe; + return result; } /* @@ -1495,11 +1279,30 @@ ConnectionExists(struct Curl_easy *data, */ #ifndef CURL_DISABLE_VERBOSE_STRINGS void Curl_verboseconnect(struct Curl_easy *data, - struct connectdata *conn) + struct connectdata *conn, int sockindex) { - if(data->set.verbose) + if(data->set.verbose && sockindex == SECONDARYSOCKET) + infof(data, "Connected 2nd connection to %s port %u", + conn->secondary.remote_ip, conn->secondary.remote_port); + else infof(data, "Connected to %s (%s) port %u", - CURL_CONN_HOST_DISPNAME(conn), conn->primary_ip, conn->port); + CURL_CONN_HOST_DISPNAME(conn), conn->primary.remote_ip, + conn->primary.remote_port); +#if !defined(CURL_DISABLE_HTTP) + if(conn->handler->protocol & PROTO_FAMILY_HTTP) { + switch(conn->alpn) { + case CURL_HTTP_VERSION_3: + infof(data, "using HTTP/3"); + break; + case CURL_HTTP_VERSION_2: + infof(data, "using HTTP/2"); + break; + default: + infof(data, "using HTTP/1.x"); + break; + } + } +#endif } #endif @@ -1516,11 +1319,13 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ + conn->sockfd = CURL_SOCKET_BAD; + conn->writesockfd = CURL_SOCKET_BAD; conn->connection_id = -1; /* no ID */ - conn->port = -1; /* unknown at this point */ + conn->primary.remote_port = -1; /* unknown at this point */ conn->remote_port = -1; /* unknown at this point */ - /* Default protocol-independent behavior doesn't support persistent + /* Default protocol-independent behavior does not support persistent connections, so we set this to force-close. Protocols that support this need to set this to FALSE in their "curl_do" functions. */ connclose(conn, "Default to force-close"); @@ -1538,107 +1343,288 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) /* note that these two proxy bits are now just on what looks to be requested, they may be altered down the road */ conn->bits.proxy = (data->set.str[STRING_PROXY] && - *data->set.str[STRING_PROXY]) ? TRUE : FALSE; + *data->set.str[STRING_PROXY]); conn->bits.httpproxy = (conn->bits.proxy && (conn->http_proxy.proxytype == CURLPROXY_HTTP || conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0 || - IS_HTTPS_PROXY(conn->http_proxy.proxytype))) ? - TRUE : FALSE; - conn->bits.socksproxy = (conn->bits.proxy && - !conn->bits.httpproxy) ? TRUE : FALSE; + IS_HTTPS_PROXY(conn->http_proxy.proxytype))); + conn->bits.socksproxy = (conn->bits.proxy && !conn->bits.httpproxy); if(data->set.str[STRING_PRE_PROXY] && *data->set.str[STRING_PRE_PROXY]) { conn->bits.proxy = TRUE; conn->bits.socksproxy = TRUE; } - conn->bits.proxy_user_passwd = - (data->state.aptr.proxyuser) ? TRUE : FALSE; + conn->bits.proxy_user_passwd = !!data->state.aptr.proxyuser; conn->bits.tunnel_proxy = data->set.tunnel_thru_httpproxy; #endif /* CURL_DISABLE_PROXY */ #ifndef CURL_DISABLE_FTP conn->bits.ftp_use_epsv = data->set.ftp_use_epsv; conn->bits.ftp_use_eprt = data->set.ftp_use_eprt; -#endif - conn->ssl_config.verifystatus = data->set.ssl.primary.verifystatus; - conn->ssl_config.verifypeer = data->set.ssl.primary.verifypeer; - conn->ssl_config.verifyhost = data->set.ssl.primary.verifyhost; - conn->ssl_config.ssl_options = data->set.ssl.primary.ssl_options; -#ifndef CURL_DISABLE_PROXY - conn->proxy_ssl_config.verifystatus = - data->set.proxy_ssl.primary.verifystatus; - conn->proxy_ssl_config.verifypeer = data->set.proxy_ssl.primary.verifypeer; - conn->proxy_ssl_config.verifyhost = data->set.proxy_ssl.primary.verifyhost; - conn->proxy_ssl_config.ssl_options = data->set.proxy_ssl.primary.ssl_options; #endif conn->ip_version = data->set.ipver; conn->connect_only = data->set.connect_only; conn->transport = TRNSPRT_TCP; /* most of them are TCP streams */ -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) - conn->ntlm.ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; - conn->proxyntlm.ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; + /* Initialize the easy handle list */ + Curl_llist_init(&conn->easyq, NULL); + +#ifdef HAVE_GSSAPI + conn->data_prot = PROT_CLEAR; +#endif + + /* Store the local bind parameters that will be used for this connection */ + if(data->set.str[STRING_DEVICE]) { + conn->localdev = strdup(data->set.str[STRING_DEVICE]); + if(!conn->localdev) + goto error; + } +#ifndef CURL_DISABLE_BINDLOCAL + conn->localportrange = data->set.localportrange; + conn->localport = data->set.localport; +#endif + + /* the close socket stuff needs to be copied to the connection struct as + it may live on without (this specific) Curl_easy */ + conn->fclosesocket = data->set.fclosesocket; + conn->closesocket_client = data->set.closesocket_client; + conn->lastused = conn->created; + conn->gssapi_delegation = data->set.gssapi_delegation; + + return conn; +error: + + free(conn->localdev); + free(conn); + return NULL; +} + +const struct Curl_handler *Curl_get_scheme_handler(const char *scheme) +{ + return Curl_getn_scheme_handler(scheme, strlen(scheme)); +} + +/* returns the handler if the given scheme is built-in */ +const struct Curl_handler *Curl_getn_scheme_handler(const char *scheme, + size_t len) +{ + /* table generated by schemetable.c: + 1. gcc schemetable.c && ./a.out + 2. check how small the table gets + 3. tweak the hash algorithm, then rerun from 1 + 4. when the table is good enough + 5. copy the table into this source code + 6. make sure this function uses the same hash function that worked for + schemetable.c + 7. if needed, adjust the #ifdefs in schemetable.c and rerun + */ + static const struct Curl_handler * const protocols[67] = { +#ifndef CURL_DISABLE_FILE + &Curl_handler_file, +#else + NULL, +#endif + NULL, NULL, +#if defined(USE_SSL) && !defined(CURL_DISABLE_GOPHER) + &Curl_handler_gophers, +#else + NULL, +#endif + NULL, +#ifdef USE_LIBRTMP + &Curl_handler_rtmpe, +#else + NULL, +#endif +#ifndef CURL_DISABLE_SMTP + &Curl_handler_smtp, +#else + NULL, +#endif +#if defined(USE_SSH) + &Curl_handler_sftp, +#else + NULL, +#endif +#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) && \ + (SIZEOF_CURL_OFF_T > 4) + &Curl_handler_smb, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_SMTP) + &Curl_handler_smtps, +#else + NULL, +#endif +#ifndef CURL_DISABLE_TELNET + &Curl_handler_telnet, +#else + NULL, +#endif +#ifndef CURL_DISABLE_GOPHER + &Curl_handler_gopher, +#else + NULL, +#endif +#ifndef CURL_DISABLE_TFTP + &Curl_handler_tftp, +#else + NULL, +#endif + NULL, NULL, NULL, +#if defined(USE_SSL) && !defined(CURL_DISABLE_FTP) + &Curl_handler_ftps, +#else + NULL, +#endif +#ifndef CURL_DISABLE_HTTP + &Curl_handler_http, +#else + NULL, +#endif +#ifndef CURL_DISABLE_IMAP + &Curl_handler_imap, +#else + NULL, +#endif +#ifdef USE_LIBRTMP + &Curl_handler_rtmps, +#else + NULL, +#endif +#ifdef USE_LIBRTMP + &Curl_handler_rtmpt, +#else + NULL, +#endif + NULL, NULL, NULL, +#if !defined(CURL_DISABLE_LDAP) && \ + !defined(CURL_DISABLE_LDAPS) && \ + ((defined(USE_OPENLDAP) && defined(USE_SSL)) || \ + (!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL))) + &Curl_handler_ldaps, +#else + NULL, +#endif +#if !defined(CURL_DISABLE_WEBSOCKETS) && \ + defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) + &Curl_handler_wss, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) + &Curl_handler_https, +#else + NULL, +#endif + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +#ifndef CURL_DISABLE_RTSP + &Curl_handler_rtsp, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_SMB) && \ + defined(USE_CURL_NTLM_CORE) && (SIZEOF_CURL_OFF_T > 4) + &Curl_handler_smbs, +#else + NULL, +#endif +#if defined(USE_SSH) && !defined(USE_WOLFSSH) + &Curl_handler_scp, +#else + NULL, +#endif + NULL, NULL, NULL, +#ifndef CURL_DISABLE_POP3 + &Curl_handler_pop3, +#else + NULL, +#endif + NULL, NULL, +#ifdef USE_LIBRTMP + &Curl_handler_rtmp, +#else + NULL, +#endif + NULL, NULL, NULL, +#ifdef USE_LIBRTMP + &Curl_handler_rtmpte, +#else + NULL, +#endif + NULL, NULL, NULL, +#ifndef CURL_DISABLE_DICT + &Curl_handler_dict, +#else + NULL, +#endif + NULL, NULL, NULL, +#ifndef CURL_DISABLE_MQTT + &Curl_handler_mqtt, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_POP3) + &Curl_handler_pop3s, +#else + NULL, +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_IMAP) + &Curl_handler_imaps, +#else + NULL, +#endif + NULL, +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) + &Curl_handler_ws, +#else + NULL, +#endif + NULL, +#ifdef USE_LIBRTMP + &Curl_handler_rtmpts, +#else + NULL, #endif - - /* Initialize the easy handle list */ - Curl_llist_init(&conn->easyq, NULL); - -#ifdef HAVE_GSSAPI - conn->data_prot = PROT_CLEAR; +#ifndef CURL_DISABLE_LDAP + &Curl_handler_ldap, +#else + NULL, #endif - - /* Store the local bind parameters that will be used for this connection */ - if(data->set.str[STRING_DEVICE]) { - conn->localdev = strdup(data->set.str[STRING_DEVICE]); - if(!conn->localdev) - goto error; - } -#ifndef CURL_DISABLE_BINDLOCAL - conn->localportrange = data->set.localportrange; - conn->localport = data->set.localport; + NULL, NULL, +#ifndef CURL_DISABLE_FTP + &Curl_handler_ftp, +#else + NULL, #endif + }; - /* the close socket stuff needs to be copied to the connection struct as - it may live on without (this specific) Curl_easy */ - conn->fclosesocket = data->set.fclosesocket; - conn->closesocket_client = data->set.closesocket_client; - conn->lastused = conn->created; - conn->gssapi_delegation = data->set.gssapi_delegation; - - return conn; -error: + if(len && (len <= 7)) { + const char *s = scheme; + size_t l = len; + const struct Curl_handler *h; + unsigned int c = 978; + while(l) { + c <<= 5; + c += (unsigned int)Curl_raw_tolower(*s); + s++; + l--; + } - free(conn->localdev); - free(conn); + h = protocols[c % 67]; + if(h && strncasecompare(scheme, h->scheme, len) && !h->scheme[len]) + return h; + } return NULL; } -/* returns the handler if the given scheme is built-in */ -const struct Curl_handler *Curl_builtin_scheme(const char *scheme, - size_t schemelen) -{ - const struct Curl_handler * const *pp; - const struct Curl_handler *p; - /* Scan protocol handler table and match against 'scheme'. The handler may - be changed later when the protocol specific setup function is called. */ - if(schemelen == CURL_ZERO_TERMINATED) - schemelen = strlen(scheme); - for(pp = protocols; (p = *pp) != NULL; pp++) - if(strncasecompare(p->scheme, scheme, schemelen) && !p->scheme[schemelen]) - /* Protocol found in table. */ - return p; - return NULL; /* not found */ -} - - static CURLcode findprotocol(struct Curl_easy *data, struct connectdata *conn, const char *protostr) { - const struct Curl_handler *p = Curl_builtin_scheme(protostr, - CURL_ZERO_TERMINATED); + const struct Curl_handler *p = Curl_get_scheme_handler(protostr); if(p && /* Protocol found in table. Check if allowed */ (data->set.allowed_protocols & p->protocol)) { @@ -1652,17 +1638,17 @@ static CURLcode findprotocol(struct Curl_easy *data, else { /* Perform setup complement if some. */ conn->handler = conn->given = p; - /* 'port' and 'remote_port' are set in setup_connection_internals() */ return CURLE_OK; } } - /* The protocol was not found in the table, but we don't have to assign it + /* The protocol was not found in the table, but we do not have to assign it to anything since it is already assigned to a dummy-struct in the create_conn() function when the connectdata struct is allocated. */ - failf(data, "Protocol \"%s\" not supported or disabled in " LIBCURL_NAME, - protostr); + failf(data, "Protocol \"%s\" %s%s", protostr, + p ? "disabled" : "not supported", + data->state.this_is_a_follow ? " (in redirect)":""); return CURLE_UNSUPPORTED_PROTOCOL; } @@ -1682,7 +1668,7 @@ CURLcode Curl_uc_to_curlcode(CURLUcode uc) } } -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 /* * If the URL was set with an IPv6 numerical address with a zone id part, set * the scope_id based on that! @@ -1705,14 +1691,14 @@ static void zonefrom_url(CURLU *uh, struct Curl_easy *data, conn->scope_id = (unsigned int)scope; #if defined(HAVE_IF_NAMETOINDEX) else { -#elif defined(WIN32) +#elif defined(_WIN32) else if(Curl_if_nametoindex) { #endif -#if defined(HAVE_IF_NAMETOINDEX) || defined(WIN32) +#if defined(HAVE_IF_NAMETOINDEX) || defined(_WIN32) /* Zone identifier is not numeric */ unsigned int scopeidx = 0; -#if defined(WIN32) +#if defined(_WIN32) scopeidx = Curl_if_nametoindex(zoneid); #else scopeidx = if_nametoindex(zoneid); @@ -1727,7 +1713,7 @@ static void zonefrom_url(CURLU *uh, struct Curl_easy *data, else conn->scope_id = scopeidx; } -#endif /* HAVE_IF_NAMETOINDEX || WIN32 */ +#endif /* HAVE_IF_NAMETOINDEX || _WIN32 */ free(zoneid); } @@ -1775,12 +1761,12 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, if(!use_set_uh) { char *newurl; - uc = curl_url_set(uh, CURLUPART_URL, data->state.url, - CURLU_GUESS_SCHEME | - CURLU_NON_SUPPORT_SCHEME | - (data->set.disallow_username_in_url ? - CURLU_DISALLOW_USER : 0) | - (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)); + uc = curl_url_set(uh, CURLUPART_URL, data->state.url, (unsigned int) + (CURLU_GUESS_SCHEME | + CURLU_NON_SUPPORT_SCHEME | + (data->set.disallow_username_in_url ? + CURLU_DISALLOW_USER : 0) | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0))); if(uc) { failf(data, "URL rejected: %s", curl_url_strerror(uc)); return Curl_uc_to_curlcode(uc); @@ -1806,7 +1792,7 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } else if(strlen(data->state.up.hostname) > MAX_URL_LEN) { - failf(data, "Too long host name (maximum is %d)", MAX_URL_LEN); + failf(data, "Too long hostname (maximum is %d)", MAX_URL_LEN); return CURLE_URL_MALFORMAT; } hostname = data->state.up.hostname; @@ -1824,7 +1810,7 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, zonefrom_url(uh, data, conn); } - /* make sure the connect struct gets its own copy of the host name */ + /* make sure the connect struct gets its own copy of the hostname */ conn->host.rawalloc = strdup(hostname ? hostname : ""); if(!conn->host.rawalloc) return CURLE_OUT_OF_MEMORY; @@ -1871,10 +1857,10 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, return result; /* - * User name and password set with their own options override the - * credentials possibly set in the URL. + * username and password set with their own options override the credentials + * possibly set in the URL, but netrc does not. */ - if(!data->set.str[STRING_PASSWORD]) { + if(!data->state.aptr.passwd || (data->state.creds_from != CREDS_OPTION)) { uc = curl_url_get(uh, CURLUPART_PASSWORD, &data->state.up.password, 0); if(!uc) { char *decoded; @@ -1887,13 +1873,14 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, result = Curl_setstropt(&data->state.aptr.passwd, decoded); if(result) return result; + data->state.creds_from = CREDS_URL; } else if(uc != CURLUE_NO_PASSWORD) return Curl_uc_to_curlcode(uc); } - if(!data->set.str[STRING_USERNAME]) { - /* we don't use the URL API's URL decoder option here since it rejects + if(!data->state.aptr.user || (data->state.creds_from != CREDS_OPTION)) { + /* we do not use the URL API's URL decoder option here since it rejects control codes and we want to allow them for some schemes in the user and password fields */ uc = curl_url_get(uh, CURLUPART_USER, &data->state.up.user, 0); @@ -1906,13 +1893,10 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, return result; conn->user = decoded; result = Curl_setstropt(&data->state.aptr.user, decoded); + data->state.creds_from = CREDS_URL; } else if(uc != CURLUE_NO_USER) return Curl_uc_to_curlcode(uc); - else if(data->state.aptr.passwd) { - /* no user was set but a password, set a blank user */ - result = Curl_setstropt(&data->state.aptr.user, ""); - } if(result) return result; } @@ -1940,14 +1924,14 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, } else { unsigned long port = strtoul(data->state.up.port, NULL, 10); - conn->port = conn->remote_port = + conn->primary.remote_port = conn->remote_port = (data->set.use_port && data->state.allow_port) ? data->set.use_port : curlx_ultous(port); } (void)curl_url_get(uh, CURLUPART_QUERY, &data->state.up.query, 0); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(data->set.scope_id) /* Override any scope that was set above. */ conn->scope_id = data->set.scope_id; @@ -1958,7 +1942,7 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, /* - * If we're doing a resumed transfer, we need to setup our stuff + * If we are doing a resumed transfer, we need to setup our stuff * properly. */ static CURLcode setup_range(struct Curl_easy *data) @@ -1970,15 +1954,15 @@ static CURLcode setup_range(struct Curl_easy *data) free(s->range); if(s->resume_from) - s->range = aprintf("%" CURL_FORMAT_CURL_OFF_T "-", s->resume_from); + s->range = aprintf("%" FMT_OFF_T "-", s->resume_from); else s->range = strdup(data->set.str[STRING_SET_RANGE]); - s->rangestringalloc = (s->range) ? TRUE : FALSE; - if(!s->range) return CURLE_OUT_OF_MEMORY; + s->rangestringalloc = TRUE; + /* tell ourselves to fetch this range */ s->use_range = TRUE; /* enable range download */ } @@ -2002,6 +1986,8 @@ static CURLcode setup_connection_internals(struct Curl_easy *data, struct connectdata *conn) { const struct Curl_handler *p; + const char *hostname; + int port; CURLcode result; /* Perform setup complement if some. */ @@ -2016,30 +2002,40 @@ static CURLcode setup_connection_internals(struct Curl_easy *data, p = conn->handler; /* May have changed. */ } - if(conn->port < 0) - /* we check for -1 here since if proxy was detected already, this - was very likely already set to the proxy port */ - conn->port = p->defport; + if(conn->primary.remote_port < 0) + /* we check for -1 here since if proxy was detected already, this was + likely already set to the proxy port */ + conn->primary.remote_port = p->defport; - return CURLE_OK; -} + /* Now create the destination name */ +#ifndef CURL_DISABLE_PROXY + if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { + hostname = conn->http_proxy.host.name; + port = conn->primary.remote_port; + } + else +#endif + { + port = conn->remote_port; + if(conn->bits.conn_to_host) + hostname = conn->conn_to_host.name; + else + hostname = conn->host.name; + } -/* - * Curl_free_request_state() should free temp data that was allocated in the - * Curl_easy for this single request. - */ +#ifdef USE_IPV6 + conn->destination = aprintf("%u/%d/%s", conn->scope_id, port, hostname); +#else + conn->destination = aprintf("%d/%s", port, hostname); +#endif + if(!conn->destination) + return CURLE_OUT_OF_MEMORY; -void Curl_free_request_state(struct Curl_easy *data) -{ - Curl_safefree(data->req.p.http); - Curl_safefree(data->req.newurl); + conn->destination_len = strlen(conn->destination) + 1; + Curl_strntolower(conn->destination, conn->destination, + conn->destination_len - 1); -#ifndef CURL_DISABLE_DOH - if(data->req.doh) { - Curl_close(&data->req.doh->probe[0].easy); - Curl_close(&data->req.doh->probe[1].easy); - } -#endif + return CURLE_OK; } @@ -2071,28 +2067,21 @@ static char *detect_proxy(struct Curl_easy *data, * the first to check for.) * * For compatibility, the all-uppercase versions of these variables are - * checked if the lowercase versions don't exist. + * checked if the lowercase versions do not exist. */ - char proxy_env[128]; - const char *protop = conn->handler->scheme; + char proxy_env[20]; char *envp = proxy_env; - char *prox; #ifdef CURL_DISABLE_VERBOSE_STRINGS (void)data; #endif - /* Now, build _proxy and check for such a one to use */ - while(*protop) - *envp++ = Curl_raw_tolower(*protop++); - - /* append _proxy */ - strcpy(envp, "_proxy"); + msnprintf(proxy_env, sizeof(proxy_env), "%s_proxy", conn->handler->scheme); /* read the protocol proxy: */ - prox = curl_getenv(proxy_env); + proxy = curl_getenv(proxy_env); /* - * We don't try the uppercase version of HTTP_PROXY because of + * We do not try the uppercase version of HTTP_PROXY because of * security reasons: * * When curl is used in a webserver application @@ -2103,23 +2092,34 @@ static char *detect_proxy(struct Curl_easy *data, * This can cause 'internal' http/ftp requests to be * arbitrarily redirected by any external attacker. */ - if(!prox && !strcasecompare("http_proxy", proxy_env)) { + if(!proxy && !strcasecompare("http_proxy", proxy_env)) { /* There was no lowercase variable, try the uppercase version: */ Curl_strntoupper(proxy_env, proxy_env, sizeof(proxy_env)); - prox = curl_getenv(proxy_env); + proxy = curl_getenv(proxy_env); } - envp = proxy_env; - if(prox) { - proxy = prox; /* use this */ - } - else { - envp = (char *)"all_proxy"; - proxy = curl_getenv(envp); /* default proxy to use */ + if(!proxy) { +#ifndef CURL_DISABLE_WEBSOCKETS + /* websocket proxy fallbacks */ + if(strcasecompare("ws_proxy", proxy_env)) { + proxy = curl_getenv("http_proxy"); + } + else if(strcasecompare("wss_proxy", proxy_env)) { + proxy = curl_getenv("https_proxy"); + if(!proxy) + proxy = curl_getenv("HTTPS_PROXY"); + } if(!proxy) { - envp = (char *)"ALL_PROXY"; - proxy = curl_getenv(envp); +#endif + envp = (char *)"all_proxy"; + proxy = curl_getenv(envp); /* default proxy to use */ + if(!proxy) { + envp = (char *)"ALL_PROXY"; + proxy = curl_getenv(envp); + } +#ifndef CURL_DISABLE_WEBSOCKETS } +#endif } if(proxy) infof(data, "Uses proxy env variable %s == '%s'", envp, proxy); @@ -2130,7 +2130,7 @@ static char *detect_proxy(struct Curl_easy *data, /* * If this is supposed to use a proxy, we need to figure out the proxy - * host name, so that we can reuse an existing connection + * hostname, so that we can reuse an existing connection * that may exist registered to the same proxy host. */ static CURLcode parse_proxy(struct Curl_easy *data, @@ -2272,11 +2272,12 @@ static CURLcode parse_proxy(struct Curl_easy *data, } if(port >= 0) { proxyinfo->port = port; - if(conn->port < 0 || sockstype || !conn->socks_proxy.host.rawalloc) - conn->port = port; + if(conn->primary.remote_port < 0 || sockstype || + !conn->socks_proxy.host.rawalloc) + conn->primary.remote_port = port; } - /* now, clone the proxy host name */ + /* now, clone the proxy hostname */ uc = curl_url_get(uhp, CURLUPART_HOST, &host, CURLU_URLDECODE); if(uc) { result = CURLE_OUT_OF_MEMORY; @@ -2344,21 +2345,20 @@ static CURLcode parse_proxy_auth(struct Curl_easy *data, data->state.aptr.proxyuser : ""; const char *proxypasswd = data->state.aptr.proxypasswd ? data->state.aptr.proxypasswd : ""; - CURLcode result = Curl_urldecode(proxyuser, 0, &conn->http_proxy.user, NULL, - REJECT_ZERO); - if(!result) - result = Curl_setstropt(&data->state.aptr.proxyuser, - conn->http_proxy.user); - if(!result) - result = Curl_urldecode(proxypasswd, 0, &conn->http_proxy.passwd, - NULL, REJECT_ZERO); - if(!result) - result = Curl_setstropt(&data->state.aptr.proxypasswd, - conn->http_proxy.passwd); + CURLcode result = CURLE_OUT_OF_MEMORY; + + conn->http_proxy.user = strdup(proxyuser); + if(conn->http_proxy.user) { + conn->http_proxy.passwd = strdup(proxypasswd); + if(conn->http_proxy.passwd) + result = CURLE_OK; + else + Curl_safefree(conn->http_proxy.user); + } return result; } -/* create_conn helper to parse and init proxy values. to be called after unix +/* create_conn helper to parse and init proxy values. to be called after Unix socket init but before any proxy vars are evaluated. */ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, struct connectdata *conn) @@ -2367,7 +2367,6 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, char *socksproxy = NULL; char *no_proxy = NULL; CURLcode result = CURLE_OK; - bool spacesep = FALSE; /************************************************************* * Extract the user and password from the authentication string @@ -2414,8 +2413,7 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, } if(Curl_check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY] ? - data->set.str[STRING_NOPROXY] : no_proxy, - &spacesep)) { + data->set.str[STRING_NOPROXY] : no_proxy)) { Curl_safefree(proxy); Curl_safefree(socksproxy); } @@ -2424,13 +2422,10 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, /* if the host is not in the noproxy list, detect proxy. */ proxy = detect_proxy(data, conn); #endif /* CURL_DISABLE_HTTP */ - if(spacesep) - infof(data, "space-separated NOPROXY patterns are deprecated"); - Curl_safefree(no_proxy); #ifdef USE_UNIX_SOCKETS - /* For the time being do not mix proxy and unix domain sockets. See #1274 */ + /* For the time being do not mix proxy and Unix domain sockets. See #1274 */ if(proxy && conn->unix_domain_socket) { free(proxy); proxy = NULL; @@ -2438,14 +2433,14 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, #endif if(proxy && (!*proxy || (conn->handler->flags & PROTOPT_NONETWORK))) { - free(proxy); /* Don't bother with an empty proxy string or if the - protocol doesn't work with network */ + free(proxy); /* Do not bother with an empty proxy string or if the + protocol does not work with network */ proxy = NULL; } if(socksproxy && (!*socksproxy || (conn->handler->flags & PROTOPT_NONETWORK))) { - free(socksproxy); /* Don't bother with an empty socks proxy string or if - the protocol doesn't work with network */ + free(socksproxy); /* Do not bother with an empty socks proxy string or if + the protocol does not work with network */ socksproxy = NULL; } @@ -2517,7 +2512,7 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, conn->bits.proxy = conn->bits.httpproxy || conn->bits.socksproxy; if(!conn->bits.proxy) { - /* we aren't using the proxy after all... */ + /* we are not using the proxy after all... */ conn->bits.proxy = FALSE; conn->bits.httpproxy = FALSE; conn->bits.socksproxy = FALSE; @@ -2539,7 +2534,7 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, /* * Curl_parse_login_details() * - * This is used to parse a login string for user name, password and options in + * This is used to parse a login string for username, password and options in * the following formats: * * user @@ -2554,14 +2549,15 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, * * Parameters: * - * login [in] - The login string. - * len [in] - The length of the login string. - * userp [in/out] - The address where a pointer to newly allocated memory + * login [in] - login string. + * len [in] - length of the login string. + * userp [in/out] - address where a pointer to newly allocated memory * holding the user will be stored upon completion. - * passwdp [in/out] - The address where a pointer to newly allocated memory + * passwdp [in/out] - address where a pointer to newly allocated memory * holding the password will be stored upon completion. - * optionsp [in/out] - The address where a pointer to newly allocated memory - * holding the options will be stored upon completion. + * optionsp [in/out] - OPTIONAL address where a pointer to newly allocated + * memory holding the options will be stored upon + * completion. * * Returns CURLE_OK on success. */ @@ -2569,19 +2565,19 @@ CURLcode Curl_parse_login_details(const char *login, const size_t len, char **userp, char **passwdp, char **optionsp) { - CURLcode result = CURLE_OK; char *ubuf = NULL; char *pbuf = NULL; - char *obuf = NULL; const char *psep = NULL; const char *osep = NULL; size_t ulen; size_t plen; size_t olen; + DEBUGASSERT(userp); + DEBUGASSERT(passwdp); + /* Attempt to find the password separator */ - if(passwdp) - psep = memchr(login, ':', len); + psep = memchr(login, ':', len); /* Attempt to find the options separator */ if(optionsp) @@ -2593,64 +2589,40 @@ CURLcode Curl_parse_login_details(const char *login, const size_t len, (osep ? (size_t)(osep - login) : len)); plen = (psep ? (osep && osep > psep ? (size_t)(osep - psep) : - (size_t)(login + len - psep)) - 1 : 0); + (size_t)(login + len - psep)) - 1 : 0); olen = (osep ? (psep && psep > osep ? (size_t)(psep - osep) : - (size_t)(login + len - osep)) - 1 : 0); + (size_t)(login + len - osep)) - 1 : 0); - /* Allocate the user portion buffer, which can be zero length */ - if(userp) { - ubuf = malloc(ulen + 1); - if(!ubuf) - result = CURLE_OUT_OF_MEMORY; - } + /* Clone the user portion buffer, which can be zero length */ + ubuf = Curl_memdup0(login, ulen); + if(!ubuf) + goto error; - /* Allocate the password portion buffer */ - if(!result && passwdp && psep) { - pbuf = malloc(plen + 1); - if(!pbuf) { - free(ubuf); - result = CURLE_OUT_OF_MEMORY; - } + /* Clone the password portion buffer */ + if(psep) { + pbuf = Curl_memdup0(&psep[1], plen); + if(!pbuf) + goto error; } /* Allocate the options portion buffer */ - if(!result && optionsp && olen) { - obuf = malloc(olen + 1); - if(!obuf) { - free(pbuf); - free(ubuf); - result = CURLE_OUT_OF_MEMORY; - } - } - - if(!result) { - /* Store the user portion if necessary */ - if(ubuf) { - memcpy(ubuf, login, ulen); - ubuf[ulen] = '\0'; - Curl_safefree(*userp); - *userp = ubuf; - } - - /* Store the password portion if necessary */ - if(pbuf) { - memcpy(pbuf, psep + 1, plen); - pbuf[plen] = '\0'; - Curl_safefree(*passwdp); - *passwdp = pbuf; - } - - /* Store the options portion if necessary */ - if(obuf) { - memcpy(obuf, osep + 1, olen); - obuf[olen] = '\0'; - Curl_safefree(*optionsp); - *optionsp = obuf; + if(optionsp) { + char *obuf = NULL; + if(olen) { + obuf = Curl_memdup0(&osep[1], olen); + if(!obuf) + goto error; } + *optionsp = obuf; } - - return result; + *userp = ubuf; + *passwdp = pbuf; + return CURLE_OK; +error: + free(ubuf); + free(pbuf); + return CURLE_OUT_OF_MEMORY; } /************************************************************* @@ -2708,18 +2680,21 @@ static CURLcode override_login(struct Curl_easy *data, int ret; bool url_provided = FALSE; - if(data->state.aptr.user) { - /* there was a user name in the URL. Use the URL decoded version */ + if(data->state.aptr.user && + (data->state.creds_from != CREDS_NETRC)) { + /* there was a username in the URL. Use the URL decoded version */ userp = &data->state.aptr.user; url_provided = TRUE; } - ret = Curl_parsenetrc(conn->host.name, + ret = Curl_parsenetrc(&data->state.netrc, conn->host.name, userp, passwdp, data->set.str[STRING_NETRC_FILE]); if(ret > 0) { infof(data, "Couldn't find host %s in the %s file; using defaults", - conn->host.name, data->set.str[STRING_NETRC_FILE]); + conn->host.name, + (data->set.str[STRING_NETRC_FILE] ? + data->set.str[STRING_NETRC_FILE] : ".netrc")); } else if(ret < 0) { failf(data, ".netrc parser error"); @@ -2754,6 +2729,7 @@ static CURLcode override_login(struct Curl_easy *data, result = Curl_setstropt(&data->state.aptr.user, *userp); if(result) return result; + data->state.creds_from = CREDS_NETRC; } } if(data->state.aptr.user) { @@ -2771,6 +2747,7 @@ static CURLcode override_login(struct Curl_easy *data, CURLcode result = Curl_setstropt(&data->state.aptr.passwd, *passwdp); if(result) return result; + data->state.creds_from = CREDS_NETRC; } if(data->state.aptr.passwd) { uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD, @@ -2788,7 +2765,7 @@ static CURLcode override_login(struct Curl_easy *data, } /* - * Set the login details so they're available in the connection + * Set the login details so they are available in the connection */ static CURLcode set_login(struct Curl_easy *data, struct connectdata *conn) @@ -2859,7 +2836,7 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, /* detect and extract RFC6874-style IPv6-addresses */ if(*hostptr == '[') { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 char *ptr = ++hostptr; /* advance beyond the initial bracket */ while(*ptr && (ISXDIGIT(*ptr) || (*ptr == ':') || (*ptr == '.'))) ptr++; @@ -2879,8 +2856,8 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, else infof(data, "Invalid IPv6 address format"); portptr = ptr; - /* Note that if this didn't end with a bracket, we still advanced the - * hostptr first, but I can't see anything wrong with that as no host + /* Note that if this did not end with a bracket, we still advanced the + * hostptr first, but I cannot see anything wrong with that as no host * name nor a numeric can legally start with a bracket. */ #else @@ -2894,7 +2871,7 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, host_portno = strchr(portptr, ':'); if(host_portno) { char *endp = NULL; - *host_portno = '\0'; /* cut off number from host name */ + *host_portno = '\0'; /* cut off number from hostname */ host_portno++; if(*host_portno) { long portparse = strtol(host_portno, &endp, 10); @@ -2909,7 +2886,7 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, } } - /* now, clone the cleaned host name */ + /* now, clone the cleaned hostname */ DEBUGASSERT(hostptr); *hostname_result = strdup(hostptr); if(!*hostname_result) { @@ -2936,8 +2913,8 @@ static CURLcode parse_connect_to_string(struct Curl_easy *data, { CURLcode result = CURLE_OK; const char *ptr = conn_to_host; - int host_match = FALSE; - int port_match = FALSE; + bool host_match = FALSE; + bool port_match = FALSE; *host_result = NULL; *port_result = -1; @@ -3042,7 +3019,7 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, #ifndef CURL_DISABLE_ALTSVC if(data->asi && !host && (port == -1) && ((conn->handler->protocol == CURLPROTO_HTTPS) || -#ifdef CURLDEBUG +#ifdef DEBUGBUILD /* allow debug builds to circumvent the HTTPS restriction */ getenv("CURL_ALTSVC_HTTP") #else @@ -3050,35 +3027,77 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, #endif )) { /* no connect_to match, try alt-svc! */ - enum alpnid srcalpnid; - bool hit; - struct altsvc *as; + enum alpnid srcalpnid = ALPN_none; + bool use_alt_svc = FALSE; + bool hit = FALSE; + struct altsvc *as = NULL; const int allowed_versions = ( ALPN_h1 #ifdef USE_HTTP2 | ALPN_h2 #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 | ALPN_h3 #endif ) & data->asi->flags; + static enum alpnid alpn_ids[] = { +#ifdef USE_HTTP3 + ALPN_h3, +#endif +#ifdef USE_HTTP2 + ALPN_h2, +#endif + ALPN_h1, + }; + size_t i; + + switch(data->state.httpwant) { + case CURL_HTTP_VERSION_1_0: + break; + case CURL_HTTP_VERSION_1_1: + use_alt_svc = TRUE; + srcalpnid = ALPN_h1; /* only regard alt-svc advice for http/1.1 */ + break; + case CURL_HTTP_VERSION_2_0: + use_alt_svc = TRUE; + srcalpnid = ALPN_h2; /* only regard alt-svc advice for h2 */ + break; + case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE: + break; + case CURL_HTTP_VERSION_3: + use_alt_svc = TRUE; + srcalpnid = ALPN_h3; /* only regard alt-svc advice for h3 */ + break; + case CURL_HTTP_VERSION_3ONLY: + break; + default: /* no specific HTTP version wanted, look at all of alt-svc */ + use_alt_svc = TRUE; + srcalpnid = ALPN_none; + break; + } + if(!use_alt_svc) + return CURLE_OK; host = conn->host.rawalloc; -#ifdef USE_HTTP2 - /* with h2 support, check that first */ - srcalpnid = ALPN_h2; - hit = Curl_altsvc_lookup(data->asi, - srcalpnid, host, conn->remote_port, /* from */ - &as /* to */, - allowed_versions); - if(!hit) -#endif - { - srcalpnid = ALPN_h1; + DEBUGF(infof(data, "check Alt-Svc for host %s", host)); + if(srcalpnid == ALPN_none) { + /* scan all alt-svc protocol ids in order or relevance */ + for(i = 0; !hit && (i < ARRAYSIZE(alpn_ids)); ++i) { + srcalpnid = alpn_ids[i]; + hit = Curl_altsvc_lookup(data->asi, + srcalpnid, host, conn->remote_port, /* from */ + &as /* to */, + allowed_versions); + } + } + else { + /* look for a specific alt-svc protocol id */ hit = Curl_altsvc_lookup(data->asi, srcalpnid, host, conn->remote_port, /* from */ &as /* to */, allowed_versions); } + + if(hit) { char *hostd = strdup((char *)as->dst.host); if(!hostd) @@ -3105,7 +3124,7 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, conn->transport = TRNSPRT_QUIC; conn->httpversion = 30; break; - default: /* shouldn't be possible */ + default: /* should not be possible */ break; } } @@ -3144,143 +3163,85 @@ static CURLcode resolve_unix(struct Curl_easy *data, return longpath ? CURLE_COULDNT_RESOLVE_HOST : CURLE_OUT_OF_MEMORY; } - hostaddr->inuse++; + hostaddr->refcount = 1; /* connection is the only one holding this */ conn->dns_entry = hostaddr; return CURLE_OK; } #endif -#ifndef CURL_DISABLE_PROXY -static CURLcode resolve_proxy(struct Curl_easy *data, - struct connectdata *conn, - bool *async) +/************************************************************* + * Resolve the address of the server or proxy + *************************************************************/ +static CURLcode resolve_server(struct Curl_easy *data, + struct connectdata *conn, + bool *async) { - struct Curl_dns_entry *hostaddr = NULL; - struct hostname *host; + struct hostname *ehost; timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + const char *peertype = "host"; int rc; +#ifdef USE_UNIX_SOCKETS + char *unix_path = conn->unix_domain_socket; - DEBUGASSERT(conn->dns_entry == NULL); - - host = conn->bits.socksproxy ? &conn->socks_proxy.host : - &conn->http_proxy.host; - - conn->hostname_resolve = strdup(host->name); - if(!conn->hostname_resolve) - return CURLE_OUT_OF_MEMORY; +#ifndef CURL_DISABLE_PROXY + if(!unix_path && CONN_IS_PROXIED(conn) && conn->socks_proxy.host.name && + !strncmp(UNIX_SOCKET_PREFIX"/", + conn->socks_proxy.host.name, sizeof(UNIX_SOCKET_PREFIX))) + unix_path = conn->socks_proxy.host.name + sizeof(UNIX_SOCKET_PREFIX) - 1; +#endif - rc = Curl_resolv_timeout(data, conn->hostname_resolve, (int)conn->port, - &hostaddr, timeout_ms); - conn->dns_entry = hostaddr; - if(rc == CURLRESOLV_PENDING) - *async = TRUE; - else if(rc == CURLRESOLV_TIMEDOUT) - return CURLE_OPERATION_TIMEDOUT; - else if(!hostaddr) { - failf(data, "Couldn't resolve proxy '%s'", host->dispname); - return CURLE_COULDNT_RESOLVE_PROXY; + if(unix_path) { + /* TODO, this only works if previous transport is TRNSPRT_TCP. Check it? */ + conn->transport = TRNSPRT_UNIX; + return resolve_unix(data, conn, unix_path); } - - return CURLE_OK; -} #endif -static CURLcode resolve_host(struct Curl_easy *data, - struct connectdata *conn, - bool *async) -{ - struct Curl_dns_entry *hostaddr = NULL; - struct hostname *connhost; - timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - int rc; - DEBUGASSERT(conn->dns_entry == NULL); - connhost = conn->bits.conn_to_host ? &conn->conn_to_host : &conn->host; - - /* If not connecting via a proxy, extract the port from the URL, if it is - * there, thus overriding any defaults that might have been set above. */ - conn->port = conn->bits.conn_to_port ? conn->conn_to_port : - conn->remote_port; +#ifndef CURL_DISABLE_PROXY + if(CONN_IS_PROXIED(conn)) { + ehost = conn->bits.socksproxy ? &conn->socks_proxy.host : + &conn->http_proxy.host; + peertype = "proxy"; + } + else +#endif + { + ehost = conn->bits.conn_to_host ? &conn->conn_to_host : &conn->host; + /* If not connecting via a proxy, extract the port from the URL, if it is + * there, thus overriding any defaults that might have been set above. */ + conn->primary.remote_port = conn->bits.conn_to_port ? conn->conn_to_port : + conn->remote_port; + } /* Resolve target host right on */ - conn->hostname_resolve = strdup(connhost->name); + conn->hostname_resolve = strdup(ehost->name); if(!conn->hostname_resolve) return CURLE_OUT_OF_MEMORY; - rc = Curl_resolv_timeout(data, conn->hostname_resolve, (int)conn->port, - &hostaddr, timeout_ms); - conn->dns_entry = hostaddr; + rc = Curl_resolv_timeout(data, conn->hostname_resolve, + conn->primary.remote_port, + &conn->dns_entry, timeout_ms); if(rc == CURLRESOLV_PENDING) *async = TRUE; else if(rc == CURLRESOLV_TIMEDOUT) { - failf(data, "Failed to resolve host '%s' with timeout after %ld ms", - connhost->dispname, + failf(data, "Failed to resolve %s '%s' with timeout after %" + FMT_TIMEDIFF_T " ms", peertype, ehost->dispname, Curl_timediff(Curl_now(), data->progress.t_startsingle)); return CURLE_OPERATION_TIMEDOUT; } - else if(!hostaddr) { - failf(data, "Could not resolve host: %s", connhost->dispname); + else if(!conn->dns_entry) { + failf(data, "Could not resolve %s: %s", peertype, ehost->dispname); return CURLE_COULDNT_RESOLVE_HOST; } return CURLE_OK; } -/* Perform a fresh resolve */ -static CURLcode resolve_fresh(struct Curl_easy *data, - struct connectdata *conn, - bool *async) -{ -#ifdef USE_UNIX_SOCKETS - char *unix_path = conn->unix_domain_socket; - -#ifndef CURL_DISABLE_PROXY - if(!unix_path && conn->socks_proxy.host.name && - !strncmp(UNIX_SOCKET_PREFIX"/", - conn->socks_proxy.host.name, sizeof(UNIX_SOCKET_PREFIX))) - unix_path = conn->socks_proxy.host.name + sizeof(UNIX_SOCKET_PREFIX) - 1; -#endif - - if(unix_path) { - conn->transport = TRNSPRT_UNIX; - return resolve_unix(data, conn, unix_path); - } -#endif - -#ifndef CURL_DISABLE_PROXY - if(CONN_IS_PROXIED(conn)) - return resolve_proxy(data, conn, async); -#endif - - return resolve_host(data, conn, async); -} - -/************************************************************* - * Resolve the address of the server or proxy - *************************************************************/ -static CURLcode resolve_server(struct Curl_easy *data, - struct connectdata *conn, - bool *async) -{ - DEBUGASSERT(conn); - DEBUGASSERT(data); - - /* Resolve the name of the server or proxy */ - if(conn->bits.reuse) { - /* We're reusing the connection - no need to resolve anything, and - idnconvert_hostname() was called already in create_conn() for the reuse - case. */ - *async = FALSE; - return CURLE_OK; - } - - return resolve_fresh(data, conn, async); -} - /* * Cleanup the connection `temp`, just allocated for `data`, before using the - * previously `existing` one for `data`. All relevant info is copied over + * previously `existing` one for `data`. All relevant info is copied over * and `temp` is freed. */ static void reuse_conn(struct Curl_easy *data, @@ -3290,7 +3251,7 @@ static void reuse_conn(struct Curl_easy *data, /* get the user+password information from the temp struct since it may * be new for this request even when we reuse an existing connection */ if(temp->user) { - /* use the new user name and password though */ + /* use the new username and password though */ Curl_safefree(existing->user); Curl_safefree(existing->passwd); existing->user = temp->user; @@ -3302,7 +3263,7 @@ static void reuse_conn(struct Curl_easy *data, #ifndef CURL_DISABLE_PROXY existing->bits.proxy_user_passwd = temp->bits.proxy_user_passwd; if(existing->bits.proxy_user_passwd) { - /* use the new proxy user name and proxy password though */ + /* use the new proxy username and proxy password though */ Curl_safefree(existing->http_proxy.user); Curl_safefree(existing->socks_proxy.user); Curl_safefree(existing->http_proxy.passwd); @@ -3318,7 +3279,7 @@ static void reuse_conn(struct Curl_easy *data, } #endif - /* Finding a connection for reuse in the cache matches, among other + /* Finding a connection for reuse in the cpool matches, among other * things on the "remote-relevant" hostname. This is not necessarily * the authority of the URL, e.g. conn->host. For example: * - we use a proxy (not tunneling). we want to send all requests @@ -3349,14 +3310,14 @@ static void reuse_conn(struct Curl_easy *data, temp->hostname_resolve = NULL; /* reuse init */ - existing->bits.reuse = TRUE; /* yes, we're reusing here */ + existing->bits.reuse = TRUE; /* yes, we are reusing here */ - conn_free(data, temp); + Curl_conn_free(data, temp); } /** * create_conn() sets up a new connectdata struct, or reuses an already - * existing one, and resolves host name. + * existing one, and resolves hostname. * * if this function returns CURLE_OK and *async is set to TRUE, the resolve * response will be coming asynchronously. If *async is FALSE, the name is @@ -3380,8 +3341,6 @@ static CURLcode create_conn(struct Curl_easy *data, bool connections_available = TRUE; bool force_reuse = FALSE; bool waitpipe = FALSE; - size_t max_host_connections = Curl_multi_max_host_connections(data->multi); - size_t max_total_connections = Curl_multi_max_total_connections(data->multi); *async = FALSE; *in_connect = NULL; @@ -3441,7 +3400,7 @@ static CURLcode create_conn(struct Curl_easy *data, } #endif - /* After the unix socket init but before the proxy vars are used, parse and + /* After the Unix socket init but before the proxy vars are used, parse and initialize the proxy vars */ #ifndef CURL_DISABLE_PROXY result = create_conn_helper_init_proxy(data, conn); @@ -3538,7 +3497,7 @@ static CURLcode create_conn(struct Curl_easy *data, goto out; /*********************************************************************** - * file: is a special case in that it doesn't need a network connection + * file: is a special case in that it does not need a network connection ***********************************************************************/ #ifndef CURL_DISABLE_FILE if(conn->handler->flags & PROTOPT_NONETWORK) { @@ -3546,13 +3505,15 @@ static CURLcode create_conn(struct Curl_easy *data, /* this is supposed to be the connect function so we better at least check that the file is present here! */ DEBUGASSERT(conn->handler->connect_it); - Curl_persistconninfo(data, conn, NULL, -1); + data->info.conn_scheme = conn->handler->scheme; + /* conn_protocol can only provide "old" protocols */ + data->info.conn_protocol = (conn->handler->protocol) & CURLPROTO_MASK; result = conn->handler->connect_it(data, &done); - /* Setup a "faked" transfer that'll do nothing */ + /* Setup a "faked" transfer that will do nothing */ if(!result) { Curl_attach_connection(data, conn); - result = Curl_conncache_add_conn(data); + result = Curl_cpool_add_conn(data, conn); if(result) goto out; @@ -3566,7 +3527,7 @@ static CURLcode create_conn(struct Curl_easy *data, (void)conn->handler->done(data, result, FALSE); goto out; } - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); } /* since we skip do_init() */ @@ -3577,93 +3538,19 @@ static CURLcode create_conn(struct Curl_easy *data, #endif /* Setup filter for network connections */ - conn->recv[FIRSTSOCKET] = Curl_conn_recv; - conn->send[FIRSTSOCKET] = Curl_conn_send; - conn->recv[SECONDARYSOCKET] = Curl_conn_recv; - conn->send[SECONDARYSOCKET] = Curl_conn_send; + conn->recv[FIRSTSOCKET] = Curl_cf_recv; + conn->send[FIRSTSOCKET] = Curl_cf_send; + conn->recv[SECONDARYSOCKET] = Curl_cf_recv; + conn->send[SECONDARYSOCKET] = Curl_cf_send; conn->bits.tcp_fastopen = data->set.tcp_fastopen; - /* Get a cloned copy of the SSL config situation stored in the - connection struct. But to get this going nicely, we must first make - sure that the strings in the master copy are pointing to the correct - strings in the session handle strings array! - - Keep in mind that the pointers in the master copy are pointing to strings - that will be freed as part of the Curl_easy struct, but all cloned - copies will be separately allocated. - */ - data->set.ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH]; - data->set.ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE]; - data->set.ssl.primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT]; - data->set.ssl.primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; - data->set.ssl.primary.cipher_list = - data->set.str[STRING_SSL_CIPHER_LIST]; - data->set.ssl.primary.cipher_list13 = - data->set.str[STRING_SSL_CIPHER13_LIST]; - data->set.ssl.primary.pinned_key = - data->set.str[STRING_SSL_PINNEDPUBLICKEY]; - data->set.ssl.primary.cert_blob = data->set.blobs[BLOB_CERT]; - data->set.ssl.primary.ca_info_blob = data->set.blobs[BLOB_CAINFO]; - data->set.ssl.primary.curves = data->set.str[STRING_SSL_EC_CURVES]; - -#ifndef CURL_DISABLE_PROXY - data->set.proxy_ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY]; - data->set.proxy_ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE_PROXY]; - data->set.proxy_ssl.primary.cipher_list = - data->set.str[STRING_SSL_CIPHER_LIST_PROXY]; - data->set.proxy_ssl.primary.cipher_list13 = - data->set.str[STRING_SSL_CIPHER13_LIST_PROXY]; - data->set.proxy_ssl.primary.pinned_key = - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]; - data->set.proxy_ssl.primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY]; - data->set.proxy_ssl.primary.ca_info_blob = - data->set.blobs[BLOB_CAINFO_PROXY]; - data->set.proxy_ssl.primary.issuercert = - data->set.str[STRING_SSL_ISSUERCERT_PROXY]; - data->set.proxy_ssl.primary.issuercert_blob = - data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; - data->set.proxy_ssl.primary.CRLfile = - data->set.str[STRING_SSL_CRLFILE_PROXY]; - data->set.proxy_ssl.cert_type = data->set.str[STRING_CERT_TYPE_PROXY]; - data->set.proxy_ssl.key = data->set.str[STRING_KEY_PROXY]; - data->set.proxy_ssl.key_type = data->set.str[STRING_KEY_TYPE_PROXY]; - data->set.proxy_ssl.key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY]; - data->set.proxy_ssl.primary.clientcert = data->set.str[STRING_CERT_PROXY]; - data->set.proxy_ssl.key_blob = data->set.blobs[BLOB_KEY_PROXY]; -#endif - data->set.ssl.primary.CRLfile = data->set.str[STRING_SSL_CRLFILE]; - data->set.ssl.cert_type = data->set.str[STRING_CERT_TYPE]; - data->set.ssl.key = data->set.str[STRING_KEY]; - data->set.ssl.key_type = data->set.str[STRING_KEY_TYPE]; - data->set.ssl.key_passwd = data->set.str[STRING_KEY_PASSWD]; - data->set.ssl.primary.clientcert = data->set.str[STRING_CERT]; -#ifdef USE_TLS_SRP - data->set.ssl.primary.username = data->set.str[STRING_TLSAUTH_USERNAME]; - data->set.ssl.primary.password = data->set.str[STRING_TLSAUTH_PASSWORD]; -#ifndef CURL_DISABLE_PROXY - data->set.proxy_ssl.primary.username = - data->set.str[STRING_TLSAUTH_USERNAME_PROXY]; - data->set.proxy_ssl.primary.password = - data->set.str[STRING_TLSAUTH_PASSWORD_PROXY]; -#endif -#endif - data->set.ssl.key_blob = data->set.blobs[BLOB_KEY]; - - if(!Curl_clone_primary_ssl_config(&data->set.ssl.primary, - &conn->ssl_config)) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - -#ifndef CURL_DISABLE_PROXY - if(!Curl_clone_primary_ssl_config(&data->set.proxy_ssl.primary, - &conn->proxy_ssl_config)) { - result = CURLE_OUT_OF_MEMORY; + /* Complete the easy's SSL configuration for connection cache matching */ + result = Curl_ssl_easy_config_complete(data); + if(result) goto out; - } -#endif - prune_dead_connections(data); + /* FIXME: do we really want to run this every time we add a transfer? */ + Curl_cpool_prune_dead(data); /************************************************************* * Check the current list of connections to see if we can @@ -3696,7 +3583,7 @@ static CURLcode create_conn(struct Curl_easy *data, #ifndef CURL_DISABLE_PROXY infof(data, "Re-using existing connection with %s %s", - conn->bits.proxy?"proxy":"host", + conn->bits.proxy ? "proxy" : "host", conn->socks_proxy.host.name ? conn->socks_proxy.host.dispname : conn->http_proxy.host.name ? conn->http_proxy.host.dispname : conn->host.dispname); @@ -3722,50 +3609,31 @@ static CURLcode create_conn(struct Curl_easy *data, "soon", and we wait for that */ connections_available = FALSE; else { - /* this gets a lock on the conncache */ - struct connectbundle *bundle = - Curl_conncache_find_bundle(data, conn, data->state.conn_cache); - - if(max_host_connections > 0 && bundle && - (bundle->num_connections >= max_host_connections)) { - struct connectdata *conn_candidate; - - /* The bundle is full. Extract the oldest connection. */ - conn_candidate = Curl_conncache_extract_bundle(data, bundle); - CONNCACHE_UNLOCK(data); - - if(conn_candidate) - Curl_disconnect(data, conn_candidate, FALSE); - else { - infof(data, "No more connections allowed to host: %zu", - max_host_connections); + switch(Curl_cpool_check_limits(data, conn)) { + case CPOOL_LIMIT_DEST: + infof(data, "No more connections allowed to host"); + connections_available = FALSE; + break; + case CPOOL_LIMIT_TOTAL: +#ifndef CURL_DISABLE_DOH + if(data->set.dohfor_mid >= 0) + infof(data, "Allowing DoH to override max connection limit"); + else +#endif + { + infof(data, "No connections available in cache"); connections_available = FALSE; } - } - else - CONNCACHE_UNLOCK(data); - - } - - if(connections_available && - (max_total_connections > 0) && - (Curl_conncache_size(data) >= max_total_connections)) { - struct connectdata *conn_candidate; - - /* The cache is full. Let's see if we can kill a connection. */ - conn_candidate = Curl_conncache_extract_oldest(data); - if(conn_candidate) - Curl_disconnect(data, conn_candidate, FALSE); - else { - infof(data, "No connections available in cache"); - connections_available = FALSE; + break; + default: + break; } } if(!connections_available) { infof(data, "No connections available."); - conn_free(data, conn); + Curl_conn_free(data, conn); *in_connect = NULL; result = CURLE_NO_CONNECTION_AVAILABLE; @@ -3776,24 +3644,30 @@ static CURLcode create_conn(struct Curl_easy *data, * This is a brand new connection, so let's store it in the connection * cache of ours! */ + result = Curl_ssl_conn_config_init(data, conn); + if(result) { + DEBUGF(fprintf(stderr, "Error: init connection ssl config\n")); + goto out; + } + Curl_attach_connection(data, conn); - result = Curl_conncache_add_conn(data); + result = Curl_cpool_add_conn(data, conn); if(result) goto out; } #if defined(USE_NTLM) - /* If NTLM is requested in a part of this connection, make sure we don't + /* If NTLM is requested in a part of this connection, make sure we do not assume the state is fine as this is a fresh connection and NTLM is connection based. */ - if((data->state.authhost.picked & (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && + if((data->state.authhost.picked & CURLAUTH_NTLM) && data->state.authhost.done) { infof(data, "NTLM picked AND auth done set, clear picked"); data->state.authhost.picked = CURLAUTH_NONE; data->state.authhost.done = FALSE; } - if((data->state.authproxy.picked & (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && + if((data->state.authproxy.picked & CURLAUTH_NTLM) && data->state.authproxy.done) { infof(data, "NTLM-proxy picked AND auth done set, clear picked"); data->state.authproxy.picked = CURLAUTH_NONE; @@ -3814,23 +3688,35 @@ static CURLcode create_conn(struct Curl_easy *data, /* Continue connectdata initialization here. */ - /* - * Inherit the proper values from the urldata struct AFTER we have arranged - * the persistent connection stuff - */ - conn->seek_func = data->set.seek_func; - conn->seek_client = data->set.seek_client; + if(conn->bits.reuse) { + /* We are reusing the connection - no need to resolve anything, and + idnconvert_hostname() was called already in create_conn() for the reuse + case. */ + *async = FALSE; + } + else { + /************************************************************* + * Resolve the address of the server or proxy + *************************************************************/ + result = resolve_server(data, conn, async); + if(result) + goto out; + } - /************************************************************* - * Resolve the address of the server or proxy - *************************************************************/ - result = resolve_server(data, conn, async); - if(result) - goto out; + /* persist the scheme and handler the transfer is using */ + data->info.conn_scheme = conn->handler->scheme; + /* conn_protocol can only provide "old" protocols */ + data->info.conn_protocol = (conn->handler->protocol) & CURLPROTO_MASK; + data->info.used_proxy = +#ifdef CURL_DISABLE_PROXY + 0 +#else + conn->bits.proxy +#endif + ; /* Everything general done, inform filters that they need - * to prepare for a data transfer. - */ + * to prepare for a data transfer. */ result = Curl_conn_ev_data_setup(data); out: @@ -3856,24 +3742,15 @@ CURLcode Curl_setup_conn(struct Curl_easy *data, return result; } -#ifndef CURL_DISABLE_PROXY - /* set proxy_connect_closed to false unconditionally already here since it - is used strictly to provide extra information to a parent function in the - case of proxy CONNECT failures and we must make sure we don't have it - lingering set from a previous invoke */ - conn->bits.proxy_connect_closed = FALSE; -#endif - -#ifdef CURL_DO_LINEEND_CONV - data->state.crlf_conversions = 0; /* reset CRLF conversion counter */ -#endif /* CURL_DO_LINEEND_CONV */ - /* set start time here for timeout purposes in the connect procedure, it is later set again for the progress meter purpose */ conn->now = Curl_now(); if(!conn->bits.reuse) result = Curl_conn_setup(data, conn, FIRSTSOCKET, conn->dns_entry, CURL_CF_SSL_DEFAULT); + if(!result) + result = Curl_headers_init(data); + /* not sure we need this flag to be passed around any more */ *protocol_done = FALSE; return result; @@ -3888,11 +3765,8 @@ CURLcode Curl_connect(struct Curl_easy *data, *asyncp = FALSE; /* assume synchronous resolves by default */ - /* init the single-transfer specific data */ - Curl_free_request_state(data); - memset(&data->req, 0, sizeof(struct SingleRequest)); - data->req.size = data->req.maxdownload = -1; - data->req.no_body = data->set.opt_no_body; + /* Set the request to virgin state based on transfer settings */ + Curl_req_hard_reset(&data->req, data); /* call the stuff that needs to be called */ result = create_conn(data, &conn, asyncp); @@ -3902,7 +3776,7 @@ CURLcode Curl_connect(struct Curl_easy *data, /* multiplexed */ *protocol_done = TRUE; else if(!*asyncp) { - /* DNS resolution is done: that's either because this is a reused + /* DNS resolution is done: that is either because this is a reused connection, in which case DNS was unnecessary, or because DNS really did finish already (synch resolver/fast async resolve) */ result = Curl_setup_conn(data, protocol_done); @@ -3913,11 +3787,10 @@ CURLcode Curl_connect(struct Curl_easy *data, return result; } else if(result && conn) { - /* We're not allowed to return failure with memory left allocated in the + /* We are not allowed to return failure with memory left allocated in the connectdata struct, free those here */ Curl_detach_connection(data); - Curl_conncache_remove_conn(data, conn, TRUE); - Curl_disconnect(data, conn, TRUE); + Curl_cpool_disconnect(data, conn, TRUE); } return result; @@ -3935,39 +3808,31 @@ CURLcode Curl_connect(struct Curl_easy *data, CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn) { - struct SingleRequest *k = &data->req; - /* if this is a pushed stream, we need this: */ - CURLcode result = Curl_preconnect(data); - if(result) - return result; + CURLcode result; if(conn) { - conn->bits.do_more = FALSE; /* by default there's no curl_do_more() to + conn->bits.do_more = FALSE; /* by default there is no curl_do_more() to use */ - /* if the protocol used doesn't support wildcards, switch it off */ + /* if the protocol used does not support wildcards, switch it off */ if(data->state.wildcardmatch && !(conn->handler->flags & PROTOPT_WILDCARD)) data->state.wildcardmatch = FALSE; } data->state.done = FALSE; /* *_done() is not called yet */ - data->state.expect100header = FALSE; if(data->req.no_body) /* in HTTP lingo, no body means using the HEAD request... */ data->state.httpreq = HTTPREQ_HEAD; - k->start = Curl_now(); /* start time */ - k->header = TRUE; /* assume header */ - k->bytecount = 0; - k->ignorebody = FALSE; - - Curl_speedinit(data); - Curl_pgrsSetUploadCounter(data, 0); - Curl_pgrsSetDownloadCounter(data, 0); - - return CURLE_OK; + result = Curl_req_start(&data->req, data); + if(!result) { + Curl_speedinit(data); + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + } + return result; } #if defined(USE_HTTP2) || defined(USE_HTTP3) diff --git a/deps/curl/lib/url.h b/deps/curl/lib/url.h index f6a5b257371..47c1db44f3e 100644 --- a/deps/curl/lib/url.h +++ b/deps/curl/lib/url.h @@ -37,28 +37,49 @@ void Curl_freeset(struct Curl_easy *data); CURLcode Curl_uc_to_curlcode(CURLUcode uc); CURLcode Curl_close(struct Curl_easy **datap); /* opposite of curl_open() */ CURLcode Curl_connect(struct Curl_easy *, bool *async, bool *protocol_connect); -void Curl_disconnect(struct Curl_easy *data, - struct connectdata *, bool dead_connection); +bool Curl_on_disconnect(struct Curl_easy *data, + struct connectdata *, bool aborted); CURLcode Curl_setup_conn(struct Curl_easy *data, bool *protocol_done); -void Curl_free_request_state(struct Curl_easy *data); +void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn); CURLcode Curl_parse_login_details(const char *login, const size_t len, char **userptr, char **passwdptr, char **optionsptr); -const struct Curl_handler *Curl_builtin_scheme(const char *scheme, - size_t schemelen); +/* Get protocol handler for a URI scheme + * @param scheme URI scheme, case-insensitive + * @return NULL of handler not found + */ +const struct Curl_handler *Curl_get_scheme_handler(const char *scheme); +const struct Curl_handler *Curl_getn_scheme_handler(const char *scheme, + size_t len); #define CURL_DEFAULT_PROXY_PORT 1080 /* default proxy port unless specified */ #define CURL_DEFAULT_HTTPS_PROXY_PORT 443 /* default https proxy port unless specified */ #ifdef CURL_DISABLE_VERBOSE_STRINGS -#define Curl_verboseconnect(x,y) Curl_nop_stmt +#define Curl_verboseconnect(x,y,z) Curl_nop_stmt #else -void Curl_verboseconnect(struct Curl_easy *data, struct connectdata *conn); +void Curl_verboseconnect(struct Curl_easy *data, struct connectdata *conn, + int sockindex); #endif +/** + * Return TRUE iff the given connection is considered dead. + * @param nowp NULL or pointer to time being checked against. + */ +bool Curl_conn_seems_dead(struct connectdata *conn, + struct Curl_easy *data, + struct curltime *nowp); + +/** + * Perform upkeep operations on the connection. + */ +CURLcode Curl_conn_upkeep(struct Curl_easy *data, + struct connectdata *conn, + struct curltime *now); + #if defined(USE_HTTP2) || defined(USE_HTTP3) void Curl_data_priority_clear_state(struct Curl_easy *data); #else diff --git a/deps/curl/lib/urlapi-int.h b/deps/curl/lib/urlapi-int.h index d6e240aa369..fcffab2e954 100644 --- a/deps/curl/lib/urlapi-int.h +++ b/deps/curl/lib/urlapi-int.h @@ -28,12 +28,11 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, bool guess_scheme); -CURLUcode Curl_url_set_authority(CURLU *u, const char *authority, - unsigned int flags); +CURLUcode Curl_url_set_authority(CURLU *u, const char *authority); -#ifdef DEBUGBUILD -CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, - bool has_scheme); +#ifdef UNITTESTS +UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, + bool has_scheme); #endif #endif /* HEADER_CURL_URLAPI_INT_H */ diff --git a/deps/curl/lib/urlapi.c b/deps/curl/lib/urlapi.c index 80299e7c0e2..98c8f6fe6d5 100644 --- a/deps/curl/lib/urlapi.c +++ b/deps/curl/lib/urlapi.c @@ -34,20 +34,19 @@ #include "inet_ntop.h" #include "strdup.h" #include "idn.h" -#include "curl_memrchr.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" - /* MSDOS/Windows style drive prefix, eg c: in c:foo */ + /* MS-DOS/Windows style drive prefix, eg c: in c:foo */ #define STARTS_WITH_DRIVE_PREFIX(str) \ ((('a' <= str[0] && str[0] <= 'z') || \ ('A' <= str[0] && str[0] <= 'Z')) && \ (str[1] == ':')) - /* MSDOS/Windows style drive prefix, optionally with + /* MS-DOS/Windows style drive prefix, optionally with * a '|' instead of ':', followed by a slash or NUL */ #define STARTS_WITH_URL_DRIVE_PREFIX(str) \ ((('a' <= (str)[0] && (str)[0] <= 'z') || \ @@ -59,11 +58,11 @@ #define MAX_SCHEME_LEN 40 /* - * If ENABLE_IPV6 is disabled, we still want to parse IPv6 addresses, so make + * If USE_IPV6 is disabled, we still want to parse IPv6 addresses, so make * sure we have _some_ value for AF_INET6 without polluting our fake value * everywhere. */ -#if !defined(ENABLE_IPV6) && !defined(AF_INET6) +#if !defined(USE_IPV6) && !defined(AF_INET6) #define AF_INET6 (AF_INET + 1) #endif @@ -79,7 +78,10 @@ struct Curl_URL { char *path; char *query; char *fragment; - long portnum; /* the numerical version */ + unsigned short portnum; /* the numerical version (if 'port' is set) */ + BIT(query_present); /* to support blank */ + BIT(fragment_present); /* to support blank */ + BIT(guessed_scheme); /* when a URL without scheme is parsed */ }; #define DEFAULT_SCHEME "https" @@ -99,8 +101,8 @@ static void free_urlhandle(struct Curl_URL *u) } /* - * Find the separator at the end of the host name, or the '?' in cases like - * http://www.url.com?id=2380 + * Find the separator at the end of the hostname, or the '?' in cases like + * http://www.example.com?id=2380 */ static const char *find_host_sep(const char *url) { @@ -126,6 +128,9 @@ static const char *find_host_sep(const char *url) return sep < query ? sep : query; } +/* convert CURLcode to CURLUcode */ +#define cc2cu(x) ((x) == CURLE_TOO_LARGE ? CURLUE_TOO_LARGE : \ + CURLUE_OUT_OF_MEMORY) /* * Decide whether a character in a URL must be escaped. */ @@ -135,7 +140,7 @@ static const char hexdigits[] = "0123456789abcdef"; /* urlencode_str() writes data into an output dynbuf and URL-encodes the * spaces in the source URL accordingly. * - * URL encoding should be skipped for host names, otherwise IDN resolution + * URL encoding should be skipped for hostnames, otherwise IDN resolution * will fail. */ static CURLUcode urlencode_str(struct dynbuf *o, const char *url, @@ -146,6 +151,7 @@ static CURLUcode urlencode_str(struct dynbuf *o, const char *url, bool left = !query; const unsigned char *iptr; const unsigned char *host_sep = (const unsigned char *) url; + CURLcode result; if(!relative) host_sep = (const unsigned char *) find_host_sep(url); @@ -154,20 +160,19 @@ static CURLUcode urlencode_str(struct dynbuf *o, const char *url, len; iptr++, len--) { if(iptr < host_sep) { - if(Curl_dyn_addn(o, iptr, 1)) - return CURLUE_OUT_OF_MEMORY; + result = Curl_dyn_addn(o, iptr, 1); + if(result) + return cc2cu(result); continue; } if(*iptr == ' ') { - if(left) { - if(Curl_dyn_addn(o, "%20", 3)) - return CURLUE_OUT_OF_MEMORY; - } - else { - if(Curl_dyn_addn(o, "+", 1)) - return CURLUE_OUT_OF_MEMORY; - } + if(left) + result = Curl_dyn_addn(o, "%20", 3); + else + result = Curl_dyn_addn(o, "+", 1); + if(result) + return cc2cu(result); continue; } @@ -176,15 +181,14 @@ static CURLUcode urlencode_str(struct dynbuf *o, const char *url, if(urlchar_needs_escaping(*iptr)) { char out[3]={'%'}; - out[1] = hexdigits[*iptr>>4]; + out[1] = hexdigits[*iptr >> 4]; out[2] = hexdigits[*iptr & 0xf]; - if(Curl_dyn_addn(o, out, 3)) - return CURLUE_OUT_OF_MEMORY; - } - else { - if(Curl_dyn_addn(o, iptr, 1)) - return CURLUE_OUT_OF_MEMORY; + result = Curl_dyn_addn(o, out, 3); } + else + result = Curl_dyn_addn(o, iptr, 1); + if(result) + return cc2cu(result); } return CURLUE_OK; @@ -201,12 +205,12 @@ static CURLUcode urlencode_str(struct dynbuf *o, const char *url, size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, bool guess_scheme) { - int i = 0; + size_t i = 0; DEBUGASSERT(!buf || (buflen > MAX_SCHEME_LEN)); (void)buflen; /* only used in debug-builds */ if(buf) buf[0] = 0; /* always leave a defined value in buf */ -#ifdef WIN32 +#ifdef _WIN32 if(guess_scheme && STARTS_WITH_DRIVE_PREFIX(url)) return 0; #endif @@ -225,15 +229,13 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, if(i && (url[i] == ':') && ((url[i + 1] == '/') || !guess_scheme)) { /* If this does not guess scheme, the scheme always ends with the colon so that this also detects data: URLs etc. In guessing mode, data: could - be the host name "data" with a specified port number. */ + be the hostname "data" with a specified port number. */ /* the length of the scheme is the name part only */ size_t len = i; if(buf) { + Curl_strntolower(buf, url, i); buf[i] = 0; - while(i--) { - buf[i] = Curl_raw_tolower(url[i]); - } } return len; } @@ -248,7 +250,7 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, * * Note that this function destroys the 'base' string. */ -static char *concat_url(char *base, const char *relurl) +static CURLcode concat_url(char *base, const char *relurl, char **newurl) { /*** TRY to append this new path to the old URL @@ -260,8 +262,12 @@ static char *concat_url(char *base, const char *relurl) char *pathsep; bool host_changed = FALSE; const char *useurl = relurl; + CURLcode result = CURLE_OK; + CURLUcode uc; + bool skip_slash = FALSE; + *newurl = NULL; - /* protsep points to the start of the host name */ + /* protsep points to the start of the hostname */ protsep = strstr(base, "//"); if(!protsep) protsep = base; @@ -271,55 +277,57 @@ static char *concat_url(char *base, const char *relurl) if('/' != relurl[0]) { int level = 0; - /* First we need to find out if there's a ?-letter in the URL, + /* First we need to find out if there is a ?-letter in the URL, and cut it and the right-side of that off */ pathsep = strchr(protsep, '?'); if(pathsep) *pathsep = 0; - /* we have a relative path to append to the last slash if there's one - available, or if the new URL is just a query string (starts with a - '?') we append the new one at the end of the entire currently worked - out URL */ - if(useurl[0] != '?') { + /* we have a relative path to append to the last slash if there is one + available, or the new URL is just a query string (starts with a '?') or + a fragment (starts with '#') we append the new one at the end of the + current URL */ + if((useurl[0] != '?') && (useurl[0] != '#')) { pathsep = strrchr(protsep, '/'); if(pathsep) *pathsep = 0; - } - /* Check if there's any slash after the host name, and if so, remember - that position instead */ - pathsep = strchr(protsep, '/'); - if(pathsep) - protsep = pathsep + 1; - else - protsep = NULL; + /* Check if there is any slash after the hostname, and if so, remember + that position instead */ + pathsep = strchr(protsep, '/'); + if(pathsep) + protsep = pathsep + 1; + else + protsep = NULL; - /* now deal with one "./" or any amount of "../" in the newurl - and act accordingly */ + /* now deal with one "./" or any amount of "../" in the newurl + and act accordingly */ - if((useurl[0] == '.') && (useurl[1] == '/')) - useurl += 2; /* just skip the "./" */ + if((useurl[0] == '.') && (useurl[1] == '/')) + useurl += 2; /* just skip the "./" */ - while((useurl[0] == '.') && - (useurl[1] == '.') && - (useurl[2] == '/')) { - level++; - useurl += 3; /* pass the "../" */ - } + while((useurl[0] == '.') && + (useurl[1] == '.') && + (useurl[2] == '/')) { + level++; + useurl += 3; /* pass the "../" */ + } - if(protsep) { - while(level--) { - /* cut off one more level from the right of the original URL */ - pathsep = strrchr(protsep, '/'); - if(pathsep) - *pathsep = 0; - else { - *protsep = 0; - break; + if(protsep) { + while(level--) { + /* cut off one more level from the right of the original URL */ + pathsep = strrchr(protsep, '/'); + if(pathsep) + *pathsep = 0; + else { + *protsep = 0; + break; + } } } } + else + skip_slash = TRUE; } else { /* We got a new absolute path for this server */ @@ -338,8 +346,8 @@ static char *concat_url(char *base, const char *relurl) pathsep = strchr(protsep, '/'); if(pathsep) { /* When people use badly formatted URLs, such as - "http://www.url.com?dir=/home/daniel" we must not use the first - slash, if there's a ?-letter before it! */ + "http://www.example.com?dir=/home/daniel" we must not use the first + slash, if there is a ?-letter before it! */ char *sep = strchr(protsep, '?'); if(sep && (sep < pathsep)) pathsep = sep; @@ -347,9 +355,9 @@ static char *concat_url(char *base, const char *relurl) } else { /* There was no slash. Now, since we might be operating on a badly - formatted URL, such as "http://www.url.com?id=2380" which doesn't - use a slash separator as it is supposed to, we need to check for a - ?-letter as well! */ + formatted URL, such as "http://www.example.com?id=2380" which does + not use a slash separator as it is supposed to, we need to check + for a ?-letter as well! */ pathsep = strchr(protsep, '?'); if(pathsep) *pathsep = 0; @@ -359,22 +367,28 @@ static char *concat_url(char *base, const char *relurl) Curl_dyn_init(&newest, CURL_MAX_INPUT_LENGTH); - /* copy over the root url part */ - if(Curl_dyn_add(&newest, base)) - return NULL; + /* copy over the root URL part */ + result = Curl_dyn_add(&newest, base); + if(result) + return result; /* check if we need to append a slash */ - if(('/' == useurl[0]) || (protsep && !*protsep) || ('?' == useurl[0])) + if(('/' == useurl[0]) || (protsep && !*protsep) || skip_slash) ; else { - if(Curl_dyn_addn(&newest, "/", 1)) - return NULL; + result = Curl_dyn_addn(&newest, "/", 1); + if(result) + return result; } /* then append the new piece on the right side */ - urlencode_str(&newest, useurl, strlen(useurl), !host_changed, FALSE); + uc = urlencode_str(&newest, useurl, strlen(useurl), !host_changed, + FALSE); + if(uc) + return (uc == CURLUE_TOO_LARGE) ? CURLE_TOO_LARGE : CURLE_OUT_OF_MEMORY; - return Curl_dyn_ptr(&newest); + *newurl = Curl_dyn_ptr(&newest); + return CURLE_OK; } /* scan for byte values <= 31, 127 and sometimes space */ @@ -406,15 +420,15 @@ static CURLUcode junkscan(const char *url, size_t *urllen, unsigned int flags) /* * parse_hostname_login() * - * Parse the login details (user name, password and options) from the URL and - * strip them out of the host name + * Parse the login details (username, password and options) from the URL and + * strip them out of the hostname * */ static CURLUcode parse_hostname_login(struct Curl_URL *u, const char *login, size_t len, unsigned int flags, - size_t *offset) /* to the host name */ + size_t *offset) /* to the hostname */ { CURLUcode result = CURLUE_OK; CURLcode ccode; @@ -446,14 +460,14 @@ static CURLUcode parse_hostname_login(struct Curl_URL *u, /* if this is a known scheme, get some details */ if(u->scheme) - h = Curl_builtin_scheme(u->scheme, CURL_ZERO_TERMINATED); + h = Curl_get_scheme_handler(u->scheme); /* We could use the login information in the URL so extract it. Only parse options if the handler says we should. Note that 'h' might be NULL! */ ccode = Curl_parse_login_details(login, ptr - login - 1, &userp, &passwdp, (h && (h->flags & PROTOPT_URLOPTIONS)) ? - &optionsp:NULL); + &optionsp : NULL); if(ccode) { result = CURLUE_BAD_LOGIN; goto out; @@ -461,7 +475,7 @@ static CURLUcode parse_hostname_login(struct Curl_URL *u, if(userp) { if(flags & CURLU_DISALLOW_USER) { - /* Option DISALLOW_USER is set and url contains username. */ + /* Option DISALLOW_USER is set and URL contains username. */ result = CURLUE_USER_NOT_ALLOWED; goto out; } @@ -479,7 +493,7 @@ static CURLUcode parse_hostname_login(struct Curl_URL *u, u->options = optionsp; } - /* the host name starts at this offset */ + /* the hostname starts at this offset */ *offset = ptr - login; return CURLUE_OK; @@ -520,15 +534,15 @@ UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, portptr = strchr(hostname, ':'); if(portptr) { - char *rest; - long port; + char *rest = NULL; + unsigned long port; size_t keep = portptr - hostname; - /* Browser behavior adaptation. If there's a colon with no digits after, + /* Browser behavior adaptation. If there is a colon with no digits after, just cut off the name there which makes us ignore the colon and just use the default port. Firefox, Chrome and Safari all do that. - Don't do it if the URL has no scheme, to make something that looks like + Do not do it if the URL has no scheme, to make something that looks like a scheme not work! */ Curl_dyn_setlen(host, keep); @@ -539,15 +553,13 @@ UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, if(!ISDIGIT(*portptr)) return CURLUE_BAD_PORT_NUMBER; - port = strtol(portptr, &rest, 10); /* Port number must be decimal */ - - if(port > 0xffff) - return CURLUE_BAD_PORT_NUMBER; + errno = 0; + port = strtoul(portptr, &rest, 10); /* Port number must be decimal */ - if(rest[0]) + if(errno || (port > 0xffff) || *rest) return CURLUE_BAD_PORT_NUMBER; - u->portnum = port; + u->portnum = (unsigned short) port; /* generate a new port number string to get rid of leading zeroes etc */ free(u->port); u->port = aprintf("%ld", port); @@ -579,7 +591,7 @@ static CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, char zoneid[16]; int i = 0; char *h = &hostname[len + 1]; - /* pass '25' if present and is a url encoded percent sign */ + /* pass '25' if present and is a URL encoded percent sign */ if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']')) h += 2; while(*h && (*h != ']') && (i < 15)) @@ -598,19 +610,14 @@ static CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, /* hostname is fine */ } - /* Check the IPv6 address. */ + /* Normalize the IPv6 address */ { char dest[16]; /* fits a binary IPv6 address */ - char norm[MAX_IPADR_LEN]; hostname[hlen] = 0; /* end the address there */ if(1 != Curl_inet_pton(AF_INET6, hostname, dest)) return CURLUE_BAD_IPV6; - - /* check if it can be done shorter */ - if(Curl_inet_ntop(AF_INET6, dest, norm, sizeof(norm)) && - (strlen(norm) < hlen)) { - strcpy(hostname, norm); - hlen = strlen(norm); + if(Curl_inet_ntop(AF_INET6, dest, hostname, hlen)) { + hlen = strlen(hostname); /* might be shorter now */ hostname[hlen + 1] = 0; } hostname[hlen] = ']'; /* restore ending bracket */ @@ -652,7 +659,6 @@ static CURLUcode hostname_check(struct Curl_URL *u, char *hostname, */ #define HOST_ERROR -1 /* out of memory */ -#define HOST_BAD -2 /* bad IPv4 address */ #define HOST_NAME 1 #define HOST_IPV4 2 @@ -669,13 +675,21 @@ static int ipv4_normalize(struct dynbuf *host) if(*c == '[') return HOST_IPV6; + errno = 0; /* for strtoul */ while(!done) { - char *endp; + char *endp = NULL; unsigned long l; if(!ISDIGIT(*c)) - /* most importantly this doesn't allow a leading plus or minus */ + /* most importantly this does not allow a leading plus or minus */ return HOST_NAME; l = strtoul(c, &endp, 0); + if(errno) + return HOST_NAME; +#if SIZEOF_LONG > 4 + /* a value larger than 32 bits */ + if(l > UINT_MAX) + return HOST_NAME; +#endif parts[n] = l; c = endp; @@ -695,16 +709,6 @@ static int ipv4_normalize(struct dynbuf *host) default: return HOST_NAME; } - - /* overflow */ - if((l == ULONG_MAX) && (errno == ERANGE)) - return HOST_NAME; - -#if SIZEOF_LONG > 4 - /* a value larger than 32 bits */ - if(l > UINT_MAX) - return HOST_NAME; -#endif } switch(n) { @@ -712,24 +716,30 @@ static int ipv4_normalize(struct dynbuf *host) Curl_dyn_reset(host); result = Curl_dyn_addf(host, "%u.%u.%u.%u", - parts[0] >> 24, (parts[0] >> 16) & 0xff, - (parts[0] >> 8) & 0xff, parts[0] & 0xff); + (unsigned int)(parts[0] >> 24), + (unsigned int)((parts[0] >> 16) & 0xff), + (unsigned int)((parts[0] >> 8) & 0xff), + (unsigned int)(parts[0] & 0xff)); break; case 1: /* a.b -- 8.24 bits */ if((parts[0] > 0xff) || (parts[1] > 0xffffff)) return HOST_NAME; Curl_dyn_reset(host); result = Curl_dyn_addf(host, "%u.%u.%u.%u", - parts[0], (parts[1] >> 16) & 0xff, - (parts[1] >> 8) & 0xff, parts[1] & 0xff); + (unsigned int)(parts[0]), + (unsigned int)((parts[1] >> 16) & 0xff), + (unsigned int)((parts[1] >> 8) & 0xff), + (unsigned int)(parts[1] & 0xff)); break; case 2: /* a.b.c -- 8.8.16 bits */ if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff)) return HOST_NAME; Curl_dyn_reset(host); result = Curl_dyn_addf(host, "%u.%u.%u.%u", - parts[0], parts[1], (parts[2] >> 8) & 0xff, - parts[2] & 0xff); + (unsigned int)(parts[0]), + (unsigned int)(parts[1]), + (unsigned int)((parts[2] >> 8) & 0xff), + (unsigned int)(parts[2] & 0xff)); break; case 3: /* a.b.c.d -- 8.8.8.8 bits */ if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) || @@ -737,7 +747,10 @@ static int ipv4_normalize(struct dynbuf *host) return HOST_NAME; Curl_dyn_reset(host); result = Curl_dyn_addf(host, "%u.%u.%u.%u", - parts[0], parts[1], parts[2], parts[3]); + (unsigned int)(parts[0]), + (unsigned int)(parts[1]), + (unsigned int)(parts[2]), + (unsigned int)(parts[3])); break; } if(result) @@ -766,7 +779,7 @@ static CURLUcode urldecode_host(struct dynbuf *host) result = Curl_dyn_addn(host, decoded, dlen); free(decoded); if(result) - return CURLUE_OUT_OF_MEMORY; + return cc2cu(result); } return CURLUE_OK; @@ -779,22 +792,24 @@ static CURLUcode parse_authority(struct Curl_URL *u, bool has_scheme) { size_t offset; - CURLUcode result; + CURLUcode uc; + CURLcode result; /* - * Parse the login details and strip them out of the host name. + * Parse the login details and strip them out of the hostname. */ - result = parse_hostname_login(u, auth, authlen, flags, &offset); - if(result) + uc = parse_hostname_login(u, auth, authlen, flags, &offset); + if(uc) goto out; - if(Curl_dyn_addn(host, auth + offset, authlen - offset)) { - result = CURLUE_OUT_OF_MEMORY; + result = Curl_dyn_addn(host, auth + offset, authlen - offset); + if(result) { + uc = cc2cu(result); goto out; } - result = Curl_parse_port(u, host, has_scheme); - if(result) + uc = Curl_parse_port(u, host, has_scheme); + if(uc) goto out; if(!Curl_dyn_len(host)) @@ -804,28 +819,27 @@ static CURLUcode parse_authority(struct Curl_URL *u, case HOST_IPV4: break; case HOST_IPV6: - result = ipv6_parse(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); + uc = ipv6_parse(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); break; case HOST_NAME: - result = urldecode_host(host); - if(!result) - result = hostname_check(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); + uc = urldecode_host(host); + if(!uc) + uc = hostname_check(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); break; case HOST_ERROR: - result = CURLUE_OUT_OF_MEMORY; + uc = CURLUE_OUT_OF_MEMORY; break; - case HOST_BAD: default: - result = CURLUE_BAD_HOSTNAME; /* Bad IPv4 address even */ + uc = CURLUE_BAD_HOSTNAME; /* Bad IPv4 address even */ break; } out: - return result; + return uc; } -CURLUcode Curl_url_set_authority(CURLU *u, const char *authority, - unsigned int flags) +/* used for HTTP/2 server push */ +CURLUcode Curl_url_set_authority(CURLU *u, const char *authority) { CURLUcode result; struct dynbuf host; @@ -833,8 +847,8 @@ CURLUcode Curl_url_set_authority(CURLU *u, const char *authority, DEBUGASSERT(authority); Curl_dyn_init(&host, CURL_MAX_INPUT_LENGTH); - result = parse_authority(u, authority, strlen(authority), flags, - &host, !!u->scheme); + result = parse_authority(u, authority, strlen(authority), + CURLU_DISALLOW_USER, &host, !!u->scheme); if(result) Curl_dyn_free(&host); else { @@ -886,7 +900,7 @@ UNITTEST int dedotdotify(const char *input, size_t clen, char **outp) do { bool dotdot = TRUE; if(*input == '.') { - /* A. If the input buffer begins with a prefix of "../" or "./", then + /* A. If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise, */ if(!strncmp("./", input, 2)) { @@ -897,7 +911,7 @@ UNITTEST int dedotdotify(const char *input, size_t clen, char **outp) input += 3; clen -= 3; } - /* D. if the input buffer consists only of "." or "..", then remove + /* D. if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise, */ else if(!strcmp(".", input) || !strcmp("..", input) || @@ -909,7 +923,7 @@ UNITTEST int dedotdotify(const char *input, size_t clen, char **outp) dotdot = FALSE; } else if(*input == '/') { - /* B. if the input buffer begins with a prefix of "/./" or "/.", where + /* B. if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise, */ if(!strncmp("/./", input, 3)) { @@ -922,7 +936,7 @@ UNITTEST int dedotdotify(const char *input, size_t clen, char **outp) break; } - /* C. if the input buffer begins with a prefix of "/../" or "/..", + /* C. if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise, */ @@ -956,7 +970,7 @@ UNITTEST int dedotdotify(const char *input, size_t clen, char **outp) dotdot = FALSE; if(!dotdot) { - /* E. move the first path segment in the input buffer to the end of + /* E. move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer. */ @@ -1049,19 +1063,19 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) * Appendix E, but believe me, it was meant to be there. --MK) */ if(ptr[0] != '/' && !STARTS_WITH_URL_DRIVE_PREFIX(ptr)) { - /* the URL includes a host name, it must match "localhost" or + /* the URL includes a hostname, it must match "localhost" or "127.0.0.1" to be valid */ if(checkprefix("localhost/", ptr) || checkprefix("127.0.0.1/", ptr)) { ptr += 9; /* now points to the slash after the host */ } else { -#if defined(WIN32) +#if defined(_WIN32) size_t len; - /* the host name, NetBIOS computer name, can not contain disallowed + /* the hostname, NetBIOS computer name, can not contain disallowed chars, and the delimiting slash character must be appended to the - host name */ + hostname */ path = strpbrk(ptr, "/\\:*?\"<>|"); if(!path || *path != '/') { result = CURLUE_BAD_FILE_URL; @@ -1070,8 +1084,9 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) len = path - ptr; if(len) { - if(Curl_dyn_addn(&host, ptr, len)) { - result = CURLUE_OUT_OF_MEMORY; + CURLcode code = Curl_dyn_addn(&host, ptr, len); + if(code) { + result = cc2cu(code); goto fail; } uncpath = TRUE; @@ -1095,12 +1110,12 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) /* no host for file: URLs by default */ Curl_dyn_reset(&host); -#if !defined(MSDOS) && !defined(WIN32) && !defined(__CYGWIN__) - /* Don't allow Windows drive letters when not in Windows. +#if !defined(_WIN32) && !defined(MSDOS) && !defined(__CYGWIN__) + /* Do not allow Windows drive letters when not in Windows. * This catches both "file:/c:" and "file:c:" */ if(('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) || STARTS_WITH_URL_DRIVE_PREFIX(path)) { - /* File drive letters are only accepted in MSDOS/Windows */ + /* File drive letters are only accepted in MS-DOS/Windows */ result = CURLUE_BAD_FILE_URL; goto fail; } @@ -1129,7 +1144,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) } schemep = schemebuf; - if(!Curl_builtin_scheme(schemep, CURL_ZERO_TERMINATED) && + if(!Curl_get_scheme_handler(schemep) && !(flags & CURLU_NON_SUPPORT_SCHEME)) { result = CURLUE_UNSUPPORTED_SCHEME; goto fail; @@ -1140,7 +1155,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) result = CURLUE_BAD_SLASHES; goto fail; } - hostp = p; /* host name starts here */ + hostp = p; /* hostname starts here */ } else { /* no scheme! */ @@ -1166,7 +1181,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) } } - /* find the end of the host name + port number */ + /* find the end of the hostname + port number */ hostlen = strcspn(hostp, "/?#"); path = &hostp[hostlen]; @@ -1180,7 +1195,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) if((flags & CURLU_GUESS_SCHEME) && !schemep) { const char *hostname = Curl_dyn_ptr(&host); - /* legacy curl-style guess based on host name */ + /* legacy curl-style guess based on hostname */ if(checkprefix("ftp.", hostname)) schemep = "ftp"; else if(checkprefix("dict.", hostname)) @@ -1201,6 +1216,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) result = CURLUE_OUT_OF_MEMORY; goto fail; } + u->guessed_scheme = TRUE; } } else if(flags & CURLU_NO_AUTHORITY) { @@ -1219,19 +1235,19 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) fragment = strchr(path, '#'); if(fragment) { fraglen = pathlen - (fragment - path); + u->fragment_present = TRUE; if(fraglen > 1) { /* skip the leading '#' in the copy but include the terminating null */ if(flags & CURLU_URLENCODE) { struct dynbuf enc; Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); - if(urlencode_str(&enc, fragment + 1, fraglen, TRUE, FALSE)) { - result = CURLUE_OUT_OF_MEMORY; + result = urlencode_str(&enc, fragment + 1, fraglen - 1, TRUE, FALSE); + if(result) goto fail; - } u->fragment = Curl_dyn_ptr(&enc); } else { - u->fragment = Curl_memdup(fragment + 1, fraglen); + u->fragment = Curl_memdup0(fragment + 1, fraglen - 1); if(!u->fragment) { result = CURLUE_OUT_OF_MEMORY; goto fail; @@ -1242,30 +1258,28 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) pathlen -= fraglen; } - DEBUGASSERT(pathlen < urllen); query = memchr(path, '?', pathlen); if(query) { size_t qlen = fragment ? (size_t)(fragment - query) : pathlen - (query - path); pathlen -= qlen; + u->query_present = TRUE; if(qlen > 1) { if(flags & CURLU_URLENCODE) { struct dynbuf enc; Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); /* skip the leading question mark */ - if(urlencode_str(&enc, query + 1, qlen - 1, TRUE, TRUE)) { - result = CURLUE_OUT_OF_MEMORY; + result = urlencode_str(&enc, query + 1, qlen - 1, TRUE, TRUE); + if(result) goto fail; - } u->query = Curl_dyn_ptr(&enc); } else { - u->query = Curl_memdup(query + 1, qlen); + u->query = Curl_memdup0(query + 1, qlen - 1); if(!u->query) { result = CURLUE_OUT_OF_MEMORY; goto fail; } - u->query[qlen - 1] = 0; } } else { @@ -1281,10 +1295,9 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) if(pathlen && (flags & CURLU_URLENCODE)) { struct dynbuf enc; Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); - if(urlencode_str(&enc, path, pathlen, TRUE, FALSE)) { - result = CURLUE_OUT_OF_MEMORY; + result = urlencode_str(&enc, path, pathlen, TRUE, FALSE); + if(result) goto fail; - } pathlen = Curl_dyn_len(&enc); path = u->path = Curl_dyn_ptr(&enc); } @@ -1295,12 +1308,11 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) } else { if(!u->path) { - u->path = Curl_memdup(path, pathlen + 1); + u->path = Curl_memdup0(path, pathlen); if(!u->path) { result = CURLUE_OUT_OF_MEMORY; goto fail; } - u->path[pathlen] = 0; path = u->path; } else if(flags & CURLU_URLENCODE) @@ -1352,7 +1364,7 @@ static CURLUcode parseurl_and_replace(const char *url, CURLU *u, */ CURLU *curl_url(void) { - return calloc(sizeof(struct Curl_URL), 1); + return calloc(1, sizeof(struct Curl_URL)); } void curl_url_cleanup(CURLU *u) @@ -1374,7 +1386,7 @@ void curl_url_cleanup(CURLU *u) CURLU *curl_url_dup(const CURLU *in) { - struct Curl_URL *u = calloc(sizeof(struct Curl_URL), 1); + struct Curl_URL *u = calloc(1, sizeof(struct Curl_URL)); if(u) { DUP(u, in, scheme); DUP(u, in, user); @@ -1387,6 +1399,8 @@ CURLU *curl_url_dup(const CURLU *in) DUP(u, in, fragment); DUP(u, in, zoneid); u->portnum = in->portnum; + u->fragment_present = in->fragment_present; + u->query_present = in->query_present; } return u; fail: @@ -1400,8 +1414,8 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, const char *ptr; CURLUcode ifmissing = CURLUE_UNKNOWN_PART; char portbuf[7]; - bool urldecode = (flags & CURLU_URLDECODE)?1:0; - bool urlencode = (flags & CURLU_URLENCODE)?1:0; + bool urldecode = (flags & CURLU_URLDECODE) ? 1 : 0; + bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0; bool punycode = FALSE; bool depunyfy = FALSE; bool plusdecode = FALSE; @@ -1417,6 +1431,8 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, ptr = u->scheme; ifmissing = CURLUE_NO_SCHEME; urldecode = FALSE; /* never for schemes */ + if((flags & CURLU_NO_GUESS_SCHEME) && u->guessed_scheme) + return CURLUE_NO_SCHEME; break; case CURLUPART_USER: ptr = u->user; @@ -1433,8 +1449,8 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, case CURLUPART_HOST: ptr = u->host; ifmissing = CURLUE_NO_HOST; - punycode = (flags & CURLU_PUNYCODE)?1:0; - depunyfy = (flags & CURLU_PUNY2IDN)?1:0; + punycode = (flags & CURLU_PUNYCODE) ? 1 : 0; + depunyfy = (flags & CURLU_PUNY2IDN) ? 1 : 0; break; case CURLUPART_ZONEID: ptr = u->zoneid; @@ -1445,10 +1461,9 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, ifmissing = CURLUE_NO_PORT; urldecode = FALSE; /* never for port */ if(!ptr && (flags & CURLU_DEFAULT_PORT) && u->scheme) { - /* there's no stored port number, but asked to deliver + /* there is no stored port number, but asked to deliver a default one for the scheme */ - const struct Curl_handler *h = - Curl_builtin_scheme(u->scheme, CURL_ZERO_TERMINATED); + const struct Curl_handler *h = Curl_get_scheme_handler(u->scheme); if(h) { msnprintf(portbuf, sizeof(portbuf), "%u", h->defport); ptr = portbuf; @@ -1457,8 +1472,7 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, else if(ptr && u->scheme) { /* there is a stored port number, but ask to inhibit if it matches the default one for the scheme */ - const struct Curl_handler *h = - Curl_builtin_scheme(u->scheme, CURL_ZERO_TERMINATED); + const struct Curl_handler *h = Curl_get_scheme_handler(u->scheme); if(h && (h->defport == u->portnum) && (flags & CURLU_NO_DEFAULT_PORT)) ptr = NULL; @@ -1473,10 +1487,16 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, ptr = u->query; ifmissing = CURLUE_NO_QUERY; plusdecode = urldecode; + if(ptr && !ptr[0] && !(flags & CURLU_GET_EMPTY)) + /* there was a blank query and the user do not ask for it */ + ptr = NULL; break; case CURLUPART_FRAGMENT: ptr = u->fragment; ifmissing = CURLUE_NO_FRAGMENT; + if(!ptr && u->fragment_present && flags & CURLU_GET_EMPTY) + /* there was a blank fragment and the user asks for it */ + ptr = ""; break; case CURLUPART_URL: { char *url; @@ -1484,18 +1504,24 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, char *options = u->options; char *port = u->port; char *allochost = NULL; - punycode = (flags & CURLU_PUNYCODE)?1:0; - depunyfy = (flags & CURLU_PUNY2IDN)?1:0; + bool show_fragment = + u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY); + bool show_query = + (u->query && u->query[0]) || + (u->query_present && flags & CURLU_GET_EMPTY); + punycode = (flags & CURLU_PUNYCODE) ? 1 : 0; + depunyfy = (flags & CURLU_PUNY2IDN) ? 1 : 0; if(u->scheme && strcasecompare("file", u->scheme)) { url = aprintf("file://%s%s%s", u->path, - u->fragment? "#": "", - u->fragment? u->fragment : ""); + show_fragment ? "#": "", + u->fragment ? u->fragment : ""); } else if(!u->host) return CURLUE_NO_HOST; else { const struct Curl_handler *h = NULL; + char schemebuf[MAX_SCHEME_LEN + 5]; if(u->scheme) scheme = u->scheme; else if(flags & CURLU_DEFAULT_SCHEME) @@ -1503,9 +1529,9 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, else return CURLUE_NO_SCHEME; - h = Curl_builtin_scheme(scheme, CURL_ZERO_TERMINATED); + h = Curl_get_scheme_handler(scheme); if(!port && (flags & CURLU_DEFAULT_PORT)) { - /* there's no stored port number, but asked to deliver + /* there is no stored port number, but asked to deliver a default one for the scheme */ if(h) { msnprintf(portbuf, sizeof(portbuf), "%u", h->defport); @@ -1566,8 +1592,13 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, } } - url = aprintf("%s://%s%s%s%s%s%s%s%s%s%s%s%s%s%s", - scheme, + if(!(flags & CURLU_NO_GUESS_SCHEME) || !u->guessed_scheme) + msnprintf(schemebuf, sizeof(schemebuf), "%s://", scheme); + else + schemebuf[0] = 0; + + url = aprintf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + schemebuf, u->user ? u->user : "", u->password ? ":": "", u->password ? u->password : "", @@ -1578,10 +1609,10 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, port ? ":": "", port ? port : "", u->path ? u->path : "/", - (u->query && u->query[0]) ? "?": "", - (u->query && u->query[0]) ? u->query : "", - u->fragment? "#": "", - u->fragment? u->fragment : ""); + show_query ? "?": "", + u->query ? u->query : "", + show_fragment ? "#": "", + u->fragment ? u->fragment : ""); free(allochost); } if(!url) @@ -1596,7 +1627,7 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, if(ptr) { size_t partlen = strlen(ptr); size_t i = 0; - *part = Curl_memdup(ptr, partlen + 1); + *part = Curl_memdup0(ptr, partlen); if(!*part) return CURLUE_OUT_OF_MEMORY; if(plusdecode) { @@ -1623,10 +1654,11 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, } if(urlencode) { struct dynbuf enc; + CURLUcode uc; Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); - if(urlencode_str(&enc, *part, partlen, TRUE, - what == CURLUPART_QUERY)) - return CURLUE_OUT_OF_MEMORY; + uc = urlencode_str(&enc, *part, partlen, TRUE, what == CURLUPART_QUERY); + if(uc) + return uc; free(*part); *part = Curl_dyn_ptr(&enc); } @@ -1671,8 +1703,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, const char *part, unsigned int flags) { char **storep = NULL; - long port = 0; - bool urlencode = (flags & CURLU_URLENCODE)? 1 : 0; + bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0; bool plusencode = FALSE; bool urlskipslash = FALSE; bool leadingslash = FALSE; @@ -1689,6 +1720,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, break; case CURLUPART_SCHEME: storep = &u->scheme; + u->guessed_scheme = FALSE; break; case CURLUPART_USER: storep = &u->user; @@ -1714,9 +1746,11 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, break; case CURLUPART_QUERY: storep = &u->query; + u->query_present = FALSE; break; case CURLUPART_FRAGMENT: storep = &u->fragment; + u->fragment_present = FALSE; break; default: return CURLUE_UNKNOWN_PART; @@ -1743,9 +1777,8 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, if((plen > MAX_SCHEME_LEN) || (plen < 1)) /* too long or too short */ return CURLUE_BAD_SCHEME; - if(!(flags & CURLU_NON_SUPPORT_SCHEME) && - /* verify that it is a fine scheme */ - !Curl_builtin_scheme(part, CURL_ZERO_TERMINATED)) + /* verify that it is a fine scheme */ + if(!(flags & CURLU_NON_SUPPORT_SCHEME) && !Curl_get_scheme_handler(part)) return CURLUE_UNSUPPORTED_SCHEME; storep = &u->scheme; urlencode = FALSE; /* never */ @@ -1760,6 +1793,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, } else return CURLUE_BAD_SCHEME; + u->guessed_scheme = FALSE; break; } case CURLUPART_USER: @@ -1779,18 +1813,26 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, storep = &u->zoneid; break; case CURLUPART_PORT: - { - char *endp; - urlencode = FALSE; /* never */ - port = strtol(part, &endp, 10); /* Port number must be decimal */ - if((port <= 0) || (port > 0xffff)) - return CURLUE_BAD_PORT_NUMBER; - if(*endp) - /* weirdly provided number, not good! */ + if(!ISDIGIT(part[0])) + /* not a number */ return CURLUE_BAD_PORT_NUMBER; - storep = &u->port; - } - break; + else { + char *tmp; + char *endp; + unsigned long port; + errno = 0; + port = strtoul(part, &endp, 10); /* must be decimal */ + if(errno || (port > 0xffff) || *endp) + /* weirdly provided number, not good! */ + return CURLUE_BAD_PORT_NUMBER; + tmp = strdup(part); + if(!tmp) + return CURLUE_OUT_OF_MEMORY; + free(u->port); + u->port = tmp; + u->portnum = (unsigned short)port; + return CURLUE_OK; + } case CURLUPART_PATH: urlskipslash = TRUE; leadingslash = TRUE; /* enforce */ @@ -1798,12 +1840,14 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, break; case CURLUPART_QUERY: plusencode = urlencode; - appendquery = (flags & CURLU_APPENDQUERY)?1:0; + appendquery = (flags & CURLU_APPENDQUERY) ? 1 : 0; equalsencode = appendquery; storep = &u->query; + u->query_present = TRUE; break; case CURLUPART_FRAGMENT: storep = &u->fragment; + u->fragment_present = TRUE; break; case CURLUPART_URL: { /* @@ -1812,7 +1856,8 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, * If the existing contents is enough for a URL, allow a relative URL to * replace it. */ - CURLUcode result; + CURLcode result; + CURLUcode uc; char *oldurl; char *redired_url; @@ -1821,7 +1866,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, return CURLUE_MALFORMED_INPUT; /* if the new thing is absolute or the old one is not - * (we could not get an absolute url in 'oldurl'), + * (we could not get an absolute URL in 'oldurl'), * then replace the existing with the new. */ if(Curl_is_absolute_url(part, NULL, 0, flags & (CURLU_GUESS_SCHEME| @@ -1832,14 +1877,14 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, /* apply the relative part to create a new URL * and replace the existing one with it. */ - redired_url = concat_url(oldurl, part); + result = concat_url(oldurl, part, &redired_url); free(oldurl); - if(!redired_url) - return CURLUE_OUT_OF_MEMORY; + if(result) + return cc2cu(result); - result = parseurl_and_replace(redired_url, u, flags); + uc = parseurl_and_replace(redired_url, u, flags); free(redired_url); - return result; + return uc; } default: return CURLUE_UNKNOWN_PART; @@ -1853,7 +1898,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, if(leadingslash && (part[0] != '/')) { CURLcode result = Curl_dyn_addn(&enc, "/", 1); if(result) - return CURLUE_OUT_OF_MEMORY; + return cc2cu(result); } if(urlencode) { const unsigned char *i; @@ -1865,7 +1910,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, if(result) return CURLUE_OUT_OF_MEMORY; } - else if(Curl_isunreserved(*i) || + else if(ISUNRESERVED(*i) || ((*i == '/') && urlskipslash) || ((*i == '=') && equalsencode)) { if((*i == '=') && equalsencode) @@ -1873,15 +1918,15 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, equalsencode = FALSE; result = Curl_dyn_addn(&enc, i, 1); if(result) - return CURLUE_OUT_OF_MEMORY; + return cc2cu(result); } else { char out[3]={'%'}; - out[1] = hexdigits[*i>>4]; + out[1] = hexdigits[*i >> 4]; out[2] = hexdigits[*i & 0xf]; result = Curl_dyn_addn(&enc, out, 3); if(result) - return CURLUE_OUT_OF_MEMORY; + return cc2cu(result); } } } @@ -1889,7 +1934,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, char *p; CURLcode result = Curl_dyn_add(&enc, part); if(result) - return CURLUE_OUT_OF_MEMORY; + return cc2cu(result); p = Curl_dyn_ptr(&enc); while(*p) { /* make sure percent encoded are lower case */ @@ -1905,7 +1950,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, } newp = Curl_dyn_ptr(&enc); - if(appendquery) { + if(appendquery && newp) { /* Append the 'newp' string onto the old query. Add a '&' separator if none is present at the end of the existing query already */ @@ -1934,13 +1979,29 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, } } - if(what == CURLUPART_HOST) { - size_t n = strlen(newp); + else if(what == CURLUPART_HOST) { + size_t n = Curl_dyn_len(&enc); if(!n && (flags & CURLU_NO_AUTHORITY)) { - /* Skip hostname check, it's allowed to be empty. */ + /* Skip hostname check, it is allowed to be empty. */ } else { - if(!n || hostname_check(u, (char *)newp, n)) { + bool bad = FALSE; + if(!n) + bad = TRUE; /* empty hostname is not okay */ + else if(!urlencode) { + /* if the host name part was not URL encoded here, it was set ready + URL encoded so we need to decode it to check */ + size_t dlen; + char *decoded = NULL; + CURLcode result = + Curl_urldecode(newp, n, &decoded, &dlen, REJECT_CTRL); + if(result || hostname_check(u, decoded, dlen)) + bad = TRUE; + free(decoded); + } + else if(hostname_check(u, (char *)newp, n)) + bad = TRUE; + if(bad) { Curl_dyn_free(&enc); return CURLUE_BAD_HOSTNAME; } @@ -1950,9 +2011,5 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, free(*storep); *storep = (char *)newp; } - /* set after the string, to make it not assigned if the allocation above - fails */ - if(port) - u->portnum = port; return CURLUE_OK; } diff --git a/deps/curl/lib/urldata.h b/deps/curl/lib/urldata.h index 4bfb3b48d26..704fb7a1d22 100644 --- a/deps/curl/lib/urldata.h +++ b/deps/curl/lib/urldata.h @@ -53,10 +53,21 @@ #define PORT_GOPHER 70 #define PORT_MQTT 1883 -#ifdef USE_WEBSOCKETS +struct curl_trc_featt; + +#ifdef USE_ECH +/* CURLECH_ bits for the tls_ech option */ +# define CURLECH_DISABLE (1<<0) +# define CURLECH_GREASE (1<<1) +# define CURLECH_ENABLE (1<<2) +# define CURLECH_HARD (1<<3) +# define CURLECH_CLA_CFG (1<<4) +#endif + +#ifndef CURL_DISABLE_WEBSOCKETS /* CURLPROTO_GOPHERS (29) is the highest publicly used protocol bit number, * the rest are internal information. If we use higher bits we only do this on - * platforms that have a >= 64 bit type and then we use such a type for the + * platforms that have a >= 64-bit type and then we use such a type for the * protocol fields in the protocol handler. */ #define CURLPROTO_WS (1<<30) @@ -66,6 +77,10 @@ #define CURLPROTO_WSS 0 #endif +/* the default protocols accepting a redirect to */ +#define CURLPROTO_REDIR (CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | \ + CURLPROTO_FTPS) + /* This should be undefined once we need bit 32 or higher */ #define PROTO_TYPE_SMALL @@ -90,6 +105,12 @@ typedef unsigned int curl_prot_t; #define CURL_DEFAULT_USER "anonymous" #define CURL_DEFAULT_PASSWORD "ftp@example.com" +#if !defined(_WIN32) && !defined(MSDOS) && !defined(__EMX__) +/* do FTP line-end CRLF => LF conversions on platforms that prefer LF-only. It + also means: keep CRLF line endings on the CRLF platforms */ +#define CURL_PREFER_LF_LINEENDS +#endif + /* Convenience defines for checking protocols or their SSL based version. Each protocol handler should only ever have a single CURLPROTO_ in its protocol field. */ @@ -102,7 +123,7 @@ typedef unsigned int curl_prot_t; #define PROTO_FAMILY_SSH (CURLPROTO_SCP|CURLPROTO_SFTP) #if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) || \ - !defined(CURL_DISABLE_POP3) + !defined(CURL_DISABLE_POP3) || !defined(CURL_DISABLE_FILE) /* these protocols support CURLOPT_DIRLISTONLY */ #define CURL_LIST_ONLY_PROTOCOL 1 #endif @@ -141,12 +162,15 @@ typedef unsigned int curl_prot_t; #include "splay.h" #include "dynbuf.h" #include "dynhds.h" +#include "request.h" +#include "netrc.h" /* return the count of bytes sent, or -1 on error */ typedef ssize_t (Curl_send)(struct Curl_easy *data, /* transfer */ int sockindex, /* socketindex */ const void *buf, /* data to write */ size_t len, /* max amount to write */ + bool eos, /* last chunk */ CURLcode *err); /* error to return */ /* return the count of bytes read, or -1 on error */ @@ -160,7 +184,6 @@ typedef ssize_t (Curl_recv)(struct Curl_easy *data, /* transfer */ typedef CURLcode (*Curl_datastream)(struct Curl_easy *data, struct connectdata *conn, int *didwhat, - bool *done, int select_res); #endif @@ -230,8 +253,7 @@ typedef CURLcode (*Curl_datastream)(struct Curl_easy *data, #ifdef HAVE_GSSAPI /* Types needed for krb5-ftp connections */ struct krb5buffer { - void *data; - size_t size; + struct dynbuf buf; size_t index; BIT(eof_flag); }; @@ -247,27 +269,26 @@ enum protection_level { }; #endif -/* enum for the nonblocking SSL connection state machine */ -typedef enum { - ssl_connect_1, - ssl_connect_2, - ssl_connect_2_reading, - ssl_connect_2_writing, - ssl_connect_3, - ssl_connect_done -} ssl_connect_state; - -typedef enum { - ssl_connection_none, - ssl_connection_negotiating, - ssl_connection_complete -} ssl_connection_state; - /* SSL backend-specific data; declared differently by each SSL backend */ struct ssl_backend_data; +typedef enum { + CURL_SSL_PEER_DNS, + CURL_SSL_PEER_IPV4, + CURL_SSL_PEER_IPV6 +} ssl_peer_type; + +struct ssl_peer { + char *hostname; /* hostname for verification */ + char *dispname; /* display version of hostname */ + char *sni; /* SNI version of hostname or NULL if not usable */ + ssl_peer_type type; /* type of the peer information */ + int port; /* port we are talking to */ + int transport; /* one of TRNSPRT_* defines */ +}; + struct ssl_primary_config { - char *CApath; /* certificate dir (doesn't work on windows) */ + char *CApath; /* certificate dir (does not work on Windows) */ char *CAfile; /* certificate to verify peer against */ char *issuercert; /* optional issuer certificate filename */ char *clientcert; @@ -289,7 +310,7 @@ struct ssl_primary_config { BIT(verifypeer); /* set TRUE if this is desired */ BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ BIT(verifystatus); /* set TRUE if certificate status must be checked */ - BIT(sessionid); /* cache session IDs or not */ + BIT(cache_session); /* cache session or not */ }; struct ssl_config_data { @@ -298,15 +319,16 @@ struct ssl_config_data { curl_ssl_ctx_callback fsslctx; /* function to initialize ssl ctx */ void *fsslctxp; /* parameter for call back */ char *cert_type; /* format for certificate (default: PEM)*/ - char *key; /* private key file name */ + char *key; /* private key filename */ struct curl_blob *key_blob; char *key_type; /* format for private key (default: PEM) */ char *key_passwd; /* plain text private key password */ BIT(certinfo); /* gather lots of certificate info */ BIT(falsestart); + BIT(earlydata); /* use tls1.3 early data */ BIT(enable_beast); /* allow this flaw for interoperability's sake */ BIT(no_revoke); /* disable SSL certificate revocation checks */ - BIT(no_partialchain); /* don't accept partial certificate chains */ + BIT(no_partialchain); /* do not accept partial certificate chains */ BIT(revoke_best_effort); /* ignore SSL revocation offline/missing revocation list errors */ BIT(native_ca_store); /* use the native ca store of operating system */ @@ -319,16 +341,21 @@ struct ssl_general_config { int ca_cache_timeout; /* Certificate store cache timeout (seconds) */ }; +typedef void Curl_ssl_sessionid_dtor(void *sessionid, size_t idsize); + /* information stored about one single SSL session */ struct Curl_ssl_session { - char *name; /* host name for which this ID was used */ - char *conn_to_host; /* host name for the connection (may be NULL) */ + char *name; /* hostname for which this ID was used */ + char *conn_to_host; /* hostname for the connection (may be NULL) */ const char *scheme; /* protocol scheme used */ + char *alpn; /* APLN TLS negotiated protocol string */ void *sessionid; /* as returned from the SSL layer */ size_t idsize; /* if known, otherwise 0 */ + Curl_ssl_sessionid_dtor *sessionid_free; /* free `sessionid` callback */ long age; /* just a number, the higher the more recent */ int remote_port; /* remote port */ int conn_to_port; /* remote port for the connection (may be -1) */ + int transport; /* TCP or QUIC */ struct ssl_primary_config ssl_config; /* setup for this session */ }; @@ -428,15 +455,7 @@ struct ntlmdata { unsigned int flags; unsigned char nonce[8]; unsigned int target_info_len; - void *target_info; /* TargetInfo received in the ntlm type-2 message */ - -#if defined(NTLM_WB_ENABLED) - /* used for communication with Samba's winbind daemon helper ntlm_auth */ - curl_socket_t ntlm_auth_hlpr_socket; - pid_t ntlm_auth_hlpr_pid; - char *challenge; /* The received base64 encoded ntlm type-2 message */ - char *response; /* The generated base64 ntlm type-1/type-3 message */ -#endif + void *target_info; /* TargetInfo received in the NTLM type-2 message */ #endif }; #endif @@ -449,6 +468,7 @@ struct negotiatedata { gss_ctx_id_t context; gss_name_t spn; gss_buffer_desc output_token; + struct dynbuf channel_binding_data; #else #ifdef USE_WINDOWS_SSPI #ifdef SECPKG_ATTR_ENDPOINT_BINDINGS @@ -490,9 +510,6 @@ struct ConnectBits { This is implicit when SSL-protocols are used through proxies, but can also be enabled explicitly by apps */ - BIT(proxy_connect_closed); /* TRUE if a proxy disconnected the connection - in a CONNECT request with auth, so that - libcurl should reconnect and continue. */ BIT(proxy); /* if set, this transfer is done through a proxy - any type */ #endif /* always modify bits.close with the connclose() and connkeep() macros! */ @@ -512,16 +529,12 @@ struct ConnectBits { the TCP layer connect */ BIT(retry); /* this connection is about to get closed and then re-attempted at another connection. */ - BIT(authneg); /* TRUE when the auth phase has started, which means - that we are creating a request with an auth header, - but it is not the final request in the auth - negotiation. */ #ifndef CURL_DISABLE_FTP BIT(ftp_use_epsv); /* As set with CURLOPT_FTP_USE_EPSV, but if we find out - EPSV doesn't work we disable it for the forthcoming + EPSV does not work we disable it for the forthcoming requests */ BIT(ftp_use_eprt); /* As set with CURLOPT_FTP_USE_EPRT, but if we find out - EPRT doesn't work we disable it for the forthcoming + EPRT does not work we disable it for the forthcoming requests */ BIT(ftp_use_data_ssl); /* Enabled SSL for the data connection */ BIT(ftp_use_control_ssl); /* Enabled SSL for the control connection */ @@ -531,6 +544,7 @@ struct ConnectBits { #endif BIT(bound); /* set true if bind() has already been done on this socket/ connection */ + BIT(asks_multiplex); /* connection asks for multiplexing, but is not yet */ BIT(multiplex); /* connection is multiplexed */ BIT(tcp_fastopen); /* use TCP Fast Open */ BIT(tls_enable_alpn); /* TLS ALPN extension? */ @@ -545,6 +559,10 @@ struct ConnectBits { accept() */ BIT(parallel_connect); /* set TRUE when a parallel connect attempt has started (happy eyeballs) */ + BIT(aborted); /* connection was aborted, e.g. in unclean state */ + BIT(shutdown_handler); /* connection shutdown: handler shut down */ + BIT(shutdown_filters); /* connection shutdown: filters shut down */ + BIT(in_cpool); /* connection is kept in a connection pool */ }; struct hostname { @@ -568,9 +586,24 @@ struct hostname { #define KEEP_RECV_PAUSE (1<<4) /* reading is paused */ #define KEEP_SEND_PAUSE (1<<5) /* writing is paused */ +/* KEEP_SEND_TIMED is set when the transfer should attempt sending + * at timer (or other) events. A transfer waiting on a timer will + * remove KEEP_SEND to suppress POLLOUTs of the connection. + * Adding KEEP_SEND_TIMED will then attempt to send whenever the transfer + * enters the "readwrite" loop, e.g. when a timer fires. + * This is used in HTTP for 'Expect: 100-continue' waiting. */ +#define KEEP_SEND_TIMED (1<<6) + #define KEEP_RECVBITS (KEEP_RECV | KEEP_RECV_HOLD | KEEP_RECV_PAUSE) #define KEEP_SENDBITS (KEEP_SEND | KEEP_SEND_HOLD | KEEP_SEND_PAUSE) +/* transfer wants to send is not PAUSE or HOLD */ +#define CURL_WANT_SEND(data) \ + (((data)->req.keepon & KEEP_SENDBITS) == KEEP_SEND) +/* transfer receive is not on PAUSE or HOLD */ +#define CURL_WANT_RECV(data) \ + (((data)->req.keepon & KEEP_RECVBITS) == KEEP_RECV) + #if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH) #define USE_CURL_ASYNC struct Curl_async { @@ -589,145 +622,13 @@ struct Curl_async { #define FIRSTSOCKET 0 #define SECONDARYSOCKET 1 -enum expect100 { - EXP100_SEND_DATA, /* enough waiting, just send the body now */ - EXP100_AWAITING_CONTINUE, /* waiting for the 100 Continue header */ - EXP100_SENDING_REQUEST, /* still sending the request but will wait for - the 100 header once done with the request */ - EXP100_FAILED /* used on 417 Expectation Failed */ -}; - -enum upgrade101 { - UPGR101_INIT, /* default state */ - UPGR101_WS, /* upgrade to WebSockets requested */ - UPGR101_H2, /* upgrade to HTTP/2 requested */ - UPGR101_RECEIVED, /* 101 response received */ - UPGR101_WORKING /* talking upgraded protocol */ -}; - -enum doh_slots { - /* Explicit values for first two symbols so as to match hard-coded - * constants in existing code - */ - DOH_PROBE_SLOT_IPADDR_V4 = 0, /* make 'V4' stand out for readability */ - DOH_PROBE_SLOT_IPADDR_V6 = 1, /* 'V6' likewise */ - - /* Space here for (possibly build-specific) additional slot definitions */ - - /* for example */ - /* #ifdef WANT_DOH_FOOBAR_TXT */ - /* DOH_PROBE_SLOT_FOOBAR_TXT, */ - /* #endif */ - - /* AFTER all slot definitions, establish how many we have */ - DOH_PROBE_SLOTS -}; - -/* - * Request specific data in the easy handle (Curl_easy). Previously, - * these members were on the connectdata struct but since a conn struct may - * now be shared between different Curl_easys, we store connection-specific - * data here. This struct only keeps stuff that's interesting for *this* - * request, as it will be cleared between multiple ones +/* Polling requested by an easy handle. + * `action` is CURL_POLL_IN, CURL_POLL_OUT or CURL_POLL_INOUT. */ -struct SingleRequest { - curl_off_t size; /* -1 if unknown at this point */ - curl_off_t maxdownload; /* in bytes, the maximum amount of data to fetch, - -1 means unlimited */ - curl_off_t bytecount; /* total number of bytes read */ - curl_off_t writebytecount; /* number of bytes written */ - - curl_off_t pendingheader; /* this many bytes left to send is actually - header and not body */ - struct curltime start; /* transfer started at this time */ - unsigned int headerbytecount; /* only count received headers */ - unsigned int deductheadercount; /* this amount of bytes doesn't count when - we check if anything has been transferred - at the end of a connection. We use this - counter to make only a 100 reply (without - a following second response code) result - in a CURLE_GOT_NOTHING error code */ - enum { - HEADER_NORMAL, /* no bad header at all */ - HEADER_PARTHEADER, /* part of the chunk is a bad header, the rest - is normal data */ - HEADER_ALLBAD /* all was believed to be header */ - } badheader; /* the header was deemed bad and will be - written as body */ - int headerline; /* counts header lines to better track the - first one */ - char *str; /* within buf */ - curl_off_t offset; /* possible resume offset read from the - Content-Range: header */ - int httpcode; /* error code from the 'HTTP/1.? XXX' or - 'RTSP/1.? XXX' line */ - int keepon; - struct curltime start100; /* time stamp to wait for the 100 code from */ - enum expect100 exp100; /* expect 100 continue state */ - enum upgrade101 upgr101; /* 101 upgrade state */ - - /* Content unencoding stack. See sec 3.5, RFC2616. */ - struct contenc_writer *writer_stack; - time_t timeofdoc; - long bodywrites; - char *location; /* This points to an allocated version of the Location: - header data */ - char *newurl; /* Set to the new URL to use when a redirect or a retry is - wanted */ - - /* 'upload_present' is used to keep a byte counter of how much data there is - still left in the buffer, aimed for upload. */ - ssize_t upload_present; - - /* 'upload_fromhere' is used as a read-pointer when we uploaded parts of a - buffer, so the next read should read from where this pointer points to, - and the 'upload_present' contains the number of bytes available at this - position */ - char *upload_fromhere; - - /* Allocated protocol-specific data. Each protocol handler makes sure this - points to data it needs. */ - union { - struct FILEPROTO *file; - struct FTP *ftp; - struct HTTP *http; - struct IMAP *imap; - struct ldapreqinfo *ldap; - struct MQTT *mqtt; - struct POP3 *pop3; - struct RTSP *rtsp; - struct smb_request *smb; - struct SMTP *smtp; - struct SSHPROTO *ssh; - struct TELNET *telnet; - } p; -#ifndef CURL_DISABLE_DOH - struct dohdata *doh; /* DoH specific data for this request */ -#endif -#if defined(WIN32) && defined(USE_WINSOCK) - struct curltime last_sndbuf_update; /* last time readwrite_upload called - win_update_buffer_size */ -#endif -#ifndef CURL_DISABLE_COOKIES - unsigned char setcookies; -#endif - unsigned char writer_stack_depth; /* Unencoding stack depth. */ - BIT(header); /* incoming data has HTTP header */ - BIT(content_range); /* set TRUE if Content-Range: was found */ - BIT(upload_done); /* set to TRUE when doing chunked transfer-encoding - upload and we're uploading the last chunk */ - BIT(ignorebody); /* we read a response-body but we ignore it! */ - BIT(http_bodyless); /* HTTP response status code is between 100 and 199, - 204 or 304 */ - BIT(chunk); /* if set, this is a chunked transfer-encoding */ - BIT(ignore_cl); /* ignore content-length */ - BIT(upload_chunky); /* set TRUE if we are doing chunked transfer-encoding - on upload */ - BIT(getheader); /* TRUE if header parsing is wanted */ - BIT(forbidchunk); /* used only to explicitly forbid chunk-upload for - specific upload buffers. See readmoredata() in http.c - for details. */ - BIT(no_body); /* the response has no body */ +struct easy_pollset { + curl_socket_t sockets[MAX_SOCKSPEREASYHANDLE]; + unsigned int num; + unsigned char actions[MAX_SOCKSPEREASYHANDLE]; }; /* @@ -735,7 +636,7 @@ struct SingleRequest { */ struct Curl_handler { - const char *scheme; /* URL scheme name. */ + const char *scheme; /* URL scheme name in lowercase */ /* Complement to setup_connection_internals(). This is done before the transfer "owns" the connection. */ @@ -755,7 +656,7 @@ struct Curl_handler { /* This function *MAY* be set to a protocol-dependent function that is run * after the connect() and everything is done, as a step in the connection. * The 'done' pointer points to a bool that should be set to TRUE if the - * function completes before return. If it doesn't complete, the caller + * function completes before return. If it does not complete, the caller * should call the ->connecting() function until it is. */ CURLcode (*connect_it)(struct Curl_easy *data, bool *done); @@ -786,7 +687,7 @@ struct Curl_handler { struct connectdata *conn, curl_socket_t *socks); /* This function *MAY* be set to a protocol-dependent function that is run - * by the curl_disconnect(), as a step in the disconnection. If the handler + * by the curl_disconnect(), as a step in the disconnection. If the handler * is called because the connection has been considered dead, * dead_connection is set to TRUE. The connection is (again) associated with * the transfer here. @@ -794,10 +695,17 @@ struct Curl_handler { CURLcode (*disconnect)(struct Curl_easy *, struct connectdata *, bool dead_connection); - /* If used, this function gets called from transfer.c:readwrite_data() to - allow the protocol to do extra reads/writes */ - CURLcode (*readwrite)(struct Curl_easy *data, struct connectdata *conn, - ssize_t *nread, bool *readmore); + /* If used, this function gets called from transfer.c to + allow the protocol to do extra handling in writing response to + the client. */ + CURLcode (*write_resp)(struct Curl_easy *data, const char *buf, size_t blen, + bool is_eos); + + /* If used, this function gets called from transfer.c to + allow the protocol to do extra handling in writing a single response + header line to the client. */ + CURLcode (*write_resp_hd)(struct Curl_easy *data, + const char *hd, size_t hdlen, bool is_eos); /* This function can perform various checks on the connection. See CONNCHECK_* for more information about the checks that can be performed, @@ -827,11 +735,11 @@ struct Curl_handler { the send function might need to be called while uploading, or vice versa. */ #define PROTOPT_DIRLOCK (1<<3) -#define PROTOPT_NONETWORK (1<<4) /* protocol doesn't use the network! */ +#define PROTOPT_NONETWORK (1<<4) /* protocol does not use the network! */ #define PROTOPT_NEEDSPWD (1<<5) /* needs a password, and if none is set it gets a default */ -#define PROTOPT_NOURLQUERY (1<<6) /* protocol can't handle - url query strings (?foo=bar) ! */ +#define PROTOPT_NOURLQUERY (1<<6) /* protocol cannot handle + URL query strings (?foo=bar) ! */ #define PROTOPT_CREDSPERREQUEST (1<<7) /* requires login credentials per request instead of per connection */ #define PROTOPT_ALPN (1<<8) /* set ALPN for this */ @@ -842,9 +750,9 @@ struct Curl_handler { HTTP proxy as HTTP proxies may know this protocol and act as a gateway */ #define PROTOPT_WILDCARD (1<<12) /* protocol supports wildcard matching */ -#define PROTOPT_USERPWDCTRL (1<<13) /* Allow "control bytes" (< 32 ascii) in - user name and password */ -#define PROTOPT_NOTCPPROXY (1<<14) /* this protocol can't proxy over TCP */ +#define PROTOPT_USERPWDCTRL (1<<13) /* Allow "control bytes" (< 32 ASCII) in + username and password */ +#define PROTOPT_NOTCPPROXY (1<<14) /* this protocol cannot proxy over TCP */ #define CONNCHECK_NONE 0 /* No checks */ #define CONNCHECK_ISDEAD (1<<0) /* Check if the connection is dead. */ @@ -853,12 +761,19 @@ struct Curl_handler { #define CONNRESULT_NONE 0 /* No extra information. */ #define CONNRESULT_DEAD (1<<0) /* The connection is dead. */ +struct ip_quadruple { + char remote_ip[MAX_IPADR_LEN]; + char local_ip[MAX_IPADR_LEN]; + int remote_port; + int local_port; +}; + struct proxy_info { struct hostname host; int port; unsigned char proxytype; /* curl_proxytype: what kind of proxy that is in use */ - char *user; /* proxy user name string, allocated */ + char *user; /* proxy username string, allocated */ char *passwd; /* proxy password string, allocated */ }; @@ -874,25 +789,22 @@ struct ldapconninfo; * unique for an entire connection. */ struct connectdata { - struct Curl_llist_element bundle_node; /* conncache */ - - /* chunk is for HTTP chunked encoding, but is in the general connectdata - struct only because we can do just about any protocol through an HTTP - proxy and an HTTP proxy may in fact respond using chunked encoding */ - struct Curl_chunker chunk; + struct Curl_llist_node cpool_node; /* conncache lists */ curl_closesocket_callback fclosesocket; /* function closing the socket(s) */ void *closesocket_client; - /* This is used by the connection cache logic. If this returns TRUE, this + /* This is used by the connection pool logic. If this returns TRUE, this handle is still used by one or more easy handles and can only used by any other easy handle without careful consideration (== only for multiplexing) and it cannot be used by another multi handle! */ -#define CONN_INUSE(c) ((c)->easyq.size) +#define CONN_INUSE(c) Curl_llist_count(&(c)->easyq) /**** Fields set when inited and not modified again */ curl_off_t connection_id; /* Contains a unique number to make it easier to track the connections in the log output */ + char *destination; /* string carrying normalized hostname+port+scope */ + size_t destination_len; /* strlen(destination) + 1 */ /* 'dns_entry' is the particular host we use. This points to an entry in the DNS cache and it will not get pruned while locked. It gets unlocked in @@ -905,35 +817,42 @@ struct connectdata { const struct Curl_sockaddr_ex *remote_addr; struct hostname host; - char *hostname_resolve; /* host name to resolve to address, allocated */ - char *secondaryhostname; /* secondary socket host name (ftp) */ + char *hostname_resolve; /* hostname to resolve to address, allocated */ + char *secondaryhostname; /* secondary socket hostname (ftp) */ struct hostname conn_to_host; /* the host to connect to. valid only if bits.conn_to_host is set */ #ifndef CURL_DISABLE_PROXY struct proxy_info socks_proxy; struct proxy_info http_proxy; #endif - /* 'primary_ip' and 'primary_port' get filled with peer's numerical - ip address and port number whenever an outgoing connection is - *attempted* from the primary socket to a remote address. When more - than one address is tried for a connection these will hold data + /* 'primary' and 'secondary' get filled with IP quadruple + (local/remote numerical ip address and port) whenever a connect is + *attempted*. + When more than one address is tried for a connection these will hold data for the last attempt. When the connection is actually established these are updated with data which comes directly from the socket. */ - - char primary_ip[MAX_IPADR_LEN]; - char *user; /* user name string, allocated */ + struct ip_quadruple primary; + struct ip_quadruple secondary; + char *user; /* username string, allocated */ char *passwd; /* password string, allocated */ char *options; /* options string, allocated */ char *sasl_authzid; /* authorization identity string, allocated */ char *oauth_bearer; /* OAUTH2 bearer, allocated */ struct curltime now; /* "current" time */ struct curltime created; /* creation time */ - struct curltime lastused; /* when returned to the connection cache */ + struct curltime lastused; /* when returned to the connection poolas idle */ curl_socket_t sock[2]; /* two sockets, the second is used for the data transfer when doing FTP */ Curl_recv *recv[2]; Curl_send *send[2]; struct Curl_cfilter *cfilter[2]; /* connection filters */ + struct { + struct curltime start[2]; /* when filter shutdown started */ + unsigned int timeout_ms; /* 0 means no timeout */ + } shutdown; + /* Last pollset used in connection shutdown. Used to detect changes + * for multi_socket API. */ + struct easy_pollset shutdown_poll; struct ssl_primary_config ssl_config; #ifndef CURL_DISABLE_PROXY @@ -952,9 +871,8 @@ struct connectdata { /**** curl_get() phase fields */ curl_socket_t sockfd; /* socket to read from or CURL_SOCKET_BAD */ - curl_socket_t writesockfd; /* socket to write to, it may very - well be the same we read from. - CURL_SOCKET_BAD disables */ + curl_socket_t writesockfd; /* socket to write to, it may be the same we read + from. CURL_SOCKET_BAD disables */ #ifdef HAVE_GSSAPI BIT(sec_complete); /* if Kerberos is enabled for this connection */ @@ -973,8 +891,6 @@ struct connectdata { #endif /* however, some of them are ftp specific. */ struct Curl_llist easyq; /* List of easy handles using this connection */ - curl_seek_callback seek_func; /* function that seeks the input */ - void *seek_client; /* pointer to pass to the seek() above */ /*************** Request - specific items ************/ #if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS) @@ -1003,11 +919,6 @@ struct connectdata { struct negotiatedata proxyneg; /* state data for proxy Negotiate auth */ #endif -#ifndef CURL_DISABLE_HTTP - /* for chunked-encoded trailer */ - struct dynbuf trailer; -#endif - union { #ifndef CURL_DISABLE_FTP struct ftp_conn ftpc; @@ -1042,13 +953,12 @@ struct connectdata { #ifndef CURL_DISABLE_MQTT struct mqtt_conn mqtt; #endif -#ifdef USE_WEBSOCKETS +#ifndef CURL_DISABLE_WEBSOCKETS struct websocket *ws; #endif unsigned int unused:1; /* avoids empty union */ } proto; - struct connectbundle *bundle; /* The bundle we are member of */ #ifdef USE_UNIX_SOCKETS char *unix_domain_socket; #endif @@ -1059,7 +969,7 @@ struct connectdata { /* When this connection is created, store the conditions for the local end bind. This is stored before the actual bind and before any connection is made and will serve the purpose of being used for comparison reasons so - that subsequent bound-requested connections aren't accidentally reusing + that subsequent bound-requested connections are not accidentally reusing wrong connections. */ char *localdev; unsigned short localportrange; @@ -1068,17 +978,15 @@ struct connectdata { int socks5_gssapi_enctype; #endif /* The field below gets set in connect.c:connecthost() */ - int port; /* which port to use locally - to connect to */ int remote_port; /* the remote port, not the proxy port! */ int conn_to_port; /* the remote port to connect to. valid only if bits.conn_to_port is set */ -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int scope_id; /* Scope id for IPv6 */ #endif unsigned short localport; unsigned short secondary_port; /* secondary socket remote port to connect to (ftp) */ - unsigned char cselect_bits; /* bitmask of socket events */ unsigned char alpn; /* APLN TLS negotiated protocol, a CURL_HTTP_VERSION* value */ #ifndef CURL_DISABLE_PROXY @@ -1120,58 +1028,62 @@ struct PureInfo { unsigned long httpauthavail; /* what host auth types were announced */ long numconnects; /* how many new connection did libcurl created */ char *contenttype; /* the content type of the object */ - char *wouldredirect; /* URL this would've been redirected to if asked to */ + char *wouldredirect; /* URL this would have been redirected to if asked to */ curl_off_t retry_after; /* info from Retry-After: header */ unsigned int header_size; /* size of read header(s) in bytes */ - /* PureInfo members 'conn_primary_ip', 'conn_primary_port', 'conn_local_ip' - and, 'conn_local_port' are copied over from the connectdata struct in - order to allow curl_easy_getinfo() to return this information even when - the session handle is no longer associated with a connection, and also - allow curl_easy_reset() to clear this information from the session handle - without disturbing information which is still alive, and that might be - reused, in the connection cache. */ - - char conn_primary_ip[MAX_IPADR_LEN]; - int conn_primary_port; /* this is the destination port to the connection, - which might have been a proxy */ + /* PureInfo primary ip_quadruple is copied over from the connectdata + struct in order to allow curl_easy_getinfo() to return this information + even when the session handle is no longer associated with a connection, + and also allow curl_easy_reset() to clear this information from the + session handle without disturbing information which is still alive, and + that might be reused, in the connection pool. */ + struct ip_quadruple primary; int conn_remote_port; /* this is the "remote port", which is the port number of the used URL, independent of proxy or not */ - char conn_local_ip[MAX_IPADR_LEN]; - int conn_local_port; const char *conn_scheme; unsigned int conn_protocol; struct curl_certinfo certs; /* info about the certs. Asked for with CURLOPT_CERTINFO / CURLINFO_CERTINFO */ CURLproxycode pxcode; - BIT(timecond); /* set to TRUE if the time condition didn't match, which + BIT(timecond); /* set to TRUE if the time condition did not match, which thus made the document NOT get fetched */ + BIT(used_proxy); /* the transfer used a proxy */ +}; + +struct pgrs_measure { + struct curltime start; /* when measure started */ + curl_off_t start_size; /* the 'cur_size' the measure started at */ }; +struct pgrs_dir { + curl_off_t total_size; /* total expected bytes */ + curl_off_t cur_size; /* transferred bytes so far */ + curl_off_t speed; /* bytes per second transferred */ + struct pgrs_measure limit; +}; struct Progress { time_t lastshow; /* time() of the last displayed progress meter or NULL to force redraw at next call */ - curl_off_t size_dl; /* total expected size */ - curl_off_t size_ul; /* total expected size */ - curl_off_t downloaded; /* transferred so far */ - curl_off_t uploaded; /* transferred so far */ + struct pgrs_dir ul; + struct pgrs_dir dl; curl_off_t current_speed; /* uses the currently fastest transfer */ + curl_off_t earlydata_sent; int width; /* screen width at download start */ int flags; /* see progress.h */ timediff_t timespent; - curl_off_t dlspeed; - curl_off_t ulspeed; - + timediff_t t_postqueue; timediff_t t_nslookup; timediff_t t_connect; timediff_t t_appconnect; timediff_t t_pretransfer; + timediff_t t_posttransfer; timediff_t t_starttransfer; timediff_t t_redirect; @@ -1180,14 +1092,6 @@ struct Progress { struct curltime t_startop; struct curltime t_acceptdata; - - /* upload speed limit */ - struct curltime ul_limit_start; - curl_off_t ul_limit_size; - /* download speed limit */ - struct curltime dl_limit_start; - curl_off_t dl_limit_size; - #define CURR_TIME (5 + 1) /* 6 entries for 5 seconds */ curl_off_t speeder[ CURR_TIME ]; @@ -1251,17 +1155,6 @@ struct Curl_data_priority { #endif }; -/* - * This struct is for holding data that was attempted to get sent to the user's - * callback but is held due to pausing. One instance per type (BOTH, HEADER, - * BODY). - */ -struct tempbuf { - struct dynbuf b; - int type; /* type of the 'tempwrite' buffer as a bitmask that is used with - Curl_client_write() */ -}; - /* Timers */ typedef enum { EXPIRE_100_TIMEOUT, @@ -1295,7 +1188,7 @@ typedef enum { * One instance for each timeout an easy handle can set. */ struct time_node { - struct Curl_llist_element list; + struct Curl_llist_node list; struct curltime time; expire_id eid; }; @@ -1312,9 +1205,12 @@ struct urlpieces { char *query; }; +#define CREDS_NONE 0 +#define CREDS_URL 1 /* from URL */ +#define CREDS_OPTION 2 /* set with a CURLOPT_ */ +#define CREDS_NETRC 3 /* found in netrc */ + struct UrlState { - /* Points to the connection cache */ - struct conncache *conn_cache; /* buffers to store authentication data in, as parsed from input options */ struct curltime keeps_speed; /* for the progress meter really */ @@ -1322,14 +1218,13 @@ struct UrlState { curl_off_t recent_conn_id; /* The most recent connection used, might no * longer exist */ struct dynbuf headerb; /* buffer to store headers in */ - - char *buffer; /* download buffer */ - char *ulbuf; /* allocated upload buffer or NULL */ + struct curl_slist *hstslist; /* list of HSTS files set by + curl_easy_setopt(HSTS) calls */ curl_off_t current_speed; /* the ProgressShow() function sets this, bytes / second */ - /* host name, port number and protocol of the first (not followed) request. - if set, this should be the host name that we will sent authorization to, + /* hostname, port number and protocol of the first (not followed) request. + if set, this should be the hostname that we will sent authorization to, no else. Used to make Location: following not keep sending user+password. This is strdup()ed data. */ char *first_host; @@ -1339,10 +1234,7 @@ struct UrlState { int retrycount; /* number of retries on a new connection */ struct Curl_ssl_session *session; /* array of 'max_ssl_sessions' size */ long sessionage; /* number of the most recent session */ - struct tempbuf tempwrite[3]; /* BOTH, HEADER, BODY */ - unsigned int tempcount; /* number of entries in use in tempwrite, 0 - 3 */ int os_errno; /* filled in with errno whenever an error occurs */ - char *scratch; /* huge buffer[set.buffer_size*2] for upload CRLF replacing */ long followlocation; /* redirect counter */ int requests; /* request counter: redirects + authentication retakes */ #ifdef HAVE_SIGNAL @@ -1370,14 +1262,6 @@ struct UrlState { /* a place to store the most recently set (S)FTP entrypath */ char *most_recent_ftp_entrypath; -#if !defined(WIN32) && !defined(MSDOS) && !defined(__EMX__) -/* do FTP line-end conversions on most platforms */ -#define CURL_DO_LINEEND_CONV - /* for FTP downloads: track CRLF sequences that span blocks */ - BIT(prev_block_had_trailing_cr); - /* for FTP downloads: how many CRLFs did we converted to LFs? */ - curl_off_t crlf_conversions; -#endif char *range; /* range, if used. See README for detailed specification on this syntax. */ curl_off_t resume_from; /* continue [ftp] transfer from here */ @@ -1408,8 +1292,10 @@ struct UrlState { this should be dealt with in pretransfer */ #ifndef CURL_DISABLE_HTTP curl_mimepart *mimepost; - curl_mimepart *formp; /* storage for old API form-posting, alloced on +#ifndef CURL_DISABLE_FORM_API + curl_mimepart *formp; /* storage for old API form-posting, allocated on demand */ +#endif size_t trailers_bytes_sent; struct dynbuf trailers_buf; /* a buffer containing the compiled trailing headers */ @@ -1419,44 +1305,61 @@ struct UrlState { trailers_state trailers_state; /* whether we are sending trailers and what stage are we at */ #endif +#ifndef CURL_DISABLE_COOKIES + struct curl_slist *cookielist; /* list of cookie files set by + curl_easy_setopt(COOKIEFILE) calls */ +#endif #ifdef USE_HYPER bool hconnect; /* set if a CONNECT request */ CURLcode hresult; /* used to pass return codes back from hyper callbacks */ #endif +#ifndef CURL_DISABLE_VERBOSE_STRINGS + struct curl_trc_feat *feat; /* opt. trace feature transfer is part of */ +#endif + +#ifndef CURL_DISABLE_NETRC + struct store_netrc netrc; +#endif + /* Dynamically allocated strings, MUST be freed before this struct is killed. */ struct dynamically_allocated_data { - char *proxyuserpwd; char *uagent; char *accept_encoding; char *userpwd; char *rangeline; char *ref; char *host; +#ifndef CURL_DISABLE_COOKIES char *cookiehost; +#endif +#ifndef CURL_DISABLE_RTSP char *rtsp_transport; +#endif char *te; /* TE: request header */ /* transfer credentials */ char *user; char *passwd; +#ifndef CURL_DISABLE_PROXY + char *proxyuserpwd; char *proxyuser; char *proxypasswd; +#endif } aptr; - unsigned char httpwant; /* when non-zero, a specific HTTP version requested to be used in the library's request(s) */ unsigned char httpversion; /* the lowest HTTP version*10 reported by any server involved in this request */ unsigned char httpreq; /* Curl_HttpReq; what kind of HTTP request (if any) is this */ - unsigned char dselect_bits; /* != 0 -> bitmask of socket events for this + unsigned char select_bits; /* != 0 -> bitmask of socket events for this transfer overriding anything the socket may report */ -#ifdef CURLDEBUG - BIT(conncache_lock); -#endif + unsigned int creds_from:2; /* where is the server credentials originating + from, see the CREDS_* defines above */ + /* when curl_easy_perform() is called, the multi handle is "owned" by the easy handle so curl_easy_cleanup() on such an easy handle will also close the multi handle! */ @@ -1469,10 +1372,9 @@ struct UrlState { called. */ BIT(allow_port); /* Is set.use_port allowed to take effect or not. This is always set TRUE when curl_easy_perform() is called. */ - BIT(authproblem); /* TRUE if there's some problem authenticating */ + BIT(authproblem); /* TRUE if there is some problem authenticating */ /* set after initial USER failure, to prevent an authentication loop */ BIT(wildcardmatch); /* enable wildcard matching */ - BIT(expect100header); /* TRUE if we added Expect: 100-continue */ BIT(disableexpect); /* TRUE if Expect: is disabled due to a previous 417 response */ BIT(use_range); @@ -1480,7 +1382,6 @@ struct UrlState { BIT(done); /* set to FALSE when Curl_init_do() is called and set to TRUE when multi_done() is called, to prevent multi_done() to get invoked twice when the multi interface is used. */ - BIT(previouslypending); /* this transfer WAS in the multi->pending queue */ #ifndef CURL_DISABLE_COOKIES BIT(cookie_engine); #endif @@ -1491,10 +1392,10 @@ struct UrlState { BIT(url_alloc); /* URL string is malloc()'ed */ BIT(referer_alloc); /* referer string is malloc()ed */ BIT(wildcard_resolve); /* Set to true if any resolve change is a wildcard */ - BIT(rewindbeforesend);/* TRUE when the sending couldn't be stopped even - though it will be discarded. We must call the data - rewind callback before trying to send again. */ BIT(upload); /* upload request */ + BIT(internal); /* internal: true if this easy handle was created for + internal use and the user does not have ownership of the + handle. */ }; /* @@ -1508,95 +1409,129 @@ struct UrlState { struct Curl_multi; /* declared in multihandle.c */ -/* - * This enumeration MUST not use conditional directives (#ifdefs), new - * null terminated strings MUST be added to the enumeration immediately - * before STRING_LASTZEROTERMINATED, binary fields immediately before - * STRING_LAST. When doing so, ensure that the packages/OS400/chkstring.c - * test is updated and applicable changes for EBCDIC to ASCII conversion - * are catered for in curl_easy_setopt_ccsid() - */ enum dupstring { - STRING_CERT, /* client certificate file name */ - STRING_CERT_PROXY, /* client certificate file name */ + STRING_CERT, /* client certificate filename */ STRING_CERT_TYPE, /* format for certificate (default: PEM)*/ + STRING_KEY, /* private key filename */ + STRING_KEY_PASSWD, /* plain text private key password */ + STRING_KEY_TYPE, /* format for private key (default: PEM) */ + STRING_SSL_CAPATH, /* CA directory name (does not work on Windows) */ + STRING_SSL_CAFILE, /* certificate file to verify peer against */ + STRING_SSL_PINNEDPUBLICKEY, /* public key file to verify peer against */ + STRING_SSL_CIPHER_LIST, /* list of ciphers to use */ + STRING_SSL_CIPHER13_LIST, /* list of TLS 1.3 ciphers to use */ + STRING_SSL_CRLFILE, /* crl file to check certificate */ + STRING_SSL_ISSUERCERT, /* issuer cert file to check certificate */ + STRING_SERVICE_NAME, /* Service name */ +#ifndef CURL_DISABLE_PROXY + STRING_CERT_PROXY, /* client certificate filename */ STRING_CERT_TYPE_PROXY, /* format for certificate (default: PEM)*/ + STRING_KEY_PROXY, /* private key filename */ + STRING_KEY_PASSWD_PROXY, /* plain text private key password */ + STRING_KEY_TYPE_PROXY, /* format for private key (default: PEM) */ + STRING_SSL_CAPATH_PROXY, /* CA directory name (does not work on Windows) */ + STRING_SSL_CAFILE_PROXY, /* certificate file to verify peer against */ + STRING_SSL_PINNEDPUBLICKEY_PROXY, /* public key file to verify proxy */ + STRING_SSL_CIPHER_LIST_PROXY, /* list of ciphers to use */ + STRING_SSL_CIPHER13_LIST_PROXY, /* list of TLS 1.3 ciphers to use */ + STRING_SSL_CRLFILE_PROXY, /* crl file to check certificate */ + STRING_SSL_ISSUERCERT_PROXY, /* issuer cert file to check certificate */ + STRING_PROXY_SERVICE_NAME, /* Proxy service name */ +#endif +#ifndef CURL_DISABLE_COOKIES STRING_COOKIE, /* HTTP cookie string to send */ STRING_COOKIEJAR, /* dump all cookies to this file */ +#endif STRING_CUSTOMREQUEST, /* HTTP/FTP/RTSP request/method to use */ - STRING_DEFAULT_PROTOCOL, /* Protocol to use when the URL doesn't specify */ + STRING_DEFAULT_PROTOCOL, /* Protocol to use when the URL does not specify */ STRING_DEVICE, /* local network interface/address to use */ + STRING_INTERFACE, /* local network interface to use */ + STRING_BINDHOST, /* local address to use */ STRING_ENCODING, /* Accept-Encoding string */ +#ifndef CURL_DISABLE_FTP STRING_FTP_ACCOUNT, /* ftp account data */ STRING_FTP_ALTERNATIVE_TO_USER, /* command to send if USER/PASS fails */ STRING_FTPPORT, /* port to send with the FTP PORT command */ - STRING_KEY, /* private key file name */ - STRING_KEY_PROXY, /* private key file name */ - STRING_KEY_PASSWD, /* plain text private key password */ - STRING_KEY_PASSWD_PROXY, /* plain text private key password */ - STRING_KEY_TYPE, /* format for private key (default: PEM) */ - STRING_KEY_TYPE_PROXY, /* format for private key (default: PEM) */ +#endif +#if defined(HAVE_GSSAPI) STRING_KRB_LEVEL, /* krb security level */ +#endif +#ifndef CURL_DISABLE_NETRC STRING_NETRC_FILE, /* if not NULL, use this instead of trying to find $HOME/.netrc */ +#endif +#ifndef CURL_DISABLE_PROXY STRING_PROXY, /* proxy to use */ STRING_PRE_PROXY, /* pre socks proxy to use */ +#endif STRING_SET_RANGE, /* range, if used */ STRING_SET_REFERER, /* custom string for the HTTP referer field */ STRING_SET_URL, /* what original URL to work on */ - STRING_SSL_CAPATH, /* CA directory name (doesn't work on windows) */ - STRING_SSL_CAPATH_PROXY, /* CA directory name (doesn't work on windows) */ - STRING_SSL_CAFILE, /* certificate file to verify peer against */ - STRING_SSL_CAFILE_PROXY, /* certificate file to verify peer against */ - STRING_SSL_PINNEDPUBLICKEY, /* public key file to verify peer against */ - STRING_SSL_PINNEDPUBLICKEY_PROXY, /* public key file to verify proxy */ - STRING_SSL_CIPHER_LIST, /* list of ciphers to use */ - STRING_SSL_CIPHER_LIST_PROXY, /* list of ciphers to use */ - STRING_SSL_CIPHER13_LIST, /* list of TLS 1.3 ciphers to use */ - STRING_SSL_CIPHER13_LIST_PROXY, /* list of TLS 1.3 ciphers to use */ STRING_USERAGENT, /* User-Agent string */ - STRING_SSL_CRLFILE, /* crl file to check certificate */ - STRING_SSL_CRLFILE_PROXY, /* crl file to check certificate */ - STRING_SSL_ISSUERCERT, /* issuer cert file to check certificate */ - STRING_SSL_ISSUERCERT_PROXY, /* issuer cert file to check certificate */ STRING_SSL_ENGINE, /* name of ssl engine */ STRING_USERNAME, /* , if used */ STRING_PASSWORD, /* , if used */ STRING_OPTIONS, /* , if used */ +#ifndef CURL_DISABLE_PROXY STRING_PROXYUSERNAME, /* Proxy , if used */ STRING_PROXYPASSWORD, /* Proxy , if used */ STRING_NOPROXY, /* List of hosts which should not use the proxy, if used */ +#endif +#ifndef CURL_DISABLE_RTSP STRING_RTSP_SESSION_ID, /* Session ID to use */ STRING_RTSP_STREAM_URI, /* Stream URI for this request */ STRING_RTSP_TRANSPORT, /* Transport for this session */ +#endif +#ifdef USE_SSH STRING_SSH_PRIVATE_KEY, /* path to the private key file for auth */ STRING_SSH_PUBLIC_KEY, /* path to the public key file for auth */ - STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */ + STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ASCII hex */ STRING_SSH_HOST_PUBLIC_KEY_SHA256, /* sha256 of host public key in base64 */ - STRING_SSH_KNOWNHOSTS, /* file name of knownhosts file */ - STRING_PROXY_SERVICE_NAME, /* Proxy service name */ - STRING_SERVICE_NAME, /* Service name */ + STRING_SSH_KNOWNHOSTS, /* filename of knownhosts file */ +#endif +#ifndef CURL_DISABLE_SMTP STRING_MAIL_FROM, STRING_MAIL_AUTH, +#endif +#ifdef USE_TLS_SRP STRING_TLSAUTH_USERNAME, /* TLS auth */ - STRING_TLSAUTH_USERNAME_PROXY, /* TLS auth */ STRING_TLSAUTH_PASSWORD, /* TLS auth */ +#ifndef CURL_DISABLE_PROXY + STRING_TLSAUTH_USERNAME_PROXY, /* TLS auth */ STRING_TLSAUTH_PASSWORD_PROXY, /* TLS auth */ +#endif +#endif STRING_BEARER, /* , if used */ +#ifdef USE_UNIX_SOCKETS STRING_UNIX_SOCKET_PATH, /* path to Unix socket, if used */ +#endif STRING_TARGET, /* CURLOPT_REQUEST_TARGET */ +#ifndef CURL_DISABLE_DOH STRING_DOH, /* CURLOPT_DOH_URL */ +#endif +#ifndef CURL_DISABLE_ALTSVC STRING_ALTSVC, /* CURLOPT_ALTSVC */ +#endif +#ifndef CURL_DISABLE_HSTS STRING_HSTS, /* CURLOPT_HSTS */ +#endif STRING_SASL_AUTHZID, /* CURLOPT_SASL_AUTHZID */ +#ifdef USE_ARES STRING_DNS_SERVERS, STRING_DNS_INTERFACE, STRING_DNS_LOCAL_IP4, STRING_DNS_LOCAL_IP6, +#endif STRING_SSL_EC_CURVES, +#ifndef CURL_DISABLE_AWS STRING_AWS_SIGV4, /* Parameters for V4 signature */ +#endif +#ifndef CURL_DISABLE_PROXY STRING_HAPROXY_CLIENT_IP, /* CURLOPT_HAPROXY_CLIENT_IP */ +#endif + STRING_ECH_CONFIG, /* CURLOPT_ECH_CONFIG */ + STRING_ECH_PUBLIC, /* CURLOPT_ECH_PUBLIC */ /* -- end of null-terminated strings -- */ @@ -1611,18 +1546,20 @@ enum dupstring { enum dupblob { BLOB_CERT, - BLOB_CERT_PROXY, BLOB_KEY, - BLOB_KEY_PROXY, BLOB_SSL_ISSUERCERT, - BLOB_SSL_ISSUERCERT_PROXY, BLOB_CAINFO, +#ifndef CURL_DISABLE_PROXY + BLOB_CERT_PROXY, + BLOB_KEY_PROXY, + BLOB_SSL_ISSUERCERT_PROXY, BLOB_CAINFO_PROXY, +#endif BLOB_LAST }; /* callback that gets called when this easy handle is completed within a multi - handle. Only used for internally created transfers, like for example + handle. Only used for internally created transfers, like for example DoH. */ typedef int (*multidone_func)(struct Curl_easy *easy, CURLcode result); @@ -1647,7 +1584,7 @@ struct UserDefined { #ifndef CURL_DISABLE_BINDLOCAL unsigned short localport; /* local port number to bind to */ unsigned short localportrange; /* number of additional port numbers to test - in case the 'localport' one can't be + in case the 'localport' one cannot be bind()ed */ #endif curl_write_callback fwrite_func; /* function that stores the output */ @@ -1671,13 +1608,7 @@ struct UserDefined { void *prereq_userp; /* pre-initial request user data */ void *seek_client; /* pointer to pass to the seek callback */ -#ifndef CURL_DISABLE_COOKIES - struct curl_slist *cookielist; /* list of cookie files set by - curl_easy_setopt(COOKIEFILE) calls */ -#endif #ifndef CURL_DISABLE_HSTS - struct curl_slist *hstslist; /* list of HSTS files set by - curl_easy_setopt(HSTS) calls */ curl_hstsread_callback hsts_read; void *hsts_read_userp; curl_hstswrite_callback hsts_write; @@ -1686,9 +1617,10 @@ struct UserDefined { void *progress_client; /* pointer to pass to the progress callback */ void *ioctl_client; /* pointer to pass to the ioctl callback */ unsigned int timeout; /* ms, 0 means no timeout */ - unsigned int connecttimeout; /* ms, 0 means no timeout */ + unsigned int connecttimeout; /* ms, 0 means default timeout */ unsigned int happy_eyeballs_timeout; /* ms, 0 is a valid value */ unsigned int server_response_timeout; /* ms, 0 means no timeout */ + unsigned int shutdowntimeout; /* ms, 0 means default timeout */ long maxage_conn; /* in seconds, max idle time to allow a connection that is to be reused */ long maxlifetime_conn; /* in seconds, max time since creation to allow a @@ -1705,7 +1637,9 @@ struct UserDefined { curl_off_t set_resume_from; /* continue [ftp] transfer from here */ struct curl_slist *headers; /* linked list of extra headers */ struct curl_httppost *httppost; /* linked list of old POST data */ +#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) curl_mimepart mimepost; /* MIME/POST data. */ +#endif #ifndef CURL_DISABLE_TELNET struct curl_slist *telnet_options; /* linked list of telnet options */ #endif @@ -1751,7 +1685,7 @@ struct UserDefined { struct curl_slist *postquote; /* after the transfer */ struct curl_slist *prequote; /* before the transfer, after type */ /* Despite the name, ftp_create_missing_dirs is for FTP(S) and SFTP - 1 - create directories that don't exist + 1 - create directories that do not exist 2 - the same but also allow MKD to fail once */ unsigned char ftp_create_missing_dirs; @@ -1772,14 +1706,11 @@ struct UserDefined { unsigned int new_file_perms; /* when creating remote files */ char *str[STRING_LAST]; /* array of strings, pointing to allocated memory */ struct curl_blob *blobs[BLOB_LAST]; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 unsigned int scope_id; /* Scope id for IPv6 */ #endif curl_prot_t allowed_protocols; curl_prot_t redir_protocols; -#ifndef CURL_DISABLE_MIME - unsigned int mime_options; /* Mime option flags. */ -#endif #ifndef CURL_DISABLE_RTSP void *rtp_out; /* write RTP to this if non-NULL */ /* Common RTSP header options */ @@ -1801,8 +1732,7 @@ struct UserDefined { int tcp_keepidle; /* seconds in idle before sending keepalive probe */ int tcp_keepintvl; /* seconds between TCP keepalive probes */ - - size_t maxconnects; /* Max idle connections in the connection cache */ + int tcp_keepcnt; /* maximum number of keepalive probes */ long expect_100_timeout; /* in milliseconds */ #if defined(USE_HTTP2) || defined(USE_HTTP3) @@ -1814,7 +1744,7 @@ struct UserDefined { long upkeep_interval_ms; /* Time between calls for connection upkeep. */ multidone_func fmultidone; #ifndef CURL_DISABLE_DOH - struct Curl_easy *dohfor; /* this is a DoH request for that transfer */ + curl_off_t dohfor_mid; /* this is a DoH request for that transfer */ #endif CURLU *uh; /* URL handle for the current parsed URL */ #ifndef CURL_DISABLE_HTTP @@ -1828,10 +1758,14 @@ struct UserDefined { BIT(mail_rcpt_allowfails); /* allow RCPT TO command to fail for some recipients */ #endif + unsigned int maxconnects; /* Max idle connections in the connection cache */ unsigned char use_ssl; /* if AUTH TLS is to be attempted etc, for FTP or IMAP or POP3 or others! (type: curl_usessl)*/ unsigned char connect_only; /* make connection/request, then let application use the socket */ +#ifndef CURL_DISABLE_MIME + BIT(mime_formescape); +#endif BIT(is_fread_set); /* has read callback been set to non-NULL? */ #ifndef CURL_DISABLE_TFTP BIT(tftp_no_options); /* do not send TFTP options requests */ @@ -1841,17 +1775,21 @@ struct UserDefined { BIT(cookiesession); /* new cookie session? */ #endif BIT(crlf); /* convert crlf on ftp upload(?) */ +#ifdef USE_SSH BIT(ssh_compression); /* enable SSH compression */ +#endif /* Here follows boolean settings that define how to behave during this session. They are STATIC, set by libcurl users or at least initially - and they don't change during operations. */ + and they do not change during operations. */ BIT(quick_exit); /* set 1L when it is okay to leak things (like - threads), as we're about to exit() anyway and - don't want lengthy cleanups to delay termination, + threads), as we are about to exit() anyway and + do not want lengthy cleanups to delay termination, e.g. after a DNS timeout */ BIT(get_filetime); /* get the time and get of the remote file */ +#ifndef CURL_DISABLE_PROXY BIT(tunnel_thru_httpproxy); /* use CONNECT through an HTTP proxy */ +#endif BIT(prefer_ascii); /* ASCII rather than binary */ BIT(remote_append); /* append, not overwrite, on upload */ #ifdef CURL_LIST_ONLY_PROTOCOL @@ -1866,7 +1804,7 @@ struct UserDefined { us */ BIT(wildcard_enabled); /* enable wildcard matching */ #endif - BIT(hide_progress); /* don't use the progress meter */ + BIT(hide_progress); /* do not use the progress meter */ BIT(http_fail_on_error); /* fail on HTTP error codes >= 400 */ BIT(http_keep_sending_on_error); /* for HTTP status codes >= 300 */ BIT(http_follow_location); /* follow HTTP redirects */ @@ -1878,7 +1816,9 @@ struct UserDefined { location: */ BIT(opt_no_body); /* as set with CURLOPT_NOBODY */ BIT(verbose); /* output verbosity */ +#if defined(HAVE_GSSAPI) BIT(krb); /* Kerberos connection requested */ +#endif BIT(reuse_forbid); /* forbidden to be reused, close after use */ BIT(reuse_fresh); /* do not reuse an existing connection */ BIT(no_signal); /* do not use any signal/alarm handler */ @@ -1903,10 +1843,14 @@ struct UserDefined { BIT(suppress_connect_headers); /* suppress proxy CONNECT response headers from user callbacks */ BIT(dns_shuffle_addresses); /* whether to shuffle addresses before use */ +#ifndef CURL_DISABLE_PROXY BIT(haproxyprotocol); /* whether to send HAProxy PROXY protocol v1 header */ +#endif +#ifdef USE_UNIX_SOCKETS BIT(abstract_unix_socket); - BIT(disallow_username_in_url); /* disallow username in url */ +#endif + BIT(disallow_username_in_url); /* disallow username in URL */ #ifndef CURL_DISABLE_DOH BIT(doh); /* DNS-over-HTTPS enabled */ BIT(doh_verifypeer); /* DoH certificate peer verification */ @@ -1914,11 +1858,20 @@ struct UserDefined { BIT(doh_verifystatus); /* DoH certificate status verification */ #endif BIT(http09_allowed); /* allow HTTP/0.9 responses */ -#ifdef USE_WEBSOCKETS +#ifndef CURL_DISABLE_WEBSOCKETS BIT(ws_raw_mode); #endif +#ifdef USE_ECH + int tls_ech; /* TLS ECH configuration */ +#endif }; +#ifndef CURL_DISABLE_MIME +#define IS_MIME_POST(a) ((a)->set.mimepost.kind != MIMEKIND_NONE) +#else +#define IS_MIME_POST(a) FALSE +#endif + struct Names { struct Curl_hash *hostcache; enum { @@ -1942,22 +1895,21 @@ struct Curl_easy { /* First a simple identifier to easier detect if a user mix up this easy handle with a multi handle. Set this to CURLEASY_MAGIC_NUMBER */ unsigned int magic; - /* once an easy handle is tied to a connection cache - a non-negative number to distinguish this transfer from - other using the same cache. For easier tracking - in log output. - This may wrap around after LONG_MAX to 0 again, so it - has no uniqueness guarantuee for very large processings. */ + /* once an easy handle is tied to a connection pool a non-negative number to + distinguish this transfer from other using the same pool. For easier + tracking in log output. This may wrap around after LONG_MAX to 0 again, + so it has no uniqueness guarantee for large processings. Note: it has no + uniqueness either IFF more than one connection pool is used by the + libcurl application. */ curl_off_t id; - - /* first, two fields for the linked list of these */ - struct Curl_easy *next; - struct Curl_easy *prev; + /* once an easy handle is added to a multi, either explicitly by the + * libcurl application or implicitly during `curl_easy_perform()`, + * a unique identifier inside this one multi instance. */ + curl_off_t mid; struct connectdata *conn; - struct Curl_llist_element connect_queue; /* for the pending and msgsent - lists */ - struct Curl_llist_element conn_queue; /* list per connectdata */ + struct Curl_llist_node multi_queue; /* for multihandle list management */ + struct Curl_llist_node conn_queue; /* list per connectdata */ CURLMstate mstate; /* the handle's state */ CURLcode result; /* previous result */ @@ -1968,10 +1920,7 @@ struct Curl_easy { particular order. Note that all sockets are added to the sockhash, where the state etc are also kept. This array is mostly used to detect when a socket is to be removed from the hash. See singlesocket(). */ - curl_socket_t sockets[MAX_SOCKSPEREASYHANDLE]; - unsigned char actions[MAX_SOCKSPEREASYHANDLE]; /* action for each socket in - sockets[] */ - int numsocks; + struct easy_pollset last_poll; struct Names dns; struct Curl_multi *multi; /* if non-NULL, points to the multi handle diff --git a/deps/curl/lib/vauth/.checksrc b/deps/curl/lib/vauth/.checksrc new file mode 100644 index 00000000000..9066946c890 --- /dev/null +++ b/deps/curl/lib/vauth/.checksrc @@ -0,0 +1,2 @@ +enable STRERROR +enable STRNCPY diff --git a/deps/curl/lib/vauth/cleartext.c b/deps/curl/lib/vauth/cleartext.c index c651fc51453..cf8108ac5b6 100644 --- a/deps/curl/lib/vauth/cleartext.c +++ b/deps/curl/lib/vauth/cleartext.c @@ -35,7 +35,6 @@ #include "urldata.h" #include "vauth/vauth.h" -#include "curl_md5.h" #include "warnless.h" #include "strtok.h" #include "sendf.h" @@ -101,39 +100,38 @@ CURLcode Curl_auth_create_plain_message(const char *authzid, * Curl_auth_create_login_message() * * This is used to generate an already encoded LOGIN message containing the - * user name or password ready for sending to the recipient. + * username or password ready for sending to the recipient. * * Parameters: * - * valuep [in] - The user name or user's password. + * valuep [in] - The username or user's password. * out [out] - The result storage. * - * Returns CURLE_OK on success. + * Returns void. */ -CURLcode Curl_auth_create_login_message(const char *valuep, struct bufref *out) +void Curl_auth_create_login_message(const char *valuep, struct bufref *out) { Curl_bufref_set(out, valuep, strlen(valuep), NULL); - return CURLE_OK; } /* * Curl_auth_create_external_message() * * This is used to generate an already encoded EXTERNAL message containing - * the user name ready for sending to the recipient. + * the username ready for sending to the recipient. * * Parameters: * - * user [in] - The user name. + * user [in] - The username. * out [out] - The result storage. * - * Returns CURLE_OK on success. + * Returns void. */ -CURLcode Curl_auth_create_external_message(const char *user, +void Curl_auth_create_external_message(const char *user, struct bufref *out) { /* This is the same formatting as the login message */ - return Curl_auth_create_login_message(user, out); + Curl_auth_create_login_message(user, out); } #endif /* if no users */ diff --git a/deps/curl/lib/vauth/cram.c b/deps/curl/lib/vauth/cram.c index 91fb261c57a..c51c7285b47 100644 --- a/deps/curl/lib/vauth/cram.c +++ b/deps/curl/lib/vauth/cram.c @@ -51,7 +51,7 @@ * Parameters: * * chlg [in] - The challenge. - * userp [in] - The user name. + * userp [in] - The username. * passwdp [in] - The user's password. * out [out] - The result storage. * @@ -67,7 +67,7 @@ CURLcode Curl_auth_create_cram_md5_message(const struct bufref *chlg, char *response; /* Compute the digest using the password as the key */ - ctxt = Curl_HMAC_init(Curl_HMAC_MD5, + ctxt = Curl_HMAC_init(&Curl_HMAC_MD5, (const unsigned char *) passwdp, curlx_uztoui(strlen(passwdp))); if(!ctxt) diff --git a/deps/curl/lib/vauth/digest.c b/deps/curl/lib/vauth/digest.c index 12c6f7dd5be..cd3fca19ae1 100644 --- a/deps/curl/lib/vauth/digest.c +++ b/deps/curl/lib/vauth/digest.c @@ -38,6 +38,7 @@ #include "curl_hmac.h" #include "curl_md5.h" #include "curl_sha256.h" +#include "curl_sha512_256.h" #include "vtls/vtls.h" #include "warnless.h" #include "strtok.h" @@ -102,7 +103,7 @@ bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, case ',': if(!starts_with_quote) { - /* This signals the end of the content if we didn't get a starting + /* This signals the end of the content if we did not get a starting quote and then we do "sloppy" parsing */ c = 0; /* the end */ continue; @@ -125,7 +126,6 @@ bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, } else return FALSE; - break; } } @@ -142,7 +142,7 @@ bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, } #if !defined(USE_WINDOWS_SSPI) -/* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string */ +/* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ASCII string */ static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */ unsigned char *dest) /* 33 bytes */ { @@ -151,7 +151,7 @@ static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */ msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]); } -/* Convert sha256 chunk to RFC7616 -suitable ascii string */ +/* Convert sha256 or SHA-512/256 chunk to RFC7616 -suitable ASCII string */ static void auth_digest_sha256_to_ascii(unsigned char *source, /* 32 bytes */ unsigned char *dest) /* 65 bytes */ { @@ -288,7 +288,7 @@ static CURLcode auth_decode_digest_md5_message(const struct bufref *chlgref, /* Retrieve realm string from the challenge */ if(!auth_digest_get_key_value(chlg, "realm=\"", realm, rlen, '\"')) { /* Challenge does not have a realm, set empty string [RFC2831] page 6 */ - strcpy(realm, ""); + *realm = '\0'; } /* Retrieve algorithm string from the challenge */ @@ -326,7 +326,7 @@ bool Curl_auth_is_digest_supported(void) * * data [in] - The session handle. * chlg [in] - The challenge message. - * userp [in] - The user name. + * userp [in] - The username. * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. * out [out] - The result storage. @@ -388,7 +388,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, return result; /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */ - ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); if(!ctxt) return CURLE_OUT_OF_MEMORY; @@ -402,7 +402,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, curlx_uztoui(strlen(passwdp))); Curl_MD5_final(ctxt, digest); - ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); if(!ctxt) return CURLE_OUT_OF_MEMORY; @@ -425,7 +425,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; /* Calculate H(A2) */ - ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); if(!ctxt) { free(spn); @@ -443,7 +443,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, msnprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]); /* Now calculate the response hash */ - ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + ctxt = Curl_MD5_init(&Curl_DIGEST_MD5); if(!ctxt) { free(spn); @@ -602,10 +602,20 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, digest->algo = ALGO_SHA256; else if(strcasecompare(content, "SHA-256-SESS")) digest->algo = ALGO_SHA256SESS; - else if(strcasecompare(content, "SHA-512-256")) + else if(strcasecompare(content, "SHA-512-256")) { +#ifdef CURL_HAVE_SHA512_256 digest->algo = ALGO_SHA512_256; - else if(strcasecompare(content, "SHA-512-256-SESS")) +#else /* ! CURL_HAVE_SHA512_256 */ + return CURLE_NOT_BUILT_IN; +#endif /* ! CURL_HAVE_SHA512_256 */ + } + else if(strcasecompare(content, "SHA-512-256-SESS")) { +#ifdef CURL_HAVE_SHA512_256 digest->algo = ALGO_SHA512_256SESS; +#else /* ! CURL_HAVE_SHA512_256 */ + return CURLE_NOT_BUILT_IN; +#endif /* ! CURL_HAVE_SHA512_256 */ + } else return CURLE_BAD_CONTENT_ENCODING; } @@ -619,7 +629,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, } } else - break; /* We're done here */ + break; /* We are done here */ /* Pass all additional spaces here */ while(*chlg && ISBLANK(*chlg)) @@ -636,7 +646,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, if(before && !digest->stale) return CURLE_BAD_CONTENT_ENCODING; - /* We got this header without a nonce, that's a bad Digest line! */ + /* We got this header without a nonce, that is a bad Digest line! */ if(!digest->nonce) return CURLE_BAD_CONTENT_ENCODING; @@ -656,7 +666,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, * Parameters: * * data [in] - The session handle. - * userp [in] - The user name. + * userp [in] - The username. * passwdp [in] - The user's password. * request [in] - The HTTP request. * uripath [in] - The path of the HTTP uri. @@ -718,8 +728,10 @@ static CURLcode auth_create_digest_http_message( if(!hashthis) return CURLE_OUT_OF_MEMORY; - hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); free(hashthis); + if(result) + return result; convert_to_ascii(hashbuf, (unsigned char *)userh); } @@ -739,8 +751,10 @@ static CURLcode auth_create_digest_http_message( if(!hashthis) return CURLE_OUT_OF_MEMORY; - hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); free(hashthis); + if(result) + return result; convert_to_ascii(hashbuf, ha1); if(digest->algo & SESSION_ALGO) { @@ -749,8 +763,10 @@ static CURLcode auth_create_digest_http_message( if(!tmp) return CURLE_OUT_OF_MEMORY; - hash(hashbuf, (unsigned char *) tmp, strlen(tmp)); + result = hash(hashbuf, (unsigned char *) tmp, strlen(tmp)); free(tmp); + if(result) + return result; convert_to_ascii(hashbuf, ha1); } @@ -772,11 +788,15 @@ static CURLcode auth_create_digest_http_message( return CURLE_OUT_OF_MEMORY; if(digest->qop && strcasecompare(digest->qop, "auth-int")) { - /* We don't support auth-int for PUT or POST */ + /* We do not support auth-int for PUT or POST */ char hashed[65]; char *hashthis2; - hash(hashbuf, (const unsigned char *)"", 0); + result = hash(hashbuf, (const unsigned char *)"", 0); + if(result) { + free(hashthis); + return result; + } convert_to_ascii(hashbuf, (unsigned char *)hashed); hashthis2 = aprintf("%s:%s", hashthis, hashed); @@ -787,8 +807,10 @@ static CURLcode auth_create_digest_http_message( if(!hashthis) return CURLE_OUT_OF_MEMORY; - hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); free(hashthis); + if(result) + return result; convert_to_ascii(hashbuf, ha2); if(digest->qop) { @@ -802,8 +824,10 @@ static CURLcode auth_create_digest_http_message( if(!hashthis) return CURLE_OUT_OF_MEMORY; - hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); free(hashthis); + if(result) + return result; convert_to_ascii(hashbuf, request_digest); /* For test case 64 (snooped from a Mozilla 1.3a request) @@ -811,12 +835,12 @@ static CURLcode auth_create_digest_http_message( Authorization: Digest username="testuser", realm="testrealm", \ nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca" - Digest parameters are all quoted strings. Username which is provided by + Digest parameters are all quoted strings. Username which is provided by the user will need double quotes and backslashes within it escaped. realm, nonce, and opaque will need backslashes as well as they were - de-escaped when copied from request header. cnonce is generated with - web-safe characters. uri is already percent encoded. nc is 8 hex - characters. algorithm and qop with standard values only contain web-safe + de-escaped when copied from request header. cnonce is generated with + web-safe characters. uri is already percent encoded. nc is 8 hex + characters. algorithm and qop with standard values only contain web-safe characters. */ userp_quoted = auth_digest_string_quoted(digest->userhash ? userh : userp); @@ -933,7 +957,7 @@ static CURLcode auth_create_digest_http_message( * Parameters: * * data [in] - The session handle. - * userp [in] - The user name. + * userp [in] - The username. * passwdp [in] - The user's password. * request [in] - The HTTP request. * uripath [in] - The path of the HTTP uri. @@ -958,12 +982,24 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, outptr, outlen, auth_digest_md5_to_ascii, Curl_md5it); - DEBUGASSERT(digest->algo <= ALGO_SHA512_256SESS); - return auth_create_digest_http_message(data, userp, passwdp, - request, uripath, digest, - outptr, outlen, - auth_digest_sha256_to_ascii, - Curl_sha256it); + + if(digest->algo <= ALGO_SHA256SESS) + return auth_create_digest_http_message(data, userp, passwdp, + request, uripath, digest, + outptr, outlen, + auth_digest_sha256_to_ascii, + Curl_sha256it); +#ifdef CURL_HAVE_SHA512_256 + if(digest->algo <= ALGO_SHA512_256SESS) + return auth_create_digest_http_message(data, userp, passwdp, + request, uripath, digest, + outptr, outlen, + auth_digest_sha256_to_ascii, + Curl_sha512_256it); +#endif /* CURL_HAVE_SHA512_256 */ + + /* Should be unreachable */ + return CURLE_BAD_CONTENT_ENCODING; } /* diff --git a/deps/curl/lib/vauth/digest_sspi.c b/deps/curl/lib/vauth/digest_sspi.c index 02e36ea5ed8..2ae6fb30c93 100644 --- a/deps/curl/lib/vauth/digest_sspi.c +++ b/deps/curl/lib/vauth/digest_sspi.c @@ -60,15 +60,16 @@ bool Curl_auth_is_digest_supported(void) SECURITY_STATUS status; /* Query the security package for Digest */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), - &SecurityPackage); + status = + Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), + &SecurityPackage); /* Release the package buffer as it is not required anymore */ if(status == SEC_E_OK) { - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); } - return (status == SEC_E_OK ? TRUE : FALSE); + return (status == SEC_E_OK); } /* @@ -81,7 +82,7 @@ bool Curl_auth_is_digest_supported(void) * * data [in] - The session handle. * chlg [in] - The challenge message. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. * out [out] - The result storage. @@ -119,17 +120,18 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, } /* Query the security package for DigestSSP */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), - &SecurityPackage); + status = + Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), + &SecurityPackage); if(status != SEC_E_OK) { - failf(data, "SSPI: couldn't get auth info"); + failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; } token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our response buffer */ output_token = malloc(token_max); @@ -160,7 +162,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, p_identity = NULL; /* Acquire our credentials handle */ - status = s_pSecFn->AcquireCredentialsHandle(NULL, + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_DIGEST), SECPKG_CRED_OUTBOUND, NULL, p_identity, NULL, NULL, @@ -190,20 +192,20 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, resp_buf.cbBuffer = curlx_uztoul(token_max); /* Generate our response message */ - status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, spn, + status = Curl_pSecFn->InitializeSecurityContext(&credentials, NULL, spn, 0, 0, 0, &chlg_desc, 0, &context, &resp_desc, &attrs, &expiry); if(status == SEC_I_COMPLETE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE) - s_pSecFn->CompleteAuthToken(&credentials, &resp_desc); + Curl_pSecFn->CompleteAuthToken(&credentials, &resp_desc); else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { #if !defined(CURL_DISABLE_VERBOSE_STRINGS) char buffer[STRERROR_LEN]; #endif - s_pSecFn->FreeCredentialsHandle(&credentials); + Curl_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); free(spn); free(output_token); @@ -211,8 +213,10 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, if(status == SEC_E_INSUFFICIENT_MEMORY) return CURLE_OUT_OF_MEMORY; +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) infof(data, "schannel: InitializeSecurityContext failed: %s", Curl_sspi_strerror(status, buffer, sizeof(buffer))); +#endif return CURLE_AUTH_ERROR; } @@ -221,8 +225,8 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, Curl_bufref_set(out, output_token, resp_buf.cbBuffer, curl_free); /* Free our handles */ - s_pSecFn->DeleteSecurityContext(&context); - s_pSecFn->FreeCredentialsHandle(&credentials); + Curl_pSecFn->DeleteSecurityContext(&context); + Curl_pSecFn->FreeCredentialsHandle(&credentials); /* Free the identity structure */ Curl_sspi_free_identity(p_identity); @@ -289,7 +293,7 @@ CURLcode Curl_override_sspi_http_realm(const char *chlg, } } else - break; /* We're done here */ + break; /* We are done here */ /* Pass all additional spaces here */ while(*chlg && ISBLANK(*chlg)) @@ -322,10 +326,10 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, { size_t chlglen = strlen(chlg); - /* We had an input token before so if there's another one now that means we - provided bad credentials in the previous request or it's stale. */ + /* We had an input token before so if there is another one now that means we + provided bad credentials in the previous request or it is stale. */ if(digest->input_token) { - bool stale = false; + bool stale = FALSE; const char *p = chlg; /* Check for the 'stale' directive */ @@ -341,7 +345,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, if(strcasecompare(value, "stale") && strcasecompare(content, "true")) { - stale = true; + stale = TRUE; break; } @@ -377,7 +381,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * request [in] - The HTTP request. * uripath [in] - The path of the HTTP uri. @@ -408,17 +412,18 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, (void) data; /* Query the security package for DigestSSP */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), - &SecurityPackage); + status = + Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), + &SecurityPackage); if(status != SEC_E_OK) { - failf(data, "SSPI: couldn't get auth info"); + failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; } token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate the output buffer according to the max token size as indicated by the security package */ @@ -434,7 +439,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, (userp && digest->user && Curl_timestrcmp(userp, digest->user)) || (passwdp && digest->passwd && Curl_timestrcmp(passwdp, digest->passwd))) { if(digest->http_context) { - s_pSecFn->DeleteSecurityContext(digest->http_context); + Curl_pSecFn->DeleteSecurityContext(digest->http_context); Curl_safefree(digest->http_context); } Curl_safefree(digest->user); @@ -461,13 +466,14 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, chlg_buf[4].pvBuffer = output_token; chlg_buf[4].cbBuffer = curlx_uztoul(token_max); - status = s_pSecFn->MakeSignature(digest->http_context, 0, &chlg_desc, 0); + status = Curl_pSecFn->MakeSignature(digest->http_context, 0, &chlg_desc, + 0); if(status == SEC_E_OK) output_token_len = chlg_buf[4].cbBuffer; else { /* delete the context so a new one can be made */ infof(data, "digest_sspi: MakeSignature failed, error 0x%08lx", (long)status); - s_pSecFn->DeleteSecurityContext(digest->http_context); + Curl_pSecFn->DeleteSecurityContext(digest->http_context); Curl_safefree(digest->http_context); } } @@ -527,7 +533,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, } /* Acquire our credentials handle */ - status = s_pSecFn->AcquireCredentialsHandle(NULL, + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_DIGEST), SECPKG_CRED_OUTBOUND, NULL, p_identity, NULL, NULL, @@ -563,7 +569,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, spn = curlx_convert_UTF8_to_tchar((char *) uripath); if(!spn) { - s_pSecFn->FreeCredentialsHandle(&credentials); + Curl_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); free(output_token); @@ -577,7 +583,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; /* Generate our response message */ - status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, + status = Curl_pSecFn->InitializeSecurityContext(&credentials, NULL, spn, ISC_REQ_USE_HTTP_STYLE, 0, 0, &chlg_desc, 0, @@ -587,13 +593,13 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, if(status == SEC_I_COMPLETE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE) - s_pSecFn->CompleteAuthToken(&credentials, &resp_desc); + Curl_pSecFn->CompleteAuthToken(&credentials, &resp_desc); else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { #if !defined(CURL_DISABLE_VERBOSE_STRINGS) char buffer[STRERROR_LEN]; #endif - s_pSecFn->FreeCredentialsHandle(&credentials); + Curl_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); free(output_token); @@ -603,15 +609,17 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, if(status == SEC_E_INSUFFICIENT_MEMORY) return CURLE_OUT_OF_MEMORY; +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) infof(data, "schannel: InitializeSecurityContext failed: %s", Curl_sspi_strerror(status, buffer, sizeof(buffer))); +#endif return CURLE_AUTH_ERROR; } output_token_len = resp_buf.cbBuffer; - s_pSecFn->FreeCredentialsHandle(&credentials); + Curl_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); } @@ -656,7 +664,7 @@ void Curl_auth_digest_cleanup(struct digestdata *digest) /* Delete security context */ if(digest->http_context) { - s_pSecFn->DeleteSecurityContext(digest->http_context); + Curl_pSecFn->DeleteSecurityContext(digest->http_context); Curl_safefree(digest->http_context); } diff --git a/deps/curl/lib/vauth/gsasl.c b/deps/curl/lib/vauth/gsasl.c index c7d0a8d3b21..ee11b6039d6 100644 --- a/deps/curl/lib/vauth/gsasl.c +++ b/deps/curl/lib/vauth/gsasl.c @@ -59,7 +59,7 @@ bool Curl_auth_gsasl_is_supported(struct Curl_easy *data, return FALSE; } - return true; + return TRUE; } CURLcode Curl_auth_gsasl_start(struct Curl_easy *data, diff --git a/deps/curl/lib/vauth/krb5_gssapi.c b/deps/curl/lib/vauth/krb5_gssapi.c index 65eb3e1b508..e7927828380 100644 --- a/deps/curl/lib/vauth/krb5_gssapi.c +++ b/deps/curl/lib/vauth/krb5_gssapi.c @@ -65,10 +65,10 @@ bool Curl_auth_is_gssapi_supported(void) * Parameters: * * data [in] - The session handle. - * userp [in] - The user name. + * userp [in] - The username. * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. - * host [in[ - The host name. + * host [in[ - The hostname. * mutual_auth [in] - Flag specifying whether or not mutual authentication * is enabled. * chlg [in] - Optional challenge message. @@ -158,7 +158,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, gss_release_buffer(&unused_status, &output_token); } else - Curl_bufref_set(out, mutual_auth? "": NULL, 0, NULL); + Curl_bufref_set(out, mutual_auth ? "": NULL, 0, NULL); return result; } @@ -226,7 +226,8 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, /* Extract the security layer and the maximum message size */ indata = output_token.value; sec_layer = indata[0]; - max_size = (indata[1] << 16) | (indata[2] << 8) | indata[3]; + max_size = ((unsigned int)indata[1] << 16) | + ((unsigned int)indata[2] << 8) | indata[3]; /* Free the challenge as it is not required anymore */ gss_release_buffer(&unused_status, &output_token); @@ -242,7 +243,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, /* Process the maximum message size the server can receive */ if(max_size > 0) { /* The server has told us it supports a maximum receive buffer, however, as - we don't require one unless we are encrypting data, we tell the server + we do not require one unless we are encrypting data, we tell the server our receive buffer is zero. */ max_size = 0; } diff --git a/deps/curl/lib/vauth/krb5_sspi.c b/deps/curl/lib/vauth/krb5_sspi.c index c487149b9d7..4af0bd1e134 100644 --- a/deps/curl/lib/vauth/krb5_sspi.c +++ b/deps/curl/lib/vauth/krb5_sspi.c @@ -55,16 +55,16 @@ bool Curl_auth_is_gssapi_supported(void) SECURITY_STATUS status; /* Query the security package for Kerberos */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) + status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_KERBEROS), &SecurityPackage); /* Release the package buffer as it is not required anymore */ if(status == SEC_E_OK) { - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); } - return (status == SEC_E_OK ? TRUE : FALSE); + return (status == SEC_E_OK); } /* @@ -76,10 +76,10 @@ bool Curl_auth_is_gssapi_supported(void) * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. - * host [in] - The host name. + * host [in] - The hostname. * mutual_auth [in] - Flag specifying whether or not mutual authentication * is enabled. * chlg [in] - Optional challenge message. @@ -118,18 +118,18 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, if(!krb5->output_token) { /* Query the security package for Kerberos */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) + status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_KERBEROS), &SecurityPackage); if(status != SEC_E_OK) { - failf(data, "SSPI: couldn't get auth info"); + failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; } krb5->token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our response buffer */ krb5->output_token = malloc(krb5->token_max); @@ -158,7 +158,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; /* Acquire our credentials handle */ - status = s_pSecFn->AcquireCredentialsHandle(NULL, + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_KERBEROS), SECPKG_CRED_OUTBOUND, NULL, @@ -197,7 +197,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, resp_buf.cbBuffer = curlx_uztoul(krb5->token_max); /* Generate our challenge-response message */ - status = s_pSecFn->InitializeSecurityContext(krb5->credentials, + status = Curl_pSecFn->InitializeSecurityContext(krb5->credentials, chlg ? krb5->context : NULL, krb5->spn, (mutual_auth ? @@ -215,7 +215,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, return CURLE_AUTH_ERROR; if(memcmp(&context, krb5->context, sizeof(context))) { - s_pSecFn->DeleteSecurityContext(krb5->context); + Curl_pSecFn->DeleteSecurityContext(krb5->context); memcpy(krb5->context, &context, sizeof(context)); } @@ -282,7 +282,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, } /* Get our response size information */ - status = s_pSecFn->QueryContextAttributes(krb5->context, + status = Curl_pSecFn->QueryContextAttributes(krb5->context, SECPKG_ATTR_SIZES, &sizes); @@ -304,7 +304,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, input_buf[1].cbBuffer = 0; /* Decrypt the inbound challenge and obtain the qop */ - status = s_pSecFn->DecryptMessage(krb5->context, &input_desc, 0, &qop); + status = Curl_pSecFn->DecryptMessage(krb5->context, &input_desc, 0, &qop); if(status != SEC_E_OK) { infof(data, "GSSAPI handshake failure (empty security message)"); return CURLE_BAD_CONTENT_ENCODING; @@ -319,10 +319,11 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, /* Extract the security layer and the maximum message size */ indata = input_buf[1].pvBuffer; sec_layer = indata[0]; - max_size = (indata[1] << 16) | (indata[2] << 8) | indata[3]; + max_size = ((unsigned long)indata[1] << 16) | + ((unsigned long)indata[2] << 8) | indata[3]; /* Free the challenge as it is not required anymore */ - s_pSecFn->FreeContextBuffer(input_buf[1].pvBuffer); + Curl_pSecFn->FreeContextBuffer(input_buf[1].pvBuffer); /* Process the security layer */ if(!(sec_layer & KERB_WRAP_NO_ENCRYPT)) { @@ -334,7 +335,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, /* Process the maximum message size the server can receive */ if(max_size > 0) { /* The server has told us it supports a maximum receive buffer, however, as - we don't require one unless we are encrypting data, we tell the server + we do not require one unless we are encrypting data, we tell the server our receive buffer is zero. */ max_size = 0; } @@ -391,7 +392,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, wrap_buf[2].cbBuffer = sizes.cbBlockSize; /* Encrypt the data */ - status = s_pSecFn->EncryptMessage(krb5->context, KERB_WRAP_NO_ENCRYPT, + status = Curl_pSecFn->EncryptMessage(krb5->context, KERB_WRAP_NO_ENCRYPT, &wrap_desc, 0); if(status != SEC_E_OK) { free(padding); @@ -447,14 +448,14 @@ void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5) { /* Free our security context */ if(krb5->context) { - s_pSecFn->DeleteSecurityContext(krb5->context); + Curl_pSecFn->DeleteSecurityContext(krb5->context); free(krb5->context); krb5->context = NULL; } /* Free our credentials handle */ if(krb5->credentials) { - s_pSecFn->FreeCredentialsHandle(krb5->credentials); + Curl_pSecFn->FreeCredentialsHandle(krb5->credentials); free(krb5->credentials); krb5->credentials = NULL; } diff --git a/deps/curl/lib/vauth/ntlm.c b/deps/curl/lib/vauth/ntlm.c index ed7cee8deff..f8f6aea0e9e 100644 --- a/deps/curl/lib/vauth/ntlm.c +++ b/deps/curl/lib/vauth/ntlm.c @@ -44,6 +44,7 @@ #include "warnless.h" #include "rand.h" #include "vtls/vtls.h" +#include "strdup.h" #define BUILDING_CURL_NTLM_MSGS_C #include "vauth/vauth.h" @@ -58,10 +59,6 @@ /* "NTLMSSP" signature is always in ASCII regardless of the platform */ #define NTLMSSP_SIGNATURE "\x4e\x54\x4c\x4d\x53\x53\x50" -/* The fixed host name we provide, in order to not leak our real local host - name. Copy the name used by Firefox. */ -#define NTLM_HOSTNAME "WORKSTATION" - #if DEBUG_ME # define DEBUG_OUT(x) x static void ntlm_print_flags(FILE *handle, unsigned long flags) @@ -72,7 +69,7 @@ static void ntlm_print_flags(FILE *handle, unsigned long flags) fprintf(handle, "NTLMFLAG_NEGOTIATE_OEM "); if(flags & NTLMFLAG_REQUEST_TARGET) fprintf(handle, "NTLMFLAG_REQUEST_TARGET "); - if(flags & (1<<3)) + if(flags & (1 << 3)) fprintf(handle, "NTLMFLAG_UNKNOWN_3 "); if(flags & NTLMFLAG_NEGOTIATE_SIGN) fprintf(handle, "NTLMFLAG_NEGOTIATE_SIGN "); @@ -84,7 +81,7 @@ static void ntlm_print_flags(FILE *handle, unsigned long flags) fprintf(handle, "NTLMFLAG_NEGOTIATE_LM_KEY "); if(flags & NTLMFLAG_NEGOTIATE_NTLM_KEY) fprintf(handle, "NTLMFLAG_NEGOTIATE_NTLM_KEY "); - if(flags & (1<<10)) + if(flags & (1 << 10)) fprintf(handle, "NTLMFLAG_UNKNOWN_10 "); if(flags & NTLMFLAG_NEGOTIATE_ANONYMOUS) fprintf(handle, "NTLMFLAG_NEGOTIATE_ANONYMOUS "); @@ -112,15 +109,15 @@ static void ntlm_print_flags(FILE *handle, unsigned long flags) fprintf(handle, "NTLMFLAG_REQUEST_NONNT_SESSION_KEY "); if(flags & NTLMFLAG_NEGOTIATE_TARGET_INFO) fprintf(handle, "NTLMFLAG_NEGOTIATE_TARGET_INFO "); - if(flags & (1<<24)) + if(flags & (1 << 24)) fprintf(handle, "NTLMFLAG_UNKNOWN_24 "); - if(flags & (1<<25)) + if(flags & (1 << 25)) fprintf(handle, "NTLMFLAG_UNKNOWN_25 "); - if(flags & (1<<26)) + if(flags & (1 << 26)) fprintf(handle, "NTLMFLAG_UNKNOWN_26 "); - if(flags & (1<<27)) + if(flags & (1 << 27)) fprintf(handle, "NTLMFLAG_UNKNOWN_27 "); - if(flags & (1<<28)) + if(flags & (1 << 28)) fprintf(handle, "NTLMFLAG_UNKNOWN_28 "); if(flags & NTLMFLAG_NEGOTIATE_128) fprintf(handle, "NTLMFLAG_NEGOTIATE_128 "); @@ -184,11 +181,10 @@ static CURLcode ntlm_decode_type2_target(struct Curl_easy *data, } free(ntlm->target_info); /* replace any previous data */ - ntlm->target_info = malloc(target_info_len); + ntlm->target_info = Curl_memdup(&type2[target_info_offset], + target_info_len); if(!ntlm->target_info) return CURLE_OUT_OF_MEMORY; - - memcpy(ntlm->target_info, &type2[target_info_offset], target_info_len); } } @@ -325,10 +321,10 @@ static void unicodecpy(unsigned char *dest, const char *src, size_t length) * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. - * host [in] - The host name. + * host [in] - The hostname. * ntlm [in/out] - The NTLM data struct being used and modified. * out [out] - The result storage. * @@ -384,9 +380,9 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, "%c%c" /* 2 zeroes */ "%c%c" /* host length */ "%c%c" /* host allocated space */ - "%c%c" /* host name offset */ + "%c%c" /* hostname offset */ "%c%c" /* 2 zeroes */ - "%s" /* host name */ + "%s" /* hostname */ "%s", /* domain string */ 0, /* trailing zero */ 0, 0, 0, /* part of type-1 long */ @@ -448,7 +444,7 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * ntlm [in/out] - The NTLM data struct being used and modified. * out [out] - The result storage. @@ -470,7 +466,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, 12 LM/LMv2 Response security buffer 20 NTLM/NTLMv2 Response security buffer 28 Target Name security buffer - 36 User Name security buffer + 36 username security buffer 44 Workstation Name security buffer (52) Session Key security buffer (*) (60) Flags long (*) @@ -482,15 +478,17 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, CURLcode result = CURLE_OK; size_t size; unsigned char ntlmbuf[NTLM_BUFSIZE]; - int lmrespoff; + unsigned int lmrespoff; unsigned char lmresp[24]; /* fixed-size */ - int ntrespoff; + unsigned int ntrespoff; unsigned int ntresplen = 24; unsigned char ntresp[24]; /* fixed-size */ unsigned char *ptr_ntresp = &ntresp[0]; unsigned char *ntlmv2resp = NULL; - bool unicode = (ntlm->flags & NTLMFLAG_NEGOTIATE_UNICODE) ? TRUE : FALSE; - char host[HOSTNAME_MAX + 1] = ""; + bool unicode = (ntlm->flags & NTLMFLAG_NEGOTIATE_UNICODE); + /* The fixed hostname we provide, in order to not leak our real local host + name. Copy the name used by Firefox. */ + static const char host[] = "WORKSTATION"; const char *user; const char *domain = ""; size_t hostoff = 0; @@ -515,21 +513,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, user = userp; userlen = strlen(user); - -#ifndef NTLM_HOSTNAME - /* Get the machine's un-qualified host name as NTLM doesn't like the fully - qualified domain name */ - if(Curl_gethostname(host, sizeof(host))) { - infof(data, "gethostname() failed, continuing without"); - hostlen = 0; - } - else { - hostlen = strlen(host); - } -#else - (void)msnprintf(host, sizeof(host), "%s", NTLM_HOSTNAME); - hostlen = sizeof(NTLM_HOSTNAME)-1; -#endif + hostlen = sizeof(host) - 1; if(ntlm->flags & NTLMFLAG_NEGOTIATE_NTLM2_KEY) { unsigned char ntbuffer[0x18]; @@ -585,7 +569,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, return result; Curl_ntlm_core_lm_resp(lmbuffer, &ntlm->nonce[0], lmresp); - ntlm->flags &= ~NTLMFLAG_NEGOTIATE_NTLM2_KEY; + ntlm->flags &= ~(unsigned int)NTLMFLAG_NEGOTIATE_NTLM2_KEY; /* A safer but less compatible alternative is: * Curl_ntlm_core_lm_resp(ntbuffer, &ntlm->nonce[0], lmresp); @@ -722,7 +706,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, /* Make sure that the domain, user and host strings fit in the buffer before we copy them there. */ if(size + userlen + domlen + hostlen >= NTLM_BUFSIZE) { - failf(data, "user + domain + host name too big"); + failf(data, "user + domain + hostname too big"); return CURLE_OUT_OF_MEMORY; } diff --git a/deps/curl/lib/vauth/ntlm_sspi.c b/deps/curl/lib/vauth/ntlm_sspi.c index 5118963f4da..a2e5bc10269 100644 --- a/deps/curl/lib/vauth/ntlm_sspi.c +++ b/deps/curl/lib/vauth/ntlm_sspi.c @@ -34,6 +34,7 @@ #include "warnless.h" #include "curl_multibyte.h" #include "sendf.h" +#include "strdup.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -54,15 +55,15 @@ bool Curl_auth_is_ntlm_supported(void) SECURITY_STATUS status; /* Query the security package for NTLM */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), + status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), &SecurityPackage); /* Release the package buffer as it is not required anymore */ if(status == SEC_E_OK) { - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); } - return (status == SEC_E_OK ? TRUE : FALSE); + return (status == SEC_E_OK); } /* @@ -74,10 +75,10 @@ bool Curl_auth_is_ntlm_supported(void) * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. - * host [in] - The host name. + * host [in] - The hostname. * ntlm [in/out] - The NTLM data struct being used and modified. * out [out] - The result storage. * @@ -102,17 +103,17 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, Curl_auth_cleanup_ntlm(ntlm); /* Query the security package for NTLM */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), + status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), &SecurityPackage); if(status != SEC_E_OK) { - failf(data, "SSPI: couldn't get auth info"); + failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; } ntlm->token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our output buffer */ ntlm->output_token = malloc(ntlm->token_max); @@ -140,7 +141,7 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; /* Acquire our credentials handle */ - status = s_pSecFn->AcquireCredentialsHandle(NULL, + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_NTLM), SECPKG_CRED_OUTBOUND, NULL, ntlm->p_identity, NULL, NULL, @@ -166,7 +167,7 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, type_1_buf.cbBuffer = curlx_uztoul(ntlm->token_max); /* Generate our type-1 message */ - status = s_pSecFn->InitializeSecurityContext(ntlm->credentials, NULL, + status = Curl_pSecFn->InitializeSecurityContext(ntlm->credentials, NULL, ntlm->spn, 0, 0, SECURITY_NETWORK_DREP, NULL, 0, @@ -174,7 +175,7 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, &attrs, &expiry); if(status == SEC_I_COMPLETE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE) - s_pSecFn->CompleteAuthToken(ntlm->context, &type_1_desc); + Curl_pSecFn->CompleteAuthToken(ntlm->context, &type_1_desc); else if(status == SEC_E_INSUFFICIENT_MEMORY) return CURLE_OUT_OF_MEMORY; else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) @@ -213,11 +214,10 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, } /* Store the challenge for later use */ - ntlm->input_token = malloc(Curl_bufref_len(type2) + 1); + ntlm->input_token = Curl_memdup0((const char *)Curl_bufref_ptr(type2), + Curl_bufref_len(type2)); if(!ntlm->input_token) return CURLE_OUT_OF_MEMORY; - memcpy(ntlm->input_token, Curl_bufref_ptr(type2), Curl_bufref_len(type2)); - ntlm->input_token[Curl_bufref_len(type2)] = '\0'; ntlm->input_token_len = Curl_bufref_len(type2); return CURLE_OK; @@ -233,7 +233,7 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * ntlm [in/out] - The NTLM data struct being used and modified. * out [out] - The result storage. @@ -282,7 +282,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, SEC_CHANNEL_BINDINGS channelBindings; SecPkgContext_Bindings pkgBindings; pkgBindings.Bindings = &channelBindings; - status = s_pSecFn->QueryContextAttributes( + status = Curl_pSecFn->QueryContextAttributes( ntlm->sslContext, SECPKG_ATTR_ENDPOINT_BINDINGS, &pkgBindings @@ -305,7 +305,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, type_3_buf.cbBuffer = curlx_uztoul(ntlm->token_max); /* Generate our type-3 message */ - status = s_pSecFn->InitializeSecurityContext(ntlm->credentials, + status = Curl_pSecFn->InitializeSecurityContext(ntlm->credentials, ntlm->context, ntlm->spn, 0, 0, SECURITY_NETWORK_DREP, @@ -314,7 +314,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, &type_3_desc, &attrs, &expiry); if(status != SEC_E_OK) { - infof(data, "NTLM handshake failure (type-3 message): Status=%x", + infof(data, "NTLM handshake failure (type-3 message): Status=%lx", status); if(status == SEC_E_INSUFFICIENT_MEMORY) @@ -343,14 +343,14 @@ void Curl_auth_cleanup_ntlm(struct ntlmdata *ntlm) { /* Free our security context */ if(ntlm->context) { - s_pSecFn->DeleteSecurityContext(ntlm->context); + Curl_pSecFn->DeleteSecurityContext(ntlm->context); free(ntlm->context); ntlm->context = NULL; } /* Free our credentials handle */ if(ntlm->credentials) { - s_pSecFn->FreeCredentialsHandle(ntlm->credentials); + Curl_pSecFn->FreeCredentialsHandle(ntlm->credentials); free(ntlm->credentials); ntlm->credentials = NULL; } diff --git a/deps/curl/lib/vauth/oauth2.c b/deps/curl/lib/vauth/oauth2.c index a4adbdcf153..dc94afa365d 100644 --- a/deps/curl/lib/vauth/oauth2.c +++ b/deps/curl/lib/vauth/oauth2.c @@ -49,8 +49,8 @@ * * Parameters: * - * user[in] - The user name. - * host[in] - The host name. + * user[in] - The username. + * host[in] - The hostname. * port[in] - The port(when not Port 80). * bearer[in] - The bearer token. * out[out] - The result storage. @@ -87,7 +87,7 @@ CURLcode Curl_auth_create_oauth_bearer_message(const char *user, * * Parameters: * - * user[in] - The user name. + * user[in] - The username. * bearer[in] - The bearer token. * out[out] - The result storage. * diff --git a/deps/curl/lib/vauth/spnego_gssapi.c b/deps/curl/lib/vauth/spnego_gssapi.c index e1d52b755c2..f48d9b7000b 100644 --- a/deps/curl/lib/vauth/spnego_gssapi.c +++ b/deps/curl/lib/vauth/spnego_gssapi.c @@ -65,10 +65,10 @@ bool Curl_auth_is_spnego_supported(void) * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. + * userp [in] - The username in the format User or Domain\User. * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. - * host [in] - The host name. + * host [in] - The hostname. * chlg64 [in] - The optional base64 encoded challenge message. * nego [in/out] - The Negotiate data struct being used and modified. * @@ -91,14 +91,16 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, gss_buffer_desc spn_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + gss_channel_bindings_t chan_bindings = GSS_C_NO_CHANNEL_BINDINGS; + struct gss_channel_bindings_struct chan; (void) user; (void) password; if(nego->context && nego->status == GSS_S_COMPLETE) { /* We finished successfully our part of authentication, but server - * rejected it (since we're again here). Exit with an error since we - * can't invent anything better */ + * rejected it (since we are again here). Exit with an error since we + * cannot invent anything better */ Curl_auth_cleanup_spnego(nego); return CURLE_LOGIN_DENIED; } @@ -148,13 +150,21 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, input_token.length = chlglen; } + /* Set channel binding data if available */ + if(nego->channel_binding_data.leng > 0) { + memset(&chan, 0, sizeof(struct gss_channel_bindings_struct)); + chan.application_data.length = nego->channel_binding_data.leng; + chan.application_data.value = nego->channel_binding_data.bufr; + chan_bindings = &chan; + } + /* Generate our challenge-response message */ major_status = Curl_gss_init_sec_context(data, &minor_status, &nego->context, nego->spn, &Curl_spnego_mech_oid, - GSS_C_NO_CHANNEL_BINDINGS, + chan_bindings, &input_token, &output_token, TRUE, diff --git a/deps/curl/lib/vauth/spnego_sspi.c b/deps/curl/lib/vauth/spnego_sspi.c index d3245d0b189..7a27c298fc1 100644 --- a/deps/curl/lib/vauth/spnego_sspi.c +++ b/deps/curl/lib/vauth/spnego_sspi.c @@ -57,17 +57,17 @@ bool Curl_auth_is_spnego_supported(void) SECURITY_STATUS status; /* Query the security package for Negotiate */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) + status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NEGOTIATE), &SecurityPackage); /* Release the package buffer as it is not required anymore */ if(status == SEC_E_OK) { - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); } - return (status == SEC_E_OK ? TRUE : FALSE); + return (status == SEC_E_OK); } /* @@ -79,10 +79,10 @@ bool Curl_auth_is_spnego_supported(void) * Parameters: * * data [in] - The session handle. - * user [in] - The user name in the format User or Domain\User. + * user [in] - The username in the format User or Domain\User. * password [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. - * host [in] - The host name. + * host [in] - The hostname. * chlg64 [in] - The optional base64 encoded challenge message. * nego [in/out] - The Negotiate data struct being used and modified. * @@ -113,8 +113,8 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(nego->context && nego->status == SEC_E_OK) { /* We finished successfully our part of authentication, but server - * rejected it (since we're again here). Exit with an error since we - * can't invent anything better */ + * rejected it (since we are again here). Exit with an error since we + * cannot invent anything better */ Curl_auth_cleanup_spnego(nego); return CURLE_LOGIN_DENIED; } @@ -128,18 +128,18 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(!nego->output_token) { /* Query the security package for Negotiate */ - nego->status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) - TEXT(SP_NAME_NEGOTIATE), - &SecurityPackage); + nego->status = (DWORD)Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) + TEXT(SP_NAME_NEGOTIATE), + &SecurityPackage); if(nego->status != SEC_E_OK) { - failf(data, "SSPI: couldn't get auth info"); + failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; } nego->token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ - s_pSecFn->FreeContextBuffer(SecurityPackage); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our output buffer */ nego->output_token = malloc(nego->token_max); @@ -168,8 +168,8 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; /* Acquire our credentials handle */ - nego->status = - s_pSecFn->AcquireCredentialsHandle(NULL, + nego->status = (DWORD) + Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *)TEXT(SP_NAME_NEGOTIATE), SECPKG_CRED_OUTBOUND, NULL, nego->p_identity, NULL, NULL, @@ -218,7 +218,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, SEC_CHANNEL_BINDINGS channelBindings; SecPkgContext_Bindings pkgBindings; pkgBindings.Bindings = &channelBindings; - nego->status = s_pSecFn->QueryContextAttributes( + nego->status = (DWORD)Curl_pSecFn->QueryContextAttributes( nego->sslContext, SECPKG_ATTR_ENDPOINT_BINDINGS, &pkgBindings @@ -242,16 +242,16 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, resp_buf.cbBuffer = curlx_uztoul(nego->token_max); /* Generate our challenge-response message */ - nego->status = s_pSecFn->InitializeSecurityContext(nego->credentials, - chlg ? nego->context : - NULL, - nego->spn, - ISC_REQ_CONFIDENTIALITY, - 0, SECURITY_NATIVE_DREP, - chlg ? &chlg_desc : NULL, - 0, nego->context, - &resp_desc, &attrs, - &expiry); + nego->status = + (DWORD)Curl_pSecFn->InitializeSecurityContext(nego->credentials, + chlg ? nego->context : NULL, + nego->spn, + ISC_REQ_CONFIDENTIALITY, + 0, SECURITY_NATIVE_DREP, + chlg ? &chlg_desc : NULL, + 0, nego->context, + &resp_desc, &attrs, + &expiry); /* Free the decoded challenge as it is not required anymore */ free(chlg); @@ -259,7 +259,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(GSS_ERROR(nego->status)) { char buffer[STRERROR_LEN]; failf(data, "InitializeSecurityContext failed: %s", - Curl_sspi_strerror(nego->status, buffer, sizeof(buffer))); + Curl_sspi_strerror((int)nego->status, buffer, sizeof(buffer))); if(nego->status == (DWORD)SEC_E_INSUFFICIENT_MEMORY) return CURLE_OUT_OF_MEMORY; @@ -269,11 +269,12 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(nego->status == SEC_I_COMPLETE_NEEDED || nego->status == SEC_I_COMPLETE_AND_CONTINUE) { - nego->status = s_pSecFn->CompleteAuthToken(nego->context, &resp_desc); + nego->status = (DWORD)Curl_pSecFn->CompleteAuthToken(nego->context, + &resp_desc); if(GSS_ERROR(nego->status)) { char buffer[STRERROR_LEN]; failf(data, "CompleteAuthToken failed: %s", - Curl_sspi_strerror(nego->status, buffer, sizeof(buffer))); + Curl_sspi_strerror((int)nego->status, buffer, sizeof(buffer))); if(nego->status == (DWORD)SEC_E_INSUFFICIENT_MEMORY) return CURLE_OUT_OF_MEMORY; @@ -332,14 +333,14 @@ void Curl_auth_cleanup_spnego(struct negotiatedata *nego) { /* Free our security context */ if(nego->context) { - s_pSecFn->DeleteSecurityContext(nego->context); + Curl_pSecFn->DeleteSecurityContext(nego->context); free(nego->context); nego->context = NULL; } /* Free our credentials handle */ if(nego->credentials) { - s_pSecFn->FreeCredentialsHandle(nego->credentials); + Curl_pSecFn->FreeCredentialsHandle(nego->credentials); free(nego->credentials); nego->credentials = NULL; } diff --git a/deps/curl/lib/vauth/vauth.c b/deps/curl/lib/vauth/vauth.c index 62fc7c40fe8..a7e7e17f3b2 100644 --- a/deps/curl/lib/vauth/vauth.c +++ b/deps/curl/lib/vauth/vauth.c @@ -48,7 +48,7 @@ * Parameters: * * service [in] - The service type such as http, smtp, pop or imap. - * host [in] - The host name. + * host [in] - The hostname. * realm [in] - The realm. * * Returns a pointer to the newly allocated SPN. @@ -93,7 +93,7 @@ TCHAR *Curl_auth_build_spn(const char *service, const char *host, return NULL; /* Allocate and return a TCHAR based SPN. Since curlx_convert_UTF8_to_tchar - must be freed by curlx_unicodefree we'll dupe the result so that the + must be freed by curlx_unicodefree we will dupe the result so that the pointer this function returns can be normally free'd. */ tchar_spn = curlx_convert_UTF8_to_tchar(utf8_spn); free(utf8_spn); @@ -115,14 +115,14 @@ TCHAR *Curl_auth_build_spn(const char *service, const char *host, * Domain/User (curl Down-level format - for compatibility with existing code) * User@Domain (User Principal Name) * - * Note: The user name may be empty when using a GSS-API library or Windows + * Note: The username may be empty when using a GSS-API library or Windows * SSPI as the user and domain are either obtained from the credentials cache * when using GSS-API or via the currently logged in user's credentials when * using Windows SSPI. * * Parameters: * - * user [in] - The user name. + * user [in] - The username. * * Returns TRUE on success; otherwise FALSE. */ @@ -134,8 +134,7 @@ bool Curl_auth_user_contains_domain(const char *user) /* Check we have a domain name or UPN present */ char *p = strpbrk(user, "\\/@"); - valid = (p != NULL && p > user && p < user + strlen(user) - 1 ? TRUE : - FALSE); + valid = (p != NULL && p > user && p < user + strlen(user) - 1); } #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) else diff --git a/deps/curl/lib/vauth/vauth.h b/deps/curl/lib/vauth/vauth.h index 9da05408922..7e823484f68 100644 --- a/deps/curl/lib/vauth/vauth.h +++ b/deps/curl/lib/vauth/vauth.h @@ -79,12 +79,10 @@ CURLcode Curl_auth_create_plain_message(const char *authzid, struct bufref *out); /* This is used to generate a LOGIN cleartext message */ -CURLcode Curl_auth_create_login_message(const char *value, - struct bufref *out); +void Curl_auth_create_login_message(const char *value, struct bufref *out); /* This is used to generate an EXTERNAL cleartext message */ -CURLcode Curl_auth_create_external_message(const char *user, - struct bufref *out); +void Curl_auth_create_external_message(const char *user, struct bufref *out); #ifndef CURL_DISABLE_DIGEST_AUTH /* This is used to generate a CRAM-MD5 response message */ diff --git a/deps/curl/lib/version.c b/deps/curl/lib/version.c index 47304259e09..1d5b96d2eb0 100644 --- a/deps/curl/lib/version.c +++ b/deps/curl/lib/version.c @@ -39,7 +39,7 @@ #ifdef USE_ARES # if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ - defined(WIN32) + defined(_WIN32) # define CARES_STATICLIB # endif # include @@ -55,6 +55,7 @@ #ifdef USE_LIBRTMP #include +#include "curl_rtmp.h" #endif #ifdef HAVE_LIBZ @@ -62,13 +63,13 @@ #endif #ifdef HAVE_BROTLI -#if defined(__GNUC__) +#if defined(__GNUC__) || defined(__clang__) /* Ignore -Wvla warnings in brotli headers */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wvla" #endif #include -#if defined(__GNUC__) +#if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop #endif #endif @@ -92,20 +93,71 @@ static void brotli_version(char *buf, size_t bufsz) unsigned int major = brotli_version >> 24; unsigned int minor = (brotli_version & 0x00FFFFFF) >> 12; unsigned int patch = brotli_version & 0x00000FFF; - (void)msnprintf(buf, bufsz, "%u.%u.%u", major, minor, patch); + (void)msnprintf(buf, bufsz, "brotli/%u.%u.%u", major, minor, patch); } #endif #ifdef HAVE_ZSTD static void zstd_version(char *buf, size_t bufsz) { - unsigned long zstd_version = (unsigned long)ZSTD_versionNumber(); - unsigned int major = (unsigned int)(zstd_version / (100 * 100)); - unsigned int minor = (unsigned int)((zstd_version - - (major * 100 * 100)) / 100); - unsigned int patch = (unsigned int)(zstd_version - - (major * 100 * 100) - (minor * 100)); - (void)msnprintf(buf, bufsz, "%u.%u.%u", major, minor, patch); + unsigned int version = ZSTD_versionNumber(); + unsigned int major = version / (100 * 100); + unsigned int minor = (version - (major * 100 * 100)) / 100; + unsigned int patch = version - (major * 100 * 100) - (minor * 100); + (void)msnprintf(buf, bufsz, "zstd/%u.%u.%u", major, minor, patch); +} +#endif + +#ifdef USE_OPENLDAP +static void oldap_version(char *buf, size_t bufsz) +{ + LDAPAPIInfo api; + api.ldapai_info_version = LDAP_API_INFO_VERSION; + + if(ldap_get_option(NULL, LDAP_OPT_API_INFO, &api) == LDAP_OPT_SUCCESS) { + unsigned int patch = (unsigned int)(api.ldapai_vendor_version % 100); + unsigned int major = (unsigned int)(api.ldapai_vendor_version / 10000); + unsigned int minor = + (((unsigned int)api.ldapai_vendor_version - major * 10000) + - patch) / 100; + msnprintf(buf, bufsz, "%s/%u.%u.%u", + api.ldapai_vendor_name, major, minor, patch); + ldap_memfree(api.ldapai_vendor_name); + ber_memvfree((void **)api.ldapai_extensions); + } + else + msnprintf(buf, bufsz, "OpenLDAP"); +} +#endif + +#ifdef USE_LIBPSL +static void psl_version(char *buf, size_t bufsz) +{ +#if defined(PSL_VERSION_MAJOR) && (PSL_VERSION_MAJOR > 0 || \ + PSL_VERSION_MINOR >= 11) + int num = psl_check_version_number(0); + msnprintf(buf, bufsz, "libpsl/%d.%d.%d", + num >> 16, (num >> 8) & 0xff, num & 0xff); +#else + msnprintf(buf, bufsz, "libpsl/%s", psl_get_version()); +#endif +} +#endif + +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) +#define USE_IDN +#endif + +#ifdef USE_IDN +static void idn_version(char *buf, size_t bufsz) +{ +#ifdef USE_LIBIDN2 + msnprintf(buf, bufsz, "libidn2/%s", idn2_check_version(NULL)); +#elif defined(USE_WIN32_IDN) + msnprintf(buf, bufsz, "WinIDN"); +#elif defined(USE_APPLE_IDN) + msnprintf(buf, bufsz, "AppleIDN"); +#endif } #endif @@ -129,34 +181,34 @@ char *curl_version(void) char ssl_version[200]; #endif #ifdef HAVE_LIBZ - char z_version[40]; + char z_version[30]; #endif #ifdef HAVE_BROTLI - char br_version[40] = "brotli/"; + char br_version[30]; #endif #ifdef HAVE_ZSTD - char zst_version[40] = "zstd/"; + char zstd_ver[30]; #endif #ifdef USE_ARES - char cares_version[40]; + char cares_version[30]; #endif -#if defined(USE_LIBIDN2) - char idn_version[40]; +#ifdef USE_IDN + char idn_ver[30]; #endif #ifdef USE_LIBPSL - char psl_version[40]; + char psl_ver[30]; #endif #ifdef USE_SSH - char ssh_version[40]; + char ssh_version[30]; #endif #ifdef USE_NGHTTP2 - char h2_version[40]; + char h2_version[30]; #endif -#ifdef ENABLE_QUIC - char h3_version[40]; +#ifdef USE_HTTP3 + char h3_version[30]; #endif #ifdef USE_LIBRTMP - char rtmp_version[40]; + char rtmp_version[30]; #endif #ifdef USE_HYPER char hyper_buf[30]; @@ -174,8 +226,7 @@ char *curl_version(void) /* Override version string when environment variable CURL_VERSION is set */ const char *debugversion = getenv("CURL_VERSION"); if(debugversion) { - strncpy(out, debugversion, sizeof(out)-1); - out[sizeof(out)-1] = '\0'; + msnprintf(out, sizeof(out), "%s", debugversion); return out; } #endif @@ -190,31 +241,26 @@ char *curl_version(void) src[i++] = z_version; #endif #ifdef HAVE_BROTLI - brotli_version(&br_version[7], sizeof(br_version) - 7); + brotli_version(br_version, sizeof(br_version)); src[i++] = br_version; #endif #ifdef HAVE_ZSTD - zstd_version(&zst_version[5], sizeof(zst_version) - 5); - src[i++] = zst_version; + zstd_version(zstd_ver, sizeof(zstd_ver)); + src[i++] = zstd_ver; #endif #ifdef USE_ARES msnprintf(cares_version, sizeof(cares_version), "c-ares/%s", ares_version(NULL)); src[i++] = cares_version; #endif -#ifdef USE_LIBIDN2 - msnprintf(idn_version, sizeof(idn_version), - "libidn2/%s", idn2_check_version(NULL)); - src[i++] = idn_version; -#elif defined(USE_WIN32_IDN) - src[i++] = (char *)"WinIDN"; +#ifdef USE_IDN + idn_version(idn_ver, sizeof(idn_ver)); + src[i++] = idn_ver; #endif - #ifdef USE_LIBPSL - msnprintf(psl_version, sizeof(psl_version), "libpsl/%s", psl_get_version()); - src[i++] = psl_version; + psl_version(psl_ver, sizeof(psl_ver)); + src[i++] = psl_ver; #endif - #ifdef USE_SSH Curl_ssh_version(ssh_version, sizeof(ssh_version)); src[i++] = ssh_version; @@ -223,25 +269,13 @@ char *curl_version(void) Curl_http2_ver(h2_version, sizeof(h2_version)); src[i++] = h2_version; #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 Curl_quic_ver(h3_version, sizeof(h3_version)); src[i++] = h3_version; #endif #ifdef USE_LIBRTMP - { - char suff[2]; - if(RTMP_LIB_VERSION & 0xff) { - suff[0] = (RTMP_LIB_VERSION & 0xff) + 'a' - 1; - suff[1] = '\0'; - } - else - suff[0] = '\0'; - - msnprintf(rtmp_version, sizeof(rtmp_version), "librtmp/%d.%d%s", - RTMP_LIB_VERSION >> 16, (RTMP_LIB_VERSION >> 8) & 0xff, - suff); - src[i++] = rtmp_version; - } + Curl_rtmp_version(rtmp_version, sizeof(rtmp_version)); + src[i++] = rtmp_version; #endif #ifdef USE_HYPER msnprintf(hyper_buf, sizeof(hyper_buf), "Hyper/%s", hyper_version()); @@ -253,22 +287,8 @@ char *curl_version(void) src[i++] = gsasl_buf; #endif #ifdef USE_OPENLDAP - { - LDAPAPIInfo api; - api.ldapai_info_version = LDAP_API_INFO_VERSION; - - if(ldap_get_option(NULL, LDAP_OPT_API_INFO, &api) == LDAP_OPT_SUCCESS) { - unsigned int patch = api.ldapai_vendor_version % 100; - unsigned int major = api.ldapai_vendor_version / 10000; - unsigned int minor = - ((api.ldapai_vendor_version - major * 10000) - patch) / 100; - msnprintf(ldap_buf, sizeof(ldap_buf), "%s/%u.%u.%u", - api.ldapai_vendor_name, major, minor, patch); - src[i++] = ldap_buf; - ldap_memfree(api.ldapai_vendor_name); - ber_memvfree((void **)api.ldapai_extensions); - } - } + oldap_version(ldap_buf, sizeof(ldap_buf)); + src[i++] = ldap_buf; #endif DEBUGASSERT(i <= VERSION_PARTS); @@ -383,18 +403,21 @@ static const char * const supported_protocols[] = { #ifndef CURL_DISABLE_TFTP "tftp", #endif -#ifdef USE_WEBSOCKETS +#ifndef CURL_DISABLE_HTTP + /* WebSocket support relies on HTTP */ +#ifndef CURL_DISABLE_WEBSOCKETS "ws", #endif -#if defined(USE_SSL) && defined(USE_WEBSOCKETS) +#if defined(USE_SSL) && !defined(CURL_DISABLE_WEBSOCKETS) "wss", +#endif #endif NULL }; /* - * Feature presence run-time check functions. + * Feature presence runtime check functions. * * Warning: the value returned by these should not change between * curl_global_init() and curl_global_cleanup() calls. @@ -409,7 +432,8 @@ static int idn_present(curl_version_info_data *info) #define idn_present NULL #endif -#if defined(USE_SSL) && !defined(CURL_DISABLE_PROXY) +#if defined(USE_SSL) && !defined(CURL_DISABLE_PROXY) && \ + !defined(CURL_DISABLE_HTTP) static int https_proxy_present(curl_version_info_data *info) { (void) info; @@ -417,6 +441,14 @@ static int https_proxy_present(curl_version_info_data *info) } #endif +#if defined(USE_SSL) && defined(USE_ECH) +static int ech_present(curl_version_info_data *info) +{ + (void) info; + return Curl_ssl_supports(NULL, SSLSUPP_ECH); +} +#endif + /* * Features table. * @@ -445,6 +477,9 @@ static const struct feat features_table[] = { #ifdef DEBUGBUILD FEATURE("Debug", NULL, CURL_VERSION_DEBUG), #endif +#if defined(USE_SSL) && defined(USE_ECH) + FEATURE("ECH", ech_present, 0), +#endif #ifdef USE_GSASL FEATURE("gsasl", NULL, CURL_VERSION_GSASL), #endif @@ -454,19 +489,20 @@ static const struct feat features_table[] = { #ifndef CURL_DISABLE_HSTS FEATURE("HSTS", NULL, CURL_VERSION_HSTS), #endif -#if defined(USE_NGHTTP2) || defined(USE_HYPER) +#if defined(USE_NGHTTP2) FEATURE("HTTP2", NULL, CURL_VERSION_HTTP2), #endif -#if defined(ENABLE_QUIC) +#if defined(USE_HTTP3) FEATURE("HTTP3", NULL, CURL_VERSION_HTTP3), #endif -#if defined(USE_SSL) && !defined(CURL_DISABLE_PROXY) +#if defined(USE_SSL) && !defined(CURL_DISABLE_PROXY) && \ + !defined(CURL_DISABLE_HTTP) FEATURE("HTTPS-proxy", https_proxy_present, CURL_VERSION_HTTPS_PROXY), #endif -#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) FEATURE("IDN", idn_present, CURL_VERSION_IDN), #endif -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 FEATURE("IPv6", NULL, CURL_VERSION_IPV6), #endif #ifdef USE_KERBEROS5 @@ -485,10 +521,6 @@ static const struct feat features_table[] = { #ifdef USE_NTLM FEATURE("NTLM", NULL, CURL_VERSION_NTLM), #endif -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) - FEATURE("NTLM_WB", NULL, CURL_VERSION_NTLM_WB), -#endif #if defined(USE_LIBPSL) FEATURE("PSL", NULL, CURL_VERSION_PSL), #endif @@ -510,7 +542,7 @@ static const struct feat features_table[] = { #ifdef CURLDEBUG FEATURE("TrackMemory", NULL, CURL_VERSION_CURLDEBUG), #endif -#if defined(WIN32) && defined(UNICODE) && defined(_UNICODE) +#if defined(_WIN32) && defined(UNICODE) && defined(_UNICODE) FEATURE("Unicode", NULL, CURL_VERSION_UNICODE), #endif #ifdef USE_UNIX_SOCKETS @@ -530,8 +562,8 @@ static curl_version_info_data version_info = { CURLVERSION_NOW, LIBCURL_VERSION, LIBCURL_VERSION_NUM, - OS, /* as found by configure or set by hand at build-time */ - 0, /* features bitmask is built at run-time */ + CURL_OS, /* as found by configure or set by hand at build-time */ + 0, /* features bitmask is built at runtime */ NULL, /* ssl_version */ 0, /* ssl_version_num, this is kept at zero */ NULL, /* zlib_version */ @@ -560,7 +592,8 @@ static curl_version_info_data version_info = { NULL, /* zstd version */ NULL, /* Hyper version */ NULL, /* gsasl version */ - feature_names + feature_names, + NULL /* rtmp version */ }; curl_version_info_data *curl_version_info(CURLversion stamp) @@ -570,7 +603,7 @@ curl_version_info_data *curl_version_info(CURLversion stamp) int features = 0; #if defined(USE_SSH) - static char ssh_buffer[80]; + static char ssh_buf[80]; /* 'ssh_buffer' clashes with libssh/libssh.h */ #endif #ifdef USE_SSL #ifdef CURL_WITH_MULTI_SSL @@ -586,7 +619,7 @@ curl_version_info_data *curl_version_info(CURLversion stamp) static char zstd_buffer[80]; #endif - (void)stamp; /* avoid compiler warnings, we don't use this */ + (void)stamp; /* avoid compiler warnings, we do not use this */ #ifdef USE_SSL Curl_ssl_version(ssl_buffer, sizeof(ssl_buffer)); @@ -611,8 +644,8 @@ curl_version_info_data *curl_version_info(CURLversion stamp) #endif #if defined(USE_SSH) - Curl_ssh_version(ssh_buffer, sizeof(ssh_buffer)); - version_info.libssh_version = ssh_buffer; + Curl_ssh_version(ssh_buf, sizeof(ssh_buf)); + version_info.libssh_version = ssh_buf; #endif #ifdef HAVE_BROTLI @@ -630,12 +663,12 @@ curl_version_info_data *curl_version_info(CURLversion stamp) #ifdef USE_NGHTTP2 { nghttp2_info *h2 = nghttp2_version(0); - version_info.nghttp2_ver_num = h2->version_num; + version_info.nghttp2_ver_num = (unsigned int)h2->version_num; version_info.nghttp2_version = h2->version_str; } #endif -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 { static char quicbuffer[80]; Curl_quic_ver(quicbuffer, sizeof(quicbuffer)); @@ -668,5 +701,13 @@ curl_version_info_data *curl_version_info(CURLversion stamp) feature_names[n] = NULL; /* Terminate array. */ version_info.features = features; +#ifdef USE_LIBRTMP + { + static char rtmp_version[30]; + Curl_rtmp_version(rtmp_version, sizeof(rtmp_version)); + version_info.rtmp_version = rtmp_version; + } +#endif + return &version_info; } diff --git a/deps/curl/lib/version_win32.c b/deps/curl/lib/version_win32.c index 872d5b4f3c5..21a122f2a62 100644 --- a/deps/curl/lib/version_win32.c +++ b/deps/curl/lib/version_win32.c @@ -24,14 +24,16 @@ #include "curl_setup.h" -#if defined(WIN32) +#if defined(_WIN32) #include #include "version_win32.h" #include "warnless.h" -/* The last #include files should be: */ +/* The last 2 #include files should be in this order */ +#ifdef BUILDING_LIBCURL #include "curl_memory.h" +#endif #include "memdebug.h" /* This Unicode version struct works for VerifyVersionInfoW (OSVERSIONINFOEXW) @@ -53,7 +55,7 @@ struct OUR_OSVERSIONINFOEXW { /* * curlx_verify_windows_version() * - * This is used to verify if we are running on a specific windows version. + * This is used to verify if we are running on a specific Windows version. * * Parameters: * @@ -63,7 +65,7 @@ struct OUR_OSVERSIONINFOEXW { * ignored. * platform [in] - The optional platform identifier. * condition [in] - The test condition used to specifier whether we are - * checking a version less then, equal to or greater than + * checking a version less than, equal to or greater than * what is specified in the major and minor version * numbers. * @@ -77,14 +79,14 @@ bool curlx_verify_windows_version(const unsigned int majorVersion, { bool matched = FALSE; -#if defined(CURL_WINDOWS_APP) - (void)buildVersion; - +#if defined(CURL_WINDOWS_UWP) /* We have no way to determine the Windows version from Windows apps, - so let's assume we're running on the target Windows version. */ + so let's assume we are running on the target Windows version. */ const WORD fullVersion = MAKEWORD(minorVersion, majorVersion); const WORD targetVersion = (WORD)_WIN32_WINNT; + (void)buildVersion; + switch(condition) { case VERSION_LESS_THAN: matched = targetVersion < fullVersion; @@ -108,7 +110,7 @@ bool curlx_verify_windows_version(const unsigned int majorVersion, } if(matched && (platform == PLATFORM_WINDOWS)) { - /* we're always running on PLATFORM_WINNT */ + /* we are always running on PLATFORM_WINNT */ matched = FALSE; } #elif !defined(_WIN32_WINNT) || !defined(_WIN32_WINNT_WIN2K) || \ @@ -207,12 +209,12 @@ bool curlx_verify_windows_version(const unsigned int majorVersion, typedef LONG (APIENTRY *RTLVERIFYVERSIONINFO_FN) (struct OUR_OSVERSIONINFOEXW *, ULONG, ULONGLONG); static RTLVERIFYVERSIONINFO_FN pRtlVerifyVersionInfo; - static bool onetime = true; /* safe because first call is during init */ + static bool onetime = TRUE; /* safe because first call is during init */ if(onetime) { pRtlVerifyVersionInfo = CURLX_FUNCTION_CAST(RTLVERIFYVERSIONINFO_FN, (GetProcAddress(GetModuleHandleA("ntdll"), "RtlVerifyVersionInfo"))); - onetime = false; + onetime = FALSE; } switch(condition) { @@ -316,4 +318,4 @@ bool curlx_verify_windows_version(const unsigned int majorVersion, return matched; } -#endif /* WIN32 */ +#endif /* _WIN32 */ diff --git a/deps/curl/lib/version_win32.h b/deps/curl/lib/version_win32.h index 3899174a311..95a9e7f2152 100644 --- a/deps/curl/lib/version_win32.h +++ b/deps/curl/lib/version_win32.h @@ -26,7 +26,7 @@ #include "curl_setup.h" -#if defined(WIN32) +#if defined(_WIN32) /* Version condition */ typedef enum { @@ -44,13 +44,13 @@ typedef enum { PLATFORM_WINNT } PlatformIdentifier; -/* This is used to verify if we are running on a specific windows version */ +/* This is used to verify if we are running on a specific Windows version */ bool curlx_verify_windows_version(const unsigned int majorVersion, const unsigned int minorVersion, const unsigned int buildVersion, const PlatformIdentifier platform, const VersionCondition condition); -#endif /* WIN32 */ +#endif /* _WIN32 */ #endif /* HEADER_CURL_VERSION_WIN32_H */ diff --git a/deps/curl/lib/vquic/.checksrc b/deps/curl/lib/vquic/.checksrc new file mode 100644 index 00000000000..9066946c890 --- /dev/null +++ b/deps/curl/lib/vquic/.checksrc @@ -0,0 +1,2 @@ +enable STRERROR +enable STRNCPY diff --git a/deps/curl/lib/vquic/curl_msh3.c b/deps/curl/lib/vquic/curl_msh3.c index 6bd0d23316a..fe812f87d2a 100644 --- a/deps/curl/lib/vquic/curl_msh3.c +++ b/deps/curl/lib/vquic/curl_msh3.c @@ -27,6 +27,7 @@ #ifdef USE_MSH3 #include "urldata.h" +#include "hash.h" #include "timeval.h" #include "multiif.h" #include "sendf.h" @@ -38,6 +39,7 @@ #include "http1.h" #include "curl_msh3.h" #include "socketpair.h" +#include "vtls/vtls.h" #include "vquic/vquic.h" /* The last 3 #include files should be in this order */ @@ -45,6 +47,10 @@ #include "curl_memory.h" #include "memdebug.h" +#ifdef CURL_DISABLE_SOCKETPAIR +#error "MSH3 cannot be build with CURL_DISABLE_SOCKETPAIR set" +#endif + #define H3_STREAM_WINDOW_SIZE (128 * 1024) #define H3_STREAM_CHUNK_SIZE (16 * 1024) #define H3_STREAM_RECV_CHUNKS \ @@ -65,7 +71,7 @@ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \ pthread_mutex_init(lock, &attr); \ pthread_mutexattr_destroy(&attr); \ -}while(0) +} while(0) #define msh3_lock_uninitialize(lock) pthread_mutex_destroy(lock) #define msh3_lock_acquire(lock) pthread_mutex_lock(lock) #define msh3_lock_release(lock) pthread_mutex_unlock(lock) @@ -113,15 +119,40 @@ struct cf_msh3_ctx { struct cf_call_data call_data; struct curltime connect_started; /* time the current attempt started */ struct curltime handshake_at; /* time connect handshake finished */ + struct Curl_hash streams; /* hash `data->mid` to `stream_ctx` */ /* Flags written by msh3/msquic thread */ bool handshake_complete; bool handshake_succeeded; bool connected; + BIT(initialized); /* Flags written by curl thread */ BIT(verbose); BIT(active); }; +static void h3_stream_hash_free(void *stream); + +static void cf_msh3_ctx_init(struct cf_msh3_ctx *ctx, + const struct Curl_addrinfo *ai) +{ + DEBUGASSERT(!ctx->initialized); + Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free); + Curl_sock_assign_addr(&ctx->addr, ai, TRNSPRT_QUIC); + ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; + ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD; + ctx->initialized = TRUE; +} + +static void cf_msh3_ctx_free(struct cf_msh3_ctx *ctx) +{ + if(ctx && ctx->initialized) { + Curl_hash_destroy(&ctx->streams); + } + free(ctx); +} + +static struct cf_msh3_ctx *h3_get_msh3_ctx(struct Curl_easy *data); + /* How to access `call_data` from a cf_msh3 filter */ #undef CF_CTX_CALL_DATA #define CF_CTX_CALL_DATA(cf) \ @@ -148,18 +179,26 @@ struct stream_ctx { bool recv_header_complete; }; -#define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->h3_ctx \ - : NULL)) -#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx -#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ - H3_STREAM_CTX(d)->id : -2) +#define H3_STREAM_CTX(ctx,data) ((struct stream_ctx *)((data && ctx)? \ + Curl_hash_offt_get(&(ctx)->streams, (data)->mid) : NULL)) +static void h3_stream_ctx_free(struct stream_ctx *stream) +{ + Curl_bufq_free(&stream->recvbuf); + free(stream); +} + +static void h3_stream_hash_free(void *stream) +{ + DEBUGASSERT(stream); + h3_stream_ctx_free((struct stream_ctx *)stream); +} static CURLcode h3_data_setup(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream) return CURLE_OK; @@ -168,25 +207,29 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, if(!stream) return CURLE_OUT_OF_MEMORY; - H3_STREAM_LCTX(data) = stream; stream->req = ZERO_NULL; msh3_lock_initialize(&stream->recv_lock); Curl_bufq_init2(&stream->recvbuf, H3_STREAM_CHUNK_SIZE, H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); CURL_TRC_CF(data, cf, "data setup"); + + if(!Curl_hash_offt_set(&ctx->streams, data->mid, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)cf; if(stream) { CURL_TRC_CF(data, cf, "easy handle is done"); - Curl_bufq_free(&stream->recvbuf); - free(stream); - H3_STREAM_LCTX(data) = NULL; + Curl_hash_offt_remove(&ctx->streams, data->mid); } } @@ -199,24 +242,25 @@ static void drain_stream_from_other_thread(struct Curl_easy *data, bits = CURL_CSELECT_IN; if(stream && !stream->upload_done) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - data->state.dselect_bits = bits; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; /* cannot expire from other thread */ } } -static void drain_stream(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); unsigned char bits; (void)cf; bits = CURL_CSELECT_IN; if(stream && !stream->upload_done) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - data->state.dselect_bits = bits; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; Curl_expire(data, 0, EXPIRE_RUN_NOW); } } @@ -236,9 +280,9 @@ static void MSH3_CALL msh3_conn_connected(MSH3_CONNECTION *Connection, (void)Connection; CURL_TRC_CF(data, cf, "[MSH3] connected"); - ctx->handshake_succeeded = true; - ctx->connected = true; - ctx->handshake_complete = true; + ctx->handshake_succeeded = TRUE; + ctx->connected = TRUE; + ctx->handshake_complete = TRUE; } static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection, @@ -250,8 +294,8 @@ static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection, (void)Connection; CURL_TRC_CF(data, cf, "[MSH3] shutdown complete"); - ctx->connected = false; - ctx->handshake_complete = true; + ctx->connected = FALSE; + ctx->handshake_complete = TRUE; } static void MSH3_CALL msh3_conn_new_request(MSH3_CONNECTION *Connection, @@ -271,7 +315,7 @@ static const MSH3_REQUEST_IF msh3_request_if = { msh3_data_sent }; -/* Decode HTTP status code. Returns -1 if no valid status code was +/* Decode HTTP status code. Returns -1 if no valid status code was decoded. (duplicate from http2.c) */ static int decode_status_code(const char *value, size_t len) { @@ -306,7 +350,8 @@ static int decode_status_code(const char *value, size_t len) static CURLcode write_resp_raw(struct Curl_easy *data, const void *mem, size_t memlen) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result = CURLE_OK; ssize_t nwritten; @@ -332,10 +377,12 @@ static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, const MSH3_HEADER *hd) { struct Curl_easy *data = userp; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result; (void)Request; + DEBUGF(infof(data, "[MSH3] header received, stream=%d", !!stream)); if(!stream || stream->recv_header_complete) { return; } @@ -381,7 +428,8 @@ static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, const uint8_t *buf) { struct Curl_easy *data = IfContext; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result; bool rv = FALSE; @@ -402,7 +450,7 @@ static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, stream->recv_error = result; goto out; } - stream->recv_header_complete = true; + stream->recv_header_complete = TRUE; } result = write_resp_raw(data, buf, *buflen); @@ -420,14 +468,15 @@ static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext, bool aborted, uint64_t error) { struct Curl_easy *data = IfContext; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)Request; if(!stream) return; msh3_lock_acquire(&stream->recv_lock); stream->closed = TRUE; - stream->recv_header_complete = true; + stream->recv_header_complete = TRUE; if(error) stream->error3 = error; if(aborted) @@ -439,7 +488,8 @@ static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request, void *IfContext) { struct Curl_easy *data = IfContext; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(!stream) return; @@ -451,7 +501,8 @@ static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request, void *IfContext, void *SendContext) { struct Curl_easy *data = IfContext; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(!stream) return; (void)Request; @@ -463,7 +514,8 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode *err) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; if(!stream) { @@ -496,7 +548,8 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, static void set_quic_expire(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); /* we have no indication from msh3 when it would be a good time * to juggle the connection again. So, we compromise by calling @@ -513,17 +566,17 @@ static void set_quic_expire(struct Curl_cfilter *cf, struct Curl_easy *data) static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; struct cf_call_data save; - (void)cf; + CURL_TRC_CF(data, cf, "cf_recv(len=%zu), stream=%d", len, !!stream); if(!stream) { *err = CURLE_RECV_ERROR; return -1; } CF_DATA_SAVE(save, cf, data); - CURL_TRC_CF(data, cf, "req: recv with %zu byte buffer", len); msh3_lock_acquire(&stream->recv_lock); @@ -543,7 +596,7 @@ static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(nread < 0) goto out; if(stream->closed) - drain_stream(cf, data); + h3_drain_stream(cf, data); } else if(stream->closed) { nread = recv_closed_stream(cf, data, err); @@ -562,17 +615,17 @@ static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct h1_req_parser h1; struct dynhds h2_headers; MSH3_HEADER *nva = NULL; size_t nheader, i; ssize_t nwritten = -1; struct cf_call_data save; - bool eos; CF_DATA_SAVE(save, cf, data); @@ -615,21 +668,6 @@ static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data, nva[i].ValueLength = e->valuelen; } - switch(data->state.httpreq) { - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - case HTTPREQ_PUT: - /* known request body size or -1 */ - eos = FALSE; - break; - default: - /* there is not request body */ - eos = TRUE; - stream->upload_done = TRUE; - break; - } - CURL_TRC_CF(data, cf, "req: send %zu headers", nheader); stream->req = MsH3RequestOpen(ctx->qconn, &msh3_request_if, data, nva, nheader, @@ -658,7 +696,7 @@ static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data, } /* TODO - msh3/msquic will hold onto this memory until the send complete - event. How do we make sure curl doesn't free it until then? */ + event. How do we make sure curl does not free it until then? */ *err = CURLE_OK; nwritten = len; } @@ -672,37 +710,32 @@ static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data, return nwritten; } -static int cf_msh3_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) +static void cf_msh3_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); - int bitmap = GETSOCK_BLANK; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; CF_DATA_SAVE(save, cf, data); if(stream && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) { - socks[0] = ctx->sock[SP_LOCAL]; - if(stream->recv_error) { - bitmap |= GETSOCK_READSOCK(0); - drain_stream(cf, data); + Curl_pollset_add_in(data, ps, ctx->sock[SP_LOCAL]); + h3_drain_stream(cf, data); } else if(stream->req) { - bitmap |= GETSOCK_READSOCK(0); - drain_stream(cf, data); + Curl_pollset_add_out(data, ps, ctx->sock[SP_LOCAL]); + h3_drain_stream(cf, data); } } - CURL_TRC_CF(data, cf, "select_sock -> %d", bitmap); - CF_DATA_RESTORE(cf, save); - return bitmap; } static bool cf_msh3_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; bool pending = FALSE; @@ -716,36 +749,19 @@ static bool cf_msh3_data_pending(struct Curl_cfilter *cf, pending = !Curl_bufq_is_empty(&stream->recvbuf); msh3_lock_release(&stream->recv_lock); if(pending) - drain_stream(cf, (struct Curl_easy *)data); + h3_drain_stream(cf, (struct Curl_easy *)data); } CF_DATA_RESTORE(cf, save); return pending; } -static void cf_msh3_active(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct cf_msh3_ctx *ctx = cf->ctx; - - /* use this socket from now on */ - cf->conn->sock[cf->sockindex] = ctx->sock[SP_LOCAL]; - /* the first socket info gets set at conn and data */ - if(cf->sockindex == FIRSTSOCKET) { - cf->conn->remote_addr = &ctx->addr; - #ifdef ENABLE_IPV6 - cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE; - #endif - Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port); - } - ctx->active = TRUE; -} - static CURLcode h3_data_pause(struct Curl_cfilter *cf, struct Curl_easy *data, bool pause) { if(!pause) { - drain_stream(cf, data); + h3_drain_stream(cf, data); Curl_expire(data, 0, EXPIRE_RUN_NOW); } return CURLE_OK; @@ -755,7 +771,8 @@ static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf, struct Curl_easy *data, int event, int arg1, void *arg2) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; CURLcode result = CURLE_OK; @@ -786,10 +803,6 @@ static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf, } } break; - case CF_CTRL_CONN_INFO_UPDATE: - CURL_TRC_CF(data, cf, "req: update info"); - cf_msh3_active(cf, data); - break; default: break; } @@ -802,14 +815,21 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_msh3_ctx *ctx = cf->ctx; - bool verify = !!cf->conn->ssl_config.verifypeer; + struct ssl_primary_config *conn_config; MSH3_ADDR addr = {0}; CURLcode result; + bool verify; + + DEBUGASSERT(ctx->initialized); + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) + return CURLE_FAILED_INIT; + verify = !!conn_config->verifypeer; - memcpy(&addr, &ctx->addr.sa_addr, ctx->addr.addrlen); + memcpy(&addr, &ctx->addr.curl_sa_addr, ctx->addr.addrlen); MSH3_SET_PORT(&addr, (uint16_t)cf->conn->remote_port); - if(verify && (cf->conn->ssl_config.CAfile || cf->conn->ssl_config.CApath)) { + if(verify && (conn_config->CAfile || conn_config->CApath)) { /* TODO: need a way to provide trust anchors to MSH3 */ #ifdef DEBUGBUILD /* we need this for our test cases to run */ @@ -827,7 +847,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, ctx->api = MsH3ApiOpen(); if(!ctx->api) { - failf(data, "can't create msh3 api"); + failf(data, "cannot create msh3 api"); return CURLE_FAILED_INIT; } @@ -838,7 +858,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, &addr, !verify); if(!ctx->qconn) { - failf(data, "can't create msh3 connection"); + failf(data, "cannot create msh3 connection"); if(ctx->api) { MsH3ApiClose(ctx->api); ctx->api = NULL; @@ -870,7 +890,7 @@ static CURLcode cf_msh3_connect(struct Curl_cfilter *cf, CF_DATA_SAVE(save, cf, data); if(ctx->sock[SP_LOCAL] == CURL_SOCKET_BAD) { - if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx->sock[0]) < 0) { + if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx->sock[0], FALSE) < 0) { ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD; return CURLE_COULDNT_CONNECT; @@ -891,7 +911,6 @@ static CURLcode cf_msh3_connect(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "handshake succeeded"); cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ cf->conn->httpversion = 30; - cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; cf->connected = TRUE; cf->conn->alpn = CURL_HTTP_VERSION_3; *done = TRUE; @@ -965,10 +984,11 @@ static void cf_msh3_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) CF_DATA_SAVE(save, cf, data); cf_msh3_close(cf, data); - free(cf->ctx); - cf->ctx = NULL; + if(cf->ctx) { + cf_msh3_ctx_free(cf->ctx); + cf->ctx = NULL; + } /* no CF_DATA_RESTORE(cf, save); its gone */ - } static CURLcode cf_msh3_query(struct Curl_cfilter *cf, @@ -1000,7 +1020,7 @@ static CURLcode cf_msh3_query(struct Curl_cfilter *cf, default: break; } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -1024,8 +1044,9 @@ struct Curl_cftype Curl_cft_http3 = { cf_msh3_destroy, cf_msh3_connect, cf_msh3_close, + Curl_cf_def_shutdown, Curl_cf_def_get_host, - cf_msh3_get_select_socks, + cf_msh3_adjust_pollset, cf_msh3_data_pending, cf_msh3_send, cf_msh3_recv, @@ -1035,6 +1056,20 @@ struct Curl_cftype Curl_cft_http3 = { cf_msh3_query, }; +static struct cf_msh3_ctx *h3_get_msh3_ctx(struct Curl_easy *data) +{ + if(data && data->conn) { + struct Curl_cfilter *cf = data->conn->cfilter[FIRSTSOCKET]; + while(cf) { + if(cf->cft == &Curl_cft_http3) + return cf->ctx; + cf = cf->next; + } + } + DEBUGF(infof(data, "no filter context found")); + return NULL; +} + CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf, struct Curl_easy *data, struct connectdata *conn, @@ -1047,22 +1082,20 @@ CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf, (void)data; (void)conn; (void)ai; /* TODO: msh3 resolves itself? */ - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - Curl_sock_assign_addr(&ctx->addr, ai, TRNSPRT_QUIC); - ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; - ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD; + cf_msh3_ctx_init(ctx, ai); result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); out: - *pcf = (!result)? cf : NULL; + *pcf = (!result) ? cf : NULL; if(result) { Curl_safefree(cf); - Curl_safefree(ctx); + cf_msh3_ctx_free(ctx); } return result; @@ -1072,7 +1105,7 @@ bool Curl_conn_is_msh3(const struct Curl_easy *data, const struct connectdata *conn, int sockindex) { - struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + struct Curl_cfilter *cf = conn ? conn->cfilter[sockindex] : NULL; (void)data; for(; cf; cf = cf->next) { diff --git a/deps/curl/lib/vquic/curl_ngtcp2.c b/deps/curl/lib/vquic/curl_ngtcp2.c index 03e911d1842..37009abdc16 100644 --- a/deps/curl/lib/vquic/curl_ngtcp2.c +++ b/deps/curl/lib/vquic/curl_ngtcp2.c @@ -30,7 +30,7 @@ #ifdef USE_OPENSSL #include -#ifdef OPENSSL_IS_BORINGSSL +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) #include #else #include @@ -45,6 +45,7 @@ #endif #include "urldata.h" +#include "hash.h" #include "sendf.h" #include "strdup.h" #include "rand.h" @@ -59,8 +60,10 @@ #include "http1.h" #include "select.h" #include "inet_pton.h" +#include "transfer.h" #include "vquic.h" #include "vquic_int.h" +#include "vquic-tls.h" #include "vtls/keylog.h" #include "vtls/vtls.h" #include "curl_ngtcp2.h" @@ -73,12 +76,8 @@ #include "memdebug.h" -#define H3_ALPN_H3_29 "\x5h3-29" -#define H3_ALPN_H3 "\x2h3" - #define QUIC_MAX_STREAMS (256*1024) #define QUIC_MAX_DATA (1*1024*1024) -#define QUIC_IDLE_TIMEOUT (60*NGTCP2_SECONDS) #define QUIC_HANDSHAKE_TIMEOUT (10*NGTCP2_SECONDS) /* A stream window is the maximum amount we need to buffer for @@ -90,7 +89,7 @@ /* The pool keeps spares around and half of a full stream windows * seems good. More does not seem to improve performance. * The benefit of the pool is that stream buffer to not keep - * spares. So memory consumption goes down when streams run empty, + * spares. Memory consumption goes down when streams run empty, * have a large upload done, etc. */ #define H3_STREAM_POOL_SPARES \ (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE ) / 2 @@ -102,25 +101,6 @@ (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) -#ifdef USE_OPENSSL -#define QUIC_CIPHERS \ - "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ - "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" -#define QUIC_GROUPS "P-256:X25519:P-384:P-521" -#elif defined(USE_GNUTLS) -#define QUIC_PRIORITY \ - "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \ - "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \ - "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \ - "%DISABLE_TLS13_COMPAT_MODE" -#elif defined(USE_WOLFSSL) -#define QUIC_CIPHERS \ - "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ - "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" -#define QUIC_GROUPS "P-256:P-384:P-521" -#endif - - /* * Store ngtcp2 version info in this buffer. */ @@ -134,6 +114,8 @@ void Curl_ngtcp2_ver(char *p, size_t len) struct cf_ngtcp2_ctx { struct cf_quic_ctx q; + struct ssl_peer peer; + struct curl_tls_ctx tls; ngtcp2_path connected_path; ngtcp2_conn *qconn; ngtcp2_cid dcid; @@ -143,29 +125,21 @@ struct cf_ngtcp2_ctx { ngtcp2_transport_params transport_params; ngtcp2_ccerr last_error; ngtcp2_crypto_conn_ref conn_ref; -#ifdef USE_OPENSSL - SSL_CTX *sslctx; - SSL *ssl; -#elif defined(USE_GNUTLS) - struct gtls_instance *gtls; -#elif defined(USE_WOLFSSL) - WOLFSSL_CTX *sslctx; - WOLFSSL *ssl; -#endif struct cf_call_data call_data; nghttp3_conn *h3conn; nghttp3_settings h3settings; struct curltime started_at; /* time the current attempt started */ struct curltime handshake_at; /* time connect handshake finished */ - struct curltime first_byte_at; /* when first byte was recvd */ - struct curltime reconnect_at; /* time the next attempt should start */ struct bufc_pool stream_bufcp; /* chunk pool for streams */ + struct dynbuf scratch; /* temp buffer for header construction */ + struct Curl_hash streams; /* hash `data->mid` to `h3_stream_ctx` */ size_t max_stream_window; /* max flow window for one stream */ + uint64_t max_idle_ms; /* max idle time for QUIC connection */ + uint64_t used_bidi_streams; /* bidi streams we have opened */ + uint64_t max_bidi_streams; /* max bidi streams we can open */ int qlogfd; - BIT(got_first_byte); /* if first byte was received */ -#ifdef USE_OPENSSL - BIT(x509_store_setup); /* if x509 store has been set up */ -#endif + BIT(initialized); + BIT(shutdown_started); /* queued shutdown packets */ }; /* How to access `call_data` from a cf_ngtcp2 filter */ @@ -173,43 +147,87 @@ struct cf_ngtcp2_ctx { #define CF_CTX_CALL_DATA(cf) \ ((struct cf_ngtcp2_ctx *)(cf)->ctx)->call_data +static void h3_stream_hash_free(void *stream); + +static void cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx) +{ + DEBUGASSERT(!ctx->initialized); + ctx->qlogfd = -1; + ctx->version = NGTCP2_PROTO_VER_MAX; + ctx->max_stream_window = H3_STREAM_WINDOW_SIZE; + ctx->max_idle_ms = CURL_QUIC_MAX_IDLE_MS; + Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, + H3_STREAM_POOL_SPARES); + Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); + Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free); + ctx->initialized = TRUE; +} + +static void cf_ngtcp2_ctx_free(struct cf_ngtcp2_ctx *ctx) +{ + if(ctx && ctx->initialized) { + Curl_bufcp_free(&ctx->stream_bufcp); + Curl_dyn_free(&ctx->scratch); + Curl_hash_clean(&ctx->streams); + Curl_hash_destroy(&ctx->streams); + Curl_ssl_peer_cleanup(&ctx->peer); + } + free(ctx); +} + +struct pkt_io_ctx; +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx); +static CURLcode cf_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx); + /** * All about the H3 internals of a stream */ struct h3_stream_ctx { - int64_t id; /* HTTP/3 protocol identifier */ + curl_int64_t id; /* HTTP/3 protocol identifier */ struct bufq sendbuf; /* h3 request body */ - struct bufq recvbuf; /* h3 response body */ struct h1_req_parser h1; /* h1 request parsing */ size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */ - size_t upload_blocked_len; /* the amount written last and EGAINed */ - size_t recv_buf_nonflow; /* buffered bytes, not counting for flow control */ - uint64_t error3; /* HTTP/3 stream error code */ + curl_uint64_t error3; /* HTTP/3 stream error code */ curl_off_t upload_left; /* number of request bytes left to upload */ int status_code; /* HTTP status code */ + CURLcode xfer_result; /* result from xfer_resp_write(_hd) */ bool resp_hds_complete; /* we have a complete, final response */ bool closed; /* TRUE on stream close */ bool reset; /* TRUE on stream reset */ bool send_closed; /* stream is local closed */ + BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ }; -#define H3_STREAM_CTX(d) ((struct h3_stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->h3_ctx \ - : NULL)) -#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx -#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ - H3_STREAM_CTX(d)->id : -2) +#define H3_STREAM_CTX(ctx,data) ((struct h3_stream_ctx *)(\ + data? Curl_hash_offt_get(&(ctx)->streams, (data)->mid) : NULL)) +#define H3_STREAM_CTX_ID(ctx,id) ((struct h3_stream_ctx *)(\ + Curl_hash_offt_get(&(ctx)->streams, (id)))) + +static void h3_stream_ctx_free(struct h3_stream_ctx *stream) +{ + Curl_bufq_free(&stream->sendbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); +} + +static void h3_stream_hash_free(void *stream) +{ + DEBUGASSERT(stream); + h3_stream_ctx_free((struct h3_stream_ctx *)stream); +} static CURLcode h3_data_setup(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - if(!data || !data->req.p.http) { - failf(data, "initialization failure, transfer not http initialized"); + if(!data) return CURLE_FAILED_INIT; - } if(stream) return CURLE_OK; @@ -223,29 +241,97 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, H3_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); stream->sendbuf_len_in_flight = 0; - /* on recv, we need a flexible buffer limit since we also write - * headers to it that are not counted against the nghttp3 flow limits. */ - Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, - H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); - stream->recv_buf_nonflow = 0; Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - H3_STREAM_LCTX(data) = stream; + if(!Curl_hash_offt_set(&ctx->streams, data->mid, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } -static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +static void cf_ngtcp2_stream_close(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream) { - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_ngtcp2_ctx *ctx = cf->ctx; + DEBUGASSERT(data); + DEBUGASSERT(stream); + if(!stream->closed && ctx->qconn && ctx->h3conn) { + CURLcode result; + + nghttp3_conn_set_stream_user_data(ctx->h3conn, stream->id, NULL); + ngtcp2_conn_set_stream_user_data(ctx->qconn, stream->id, NULL); + stream->closed = TRUE; + (void)ngtcp2_conn_shutdown_stream(ctx->qconn, 0, stream->id, + NGHTTP3_H3_REQUEST_CANCELLED); + result = cf_progress_egress(cf, data, NULL); + if(result) + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cancel stream -> %d", + stream->id, result); + } +} +static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)cf; if(stream) { - CURL_TRC_CF(data, cf, "[%"PRId64"] easy handle is done", stream->id); - Curl_bufq_free(&stream->sendbuf); - Curl_bufq_free(&stream->recvbuf); - Curl_h1_req_parse_free(&stream->h1); - free(stream); - H3_STREAM_LCTX(data) = NULL; + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] easy handle is done", + stream->id); + cf_ngtcp2_stream_close(cf, data, stream); + Curl_hash_offt_remove(&ctx->streams, data->mid); + } +} + +static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf, + struct Curl_easy *data, + int64_t stream_id, + struct h3_stream_ctx **pstream) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream; + + (void)cf; + stream = H3_STREAM_CTX(ctx, data); + if(stream && stream->id == stream_id) { + *pstream = stream; + return data; + } + else { + struct Curl_llist_node *e; + DEBUGASSERT(data->multi); + for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *sdata = Curl_node_elem(e); + if(sdata->conn != data->conn) + continue; + stream = H3_STREAM_CTX(ctx, sdata); + if(stream && stream->id == stream_id) { + *pstream = stream; + return sdata; + } + } + } + *pstream = NULL; + return NULL; +} + +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(stream && stream->upload_left && !stream->send_closed) + bits |= CURL_CSELECT_OUT; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); } } @@ -257,14 +343,17 @@ struct pkt_io_ctx { struct Curl_cfilter *cf; struct Curl_easy *data; ngtcp2_tstamp ts; - size_t pkt_count; ngtcp2_path_storage ps; }; -static ngtcp2_tstamp timestamp(void) +static void pktx_update_time(struct pkt_io_ctx *pktx, + struct Curl_cfilter *cf) { - struct curltime ct = Curl_now(); - return ct.tv_sec * NGTCP2_SECONDS + ct.tv_usec * NGTCP2_MICROSECONDS; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + + vquic_ctx_update_time(&ctx->q); + pktx->ts = (ngtcp2_tstamp)ctx->q.last_op.tv_sec * NGTCP2_SECONDS + + (ngtcp2_tstamp)ctx->q.last_op.tv_usec * NGTCP2_MICROSECONDS; } static void pktx_init(struct pkt_io_ctx *pktx, @@ -273,17 +362,10 @@ static void pktx_init(struct pkt_io_ctx *pktx, { pktx->cf = cf; pktx->data = data; - pktx->ts = timestamp(); - pktx->pkt_count = 0; ngtcp2_path_storage_zero(&pktx->ps); + pktx_update_time(pktx, cf); } -static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct pkt_io_ctx *pktx); -static CURLcode cf_progress_egress(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct pkt_io_ctx *pktx); static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, uint64_t datalen, void *user_data, void *stream_user_data); @@ -346,7 +428,7 @@ static void quic_settings(struct cf_ngtcp2_ctx *ctx, s->initial_ts = pktx->ts; s->handshake_timeout = QUIC_HANDSHAKE_TIMEOUT; s->max_window = 100 * ctx->max_stream_window; - s->max_stream_window = ctx->max_stream_window; + s->max_stream_window = 10 * ctx->max_stream_window; t->initial_max_data = 10 * ctx->max_stream_window; t->initial_max_stream_data_bidi_local = ctx->max_stream_window; @@ -354,418 +436,75 @@ static void quic_settings(struct cf_ngtcp2_ctx *ctx, t->initial_max_stream_data_uni = ctx->max_stream_window; t->initial_max_streams_bidi = QUIC_MAX_STREAMS; t->initial_max_streams_uni = QUIC_MAX_STREAMS; - t->max_idle_timeout = QUIC_IDLE_TIMEOUT; + t->max_idle_timeout = (ctx->max_idle_ms * NGTCP2_MILLISECONDS); if(ctx->qlogfd != -1) { s->qlog_write = qlog_callback; } } -#ifdef USE_OPENSSL -static void keylog_callback(const SSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} -#elif defined(USE_GNUTLS) -static int keylog_callback(gnutls_session_t session, const char *label, - const gnutls_datum_t *secret) -{ - gnutls_datum_t crandom; - gnutls_datum_t srandom; - - gnutls_session_get_random(session, &crandom, &srandom); - if(crandom.size != 32) { - return -1; - } +static CURLcode init_ngh3_conn(struct Curl_cfilter *cf); - Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); - return 0; -} -#elif defined(USE_WOLFSSL) -#if defined(HAVE_SECRET_CALLBACK) -static void keylog_callback(const WOLFSSL *ssl, const char *line) +static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data) { - (void)ssl; - Curl_tls_keylog_write_line(line); + (void)user_data; + (void)tconn; + return 0; } -#endif -#endif -static int init_ngh3_conn(struct Curl_cfilter *cf); +static void cf_ngtcp2_conn_close(struct Curl_cfilter *cf, + struct Curl_easy *data); -#ifdef USE_OPENSSL -static CURLcode quic_ssl_ctx(SSL_CTX **pssl_ctx, - struct Curl_cfilter *cf, struct Curl_easy *data) +static bool cf_ngtcp2_err_is_fatal(int code) { - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct connectdata *conn = cf->conn; - CURLcode result = CURLE_FAILED_INIT; - SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); - - if(!ssl_ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - -#ifdef OPENSSL_IS_BORINGSSL - if(ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); - goto out; - } -#else - if(ngtcp2_crypto_quictls_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_quictls_configure_client_context failed"); - goto out; - } -#endif - - SSL_CTX_set_default_verify_paths(ssl_ctx); - -#ifdef OPENSSL_IS_BORINGSSL - if(SSL_CTX_set1_curves_list(ssl_ctx, QUIC_GROUPS) != 1) { - failf(data, "SSL_CTX_set1_curves_list failed"); - goto out; - } -#else - if(SSL_CTX_set_ciphersuites(ssl_ctx, QUIC_CIPHERS) != 1) { - char error_buffer[256]; - ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); - failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer); - goto out; - } - - if(SSL_CTX_set1_groups_list(ssl_ctx, QUIC_GROUPS) != 1) { - failf(data, "SSL_CTX_set1_groups_list failed"); - goto out; - } -#endif - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); - } - - /* OpenSSL always tries to verify the peer, this only says whether it should - * fail to connect if the verification fails, or if it should continue - * anyway. In the latter case the result of the verification is checked with - * SSL_get_verify_result() below. */ - SSL_CTX_set_verify(ssl_ctx, conn->ssl_config.verifypeer ? - SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - /* When a user callback is installed to modify the SSL_CTX, - * we need to do the full initialization before calling it. - * See: #11800 */ - if(!ctx->x509_store_setup) { - result = Curl_ssl_setup_x509_store(cf, data, ssl_ctx); - if(result) - goto out; - ctx->x509_store_setup = TRUE; - } - Curl_set_in_callback(data, true); - result = (*data->set.ssl.fsslctx)(data, ssl_ctx, - data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); - if(result) { - failf(data, "error signaled by ssl ctx callback"); - goto out; - } - } - result = CURLE_OK; - -out: - *pssl_ctx = result? NULL : ssl_ctx; - if(result && ssl_ctx) - SSL_CTX_free(ssl_ctx); - return result; + return (NGTCP2_ERR_FATAL >= code) || + (NGTCP2_ERR_DROP_CONN == code) || + (NGTCP2_ERR_IDLE_CLOSE == code); } -static CURLcode quic_set_client_cert(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - SSL_CTX *ssl_ctx = ctx->sslctx; - const struct ssl_config_data *ssl_config; - - ssl_config = Curl_ssl_get_config(data, FIRSTSOCKET); - DEBUGASSERT(ssl_config); - - if(ssl_config->primary.clientcert || ssl_config->primary.cert_blob - || ssl_config->cert_type) { - return Curl_ossl_set_client_cert( - data, ssl_ctx, ssl_config->primary.clientcert, - ssl_config->primary.cert_blob, ssl_config->cert_type, - ssl_config->key, ssl_config->key_blob, - ssl_config->key_type, ssl_config->key_passwd); - } - - return CURLE_OK; -} - -/** SSL callbacks ***/ - -static CURLcode quic_init_ssl(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - const uint8_t *alpn = NULL; - size_t alpnlen = 0; - unsigned char checkip[16]; - - DEBUGASSERT(!ctx->ssl); - ctx->ssl = SSL_new(ctx->sslctx); - - SSL_set_app_data(ctx->ssl, &ctx->conn_ref); - SSL_set_connect_state(ctx->ssl); - SSL_set_quic_use_legacy_codepoint(ctx->ssl, 0); - - alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3; - alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1; - if(alpn) - SSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen); - - /* set SNI */ - if((0 == Curl_inet_pton(AF_INET, cf->conn->host.name, checkip)) -#ifdef ENABLE_IPV6 - && (0 == Curl_inet_pton(AF_INET6, cf->conn->host.name, checkip)) -#endif - ) { - char *snihost = Curl_ssl_snihost(data, cf->conn->host.name, NULL); - if(!snihost || !SSL_set_tlsext_host_name(ctx->ssl, snihost)) { - failf(data, "Failed set SNI"); - SSL_free(ctx->ssl); - ctx->ssl = NULL; - return CURLE_QUIC_CONNECT_ERROR; - } - } - return CURLE_OK; -} -#elif defined(USE_GNUTLS) -static CURLcode quic_init_ssl(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void cf_ngtcp2_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - CURLcode result; - gnutls_datum_t alpn[2]; - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - const char * const hostname = cf->conn->host.name; - long * const pverifyresult = &data->set.ssl.certverifyresult; - int rc; - - DEBUGASSERT(ctx->gtls == NULL); - ctx->gtls = calloc(1, sizeof(*(ctx->gtls))); - if(!ctx->gtls) - return CURLE_OUT_OF_MEMORY; - - result = gtls_client_init(data, &cf->conn->ssl_config, &data->set.ssl, - hostname, ctx->gtls, pverifyresult); - if(result) - return result; - - gnutls_session_set_ptr(ctx->gtls->session, &ctx->conn_ref); - - if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls->session) != 0) { - CURL_TRC_CF(data, cf, - "ngtcp2_crypto_gnutls_configure_client_session failed\n"); - return CURLE_QUIC_CONNECT_ERROR; - } - - rc = gnutls_priority_set_direct(ctx->gtls->session, QUIC_PRIORITY, NULL); - if(rc < 0) { - CURL_TRC_CF(data, cf, "gnutls_priority_set_direct failed: %s\n", - gnutls_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - gnutls_session_set_keylog_function(ctx->gtls->session, keylog_callback); - } - - /* strip the first byte (the length) from NGHTTP3_ALPN_H3 */ - alpn[0].data = (unsigned char *)H3_ALPN_H3_29 + 1; - alpn[0].size = sizeof(H3_ALPN_H3_29) - 2; - alpn[1].data = (unsigned char *)H3_ALPN_H3 + 1; - alpn[1].size = sizeof(H3_ALPN_H3) - 2; - - gnutls_alpn_set_protocols(ctx->gtls->session, - alpn, 2, GNUTLS_ALPN_MANDATORY); - return CURLE_OK; -} -#elif defined(USE_WOLFSSL) - -static CURLcode quic_ssl_ctx(WOLFSSL_CTX **pssl_ctx, - struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct connectdata *conn = cf->conn; - CURLcode result = CURLE_FAILED_INIT; - WOLFSSL_CTX *ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); - - if(!ssl_ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); - goto out; - } - - wolfSSL_CTX_set_default_verify_paths(ssl_ctx); - - if(wolfSSL_CTX_set_cipher_list(ssl_ctx, QUIC_CIPHERS) != 1) { - char error_buffer[256]; - ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); - failf(data, "wolfSSL_CTX_set_cipher_list: %s", error_buffer); - goto out; - } - - if(wolfSSL_CTX_set1_groups_list(ssl_ctx, (char *)QUIC_GROUPS) != 1) { - failf(data, "SSL_CTX_set1_groups_list failed"); - goto out; - } - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { -#if defined(HAVE_SECRET_CALLBACK) - wolfSSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); -#else - failf(data, "wolfSSL was built without keylog callback"); - goto out; -#endif - } - - if(conn->ssl_config.verifypeer) { - const char * const ssl_cafile = conn->ssl_config.CAfile; - const char * const ssl_capath = conn->ssl_config.CApath; - - wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); - if(conn->ssl_config.CAfile || conn->ssl_config.CApath) { - /* tell wolfSSL where to find CA certificates that are used to verify - the server's certificate. */ - if(!wolfSSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:" - " CAfile: %s CApath: %s", - ssl_cafile ? ssl_cafile : "none", - ssl_capath ? ssl_capath : "none"); - goto out; - } - infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); - infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); + if(!ctx->last_error.error_code) { + if(NGTCP2_ERR_CRYPTO == code) { + ngtcp2_ccerr_set_tls_alert(&ctx->last_error, + ngtcp2_conn_get_tls_alert(ctx->qconn), + NULL, 0); } -#ifdef CURL_CA_FALLBACK else { - /* verifying the peer without any CA certificates won't work so - use wolfssl's built-in default as fallback */ - wolfSSL_CTX_set_default_verify_paths(ssl_ctx); + ngtcp2_ccerr_set_liberr(&ctx->last_error, code, NULL, 0); } -#endif } - else { - wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); - } - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - Curl_set_in_callback(data, true); - result = (*data->set.ssl.fsslctx)(data, ssl_ctx, - data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); - if(result) { - failf(data, "error signaled by ssl ctx callback"); - goto out; - } - } - result = CURLE_OK; - -out: - *pssl_ctx = result? NULL : ssl_ctx; - if(result && ssl_ctx) - SSL_CTX_free(ssl_ctx); - return result; + if(cf_ngtcp2_err_is_fatal(code)) + cf_ngtcp2_conn_close(cf, data); } -/** SSL callbacks ***/ - -static CURLcode quic_init_ssl(struct Curl_cfilter *cf, - struct Curl_easy *data) +static bool cf_ngtcp2_h3_err_is_fatal(int code) { - struct cf_ngtcp2_ctx *ctx = cf->ctx; - const uint8_t *alpn = NULL; - size_t alpnlen = 0; - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - const char * const hostname = cf->conn->host.name; - - (void)data; - DEBUGASSERT(!ctx->ssl); - ctx->ssl = wolfSSL_new(ctx->sslctx); - - wolfSSL_set_app_data(ctx->ssl, &ctx->conn_ref); - wolfSSL_set_connect_state(ctx->ssl); - wolfSSL_set_quic_use_legacy_codepoint(ctx->ssl, 0); - - alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3; - alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1; - if(alpn) - wolfSSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen); - - /* set SNI */ - wolfSSL_UseSNI(ctx->ssl, WOLFSSL_SNI_HOST_NAME, - hostname, (unsigned short)strlen(hostname)); - - return CURLE_OK; + return (NGHTTP3_ERR_FATAL >= code) || + (NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM == code); } -#endif /* defined(USE_WOLFSSL) */ -static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data) +static void cf_ngtcp2_h3_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code) { - (void)user_data; - (void)tconn; - return 0; -} - -static void report_consumed_data(struct Curl_cfilter *cf, - struct Curl_easy *data, - size_t consumed) -{ - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); struct cf_ngtcp2_ctx *ctx = cf->ctx; - - if(!stream) - return; - /* the HTTP/1.1 response headers are written to the buffer, but - * consuming those does not count against flow control. */ - if(stream->recv_buf_nonflow) { - if(consumed >= stream->recv_buf_nonflow) { - consumed -= stream->recv_buf_nonflow; - stream->recv_buf_nonflow = 0; - } - else { - stream->recv_buf_nonflow -= consumed; - consumed = 0; - } - } - if(consumed > 0) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] ACK %zu bytes of DATA", - stream->id, consumed); - ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, - consumed); - ngtcp2_conn_extend_max_offset(ctx->qconn, consumed); + if(!ctx->last_error.error_code) { + ngtcp2_ccerr_set_application_error(&ctx->last_error, + nghttp3_err_infer_quic_app_error_code(code), NULL, 0); } + if(cf_ngtcp2_h3_err_is_fatal(code)) + cf_ngtcp2_conn_close(cf, data); } static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, - int64_t stream_id, uint64_t offset, + int64_t sid, uint64_t offset, const uint8_t *buf, size_t buflen, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; nghttp3_ssize nconsumed; int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0; struct Curl_easy *data = stream_user_data; @@ -774,20 +513,26 @@ static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, nconsumed = nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin); - CURL_TRC_CF(data, cf, "[%" PRId64 "] read_stream(len=%zu) -> %zd", - stream_id, buflen, nconsumed); + if(!data) + data = CF_DATA_CURRENT(cf); + if(data) + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read_stream(len=%zu) -> %zd", + stream_id, buflen, nconsumed); if(nconsumed < 0) { - ngtcp2_ccerr_set_application_error( - &ctx->last_error, - nghttp3_err_infer_quic_app_error_code((int)nconsumed), NULL, 0); + struct h3_stream_ctx *stream = H3_STREAM_CTX_ID(ctx, stream_id); + if(data && stream) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] error on known stream, " + "reset=%d, closed=%d", + stream_id, stream->reset, stream->closed); + } return NGTCP2_ERR_CALLBACK_FAILURE; } /* number of bytes inside buflen which consists of framing overhead * including QPACK HEADERS. In other words, it does not consume payload of * DATA frame. */ - ngtcp2_conn_extend_max_stream_offset(tconn, stream_id, nconsumed); - ngtcp2_conn_extend_max_offset(tconn, nconsumed); + ngtcp2_conn_extend_max_stream_offset(tconn, stream_id, (uint64_t)nconsumed); + ngtcp2_conn_extend_max_offset(tconn, (uint64_t)nconsumed); return 0; } @@ -807,7 +552,7 @@ cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id, (void)stream_user_data; rv = nghttp3_conn_add_ack_offset(ctx->h3conn, stream_id, datalen); - if(rv) { + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -815,41 +560,45 @@ cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id, } static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags, - int64_t stream3_id, uint64_t app_error_code, + int64_t sid, uint64_t app_error_code, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct Curl_easy *data = stream_user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = (curl_int64_t)sid; int rv; (void)tconn; - (void)data; /* stream is closed... */ + if(!data) + data = CF_DATA_CURRENT(cf); + if(!data) + return NGTCP2_ERR_CALLBACK_FAILURE; if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { app_error_code = NGHTTP3_H3_NO_ERROR; } - rv = nghttp3_conn_close_stream(ctx->h3conn, stream3_id, - app_error_code); - CURL_TRC_CF(data, cf, "[%" PRId64 "] quic close(err=%" - PRIu64 ") -> %d", stream3_id, app_error_code, rv); - if(rv) { - ngtcp2_ccerr_set_application_error( - &ctx->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); + rv = nghttp3_conn_close_stream(ctx->h3conn, stream_id, app_error_code); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] quic close(app_error=%" + FMT_PRIu64 ") -> %d", stream_id, (curl_uint64_t)app_error_code, + rv); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + cf_ngtcp2_h3_err_set(cf, data, rv); return NGTCP2_ERR_CALLBACK_FAILURE; } return 0; } -static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id, +static int cb_stream_reset(ngtcp2_conn *tconn, int64_t sid, uint64_t final_size, uint64_t app_error_code, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; struct Curl_easy *data = stream_user_data; int rv; (void)tconn; @@ -858,8 +607,8 @@ static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id, (void)data; rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); - CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); - if(rv) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] reset -> %d", stream_id, rv); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -878,7 +627,7 @@ static int cb_stream_stop_sending(ngtcp2_conn *tconn, int64_t stream_id, (void)stream_user_data; rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); - if(rv) { + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -889,29 +638,44 @@ static int cb_extend_max_local_streams_bidi(ngtcp2_conn *tconn, uint64_t max_streams, void *user_data) { - (void)tconn; - (void)max_streams; - (void)user_data; + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + (void)tconn; + ctx->max_bidi_streams = max_streams; + if(data) + CURL_TRC_CF(data, cf, "max bidi streams now %" FMT_PRIu64 + ", used %" FMT_PRIu64, (curl_uint64_t)ctx->max_bidi_streams, + (curl_uint64_t)ctx->used_bidi_streams); return 0; } -static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id, +static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t sid, uint64_t max_data, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct Curl_easy *s_data; + struct h3_stream_ctx *stream; int rv; (void)tconn; (void)max_data; (void)stream_user_data; rv = nghttp3_conn_unblock_stream(ctx->h3conn, stream_id); - if(rv) { + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } - + s_data = get_stream_easy(cf, data, stream_id, &stream); + if(s_data && stream && stream->quic_flow_blocked) { + CURL_TRC_CF(s_data, cf, "[%" FMT_PRId64 "] unblock quic flow", stream_id); + stream->quic_flow_blocked = FALSE; + h3_drain_stream(cf, s_data); + } return 0; } @@ -923,7 +687,7 @@ static void cb_rand(uint8_t *dest, size_t destlen, result = Curl_rand(NULL, dest, destlen); if(result) { - /* cb_rand is only used for non-cryptographic context. If Curl_rand + /* cb_rand is only used for non-cryptographic context. If Curl_rand failed, just fill 0 and call it *random*. */ memset(dest, 0, destlen); } @@ -966,6 +730,11 @@ static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_encryption_level level, return 0; } +#if defined(_MSC_VER) && defined(_DLL) +# pragma warning(push) +# pragma warning(disable:4232) /* MSVC extension, dllimport identity */ +#endif + static ngtcp2_callbacks ng_callbacks = { ngtcp2_crypto_client_initial_cb, NULL, /* recv_client_initial */ @@ -1009,6 +778,10 @@ static ngtcp2_callbacks ng_callbacks = { NULL, /* early_data_rejected */ }; +#if defined(_MSC_VER) && defined(_DLL) +# pragma warning(pop) +#endif + /** * Connection maintenance like timeouts on packet ACKs etc. are done by us, not * the OS like for TCP. POLL events on the socket therefore are not @@ -1029,7 +802,7 @@ static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, pktx = &local_pktx; } else { - pktx->ts = timestamp(); + pktx_update_time(pktx, cf); } expiry = ngtcp2_conn_get_expiry(ctx->qconn); @@ -1040,7 +813,7 @@ static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, if(rv) { failf(data, "ngtcp2_conn_handle_expiry returned error: %s", ngtcp2_strerror(rv)); - ngtcp2_ccerr_set_liberr(&ctx->last_error, rv, NULL, 0); + cf_ngtcp2_err_set(cf, data, rv); return CURLE_SEND_ERROR; } result = cf_progress_ingress(cf, data, pktx); @@ -1058,62 +831,55 @@ static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, if(timeout % NGTCP2_MILLISECONDS) { timeout += NGTCP2_MILLISECONDS; } - Curl_expire(data, timeout / NGTCP2_MILLISECONDS, EXPIRE_QUIC); + Curl_expire(data, (timediff_t)(timeout / NGTCP2_MILLISECONDS), + EXPIRE_QUIC); } } return CURLE_OK; } -static int cf_ngtcp2_get_select_socks(struct Curl_cfilter *cf, +static void cf_ngtcp2_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) + struct easy_pollset *ps) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct SingleRequest *k = &data->req; - int rv = GETSOCK_BLANK; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - socks[0] = ctx->q.sockfd; + bool want_recv, want_send; - /* in HTTP/3 we can always get a frame, so check read */ - rv |= GETSOCK_READSOCK(0); + if(!ctx->qconn) + return; - /* we're still uploading or the HTTP/2 layer wants to send data */ - if((k->keepon & KEEP_SENDBITS) == KEEP_SEND && - ngtcp2_conn_get_cwnd_left(ctx->qconn) && - ngtcp2_conn_get_max_data_left(ctx->qconn) && - stream && nghttp3_conn_is_stream_writable(ctx->h3conn, stream->id)) - rv |= GETSOCK_WRITESOCK(0); + Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); + if(!want_send && !Curl_bufq_is_empty(&ctx->q.sendbuf)) + want_send = TRUE; - CF_DATA_RESTORE(cf, save); - return rv; -} + if(want_recv || want_send) { + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct cf_call_data save; + bool c_exhaust, s_exhaust; -static void h3_drain_stream(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); - unsigned char bits; - - (void)cf; - bits = CURL_CSELECT_IN; - if(stream && stream->upload_left && !stream->send_closed) - bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - data->state.dselect_bits = bits; - Curl_expire(data, 0, EXPIRE_RUN_NOW); + CF_DATA_SAVE(save, cf, data); + c_exhaust = want_send && (!ngtcp2_conn_get_cwnd_left(ctx->qconn) || + !ngtcp2_conn_get_max_data_left(ctx->qconn)); + s_exhaust = want_send && stream && stream->id >= 0 && + stream->quic_flow_blocked; + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + !Curl_bufq_is_empty(&ctx->q.sendbuf); + + Curl_pollset_set(data, ps, ctx->q.sockfd, want_recv, want_send); + CF_DATA_RESTORE(cf, save); } } -static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_stream_close(nghttp3_conn *conn, int64_t sid, uint64_t app_error_code, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + curl_int64_t stream_id = (curl_int64_t)sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)conn; (void)stream_id; @@ -1122,64 +888,60 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, return 0; stream->closed = TRUE; - stream->error3 = app_error_code; + stream->error3 = (curl_uint64_t)app_error_code; if(stream->error3 != NGHTTP3_H3_NO_ERROR) { stream->reset = TRUE; stream->send_closed = TRUE; - CURL_TRC_CF(data, cf, "[%" PRId64 "] RESET: error %" PRId64, + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] RESET: error %" FMT_PRIu64, stream->id, stream->error3); } else { - CURL_TRC_CF(data, cf, "[%" PRId64 "] CLOSED", stream->id); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] CLOSED", stream->id); } - data->req.keepon &= ~KEEP_SEND_HOLD; h3_drain_stream(cf, data); return 0; } -/* - * write_resp_raw() copies response data in raw format to the `data`'s - * receive buffer. If not enough space is available, it appends to the - * `data`'s overflow buffer. - */ -static CURLcode write_resp_raw(struct Curl_cfilter *cf, - struct Curl_easy *data, - const void *mem, size_t memlen, - bool flow) +static void h3_xfer_write_resp_hd(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + const char *buf, size_t blen, bool eos) { - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); - CURLcode result = CURLE_OK; - ssize_t nwritten; - (void)cf; - if(!stream) { - return CURLE_RECV_ERROR; - } - nwritten = Curl_bufq_write(&stream->recvbuf, mem, memlen, &result); - if(nwritten < 0) { - return result; + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) { + stream->xfer_result = Curl_xfer_write_resp_hd(data, buf, blen, eos); + if(stream->xfer_result) + CURL_TRC_CF(data, cf, "[%"FMT_PRId64"] error %d writing %zu " + "bytes of headers", stream->id, stream->xfer_result, blen); } +} - if(!flow) - stream->recv_buf_nonflow += (size_t)nwritten; +static void h3_xfer_write_resp(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + const char *buf, size_t blen, bool eos) +{ - if((size_t)nwritten < memlen) { - /* This MUST not happen. Our recbuf is dimensioned to hold the - * full max_stream_window and then some for this very reason. */ - DEBUGASSERT(0); - return CURLE_RECV_ERROR; + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) { + stream->xfer_result = Curl_xfer_write_resp(data, buf, blen, eos); + /* If the transfer write is errored, we do not want any more data */ + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%"FMT_PRId64"] error %d writing %zu bytes " + "of data", stream->id, stream->xfer_result, blen); + } } - return result; } static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, - const uint8_t *buf, size_t buflen, + const uint8_t *buf, size_t blen, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); - CURLcode result; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)conn; (void)stream3_id; @@ -1187,14 +949,14 @@ static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, if(!stream) return NGHTTP3_ERR_CALLBACK_FAILURE; - result = write_resp_raw(cf, data, buf, buflen, TRUE); - if(result) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, ERROR receiving %d", - stream->id, buflen, result); - return NGHTTP3_ERR_CALLBACK_FAILURE; + h3_xfer_write_resp(cf, data, stream, (char *)buf, blen, FALSE); + if(blen) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] ACK %zu bytes of DATA", + stream->id, blen); + ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, blen); + ngtcp2_conn_extend_max_offset(ctx->qconn, blen); } - CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu", stream->id, buflen); - h3_drain_stream(cf, data); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] DATA len=%zu", stream->id, blen); return 0; } @@ -1214,13 +976,14 @@ static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream3_id, return 0; } -static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_end_headers(nghttp3_conn *conn, int64_t sid, int fin, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); - CURLcode result = CURLE_OK; + curl_int64_t stream_id = (curl_int64_t)sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)conn; (void)stream_id; (void)fin; @@ -1228,13 +991,10 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, if(!stream) return 0; - /* add a CRLF only if we've received some headers */ - result = write_resp_raw(cf, data, "\r\n", 2, FALSE); - if(result) { - return -1; - } + /* add a CRLF only if we have received some headers */ + h3_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), stream->closed); - CURL_TRC_CF(data, cf, "[%" PRId64 "] end_headers, status=%d", + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] end_headers, status=%d", stream_id, stream->status_code); if(stream->status_code / 100 != 1) { stream->resp_hds_complete = TRUE; @@ -1243,16 +1003,18 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, return 0; } -static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_recv_header(nghttp3_conn *conn, int64_t sid, int32_t token, nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result = CURLE_OK; (void)conn; (void)stream_id; @@ -1265,42 +1027,45 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, return 0; if(token == NGHTTP3_QPACK_TOKEN__STATUS) { - char line[14]; /* status line is always 13 characters long */ - size_t ncopy; result = Curl_http_decode_status(&stream->status_code, (const char *)h3val.base, h3val.len); if(result) return -1; - ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", - stream->status_code); - CURL_TRC_CF(data, cf, "[%" PRId64 "] status: %s", stream_id, line); - result = write_resp_raw(cf, data, line, ncopy, FALSE); + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, STRCONST("HTTP/3 ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, + (const char *)h3val.base, h3val.len); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(" \r\n")); + if(!result) + h3_xfer_write_resp_hd(cf, data, stream, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] status: %s", + stream_id, Curl_dyn_ptr(&ctx->scratch)); if(result) { return -1; } } else { /* store as an HTTP1-style header */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] header: %.*s: %.*s", + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] header: %.*s: %.*s", stream_id, (int)h3name.len, h3name.base, (int)h3val.len, h3val.base); - result = write_resp_raw(cf, data, h3name.base, h3name.len, FALSE); - if(result) { - return -1; - } - result = write_resp_raw(cf, data, ": ", 2, FALSE); - if(result) { - return -1; - } - result = write_resp_raw(cf, data, h3val.base, h3val.len, FALSE); - if(result) { - return -1; - } - result = write_resp_raw(cf, data, "\r\n", 2, FALSE); - if(result) { - return -1; - } + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, + (const char *)h3name.base, h3name.len); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(": ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, + (const char *)h3val.base, h3val.len); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST("\r\n")); + if(!result) + h3_xfer_write_resp_hd(cf, data, stream, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); } return 0; } @@ -1318,17 +1083,18 @@ static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id, rv = ngtcp2_conn_shutdown_stream_read(ctx->qconn, 0, stream_id, app_error_code); if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { - return NGTCP2_ERR_CALLBACK_FAILURE; + return NGHTTP3_ERR_CALLBACK_FAILURE; } return 0; } -static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, +static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t sid, uint64_t app_error_code, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; struct Curl_easy *data = stream_user_data; int rv; (void)conn; @@ -1336,9 +1102,9 @@ static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, 0, stream_id, app_error_code); - CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] reset -> %d", stream_id, rv); if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { - return NGTCP2_ERR_CALLBACK_FAILURE; + return NGHTTP3_ERR_CALLBACK_FAILURE; } return 0; @@ -1362,7 +1128,7 @@ static nghttp3_callbacks ngh3_callbacks = { NULL /* recv_settings */ }; -static int init_ngh3_conn(struct Curl_cfilter *cf) +static CURLcode init_ngh3_conn(struct Curl_cfilter *cf) { struct cf_ngtcp2_ctx *ctx = cf->ctx; CURLcode result; @@ -1431,15 +1197,14 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, (void)cf; if(stream->reset) { - failf(data, - "HTTP/3 stream %" PRId64 " reset by server", stream->id); - *err = stream->resp_hds_complete? CURLE_PARTIAL_FILE : CURLE_HTTP3; + failf(data, "HTTP/3 stream %" FMT_PRId64 " reset by server", stream->id); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; goto out; } else if(!stream->resp_hds_complete) { failf(data, - "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" - " all response header fields, treated as error", + "HTTP/3 stream %" FMT_PRId64 " was closed cleanly, but before " + "getting all response header fields, treated as error", stream->id); *err = CURLE_HTTP3; goto out; @@ -1453,15 +1218,16 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, /* incoming data frames on the h3 stream */ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, - char *buf, size_t len, CURLcode *err) + char *buf, size_t blen, CURLcode *err) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; struct cf_call_data save; struct pkt_io_ctx pktx; (void)ctx; + (void)buf; CF_DATA_SAVE(save, cf, data); DEBUGASSERT(cf->connected); @@ -1472,51 +1238,30 @@ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, pktx_init(&pktx, cf, data); - if(!stream) { + if(!stream || ctx->shutdown_started) { *err = CURLE_RECV_ERROR; goto out; } - if(!Curl_bufq_is_empty(&stream->recvbuf)) { - nread = Curl_bufq_read(&stream->recvbuf, - (unsigned char *)buf, len, err); - if(nread < 0) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) " - "-> %zd, %d", stream->id, len, nread, *err); - goto out; - } - report_consumed_data(cf, data, nread); - } - if(cf_progress_ingress(cf, data, &pktx)) { *err = CURLE_RECV_ERROR; nread = -1; goto out; } - /* recvbuf had nothing before, maybe after progressing ingress? */ - if(nread < 0 && !Curl_bufq_is_empty(&stream->recvbuf)) { - nread = Curl_bufq_read(&stream->recvbuf, - (unsigned char *)buf, len, err); - if(nread < 0) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) " - "-> %zd, %d", stream->id, len, nread, *err); - goto out; - } - report_consumed_data(cf, data, nread); - } - - if(nread > 0) { - h3_drain_stream(cf, data); - } - else { - if(stream->closed) { - nread = recv_closed_stream(cf, data, stream, err); - goto out; - } - *err = CURLE_AGAIN; + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] xfer write failed", stream->id); + cf_ngtcp2_stream_close(cf, data, stream); + *err = stream->xfer_result; nread = -1; + goto out; + } + else if(stream->closed) { + nread = recv_closed_stream(cf, data, stream, err); + goto out; } + *err = CURLE_AGAIN; + nread = -1; out: if(cf_progress_egress(cf, data, &pktx)) { @@ -1530,8 +1275,8 @@ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, nread = -1; } } - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv(len=%zu) -> %zd, %d", - stream? stream->id : -1, len, nread, *err); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_recv(blen=%zu) -> %zd, %d", + stream ? stream->id : -1, blen, nread, *err); CF_DATA_RESTORE(cf, save); return nread; } @@ -1541,8 +1286,9 @@ static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); size_t skiplen; (void)cf; @@ -1558,17 +1304,11 @@ static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, Curl_bufq_skip(&stream->sendbuf, skiplen); stream->sendbuf_len_in_flight -= skiplen; - /* Everything ACKed, we resume upload processing */ - if(!stream->sendbuf_len_in_flight) { + /* Resume upload processing if we have more data to send */ + if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { int rv = nghttp3_conn_resume_stream(conn, stream_id); - if(rv) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - if((data->req.keepon & KEEP_SEND_HOLD) && - (data->req.keepon & KEEP_SEND)) { - data->req.keepon &= ~KEEP_SEND_HOLD; - h3_drain_stream(cf, data); - CURL_TRC_CF(data, cf, "[%" PRId64 "] unpausing acks", stream_id); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + return NGHTTP3_ERR_CALLBACK_FAILURE; } } return 0; @@ -1581,8 +1321,9 @@ cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nwritten = 0; size_t nvecs = 0; (void)cf; @@ -1625,16 +1366,15 @@ cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, } else if(!nwritten) { /* Not EOF, and nothing to give, we signal WOULDBLOCK. */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> AGAIN", + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read req body -> AGAIN", stream->id); return NGHTTP3_ERR_WOULDBLOCK; } - CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> " - "%d vecs%s with %zu (buffered=%zu, left=%" - CURL_FORMAT_CURL_OFF_T ")", + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read req body -> " + "%d vecs%s with %zu (buffered=%zu, left=%" FMT_OFF_T ")", stream->id, (int)nvecs, - *pflags == NGHTTP3_DATA_FLAG_EOF?" EOF":"", + *pflags == NGHTTP3_DATA_FLAG_EOF ? " EOF" : "", nwritten, Curl_bufq_len(&stream->sendbuf), stream->upload_left); return (nghttp3_ssize)nvecs; @@ -1651,6 +1391,7 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, { struct cf_ngtcp2_ctx *ctx = cf->ctx; struct h3_stream_ctx *stream = NULL; + int64_t sid; struct dynhds h2_headers; size_t nheader; nghttp3_nv *nva = NULL; @@ -1665,8 +1406,12 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, *err = h3_data_setup(cf, data); if(*err) goto out; - stream = H3_STREAM_CTX(data); + stream = H3_STREAM_CTX(ctx, data); DEBUGASSERT(stream); + if(!stream) { + *err = CURLE_FAILED_INIT; + goto out; + } nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err); if(nwritten < 0) @@ -1702,12 +1447,15 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, nva[i].flags = NGHTTP3_NV_FLAG_NONE; } - rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &stream->id, NULL); + rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &sid, data); if(rc) { failf(data, "can get bidi streams"); *err = CURLE_SEND_ERROR; + nwritten = -1; goto out; } + stream->id = (curl_int64_t)sid; + ++ctx->used_bidi_streams; switch(data->state.httpreq) { case HTTPREQ_POST: @@ -1738,12 +1486,12 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, if(rc) { switch(rc) { case NGHTTP3_ERR_CONN_CLOSING: - CURL_TRC_CF(data, cf, "h3sid[%"PRId64"] failed to send, " + CURL_TRC_CF(data, cf, "h3sid[%" FMT_PRId64 "] failed to send, " "connection is closing", stream->id); break; default: - CURL_TRC_CF(data, cf, "h3sid[%"PRId64"] failed to send -> %d (%s)", - stream->id, rc, ngtcp2_strerror(rc)); + CURL_TRC_CF(data, cf, "h3sid[%" FMT_PRId64 "] failed to send -> " + "%d (%s)", stream->id, rc, nghttp3_strerror(rc)); break; } *err = CURLE_SEND_ERROR; @@ -1752,10 +1500,10 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, } if(Curl_trc_is_verbose(data)) { - infof(data, "[HTTP/3] [%" PRId64 "] OPENED stream for %s", + infof(data, "[HTTP/3] [%" FMT_PRId64 "] OPENED stream for %s", stream->id, data->state.url); for(i = 0; i < nheader; ++i) { - infof(data, "[HTTP/3] [%" PRId64 "] [%.*s: %.*s]", stream->id, + infof(data, "[HTTP/3] [%" FMT_PRId64 "] [%.*s: %.*s]", stream->id, (int)nva[i].namelen, nva[i].name, (int)nva[i].valuelen, nva[i].value); } @@ -1768,10 +1516,11 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, } static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t sent = 0; struct cf_call_data save; struct pkt_io_ctx pktx; @@ -1784,6 +1533,7 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, pktx_init(&pktx, cf, data); *err = CURLE_OK; + (void)eos; /* TODO: use for stream EOF and block handling */ result = cf_progress_ingress(cf, data, &pktx); if(result) { *err = result; @@ -1791,27 +1541,25 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, } if(!stream || stream->id < 0) { + if(ctx->shutdown_started) { + CURL_TRC_CF(data, cf, "cannot open stream on closed connection"); + *err = CURLE_SEND_ERROR; + sent = -1; + goto out; + } sent = h3_stream_open(cf, data, buf, len, err); if(sent < 0) { CURL_TRC_CF(data, cf, "failed to open stream -> %d", *err); goto out; } - stream = H3_STREAM_CTX(data); - } - else if(stream->upload_blocked_len) { - /* the data in `buf` has already been submitted or added to the - * buffers, but have been EAGAINed on the last invocation. */ - DEBUGASSERT(len >= stream->upload_blocked_len); - if(len < stream->upload_blocked_len) { - /* Did we get called again with a smaller `len`? This should not - * happen. We are not prepared to handle that. */ - failf(data, "HTTP/3 send again with decreased length"); - *err = CURLE_HTTP3; - sent = -1; - goto out; - } - sent = (ssize_t)stream->upload_blocked_len; - stream->upload_blocked_len = 0; + stream = H3_STREAM_CTX(ctx, data); + } + else if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] xfer write failed", stream->id); + cf_ngtcp2_stream_close(cf, data, stream); + *err = stream->xfer_result; + sent = -1; + goto out; } else if(stream->closed) { if(stream->resp_hds_complete) { @@ -1820,19 +1568,27 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, * body. This happens on 30x or 40x responses. * We silently discard the data sent, since this is not a transport * error situation. */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] discarding data" + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] discarding data" "on closed stream with response", stream->id); *err = CURLE_OK; sent = (ssize_t)len; goto out; } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] send_body(len=%zu) " + "-> stream closed", stream->id, len); *err = CURLE_HTTP3; sent = -1; goto out; } + else if(ctx->shutdown_started) { + CURL_TRC_CF(data, cf, "cannot send on closed connection"); + *err = CURLE_SEND_ERROR; + sent = -1; + goto out; + } else { sent = Curl_bufq_write(&stream->sendbuf, buf, len, err); - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send, add to " + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_send, add to " "sendbuf(len=%zu) -> %zd, %d", stream->id, len, sent, *err); if(sent < 0) { @@ -1848,28 +1604,14 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, sent = -1; } - if(stream && sent > 0 && stream->sendbuf_len_in_flight) { - /* We have unacknowledged DATA and cannot report success to our - * caller. Instead we EAGAIN and remember how much we have already - * "written" into our various internal connection buffers. - * We put the stream upload on HOLD, until this gets ACKed. */ - stream->upload_blocked_len = sent; - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu), " - "%zu bytes in flight -> EGAIN", stream->id, len, - stream->sendbuf_len_in_flight); - *err = CURLE_AGAIN; - sent = -1; - data->req.keepon |= KEEP_SEND_HOLD; - } - out: result = check_and_set_expiry(cf, data, &pktx); if(result) { *err = result; sent = -1; } - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %zd, %d", - stream? stream->id : -1, len, sent, *err); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_send(len=%zu) -> %zd, %d", + stream ? stream->id : -1, len, sent, *err); CF_DATA_RESTORE(cf, save); return sent; } @@ -1878,52 +1620,11 @@ static CURLcode qng_verify_peer(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - const char *hostname, *disp_hostname; - int port; - char *snihost; - - Curl_conn_get_host(data, cf->sockindex, &hostname, &disp_hostname, &port); - snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost) - return CURLE_PEER_FAILED_VERIFICATION; cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ cf->conn->httpversion = 30; - cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; - if(cf->conn->ssl_config.verifyhost) { -#ifdef USE_OPENSSL - X509 *server_cert; - server_cert = SSL_get_peer_certificate(ctx->ssl); - if(!server_cert) { - return CURLE_PEER_FAILED_VERIFICATION; - } - result = Curl_ossl_verifyhost(data, cf->conn, server_cert); - X509_free(server_cert); - if(result) - return result; -#elif defined(USE_GNUTLS) - result = Curl_gtls_verifyserver(data, ctx->gtls->session, - &cf->conn->ssl_config, &data->set.ssl, - hostname, disp_hostname, - data->set.str[STRING_SSL_PINNEDPUBLICKEY]); - if(result) - return result; -#elif defined(USE_WOLFSSL) - if(wolfSSL_check_domain_name(ctx->ssl, snihost) == SSL_FAILURE) - return CURLE_PEER_FAILED_VERIFICATION; -#endif - infof(data, "Verified certificate just fine"); - } - else - infof(data, "Skipped certificate verification"); -#ifdef USE_OPENSSL - if(data->set.ssl.certinfo) - /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, ctx->ssl); -#endif - return result; + return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); } static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, @@ -1937,27 +1638,17 @@ static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, ngtcp2_path path; int rv; - ++pktx->pkt_count; ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->q.local_addr, - ctx->q.local_addrlen); + (socklen_t)ctx->q.local_addrlen); ngtcp2_addr_init(&path.remote, (struct sockaddr *)remote_addr, remote_addrlen); pi.ecn = (uint8_t)ecn; rv = ngtcp2_conn_read_pkt(ctx->qconn, &path, &pi, pkt, pktlen, pktx->ts); if(rv) { - CURL_TRC_CF(pktx->data, pktx->cf, "ingress, read_pkt -> %s", - ngtcp2_strerror(rv)); - if(!ctx->last_error.error_code) { - if(rv == NGTCP2_ERR_CRYPTO) { - ngtcp2_ccerr_set_tls_alert(&ctx->last_error, - ngtcp2_conn_get_tls_alert(ctx->qconn), - NULL, 0); - } - else { - ngtcp2_ccerr_set_liberr(&ctx->last_error, rv, NULL, 0); - } - } + CURL_TRC_CF(pktx->data, pktx->cf, "ingress, read_pkt -> %s (%d)", + ngtcp2_strerror(rv), rv); + cf_ngtcp2_err_set(pktx->cf, pktx->data, rv); if(rv == NGTCP2_ERR_CRYPTO) /* this is a "TLS problem", but a failed certificate verification @@ -1975,41 +1666,18 @@ static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, { struct cf_ngtcp2_ctx *ctx = cf->ctx; struct pkt_io_ctx local_pktx; - size_t pkts_chunk = 128, i; - size_t pkts_max = 10 * pkts_chunk; CURLcode result = CURLE_OK; if(!pktx) { pktx_init(&local_pktx, cf, data); pktx = &local_pktx; } - else { - pktx->ts = timestamp(); - } -#ifdef USE_OPENSSL - if(!ctx->x509_store_setup) { - result = Curl_ssl_setup_x509_store(cf, data, ctx->sslctx); - if(result) - return result; - ctx->x509_store_setup = TRUE; - } -#endif + result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); + if(result) + return result; - for(i = 0; i < pkts_max; i += pkts_chunk) { - pktx->pkt_count = 0; - result = vquic_recv_packets(cf, data, &ctx->q, pkts_chunk, - recv_pkt, pktx); - if(result) /* error */ - break; - if(pktx->pkt_count < pkts_chunk) /* got less than we could */ - break; - /* give egress a chance before we receive more */ - result = cf_progress_egress(cf, data, pktx); - if(result) /* error */ - break; - } - return result; + return vquic_recv_packets(cf, data, &ctx->q, 1000, recv_pkt, pktx); } /** @@ -2050,9 +1718,7 @@ static ssize_t read_pkt_to_send(void *userp, if(veccnt < 0) { failf(x->data, "nghttp3_conn_writev_stream returned error: %s", nghttp3_strerror((int)veccnt)); - ngtcp2_ccerr_set_application_error( - &ctx->last_error, - nghttp3_err_infer_quic_app_error_code((int)veccnt), NULL, 0); + cf_ngtcp2_h3_err_set(x->cf, x->data, (int)veccnt); *err = CURLE_SEND_ERROR; return -1; } @@ -2072,11 +1738,18 @@ static ssize_t read_pkt_to_send(void *userp, } else if(n < 0) { switch(n) { - case NGTCP2_ERR_STREAM_DATA_BLOCKED: + case NGTCP2_ERR_STREAM_DATA_BLOCKED: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, x->data); DEBUGASSERT(ndatalen == -1); nghttp3_conn_block_stream(ctx->h3conn, stream_id); + CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRId64 "] block quic flow", + (curl_int64_t)stream_id); + DEBUGASSERT(stream); + if(stream) + stream->quic_flow_blocked = TRUE; n = 0; break; + } case NGTCP2_ERR_STREAM_SHUT_WR: DEBUGASSERT(ndatalen == -1); nghttp3_conn_shutdown_stream_write(ctx->h3conn, stream_id); @@ -2092,7 +1765,7 @@ static ssize_t read_pkt_to_send(void *userp, DEBUGASSERT(ndatalen == -1); failf(x->data, "ngtcp2_conn_writev_stream returned error: %s", ngtcp2_strerror((int)n)); - ngtcp2_ccerr_set_liberr(&ctx->last_error, (int)n, NULL, 0); + cf_ngtcp2_err_set(x->cf, x->data, (int)n); *err = CURLE_SEND_ERROR; nwritten = -1; goto out; @@ -2136,7 +1809,7 @@ static CURLcode cf_progress_egress(struct Curl_cfilter *cf, pktx = &local_pktx; } else { - pktx->ts = timestamp(); + pktx_update_time(pktx, cf); ngtcp2_path_storage_zero(&pktx->ps); } @@ -2150,7 +1823,7 @@ static CURLcode cf_progress_egress(struct Curl_cfilter *cf, } /* In UDP, there is a maximum theoretical packet paload length and - * a minimum payload length that is "guarantueed" to work. + * a minimum payload length that is "guaranteed" to work. * To detect if this minimum payload can be increased, ngtcp2 sends * now and then a packet payload larger than the minimum. It that * is ACKed by the peer, both parties know that it works and @@ -2189,7 +1862,7 @@ static CURLcode cf_progress_egress(struct Curl_cfilter *cf, DEBUGASSERT(nread > 0); if(pktcnt == 0) { /* first packet in buffer. This is either of a known, "good" - * payload size or it is a PMTUD. We'll see. */ + * payload size or it is a PMTUD. We will see. */ gsolen = (size_t)nread; } else if((size_t)nread > gsolen || @@ -2238,9 +1911,9 @@ static CURLcode cf_progress_egress(struct Curl_cfilter *cf, static bool cf_ngtcp2_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - const struct h3_stream_ctx *stream = H3_STREAM_CTX(data); (void)cf; - return stream && !Curl_bufq_is_empty(&stream->recvbuf); + (void)data; + return FALSE; } static CURLcode h3_data_pause(struct Curl_cfilter *cf, @@ -2273,21 +1946,24 @@ static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf, case CF_CTRL_DATA_PAUSE: result = h3_data_pause(cf, data, (arg1 != 0)); break; - case CF_CTRL_DATA_DONE: { + case CF_CTRL_DATA_DETACH: + h3_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE: h3_data_done(cf, data); break; - } case CF_CTRL_DATA_DONE_SEND: { - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream && !stream->send_closed) { stream->send_closed = TRUE; - stream->upload_left = Curl_bufq_len(&stream->sendbuf); + stream->upload_left = Curl_bufq_len(&stream->sendbuf) - + stream->sendbuf_len_in_flight; (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id); } break; } case CF_CTRL_DATA_IDLE: { - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURL_TRC_CF(data, cf, "data idle"); if(stream && !stream->closed) { result = check_and_set_expiry(cf, data, NULL); @@ -2303,87 +1979,268 @@ static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf, return result; } -static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx) +static void cf_ngtcp2_ctx_close(struct cf_ngtcp2_ctx *ctx) { struct cf_call_data save = ctx->call_data; + if(!ctx->initialized) + return; if(ctx->qlogfd != -1) { close(ctx->qlogfd); } -#ifdef USE_OPENSSL - if(ctx->ssl) - SSL_free(ctx->ssl); - if(ctx->sslctx) - SSL_CTX_free(ctx->sslctx); -#elif defined(USE_GNUTLS) - if(ctx->gtls) { - if(ctx->gtls->cred) - gnutls_certificate_free_credentials(ctx->gtls->cred); - if(ctx->gtls->session) - gnutls_deinit(ctx->gtls->session); - free(ctx->gtls); - } -#elif defined(USE_WOLFSSL) - if(ctx->ssl) - wolfSSL_free(ctx->ssl); - if(ctx->sslctx) - wolfSSL_CTX_free(ctx->sslctx); -#endif + ctx->qlogfd = -1; + Curl_vquic_tls_cleanup(&ctx->tls); vquic_ctx_free(&ctx->q); if(ctx->h3conn) nghttp3_conn_del(ctx->h3conn); if(ctx->qconn) ngtcp2_conn_del(ctx->qconn); - Curl_bufcp_free(&ctx->stream_bufcp); - - memset(ctx, 0, sizeof(*ctx)); - ctx->qlogfd = -1; ctx->call_data = save; } -static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data) +static CURLcode cf_ngtcp2_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) { struct cf_ngtcp2_ctx *ctx = cf->ctx; struct cf_call_data save; + struct pkt_io_ctx pktx; + CURLcode result = CURLE_OK; + + if(cf->shutdown || !ctx->qconn) { + *done = TRUE; + return CURLE_OK; + } CF_DATA_SAVE(save, cf, data); - if(ctx && ctx->qconn) { + *done = FALSE; + pktx_init(&pktx, cf, data); + + if(!ctx->shutdown_started) { char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; - ngtcp2_tstamp ts; - ngtcp2_ssize rc; + ngtcp2_ssize nwritten; + + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "shutdown, flushing sendbuf"); + result = cf_progress_egress(cf, data, &pktx); + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "sending shutdown packets blocked"); + result = CURLE_OK; + goto out; + } + else if(result) { + CURL_TRC_CF(data, cf, "shutdown, error %d flushing sendbuf", result); + *done = TRUE; + goto out; + } + } - CURL_TRC_CF(data, cf, "close"); - ts = timestamp(); - rc = ngtcp2_conn_write_connection_close(ctx->qconn, NULL, /* path */ - NULL, /* pkt_info */ - (uint8_t *)buffer, sizeof(buffer), - &ctx->last_error, ts); - if(rc > 0) { - while((send(ctx->q.sockfd, buffer, (SEND_TYPE_ARG3)rc, 0) == -1) && - SOCKERRNO == EINTR); + ctx->shutdown_started = TRUE; + nwritten = ngtcp2_conn_write_connection_close( + ctx->qconn, NULL, /* path */ + NULL, /* pkt_info */ + (uint8_t *)buffer, sizeof(buffer), + &ctx->last_error, pktx.ts); + CURL_TRC_CF(data, cf, "start shutdown(err_type=%d, err_code=%" + FMT_PRIu64 ") -> %d", ctx->last_error.type, + (curl_uint64_t)ctx->last_error.error_code, (int)nwritten); + if(nwritten > 0) { + Curl_bufq_write(&ctx->q.sendbuf, (const unsigned char *)buffer, + (size_t)nwritten, &result); + if(result) { + CURL_TRC_CF(data, cf, "error %d adding shutdown packets to sendbuf, " + "aborting shutdown", result); + goto out; + } + ctx->q.no_gso = TRUE; + ctx->q.gsolen = (size_t)nwritten; + ctx->q.split_len = 0; } + } - cf_ngtcp2_ctx_clear(ctx); + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "shutdown, flushing egress"); + result = vquic_flush(cf, data, &ctx->q); + if(result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "sending shutdown packets blocked"); + result = CURLE_OK; + goto out; + } + else if(result) { + CURL_TRC_CF(data, cf, "shutdown, error %d flushing sendbuf", result); + *done = TRUE; + goto out; + } } - cf->connected = FALSE; + if(Curl_bufq_is_empty(&ctx->q.sendbuf)) { + /* Sent everything off. ngtcp2 seems to have no support for graceful + * shutdowns. So, we are done. */ + CURL_TRC_CF(data, cf, "shutdown completely sent off, done"); + *done = TRUE; + result = CURLE_OK; + } +out: CF_DATA_RESTORE(cf, save); + return result; } -static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +static void cf_ngtcp2_conn_close(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + bool done; + cf_ngtcp2_shutdown(cf, data, &done); +} + +static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; struct cf_call_data save; CF_DATA_SAVE(save, cf, data); + if(ctx && ctx->qconn) { + cf_ngtcp2_conn_close(cf, data); + cf_ngtcp2_ctx_close(ctx); + CURL_TRC_CF(data, cf, "close"); + } + cf->connected = FALSE; + CF_DATA_RESTORE(cf, save); +} + +static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ CURL_TRC_CF(data, cf, "destroy"); - if(ctx) { - cf_ngtcp2_ctx_clear(ctx); - free(ctx); + if(cf->ctx) { + cf_ngtcp2_ctx_free(cf->ctx); + cf->ctx = NULL; + } +} + +#ifdef USE_OPENSSL +/* The "new session" callback must return zero if the session can be removed + * or non-zero if the session has been put into the session cache. + */ +static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) +{ + struct Curl_cfilter *cf; + struct cf_ngtcp2_ctx *ctx; + struct Curl_easy *data; + ngtcp2_crypto_conn_ref *cref; + + cref = (ngtcp2_crypto_conn_ref *)SSL_get_app_data(ssl); + cf = cref ? cref->user_data : NULL; + ctx = cf ? cf->ctx : NULL; + data = cf ? CF_DATA_CURRENT(cf) : NULL; + if(cf && data && ctx) { + Curl_ossl_add_session(cf, data, &ctx->peer, ssl_sessionid); + return 1; + } + return 0; +} +#endif /* USE_OPENSSL */ + +#ifdef USE_GNUTLS +static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, + const gnutls_datum_t *msg) +{ + ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); + struct Curl_cfilter *cf = conn_ref ? conn_ref->user_data : NULL; + struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL; + + (void)msg; + (void)incoming; + if(when && cf && ctx) { /* after message has been processed */ + struct Curl_easy *data = CF_DATA_CURRENT(cf); + DEBUGASSERT(data); + if(data) { + CURL_TRC_CF(data, cf, "handshake: %s message type %d", + incoming ? "incoming" : "outgoing", htype); + } + switch(htype) { + case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: { + (void)Curl_gtls_update_session_id(cf, data, session, &ctx->peer, "h3"); + break; + } + default: + break; + } + } + return 0; +} +#endif /* USE_GNUTLS */ + +#ifdef USE_WOLFSSL +static int wssl_quic_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) +{ + ngtcp2_crypto_conn_ref *conn_ref = wolfSSL_get_app_data(ssl); + struct Curl_cfilter *cf = conn_ref ? conn_ref->user_data : NULL; + + DEBUGASSERT(cf != NULL); + if(cf && session) { + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + DEBUGASSERT(data); + if(data && ctx) { + (void)wssl_cache_session(cf, data, &ctx->peer, session); + } + } + return 0; +} +#endif /* USE_WOLFSSL */ + +static CURLcode tls_ctx_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data) +{ + struct curl_tls_ctx *ctx = user_data; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + +#ifdef USE_OPENSSL +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + if(ngtcp2_crypto_boringssl_configure_client_context(ctx->ossl.ssl_ctx) + != 0) { + failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#else + if(ngtcp2_crypto_quictls_configure_client_context(ctx->ossl.ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_quictls_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#endif /* !OPENSSL_IS_BORINGSSL && !OPENSSL_IS_AWSLC */ + if(ssl_config->primary.cache_session) { + /* Enable the session cache because it is a prerequisite for the + * "new session" callback. Use the "external storage" mode to prevent + * OpenSSL from creating an internal session cache. + */ + SSL_CTX_set_session_cache_mode(ctx->ossl.ssl_ctx, + SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(ctx->ossl.ssl_ctx, quic_ossl_new_session_cb); + } + +#elif defined(USE_GNUTLS) + if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls.session) != 0) { + failf(data, "ngtcp2_crypto_gnutls_configure_client_session failed"); + return CURLE_FAILED_INIT; + } + if(ssl_config->primary.cache_session) { + gnutls_handshake_set_hook_function(ctx->gtls.session, + GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, + quic_gtls_handshake_cb); + } + +#elif defined(USE_WOLFSSL) + if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->wssl.ctx) != 0) { + failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); + return CURLE_FAILED_INIT; } - cf->ctx = NULL; - /* No CF_DATA_RESTORE(cf, save) possible */ - (void)save; + if(ssl_config->primary.cache_session) { + /* Register to get notified when a new session is received */ + wolfSSL_CTX_sess_set_new_cb(ctx->wssl.ctx, wssl_quic_new_session_cb); + } +#endif + return CURLE_OK; } /* @@ -2400,28 +2257,21 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, const struct Curl_sockaddr_ex *sockaddr = NULL; int qfd; - ctx->version = NGTCP2_PROTO_VER_MAX; - ctx->max_stream_window = H3_STREAM_WINDOW_SIZE; - Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, - H3_STREAM_POOL_SPARES); - -#ifdef USE_OPENSSL - result = quic_ssl_ctx(&ctx->sslctx, cf, data); + DEBUGASSERT(ctx->initialized); + result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC); if(result) return result; - result = quic_set_client_cert(cf, data); - if(result) - return result; -#elif defined(USE_WOLFSSL) - result = quic_ssl_ctx(&ctx->sslctx, cf, data); +#define H3_ALPN "\x2h3\x5h3-29" + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, + H3_ALPN, sizeof(H3_ALPN) - 1, + tls_ctx_setup, &ctx->tls, &ctx->conn_ref); if(result) return result; -#endif - result = quic_init_ssl(cf, data); - if(result) - return result; +#ifdef USE_OPENSSL + SSL_set_quic_use_legacy_codepoint(ctx->tls.ossl.ssl, 0); +#endif ctx->dcid.datalen = NGTCP2_MAX_CIDLEN; result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN); @@ -2441,8 +2291,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, if(result) return result; - Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, - &sockaddr, NULL, NULL, NULL, NULL); + Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &sockaddr, NULL); if(!sockaddr) return CURLE_QUIC_CONNECT_ERROR; ctx->q.local_addrlen = sizeof(ctx->q.local_addr); @@ -2455,7 +2304,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, (struct sockaddr *)&ctx->q.local_addr, ctx->q.local_addrlen); ngtcp2_addr_init(&ctx->connected_path.remote, - &sockaddr->sa_addr, sockaddr->addrlen); + &sockaddr->curl_sa_addr, (socklen_t)sockaddr->addrlen); rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, &ctx->connected_path, @@ -2465,10 +2314,14 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, if(rc) return CURLE_QUIC_CONNECT_ERROR; -#ifdef USE_GNUTLS - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->gtls->session); +#ifdef USE_OPENSSL + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ossl.ssl); +#elif defined(USE_GNUTLS) + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.gtls.session); +#elif defined(USE_WOLFSSL) + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.wssl.handle); #else - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ssl); + #error "ngtcp2 TLS backend not defined" #endif ngtcp2_ccerr_default(&ctx->last_error); @@ -2507,12 +2360,6 @@ static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf, CF_DATA_SAVE(save, cf, data); - if(ctx->reconnect_at.tv_sec && Curl_timediff(now, ctx->reconnect_at) < 0) { - /* Not time yet to attempt the next connect */ - CURL_TRC_CF(data, cf, "waiting for reconnect time"); - goto out; - } - if(!ctx->qconn) { ctx->started_at = now; result = cf_connect_start(cf, data, &pktx); @@ -2550,38 +2397,18 @@ static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf, ngtcp2_conn_in_draining_period(ctx->qconn)) { /* When a QUIC server instance is shutting down, it may send us a * CONNECTION_CLOSE right away. Our connection then enters the DRAINING - * state. - * This may be a stopping of the service or it may be that the server - * is reloading and a new instance will start serving soon. - * In any case, we tear down our socket and start over with a new one. - * We re-open the underlying UDP cf right now, but do not start - * connecting until called again. - */ - int reconn_delay_ms = 200; - - CURL_TRC_CF(data, cf, "connect, remote closed, reconnect after %dms", - reconn_delay_ms); - Curl_conn_cf_close(cf->next, data); - cf_ngtcp2_ctx_clear(ctx); - result = Curl_conn_cf_connect(cf->next, data, FALSE, done); - if(!result && *done) { - *done = FALSE; - ctx->reconnect_at = now; - ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000; - Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC); - result = CURLE_OK; - } + * state. The CONNECT may work in the near future again. Indicate + * that as a "weird" reply. */ + result = CURLE_WEIRD_SERVER_REPLY; } #ifndef CURL_DISABLE_VERBOSE_STRINGS if(result) { - const char *r_ip = NULL; - int r_port = 0; + struct ip_quadruple ip; - Curl_cf_socket_peek(cf->next, data, NULL, NULL, - &r_ip, &r_port, NULL, NULL); + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); infof(data, "QUIC connect to %s port %u failed: %s", - r_ip, r_port, curl_easy_strerror(result)); + ip.remote_ip, ip.remote_port, curl_easy_strerror(result)); } #endif if(!result && ctx->qconn) { @@ -2602,32 +2429,43 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, switch(query) { case CF_QUERY_MAX_CONCURRENT: { - const ngtcp2_transport_params *rp; DEBUGASSERT(pres1); - CF_DATA_SAVE(save, cf, data); - rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); - if(rp) - *pres1 = (rp->initial_max_streams_bidi > INT_MAX)? - INT_MAX : (int)rp->initial_max_streams_bidi; - else /* not arrived yet? */ - *pres1 = Curl_multi_max_concurrent_streams(data->multi); - CURL_TRC_CF(data, cf, "query max_conncurrent -> %d", *pres1); + /* Set after transport params arrived and continually updated + * by callback. QUIC counts the number over the lifetime of the + * connection, ever increasing. + * We count the *open* transfers plus the budget for new ones. */ + if(!ctx->qconn || ctx->shutdown_started) { + *pres1 = 0; + } + else if(ctx->max_bidi_streams) { + uint64_t avail_bidi_streams = 0; + uint64_t max_streams = CONN_INUSE(cf->conn); + if(ctx->max_bidi_streams > ctx->used_bidi_streams) + avail_bidi_streams = ctx->max_bidi_streams - ctx->used_bidi_streams; + max_streams += avail_bidi_streams; + *pres1 = (max_streams > INT_MAX) ? INT_MAX : (int)max_streams; + } + else /* transport params not arrived yet? take our default. */ + *pres1 = (int)Curl_multi_max_concurrent_streams(data->multi); + CURL_TRC_CF(data, cf, "query conn[%" FMT_OFF_T "]: " + "MAX_CONCURRENT -> %d (%zu in use)", + cf->conn->connection_id, *pres1, CONN_INUSE(cf->conn)); CF_DATA_RESTORE(cf, save); return CURLE_OK; } case CF_QUERY_CONNECT_REPLY_MS: - if(ctx->got_first_byte) { - timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at); - *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX; + if(ctx->q.got_first_byte) { + timediff_t ms = Curl_timediff(ctx->q.first_byte_at, ctx->started_at); + *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; } else *pres1 = -1; return CURLE_OK; case CF_QUERY_TIMER_CONNECT: { struct curltime *when = pres2; - if(ctx->got_first_byte) - *when = ctx->first_byte_at; + if(ctx->q.got_first_byte) + *when = ctx->q.first_byte_at; return CURLE_OK; } case CF_QUERY_TIMER_APPCONNECT: { @@ -2639,7 +2477,7 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, default: break; } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -2648,24 +2486,51 @@ static bool cf_ngtcp2_conn_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data, bool *input_pending) { - bool alive = TRUE; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + bool alive = FALSE; + const ngtcp2_transport_params *rp; + struct cf_call_data save; + CF_DATA_SAVE(save, cf, data); *input_pending = FALSE; + if(!ctx->qconn || ctx->shutdown_started) + goto out; + + /* Both sides of the QUIC connection announce they max idle times in + * the transport parameters. Look at the minimum of both and if + * we exceed this, regard the connection as dead. The other side + * may have completely purged it and will no longer respond + * to any packets from us. */ + rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); + if(rp) { + timediff_t idletime; + uint64_t idle_ms = ctx->max_idle_ms; + + if(rp->max_idle_timeout && + (rp->max_idle_timeout / NGTCP2_MILLISECONDS) < idle_ms) + idle_ms = (rp->max_idle_timeout / NGTCP2_MILLISECONDS); + idletime = Curl_timediff(Curl_now(), ctx->q.last_io); + if(idletime > 0 && (uint64_t)idletime > idle_ms) + goto out; + } + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) - return FALSE; + goto out; + alive = TRUE; if(*input_pending) { - /* This happens before we've sent off a request and the connection is - not in use by any other transfer, there shouldn't be any data here, + CURLcode result; + /* This happens before we have sent off a request and the connection is + not in use by any other transfer, there should not be any data here, only "protocol frames" */ *input_pending = FALSE; - if(cf_progress_ingress(cf, data, NULL)) - alive = FALSE; - else { - alive = TRUE; - } + result = cf_progress_ingress(cf, data, NULL); + CURL_TRC_CF(data, cf, "is_alive, progress ingress -> %d", result); + alive = result ? FALSE : TRUE; } +out: + CF_DATA_RESTORE(cf, save); return alive; } @@ -2676,8 +2541,9 @@ struct Curl_cftype Curl_cft_http3 = { cf_ngtcp2_destroy, cf_ngtcp2_connect, cf_ngtcp2_close, + cf_ngtcp2_shutdown, Curl_cf_def_get_host, - cf_ngtcp2_get_select_socks, + cf_ngtcp2_adjust_pollset, cf_ngtcp2_data_pending, cf_ngtcp2_send, cf_ngtcp2_recv, @@ -2697,13 +2563,12 @@ CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, CURLcode result; (void)data; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - ctx->qlogfd = -1; - cf_ngtcp2_ctx_clear(ctx); + cf_ngtcp2_ctx_init(ctx); result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); if(result) @@ -2719,12 +2584,12 @@ CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, cf->next = udp_cf; out: - *pcf = (!result)? cf : NULL; + *pcf = (!result) ? cf : NULL; if(result) { if(udp_cf) Curl_conn_cf_discard_sub(cf, udp_cf, data, TRUE); Curl_safefree(cf); - Curl_safefree(ctx); + cf_ngtcp2_ctx_free(ctx); } return result; } @@ -2733,7 +2598,7 @@ bool Curl_conn_is_ngtcp2(const struct Curl_easy *data, const struct connectdata *conn, int sockindex) { - struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + struct Curl_cfilter *cf = conn ? conn->cfilter[sockindex] : NULL; (void)data; for(; cf; cf = cf->next) { diff --git a/deps/curl/lib/vquic/curl_osslq.c b/deps/curl/lib/vquic/curl_osslq.c new file mode 100644 index 00000000000..e5f737f8f0a --- /dev/null +++ b/deps/curl/lib/vquic/curl_osslq.c @@ -0,0 +1,2397 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 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.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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + +#include +#include +#include +#include + +#include "urldata.h" +#include "hash.h" +#include "sendf.h" +#include "strdup.h" +#include "rand.h" +#include "multiif.h" +#include "strcase.h" +#include "cfilters.h" +#include "cf-socket.h" +#include "connect.h" +#include "progress.h" +#include "strerror.h" +#include "dynbuf.h" +#include "http1.h" +#include "select.h" +#include "inet_pton.h" +#include "vquic.h" +#include "vquic_int.h" +#include "vquic-tls.h" +#include "vtls/keylog.h" +#include "vtls/vtls.h" +#include "vtls/openssl.h" +#include "curl_osslq.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" + +/* A stream window is the maximum amount we need to buffer for + * each active transfer. We use HTTP/3 flow control and only ACK + * when we take things out of the buffer. + * Chunk size is large enough to take a full DATA frame */ +#define H3_STREAM_WINDOW_SIZE (128 * 1024) +#define H3_STREAM_CHUNK_SIZE (16 * 1024) +/* The pool keeps spares around and half of a full stream window + * seems good. More does not seem to improve performance. + * The benefit of the pool is that stream buffer to not keep + * spares. Memory consumption goes down when streams run empty, + * have a large upload done, etc. */ +#define H3_STREAM_POOL_SPARES \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE ) / 2 +/* Receive and Send max number of chunks just follows from the + * chunk size and window size */ +#define H3_STREAM_RECV_CHUNKS \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) +#define H3_STREAM_SEND_CHUNKS \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) + +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +typedef uint32_t sslerr_t; +#else +typedef unsigned long sslerr_t; +#endif + + +/* How to access `call_data` from a cf_osslq filter */ +#undef CF_CTX_CALL_DATA +#define CF_CTX_CALL_DATA(cf) \ + ((struct cf_osslq_ctx *)(cf)->ctx)->call_data + +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data); + +static const char *osslq_SSL_ERROR_to_str(int err) +{ + switch(err) { + case SSL_ERROR_NONE: + return "SSL_ERROR_NONE"; + case SSL_ERROR_SSL: + return "SSL_ERROR_SSL"; + case SSL_ERROR_WANT_READ: + return "SSL_ERROR_WANT_READ"; + case SSL_ERROR_WANT_WRITE: + return "SSL_ERROR_WANT_WRITE"; + case SSL_ERROR_WANT_X509_LOOKUP: + return "SSL_ERROR_WANT_X509_LOOKUP"; + case SSL_ERROR_SYSCALL: + return "SSL_ERROR_SYSCALL"; + case SSL_ERROR_ZERO_RETURN: + return "SSL_ERROR_ZERO_RETURN"; + case SSL_ERROR_WANT_CONNECT: + return "SSL_ERROR_WANT_CONNECT"; + case SSL_ERROR_WANT_ACCEPT: + return "SSL_ERROR_WANT_ACCEPT"; +#if defined(SSL_ERROR_WANT_ASYNC) + case SSL_ERROR_WANT_ASYNC: + return "SSL_ERROR_WANT_ASYNC"; +#endif +#if defined(SSL_ERROR_WANT_ASYNC_JOB) + case SSL_ERROR_WANT_ASYNC_JOB: + return "SSL_ERROR_WANT_ASYNC_JOB"; +#endif +#if defined(SSL_ERROR_WANT_EARLY) + case SSL_ERROR_WANT_EARLY: + return "SSL_ERROR_WANT_EARLY"; +#endif + default: + return "SSL_ERROR unknown"; + } +} + +/* Return error string for last OpenSSL error */ +static char *osslq_strerror(unsigned long error, char *buf, size_t size) +{ + DEBUGASSERT(size); + *buf = '\0'; + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + ERR_error_string_n((uint32_t)error, buf, size); +#else + ERR_error_string_n(error, buf, size); +#endif + + if(!*buf) { + const char *msg = error ? "Unknown error" : "No error"; + if(strlen(msg) < size) + strcpy(buf, msg); + } + + return buf; +} + +static CURLcode make_bio_addr(BIO_ADDR **pbio_addr, + const struct Curl_sockaddr_ex *addr) +{ + BIO_ADDR *ba; + CURLcode result = CURLE_FAILED_INIT; + + ba = BIO_ADDR_new(); + if(!ba) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + switch(addr->family) { + case AF_INET: { + struct sockaddr_in * const sin = + (struct sockaddr_in * const)(void *)&addr->curl_sa_addr; + if(!BIO_ADDR_rawmake(ba, AF_INET, &sin->sin_addr, + sizeof(sin->sin_addr), sin->sin_port)) { + goto out; + } + result = CURLE_OK; + break; + } +#ifdef USE_IPV6 + case AF_INET6: { + struct sockaddr_in6 * const sin = + (struct sockaddr_in6 * const)(void *)&addr->curl_sa_addr; + if(!BIO_ADDR_rawmake(ba, AF_INET6, &sin->sin6_addr, + sizeof(sin->sin6_addr), sin->sin6_port)) { + } + result = CURLE_OK; + break; + } +#endif /* USE_IPV6 */ + default: + /* sunsupported */ + DEBUGASSERT(0); + break; + } + +out: + if(result && ba) { + BIO_ADDR_free(ba); + ba = NULL; + } + *pbio_addr = ba; + return result; +} + +/* QUIC stream (not necessarily H3) */ +struct cf_osslq_stream { + curl_int64_t id; + SSL *ssl; + struct bufq recvbuf; /* QUIC war data recv buffer */ + BIT(recvd_eos); + BIT(closed); + BIT(reset); + BIT(send_blocked); +}; + +static CURLcode cf_osslq_stream_open(struct cf_osslq_stream *s, + SSL *conn, + uint64_t flags, + struct bufc_pool *bufcp, + void *user_data) +{ + DEBUGASSERT(!s->ssl); + Curl_bufq_initp(&s->recvbuf, bufcp, 1, BUFQ_OPT_NONE); + s->ssl = SSL_new_stream(conn, flags); + if(!s->ssl) { + return CURLE_FAILED_INIT; + } + s->id = (curl_int64_t)SSL_get_stream_id(s->ssl); + SSL_set_app_data(s->ssl, user_data); + return CURLE_OK; +} + +static void cf_osslq_stream_cleanup(struct cf_osslq_stream *s) +{ + if(s->ssl) { + SSL_set_app_data(s->ssl, NULL); + SSL_free(s->ssl); + } + Curl_bufq_free(&s->recvbuf); + memset(s, 0, sizeof(*s)); +} + +static void cf_osslq_stream_close(struct cf_osslq_stream *s) +{ + if(s->ssl) { + SSL_free(s->ssl); + s->ssl = NULL; + } +} + +struct cf_osslq_h3conn { + nghttp3_conn *conn; + nghttp3_settings settings; + struct cf_osslq_stream s_ctrl; + struct cf_osslq_stream s_qpack_enc; + struct cf_osslq_stream s_qpack_dec; + struct cf_osslq_stream remote_ctrl[3]; /* uni streams opened by the peer */ + size_t remote_ctrl_n; /* number of peer streams opened */ +}; + +static void cf_osslq_h3conn_cleanup(struct cf_osslq_h3conn *h3) +{ + size_t i; + + if(h3->conn) + nghttp3_conn_del(h3->conn); + cf_osslq_stream_cleanup(&h3->s_ctrl); + cf_osslq_stream_cleanup(&h3->s_qpack_enc); + cf_osslq_stream_cleanup(&h3->s_qpack_dec); + for(i = 0; i < h3->remote_ctrl_n; ++i) { + cf_osslq_stream_cleanup(&h3->remote_ctrl[i]); + } +} + +struct cf_osslq_ctx { + struct cf_quic_ctx q; + struct ssl_peer peer; + struct curl_tls_ctx tls; + struct cf_call_data call_data; + struct cf_osslq_h3conn h3; + struct curltime started_at; /* time the current attempt started */ + struct curltime handshake_at; /* time connect handshake finished */ + struct curltime first_byte_at; /* when first byte was recvd */ + struct bufc_pool stream_bufcp; /* chunk pool for streams */ + struct Curl_hash streams; /* hash `data->mid` to `h3_stream_ctx` */ + size_t max_stream_window; /* max flow window for one stream */ + uint64_t max_idle_ms; /* max idle time for QUIC connection */ + BIT(initialized); + BIT(got_first_byte); /* if first byte was received */ + BIT(x509_store_setup); /* if x509 store has been set up */ + BIT(protocol_shutdown); /* QUIC connection is shut down */ + BIT(need_recv); /* QUIC connection needs to receive */ + BIT(need_send); /* QUIC connection needs to send */ +}; + +static void h3_stream_hash_free(void *stream); + +static void cf_osslq_ctx_init(struct cf_osslq_ctx *ctx) +{ + DEBUGASSERT(!ctx->initialized); + Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, + H3_STREAM_POOL_SPARES); + Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free); + ctx->initialized = TRUE; +} + +static void cf_osslq_ctx_free(struct cf_osslq_ctx *ctx) +{ + if(ctx && ctx->initialized) { + Curl_bufcp_free(&ctx->stream_bufcp); + Curl_hash_clean(&ctx->streams); + Curl_hash_destroy(&ctx->streams); + Curl_ssl_peer_cleanup(&ctx->peer); + } + free(ctx); +} + +static void cf_osslq_ctx_close(struct cf_osslq_ctx *ctx) +{ + struct cf_call_data save = ctx->call_data; + + cf_osslq_h3conn_cleanup(&ctx->h3); + Curl_vquic_tls_cleanup(&ctx->tls); + vquic_ctx_free(&ctx->q); + ctx->call_data = save; +} + +static CURLcode cf_osslq_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct cf_call_data save; + CURLcode result = CURLE_OK; + int rc; + + CF_DATA_SAVE(save, cf, data); + + if(cf->shutdown || ctx->protocol_shutdown) { + *done = TRUE; + return CURLE_OK; + } + + CF_DATA_SAVE(save, cf, data); + *done = FALSE; + ctx->need_send = FALSE; + ctx->need_recv = FALSE; + + rc = SSL_shutdown_ex(ctx->tls.ossl.ssl, + SSL_SHUTDOWN_FLAG_NO_BLOCK, NULL, 0); + if(rc == 0) { /* ongoing */ + CURL_TRC_CF(data, cf, "shutdown ongoing"); + ctx->need_recv = TRUE; + goto out; + } + else if(rc == 1) { /* done */ + CURL_TRC_CF(data, cf, "shutdown finished"); + *done = TRUE; + goto out; + } + else { + long sslerr; + char err_buffer[256]; + int err = SSL_get_error(ctx->tls.ossl.ssl, rc); + + switch(err) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + CURL_TRC_CF(data, cf, "shutdown not received, but closed"); + *done = TRUE; + goto out; + case SSL_ERROR_WANT_READ: + /* SSL has send its notify and now wants to read the reply + * from the server. We are not really interested in that. */ + CURL_TRC_CF(data, cf, "shutdown sent, want receive"); + ctx->need_recv = TRUE; + goto out; + case SSL_ERROR_WANT_WRITE: + CURL_TRC_CF(data, cf, "shutdown send blocked"); + ctx->need_send = TRUE; + goto out; + default: + /* We give up on this. */ + sslerr = ERR_get_error(); + CURL_TRC_CF(data, cf, "shutdown, ignore recv error: '%s', errno %d", + (sslerr ? + osslq_strerror(sslerr, err_buffer, sizeof(err_buffer)) : + osslq_SSL_ERROR_to_str(err)), + SOCKERRNO); + *done = TRUE; + result = CURLE_OK; + goto out; + } + } +out: + CF_DATA_RESTORE(cf, save); + return result; +} + +static void cf_osslq_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + if(ctx && ctx->tls.ossl.ssl) { + CURL_TRC_CF(data, cf, "cf_osslq_close()"); + if(!cf->shutdown && !ctx->protocol_shutdown) { + /* last best effort, which OpenSSL calls a "rapid" shutdown. */ + SSL_shutdown_ex(ctx->tls.ossl.ssl, + (SSL_SHUTDOWN_FLAG_NO_BLOCK | SSL_SHUTDOWN_FLAG_RAPID), + NULL, 0); + } + cf_osslq_ctx_close(ctx); + } + + cf->connected = FALSE; + CF_DATA_RESTORE(cf, save); +} + +static void cf_osslq_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + CURL_TRC_CF(data, cf, "destroy"); + if(ctx) { + CURL_TRC_CF(data, cf, "cf_osslq_destroy()"); + if(ctx->tls.ossl.ssl) + cf_osslq_ctx_close(ctx); + cf_osslq_ctx_free(ctx); + } + cf->ctx = NULL; + /* No CF_DATA_RESTORE(cf, save) possible */ + (void)save; +} + +static CURLcode cf_osslq_h3conn_add_stream(struct cf_osslq_h3conn *h3, + SSL *stream_ssl, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)SSL_get_stream_id(stream_ssl); + + if(h3->remote_ctrl_n >= ARRAYSIZE(h3->remote_ctrl)) { + /* rejected, we are full */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] rejecting remote stream", + stream_id); + SSL_free(stream_ssl); + return CURLE_FAILED_INIT; + } + switch(SSL_get_stream_type(stream_ssl)) { + case SSL_STREAM_TYPE_READ: { + struct cf_osslq_stream *nstream = &h3->remote_ctrl[h3->remote_ctrl_n++]; + nstream->id = stream_id; + nstream->ssl = stream_ssl; + Curl_bufq_initp(&nstream->recvbuf, &ctx->stream_bufcp, 1, BUFQ_OPT_NONE); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] accepted remote uni stream", + stream_id); + break; + } + default: + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] reject remote non-uni-read" + " stream", stream_id); + SSL_free(stream_ssl); + return CURLE_FAILED_INIT; + } + return CURLE_OK; + +} + +static CURLcode cf_osslq_ssl_err(struct Curl_cfilter *cf, + struct Curl_easy *data, + int detail, CURLcode def_result) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = def_result; + sslerr_t errdetail; + char ebuf[256] = "unknown"; + const char *err_descr = ebuf; + long lerr; + int lib; + int reason; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + + errdetail = ERR_get_error(); + lib = ERR_GET_LIB(errdetail); + reason = ERR_GET_REASON(errdetail); + + if((lib == ERR_LIB_SSL) && + ((reason == SSL_R_CERTIFICATE_VERIFY_FAILED) || + (reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED))) { + result = CURLE_PEER_FAILED_VERIFICATION; + + lerr = SSL_get_verify_result(ctx->tls.ossl.ssl); + if(lerr != X509_V_OK) { + ssl_config->certverifyresult = lerr; + msnprintf(ebuf, sizeof(ebuf), + "SSL certificate problem: %s", + X509_verify_cert_error_string(lerr)); + } + else + err_descr = "SSL certificate verification failed"; + } +#if defined(SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED) + /* SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED is only available on + OpenSSL version above v1.1.1, not LibreSSL, BoringSSL, or AWS-LC */ + else if((lib == ERR_LIB_SSL) && + (reason == SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED)) { + /* If client certificate is required, communicate the + error to client */ + result = CURLE_SSL_CLIENTCERT; + osslq_strerror(errdetail, ebuf, sizeof(ebuf)); + } +#endif + else if((lib == ERR_LIB_SSL) && (reason == SSL_R_PROTOCOL_IS_SHUTDOWN)) { + ctx->protocol_shutdown = TRUE; + err_descr = "QUIC connection has been shut down"; + result = def_result; + } + else { + result = def_result; + osslq_strerror(errdetail, ebuf, sizeof(ebuf)); + } + + /* detail is already set to the SSL error above */ + + /* If we e.g. use SSLv2 request-method and the server does not like us + * (RST connection, etc.), OpenSSL gives no explanation whatsoever and + * the SO_ERROR is also lost. + */ + if(CURLE_SSL_CONNECT_ERROR == result && errdetail == 0) { + char extramsg[80]=""; + int sockerr = SOCKERRNO; + struct ip_quadruple ip; + + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); + if(sockerr && detail == SSL_ERROR_SYSCALL) + Curl_strerror(sockerr, extramsg, sizeof(extramsg)); + failf(data, "QUIC connect: %s in connection to %s:%d (%s)", + extramsg[0] ? extramsg : osslq_SSL_ERROR_to_str(detail), + ctx->peer.dispname, ip.remote_port, ip.remote_ip); + } + else { + /* Could be a CERT problem */ + failf(data, "%s", err_descr); + } + return result; +} + +static CURLcode cf_osslq_verify_peer(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->httpversion = 30; + + return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); +} + +/** + * All about the H3 internals of a stream + */ +struct h3_stream_ctx { + struct cf_osslq_stream s; + struct bufq sendbuf; /* h3 request body */ + struct bufq recvbuf; /* h3 response body */ + struct h1_req_parser h1; /* h1 request parsing */ + size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */ + size_t recv_buf_nonflow; /* buffered bytes, not counting for flow control */ + curl_uint64_t error3; /* HTTP/3 stream error code */ + curl_off_t upload_left; /* number of request bytes left to upload */ + curl_off_t download_recvd; /* number of response DATA bytes received */ + int status_code; /* HTTP status code */ + bool resp_hds_complete; /* we have a complete, final response */ + bool closed; /* TRUE on stream close */ + bool reset; /* TRUE on stream reset */ + bool send_closed; /* stream is local closed */ + BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ +}; + +#define H3_STREAM_CTX(ctx,data) ((struct h3_stream_ctx *)(\ + data? Curl_hash_offt_get(&(ctx)->streams, (data)->mid) : NULL)) + +static void h3_stream_ctx_free(struct h3_stream_ctx *stream) +{ + cf_osslq_stream_cleanup(&stream->s); + Curl_bufq_free(&stream->sendbuf); + Curl_bufq_free(&stream->recvbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); +} + +static void h3_stream_hash_free(void *stream) +{ + DEBUGASSERT(stream); + h3_stream_ctx_free((struct h3_stream_ctx *)stream); +} + +static CURLcode h3_data_setup(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + if(!data) + return CURLE_FAILED_INIT; + + if(stream) + return CURLE_OK; + + stream = calloc(1, sizeof(*stream)); + if(!stream) + return CURLE_OUT_OF_MEMORY; + + stream->s.id = -1; + /* on send, we control how much we put into the buffer */ + Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, + H3_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); + stream->sendbuf_len_in_flight = 0; + /* on recv, we need a flexible buffer limit since we also write + * headers to it that are not counted against the nghttp3 flow limits. */ + Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, + H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); + stream->recv_buf_nonflow = 0; + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + + if(!Curl_hash_offt_set(&ctx->streams, data->mid, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + (void)cf; + if(stream) { + CURL_TRC_CF(data, cf, "[%"FMT_PRId64"] easy handle is done", + stream->s.id); + if(ctx->h3.conn && !stream->closed) { + nghttp3_conn_shutdown_stream_read(ctx->h3.conn, stream->s.id); + nghttp3_conn_close_stream(ctx->h3.conn, stream->s.id, + NGHTTP3_H3_REQUEST_CANCELLED); + nghttp3_conn_set_stream_user_data(ctx->h3.conn, stream->s.id, NULL); + stream->closed = TRUE; + } + + Curl_hash_offt_remove(&ctx->streams, data->mid); + } +} + +static struct cf_osslq_stream *cf_osslq_get_qstream(struct Curl_cfilter *cf, + struct Curl_easy *data, + int64_t stream_id) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + if(stream && stream->s.id == stream_id) { + return &stream->s; + } + else if(ctx->h3.s_ctrl.id == stream_id) { + return &ctx->h3.s_ctrl; + } + else if(ctx->h3.s_qpack_enc.id == stream_id) { + return &ctx->h3.s_qpack_enc; + } + else if(ctx->h3.s_qpack_dec.id == stream_id) { + return &ctx->h3.s_qpack_dec; + } + else { + struct Curl_llist_node *e; + DEBUGASSERT(data->multi); + for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *sdata = Curl_node_elem(e); + if(sdata->conn != data->conn) + continue; + stream = H3_STREAM_CTX(ctx, sdata); + if(stream && stream->s.id == stream_id) { + return &stream->s; + } + } + } + return NULL; +} + +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(stream && stream->upload_left && !stream->send_closed) + bits |= CURL_CSELECT_OUT; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } +} + +static CURLcode h3_data_pause(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool pause) +{ + if(!pause) { + /* unpaused. make it run again right away */ + h3_drain_stream(cf, data); + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + return CURLE_OK; +} + +static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)conn; + (void)stream_id; + + /* we might be called by nghttp3 after we already cleaned up */ + if(!stream) + return 0; + + stream->closed = TRUE; + stream->error3 = app_error_code; + if(stream->error3 != NGHTTP3_H3_NO_ERROR) { + stream->reset = TRUE; + stream->send_closed = TRUE; + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] RESET: error %" FMT_PRIu64, + stream->s.id, stream->error3); + } + else { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] CLOSED", stream->s.id); + } + h3_drain_stream(cf, data); + return 0; +} + +/* + * write_resp_raw() copies response data in raw format to the `data`'s + * receive buffer. If not enough space is available, it appends to the + * `data`'s overflow buffer. + */ +static CURLcode write_resp_raw(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *mem, size_t memlen, + bool flow) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURLcode result = CURLE_OK; + ssize_t nwritten; + + (void)cf; + if(!stream) { + return CURLE_RECV_ERROR; + } + nwritten = Curl_bufq_write(&stream->recvbuf, mem, memlen, &result); + if(nwritten < 0) { + return result; + } + + if(!flow) + stream->recv_buf_nonflow += (size_t)nwritten; + + if((size_t)nwritten < memlen) { + /* This MUST not happen. Our recbuf is dimensioned to hold the + * full max_stream_window and then some for this very reason. */ + DEBUGASSERT(0); + return CURLE_RECV_ERROR; + } + return result; +} + +static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, + const uint8_t *buf, size_t buflen, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURLcode result; + + (void)conn; + (void)stream3_id; + + if(!stream) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + result = write_resp_raw(cf, data, buf, buflen, TRUE); + if(result) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] DATA len=%zu, ERROR %d", + stream->s.id, buflen, result); + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + stream->download_recvd += (curl_off_t)buflen; + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] DATA len=%zu, total=%zd", + stream->s.id, buflen, stream->download_recvd); + h3_drain_stream(cf, data); + return 0; +} + +static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream_id, + size_t consumed, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + (void)conn; + (void)stream_id; + if(stream) + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] deferred consume %zu bytes", + stream->s.id, consumed); + return 0; +} + +static int cb_h3_recv_header(nghttp3_conn *conn, int64_t sid, + int32_t token, nghttp3_rcbuf *name, + nghttp3_rcbuf *value, uint8_t flags, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + curl_int64_t stream_id = sid; + struct cf_osslq_ctx *ctx = cf->ctx; + nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); + nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURLcode result = CURLE_OK; + (void)conn; + (void)stream_id; + (void)token; + (void)flags; + (void)cf; + + /* we might have cleaned up this transfer already */ + if(!stream) + return 0; + + if(token == NGHTTP3_QPACK_TOKEN__STATUS) { + char line[14]; /* status line is always 13 characters long */ + size_t ncopy; + + result = Curl_http_decode_status(&stream->status_code, + (const char *)h3val.base, h3val.len); + if(result) + return -1; + ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", + stream->status_code); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] status: %s", stream_id, line); + result = write_resp_raw(cf, data, line, ncopy, FALSE); + if(result) { + return -1; + } + } + else { + /* store as an HTTP1-style header */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] header: %.*s: %.*s", + stream_id, (int)h3name.len, h3name.base, + (int)h3val.len, h3val.base); + result = write_resp_raw(cf, data, h3name.base, h3name.len, FALSE); + if(result) { + return -1; + } + result = write_resp_raw(cf, data, ": ", 2, FALSE); + if(result) { + return -1; + } + result = write_resp_raw(cf, data, h3val.base, h3val.len, FALSE); + if(result) { + return -1; + } + result = write_resp_raw(cf, data, "\r\n", 2, FALSE); + if(result) { + return -1; + } + } + return 0; +} + +static int cb_h3_end_headers(nghttp3_conn *conn, int64_t sid, + int fin, void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURLcode result = CURLE_OK; + (void)conn; + (void)stream_id; + (void)fin; + (void)cf; + + if(!stream) + return 0; + /* add a CRLF only if we have received some headers */ + result = write_resp_raw(cf, data, "\r\n", 2, FALSE); + if(result) { + return -1; + } + + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] end_headers, status=%d", + stream_id, stream->status_code); + if(stream->status_code / 100 != 1) { + stream->resp_hds_complete = TRUE; + } + h3_drain_stream(cf, data); + return 0; +} + +static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t sid, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)conn; + (void)app_error_code; + + if(!stream || !stream->s.ssl) + return 0; + + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] stop_sending", stream_id); + cf_osslq_stream_close(&stream->s); + return 0; +} + +static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t sid, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + int rv; + (void)conn; + + if(stream && stream->s.ssl) { + SSL_STREAM_RESET_ARGS args = {0}; + args.quic_error_code = app_error_code; + rv = !SSL_stream_reset(stream->s.ssl, &args, sizeof(args)); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] reset -> %d", stream_id, rv); + if(!rv) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static nghttp3_ssize +cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, + nghttp3_vec *vec, size_t veccnt, + uint32_t *pflags, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + ssize_t nwritten = 0; + size_t nvecs = 0; + (void)cf; + (void)conn; + (void)stream_id; + (void)user_data; + (void)veccnt; + + if(!stream) + return NGHTTP3_ERR_CALLBACK_FAILURE; + /* nghttp3 keeps references to the sendbuf data until it is ACKed + * by the server (see `cb_h3_acked_req_body()` for updates). + * `sendbuf_len_in_flight` is the amount of bytes in `sendbuf` + * that we have already passed to nghttp3, but which have not been + * ACKed yet. + * Any amount beyond `sendbuf_len_in_flight` we need still to pass + * to nghttp3. Do that now, if we can. */ + if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { + nvecs = 0; + while(nvecs < veccnt && + Curl_bufq_peek_at(&stream->sendbuf, + stream->sendbuf_len_in_flight, + (const unsigned char **)&vec[nvecs].base, + &vec[nvecs].len)) { + stream->sendbuf_len_in_flight += vec[nvecs].len; + nwritten += vec[nvecs].len; + ++nvecs; + } + DEBUGASSERT(nvecs > 0); /* we SHOULD have been be able to peek */ + } + + if(nwritten > 0 && stream->upload_left != -1) + stream->upload_left -= nwritten; + + /* When we stopped sending and everything in `sendbuf` is "in flight", + * we are at the end of the request body. */ + if(stream->upload_left == 0) { + *pflags = NGHTTP3_DATA_FLAG_EOF; + stream->send_closed = TRUE; + } + else if(!nwritten) { + /* Not EOF, and nothing to give, we signal WOULDBLOCK. */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read req body -> AGAIN", + stream->s.id); + return NGHTTP3_ERR_WOULDBLOCK; + } + + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read req body -> " + "%d vecs%s with %zu (buffered=%zu, left=%" FMT_OFF_T ")", + stream->s.id, (int)nvecs, + *pflags == NGHTTP3_DATA_FLAG_EOF ? " EOF" : "", + nwritten, Curl_bufq_len(&stream->sendbuf), + stream->upload_left); + return (nghttp3_ssize)nvecs; +} + +static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, + uint64_t datalen, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + size_t skiplen; + + (void)cf; + if(!stream) + return 0; + /* The server acknowledged `datalen` of bytes from our request body. + * This is a delta. We have kept this data in `sendbuf` for + * re-transmissions and can free it now. */ + if(datalen >= (uint64_t)stream->sendbuf_len_in_flight) + skiplen = stream->sendbuf_len_in_flight; + else + skiplen = (size_t)datalen; + Curl_bufq_skip(&stream->sendbuf, skiplen); + stream->sendbuf_len_in_flight -= skiplen; + + /* Resume upload processing if we have more data to send */ + if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { + int rv = nghttp3_conn_resume_stream(conn, stream_id); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static nghttp3_callbacks ngh3_callbacks = { + cb_h3_acked_stream_data, + cb_h3_stream_close, + cb_h3_recv_data, + cb_h3_deferred_consume, + NULL, /* begin_headers */ + cb_h3_recv_header, + cb_h3_end_headers, + NULL, /* begin_trailers */ + cb_h3_recv_header, + NULL, /* end_trailers */ + cb_h3_stop_sending, + NULL, /* end_stream */ + cb_h3_reset_stream, + NULL, /* shutdown */ + NULL /* recv_settings */ +}; + +static CURLcode cf_osslq_h3conn_init(struct cf_osslq_ctx *ctx, SSL *conn, + void *user_data) +{ + struct cf_osslq_h3conn *h3 = &ctx->h3; + CURLcode result; + int rc; + + nghttp3_settings_default(&h3->settings); + rc = nghttp3_conn_client_new(&h3->conn, + &ngh3_callbacks, + &h3->settings, + nghttp3_mem_default(), + user_data); + if(rc) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + result = cf_osslq_stream_open(&h3->s_ctrl, conn, + SSL_STREAM_FLAG_ADVANCE|SSL_STREAM_FLAG_UNI, + &ctx->stream_bufcp, NULL); + if(result) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + result = cf_osslq_stream_open(&h3->s_qpack_enc, conn, + SSL_STREAM_FLAG_ADVANCE|SSL_STREAM_FLAG_UNI, + &ctx->stream_bufcp, NULL); + if(result) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + result = cf_osslq_stream_open(&h3->s_qpack_dec, conn, + SSL_STREAM_FLAG_ADVANCE|SSL_STREAM_FLAG_UNI, + &ctx->stream_bufcp, NULL); + if(result) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + + rc = nghttp3_conn_bind_control_stream(h3->conn, h3->s_ctrl.id); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + rc = nghttp3_conn_bind_qpack_streams(h3->conn, h3->s_qpack_enc.id, + h3->s_qpack_dec.id); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + + result = CURLE_OK; +out: + return result; +} + +static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result; + int rv; + const struct Curl_sockaddr_ex *peer_addr = NULL; + BIO *bio = NULL; + BIO_ADDR *baddr = NULL; + + DEBUGASSERT(ctx->initialized); + result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC); + if(result) + goto out; + +#define H3_ALPN "\x2h3" + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, + H3_ALPN, sizeof(H3_ALPN) - 1, + NULL, NULL, NULL); + if(result) + goto out; + + result = vquic_ctx_init(&ctx->q); + if(result) + goto out; + + result = CURLE_QUIC_CONNECT_ERROR; + Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &peer_addr, NULL); + if(!peer_addr) + goto out; + + ctx->q.local_addrlen = sizeof(ctx->q.local_addr); + rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr, + &ctx->q.local_addrlen); + if(rv == -1) + goto out; + + result = make_bio_addr(&baddr, peer_addr); + if(result) { + failf(data, "error creating BIO_ADDR from sockaddr"); + goto out; + } + + /* Type conversions, see #12861: OpenSSL wants an `int`, but on 64-bit + * Win32 systems, Microsoft defines SOCKET as `unsigned long long`. + */ +#if defined(_WIN32) && !defined(__LWIP_OPT_H__) && !defined(LWIP_HDR_OPT_H) + if(ctx->q.sockfd > INT_MAX) { + failf(data, "Windows socket identifier larger than MAX_INT, " + "unable to set in OpenSSL dgram API."); + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + bio = BIO_new_dgram((int)ctx->q.sockfd, BIO_NOCLOSE); +#else + bio = BIO_new_dgram(ctx->q.sockfd, BIO_NOCLOSE); +#endif + if(!bio) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(!SSL_set1_initial_peer_addr(ctx->tls.ossl.ssl, baddr)) { + failf(data, "failed to set the initial peer address"); + result = CURLE_FAILED_INIT; + goto out; + } + if(!SSL_set_blocking_mode(ctx->tls.ossl.ssl, 0)) { + failf(data, "failed to turn off blocking mode"); + result = CURLE_FAILED_INIT; + goto out; + } + +#ifdef SSL_VALUE_QUIC_IDLE_TIMEOUT + /* Added in OpenSSL v3.3.x */ + if(!SSL_set_feature_request_uint(ctx->tls.ossl.ssl, + SSL_VALUE_QUIC_IDLE_TIMEOUT, + CURL_QUIC_MAX_IDLE_MS)) { + CURL_TRC_CF(data, cf, "error setting idle timeout, "); + result = CURLE_FAILED_INIT; + goto out; + } +#endif + + SSL_set_bio(ctx->tls.ossl.ssl, bio, bio); + bio = NULL; + SSL_set_connect_state(ctx->tls.ossl.ssl); + SSL_set_incoming_stream_policy(ctx->tls.ossl.ssl, + SSL_INCOMING_STREAM_POLICY_ACCEPT, 0); + /* setup the H3 things on top of the QUIC connection */ + result = cf_osslq_h3conn_init(ctx, ctx->tls.ossl.ssl, cf); + +out: + if(bio) + BIO_free(bio); + if(baddr) + BIO_ADDR_free(baddr); + CURL_TRC_CF(data, cf, "QUIC tls init -> %d", result); + return result; +} + +struct h3_quic_recv_ctx { + struct Curl_cfilter *cf; + struct Curl_easy *data; + struct cf_osslq_stream *s; +}; + +static ssize_t h3_quic_recv(void *reader_ctx, + unsigned char *buf, size_t len, + CURLcode *err) +{ + struct h3_quic_recv_ctx *x = reader_ctx; + size_t nread; + int rv; + + *err = CURLE_OK; + rv = SSL_read_ex(x->s->ssl, buf, len, &nread); + if(rv <= 0) { + int detail = SSL_get_error(x->s->ssl, rv); + if(detail == SSL_ERROR_WANT_READ || detail == SSL_ERROR_WANT_WRITE) { + *err = CURLE_AGAIN; + return -1; + } + else if(detail == SSL_ERROR_ZERO_RETURN) { + CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRId64 "] h3_quic_recv -> EOS", + x->s->id); + x->s->recvd_eos = TRUE; + return 0; + } + else if(SSL_get_stream_read_state(x->s->ssl) == + SSL_STREAM_STATE_RESET_REMOTE) { + uint64_t app_error_code = NGHTTP3_H3_NO_ERROR; + SSL_get_stream_read_error_code(x->s->ssl, &app_error_code); + CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRId64 "] h3_quic_recv -> RESET, " + "rv=%d, app_err=%" FMT_PRIu64, + x->s->id, rv, (curl_uint64_t)app_error_code); + if(app_error_code != NGHTTP3_H3_NO_ERROR) { + x->s->reset = TRUE; + } + x->s->recvd_eos = TRUE; + return 0; + } + else { + *err = cf_osslq_ssl_err(x->cf, x->data, detail, CURLE_RECV_ERROR); + return -1; + } + } + return (ssize_t)nread; +} + +static CURLcode cf_osslq_stream_recv(struct cf_osslq_stream *s, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + ssize_t nread; + struct h3_quic_recv_ctx x; + bool eagain = FALSE; + size_t total_recv_len = 0; + + DEBUGASSERT(s); + if(s->closed) + return CURLE_OK; + + x.cf = cf; + x.data = data; + x.s = s; + while(s->ssl && !s->closed && !eagain && + (total_recv_len < H3_STREAM_CHUNK_SIZE)) { + if(Curl_bufq_is_empty(&s->recvbuf) && !s->recvd_eos) { + while(!eagain && !s->recvd_eos && !Curl_bufq_is_full(&s->recvbuf)) { + nread = Curl_bufq_sipn(&s->recvbuf, 0, h3_quic_recv, &x, &result); + if(nread < 0) { + if(result != CURLE_AGAIN) + goto out; + result = CURLE_OK; + eagain = TRUE; + } + } + } + + /* Forward what we have to nghttp3 */ + if(!Curl_bufq_is_empty(&s->recvbuf)) { + const unsigned char *buf; + size_t blen; + + while(Curl_bufq_peek(&s->recvbuf, &buf, &blen)) { + nread = nghttp3_conn_read_stream(ctx->h3.conn, s->id, + buf, blen, 0); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] forward %zu bytes " + "to nghttp3 -> %zd", s->id, blen, nread); + if(nread < 0) { + failf(data, "nghttp3_conn_read_stream(len=%zu) error: %s", + blen, nghttp3_strerror((int)nread)); + result = CURLE_RECV_ERROR; + goto out; + } + /* success, `nread` is the flow for QUIC to count as "consumed", + * not sure how that will work with OpenSSL. Anyways, without error, + * all data that we passed is not owned by nghttp3. */ + Curl_bufq_skip(&s->recvbuf, blen); + total_recv_len += blen; + } + } + + /* When we forwarded everything, handle RESET/EOS */ + if(Curl_bufq_is_empty(&s->recvbuf) && !s->closed) { + int rv; + result = CURLE_OK; + if(s->reset) { + uint64_t app_error; + if(!SSL_get_stream_read_error_code(s->ssl, &app_error)) { + failf(data, "SSL_get_stream_read_error_code returned error"); + result = CURLE_RECV_ERROR; + goto out; + } + rv = nghttp3_conn_close_stream(ctx->h3.conn, s->id, app_error); + s->closed = TRUE; + if(rv < 0 && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_close_stream returned error: %s", + nghttp3_strerror(rv)); + result = CURLE_RECV_ERROR; + goto out; + } + } + else if(s->recvd_eos) { + rv = nghttp3_conn_close_stream(ctx->h3.conn, s->id, + NGHTTP3_H3_NO_ERROR); + s->closed = TRUE; + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] close nghttp3 stream -> %d", + s->id, rv); + if(rv < 0 && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_close_stream returned error: %s", + nghttp3_strerror(rv)); + result = CURLE_RECV_ERROR; + goto out; + } + } + } + } +out: + if(result) + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_osslq_stream_recv -> %d", + s->id, result); + return result; +} + +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(!ctx->tls.ossl.ssl) + goto out; + + ERR_clear_error(); + + /* 1. Check for new incoming streams */ + while(1) { + SSL *snew = SSL_accept_stream(ctx->tls.ossl.ssl, + SSL_ACCEPT_STREAM_NO_BLOCK); + if(!snew) + break; + + (void)cf_osslq_h3conn_add_stream(&ctx->h3, snew, cf, data); + } + + if(!SSL_handle_events(ctx->tls.ossl.ssl)) { + int detail = SSL_get_error(ctx->tls.ossl.ssl, 0); + result = cf_osslq_ssl_err(cf, data, detail, CURLE_RECV_ERROR); + } + + if(ctx->h3.conn) { + size_t i; + for(i = 0; i < ctx->h3.remote_ctrl_n; ++i) { + result = cf_osslq_stream_recv(&ctx->h3.remote_ctrl[i], cf, data); + if(result) + goto out; + } + } + + if(ctx->h3.conn) { + struct Curl_llist_node *e; + struct h3_stream_ctx *stream; + /* PULL all open streams */ + DEBUGASSERT(data->multi); + for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *sdata = Curl_node_elem(e); + if(sdata->conn == data->conn && CURL_WANT_RECV(sdata)) { + stream = H3_STREAM_CTX(ctx, sdata); + if(stream && !stream->closed && + !Curl_bufq_is_full(&stream->recvbuf)) { + result = cf_osslq_stream_recv(&stream->s, cf, sdata); + if(result) + goto out; + } + } + } + } + +out: + CURL_TRC_CF(data, cf, "progress_ingress -> %d", result); + return result; +} + +/* Iterate over all streams and check if blocked can be unblocked */ +static CURLcode cf_osslq_check_and_unblock(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream; + + if(ctx->h3.conn) { + struct Curl_llist_node *e; + for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *sdata = Curl_node_elem(e); + if(sdata->conn == data->conn) { + stream = H3_STREAM_CTX(ctx, sdata); + if(stream && stream->s.ssl && stream->s.send_blocked && + !SSL_want_write(stream->s.ssl)) { + nghttp3_conn_unblock_stream(ctx->h3.conn, stream->s.id); + stream->s.send_blocked = FALSE; + h3_drain_stream(cf, sdata); + CURL_TRC_CF(sdata, cf, "unblocked"); + } + } + } + } + return CURLE_OK; +} + +static CURLcode h3_send_streams(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(!ctx->tls.ossl.ssl || !ctx->h3.conn) + goto out; + + for(;;) { + struct cf_osslq_stream *s = NULL; + nghttp3_vec vec[16]; + nghttp3_ssize n, i; + int64_t stream_id; + size_t written; + int eos, ok, rv; + size_t total_len, acked_len = 0; + bool blocked = FALSE, eos_written = FALSE; + + n = nghttp3_conn_writev_stream(ctx->h3.conn, &stream_id, &eos, + vec, ARRAYSIZE(vec)); + if(n < 0) { + failf(data, "nghttp3_conn_writev_stream returned error: %s", + nghttp3_strerror((int)n)); + result = CURLE_SEND_ERROR; + goto out; + } + if(stream_id < 0) { + result = CURLE_OK; + goto out; + } + + /* Get the stream for this data */ + s = cf_osslq_get_qstream(cf, data, stream_id); + if(!s) { + failf(data, "nghttp3_conn_writev_stream gave unknown stream %" + FMT_PRId64, (curl_int64_t)stream_id); + result = CURLE_SEND_ERROR; + goto out; + } + /* Now write the data to the stream's SSL*, it may not all fit! */ + DEBUGASSERT(s->id == stream_id); + for(i = 0, total_len = 0; i < n; ++i) { + total_len += vec[i].len; + } + for(i = 0; (i < n) && !blocked; ++i) { + /* Without stream->s.ssl, we closed that already, so + * pretend the write did succeed. */ + uint64_t flags = (eos && ((i + 1) == n)) ? SSL_WRITE_FLAG_CONCLUDE : 0; + written = vec[i].len; + ok = !s->ssl || SSL_write_ex2(s->ssl, vec[i].base, vec[i].len, flags, + &written); + if(ok && flags & SSL_WRITE_FLAG_CONCLUDE) + eos_written = TRUE; + if(ok) { + /* As OpenSSL buffers the data, we count this as acknowledged + * from nghttp3's point of view */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] send %zu bytes to QUIC ok", + s->id, vec[i].len); + acked_len += vec[i].len; + } + else { + int detail = SSL_get_error(s->ssl, 0); + switch(detail) { + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: + /* QUIC blocked us from writing more */ + CURL_TRC_CF(data, cf, "[%"FMT_PRId64 "] send %zu bytes to " + "QUIC blocked", s->id, vec[i].len); + written = 0; + nghttp3_conn_block_stream(ctx->h3.conn, s->id); + s->send_blocked = blocked = TRUE; + break; + default: + failf(data, "[%"FMT_PRId64 "] send %zu bytes to QUIC, SSL error %d", + s->id, vec[i].len, detail); + result = cf_osslq_ssl_err(cf, data, detail, CURLE_HTTP3); + goto out; + } + } + } + + if(acked_len > 0 || (eos && !s->send_blocked)) { + /* Since QUIC buffers the data written internally, we can tell + * nghttp3 that it can move forward on it */ + ctx->q.last_io = Curl_now(); + rv = nghttp3_conn_add_write_offset(ctx->h3.conn, s->id, acked_len); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_add_write_offset returned error: %s\n", + nghttp3_strerror(rv)); + result = CURLE_SEND_ERROR; + goto out; + } + rv = nghttp3_conn_add_ack_offset(ctx->h3.conn, s->id, acked_len); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_add_ack_offset returned error: %s\n", + nghttp3_strerror(rv)); + result = CURLE_SEND_ERROR; + goto out; + } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] forwarded %zu/%zu h3 bytes " + "to QUIC, eos=%d", s->id, acked_len, total_len, eos); + } + + if(eos && !s->send_blocked && !eos_written) { + /* wrote everything and H3 indicates end of stream */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] closing QUIC stream", s->id); + SSL_stream_conclude(s->ssl, 0); + } + } + +out: + CURL_TRC_CF(data, cf, "h3_send_streams -> %d", result); + return result; +} + +static CURLcode cf_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(!ctx->tls.ossl.ssl) + goto out; + + ERR_clear_error(); + result = h3_send_streams(cf, data); + if(result) + goto out; + + if(!SSL_handle_events(ctx->tls.ossl.ssl)) { + int detail = SSL_get_error(ctx->tls.ossl.ssl, 0); + result = cf_osslq_ssl_err(cf, data, detail, CURLE_SEND_ERROR); + } + + result = cf_osslq_check_and_unblock(cf, data); + +out: + CURL_TRC_CF(data, cf, "progress_egress -> %d", result); + return result; +} + +static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct timeval tv; + timediff_t timeoutms; + int is_infinite = 1; + + if(ctx->tls.ossl.ssl && + SSL_get_event_timeout(ctx->tls.ossl.ssl, &tv, &is_infinite) && + !is_infinite) { + timeoutms = curlx_tvtoms(&tv); + /* QUIC want to be called again latest at the returned timeout */ + if(timeoutms <= 0) { + result = cf_progress_ingress(cf, data); + if(result) + goto out; + result = cf_progress_egress(cf, data); + if(result) + goto out; + if(SSL_get_event_timeout(ctx->tls.ossl.ssl, &tv, &is_infinite)) { + timeoutms = curlx_tvtoms(&tv); + } + } + if(!is_infinite) { + Curl_expire(data, timeoutms, EXPIRE_QUIC); + CURL_TRC_CF(data, cf, "QUIC expiry in %ldms", (long)timeoutms); + } + } +out: + return result; +} + +static CURLcode cf_osslq_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct cf_call_data save; + struct curltime now; + int err; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + /* Connect the UDP filter first */ + if(!cf->next->connected) { + result = Curl_conn_cf_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; + } + + *done = FALSE; + now = Curl_now(); + CF_DATA_SAVE(save, cf, data); + + if(!ctx->tls.ossl.ssl) { + ctx->started_at = now; + result = cf_osslq_ctx_start(cf, data); + if(result) + goto out; + } + + if(!ctx->got_first_byte) { + int readable = SOCKET_READABLE(ctx->q.sockfd, 0); + if(readable > 0 && (readable & CURL_CSELECT_IN)) { + ctx->got_first_byte = TRUE; + ctx->first_byte_at = Curl_now(); + } + } + + /* Since OpenSSL does its own send/recv internally, we may miss the + * moment to populate the x509 store right before the server response. + * Do it instead before we start the handshake, at the loss of the + * time to set this up. */ + result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); + if(result) + goto out; + + ERR_clear_error(); + err = SSL_do_handshake(ctx->tls.ossl.ssl); + + if(err == 1) { + /* connected */ + ctx->handshake_at = now; + ctx->q.last_io = now; + CURL_TRC_CF(data, cf, "handshake complete after %dms", + (int)Curl_timediff(now, ctx->started_at)); + result = cf_osslq_verify_peer(cf, data); + if(!result) { + CURL_TRC_CF(data, cf, "peer verified"); + cf->connected = TRUE; + cf->conn->alpn = CURL_HTTP_VERSION_3; + *done = TRUE; + connkeep(cf->conn, "HTTP/3 default"); + } + } + else { + int detail = SSL_get_error(ctx->tls.ossl.ssl, err); + switch(detail) { + case SSL_ERROR_WANT_READ: + ctx->q.last_io = now; + CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_RECV"); + goto out; + case SSL_ERROR_WANT_WRITE: + ctx->q.last_io = now; + CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_SEND"); + result = CURLE_OK; + goto out; +#ifdef SSL_ERROR_WANT_ASYNC + case SSL_ERROR_WANT_ASYNC: + ctx->q.last_io = now; + CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_ASYNC"); + result = CURLE_OK; + goto out; +#endif +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + case SSL_ERROR_WANT_RETRY_VERIFY: + result = CURLE_OK; + goto out; +#endif + default: + result = cf_osslq_ssl_err(cf, data, detail, CURLE_COULDNT_CONNECT); + goto out; + } + } + +out: + if(result == CURLE_RECV_ERROR && ctx->tls.ossl.ssl && + ctx->protocol_shutdown) { + /* When a QUIC server instance is shutting down, it may send us a + * CONNECTION_CLOSE right away. Our connection then enters the DRAINING + * state. The CONNECT may work in the near future again. Indicate + * that as a "weird" reply. */ + result = CURLE_WEIRD_SERVER_REPLY; + } + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(result) { + struct ip_quadruple ip; + + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); + infof(data, "QUIC connect to %s port %u failed: %s", + ip.remote_ip, ip.remote_port, curl_easy_strerror(result)); + } +#endif + if(!result) + result = check_and_set_expiry(cf, data); + if(result || *done) + CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); + CF_DATA_RESTORE(cf, save); + return result; +} + +static ssize_t h3_stream_open(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *buf, size_t len, + CURLcode *err) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = NULL; + struct dynhds h2_headers; + size_t nheader; + nghttp3_nv *nva = NULL; + int rc = 0; + unsigned int i; + ssize_t nwritten = -1; + nghttp3_data_reader reader; + nghttp3_data_reader *preader = NULL; + + Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + + *err = h3_data_setup(cf, data); + if(*err) + goto out; + stream = H3_STREAM_CTX(ctx, data); + DEBUGASSERT(stream); + if(!stream) { + *err = CURLE_FAILED_INIT; + goto out; + } + + nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err); + if(nwritten < 0) + goto out; + if(!stream->h1.done) { + /* need more data */ + goto out; + } + DEBUGASSERT(stream->h1.req); + + *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data); + if(*err) { + nwritten = -1; + goto out; + } + /* no longer needed */ + Curl_h1_req_parse_free(&stream->h1); + + nheader = Curl_dynhds_count(&h2_headers); + nva = malloc(sizeof(nghttp3_nv) * nheader); + if(!nva) { + *err = CURLE_OUT_OF_MEMORY; + nwritten = -1; + goto out; + } + + for(i = 0; i < nheader; ++i) { + struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i); + nva[i].name = (unsigned char *)e->name; + nva[i].namelen = e->namelen; + nva[i].value = (unsigned char *)e->value; + nva[i].valuelen = e->valuelen; + nva[i].flags = NGHTTP3_NV_FLAG_NONE; + } + + DEBUGASSERT(stream->s.id == -1); + *err = cf_osslq_stream_open(&stream->s, ctx->tls.ossl.ssl, 0, + &ctx->stream_bufcp, data); + if(*err) { + failf(data, "cannot get bidi streams"); + *err = CURLE_SEND_ERROR; + goto out; + } + + switch(data->state.httpreq) { + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + case HTTPREQ_PUT: + /* known request body size or -1 */ + if(data->state.infilesize != -1) + stream->upload_left = data->state.infilesize; + else + /* data sending without specifying the data amount up front */ + stream->upload_left = -1; /* unknown */ + break; + default: + /* there is not request body */ + stream->upload_left = 0; /* no request body */ + break; + } + + stream->send_closed = (stream->upload_left == 0); + if(!stream->send_closed) { + reader.read_data = cb_h3_read_req_body; + preader = &reader; + } + + rc = nghttp3_conn_submit_request(ctx->h3.conn, stream->s.id, + nva, nheader, preader, data); + if(rc) { + switch(rc) { + case NGHTTP3_ERR_CONN_CLOSING: + CURL_TRC_CF(data, cf, "h3sid[%"FMT_PRId64"] failed to send, " + "connection is closing", stream->s.id); + break; + default: + CURL_TRC_CF(data, cf, "h3sid[%"FMT_PRId64 "] failed to send -> %d (%s)", + stream->s.id, rc, nghttp3_strerror(rc)); + break; + } + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + + if(Curl_trc_is_verbose(data)) { + infof(data, "[HTTP/3] [%" FMT_PRId64 "] OPENED stream for %s", + stream->s.id, data->state.url); + for(i = 0; i < nheader; ++i) { + infof(data, "[HTTP/3] [%" FMT_PRId64 "] [%.*s: %.*s]", + stream->s.id, + (int)nva[i].namelen, nva[i].name, + (int)nva[i].valuelen, nva[i].value); + } + } + +out: + free(nva); + Curl_dynhds_free(&h2_headers); + return nwritten; +} + +static ssize_t cf_osslq_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, bool eos, + CURLcode *err) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct cf_call_data save; + ssize_t nwritten; + CURLcode result; + + (void)eos; /* TODO: use to end stream */ + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx->tls.ossl.ssl); + DEBUGASSERT(ctx->h3.conn); + *err = CURLE_OK; + + result = cf_progress_ingress(cf, data); + if(result) { + *err = result; + nwritten = -1; + goto out; + } + + result = cf_progress_egress(cf, data); + if(result) { + *err = result; + nwritten = -1; + goto out; + } + + if(!stream || stream->s.id < 0) { + nwritten = h3_stream_open(cf, data, buf, len, err); + if(nwritten < 0) { + CURL_TRC_CF(data, cf, "failed to open stream -> %d", *err); + goto out; + } + stream = H3_STREAM_CTX(ctx, data); + } + else if(stream->closed) { + if(stream->resp_hds_complete) { + /* Server decided to close the stream after having sent us a final + * response. This is valid if it is not interested in the request + * body. This happens on 30x or 40x responses. + * We silently discard the data sent, since this is not a transport + * error situation. */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] discarding data" + "on closed stream with response", stream->s.id); + *err = CURLE_OK; + nwritten = (ssize_t)len; + goto out; + } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] send_body(len=%zu) " + "-> stream closed", stream->s.id, len); + *err = CURLE_HTTP3; + nwritten = -1; + goto out; + } + else { + nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_send, add to " + "sendbuf(len=%zu) -> %zd, %d", + stream->s.id, len, nwritten, *err); + if(nwritten < 0) { + goto out; + } + + (void)nghttp3_conn_resume_stream(ctx->h3.conn, stream->s.id); + } + + result = cf_progress_egress(cf, data); + if(result) { + *err = result; + nwritten = -1; + } + +out: + result = check_and_set_expiry(cf, data); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_send(len=%zu) -> %zd, %d", + stream ? stream->s.id : -1, len, nwritten, *err); + CF_DATA_RESTORE(cf, save); + return nwritten; +} + +static ssize_t recv_closed_stream(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + CURLcode *err) +{ + ssize_t nread = -1; + + (void)cf; + if(stream->reset) { + failf(data, + "HTTP/3 stream %" FMT_PRId64 " reset by server", + stream->s.id); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; + goto out; + } + else if(!stream->resp_hds_complete) { + failf(data, + "HTTP/3 stream %" FMT_PRId64 + " was closed cleanly, but before getting" + " all response header fields, treated as error", + stream->s.id); + *err = CURLE_HTTP3; + goto out; + } + *err = CURLE_OK; + nread = 0; + +out: + return nread; +} + +static ssize_t cf_osslq_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + ssize_t nread = -1; + struct cf_call_data save; + CURLcode result; + + (void)ctx; + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx); + DEBUGASSERT(ctx->tls.ossl.ssl); + DEBUGASSERT(ctx->h3.conn); + *err = CURLE_OK; + + if(!stream) { + *err = CURLE_RECV_ERROR; + goto out; + } + + if(!Curl_bufq_is_empty(&stream->recvbuf)) { + nread = Curl_bufq_read(&stream->recvbuf, + (unsigned char *)buf, len, err); + if(nread < 0) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->s.id, len, nread, *err); + goto out; + } + } + + result = cf_progress_ingress(cf, data); + if(result) { + *err = result; + nread = -1; + goto out; + } + + /* recvbuf had nothing before, maybe after progressing ingress? */ + if(nread < 0 && !Curl_bufq_is_empty(&stream->recvbuf)) { + nread = Curl_bufq_read(&stream->recvbuf, + (unsigned char *)buf, len, err); + if(nread < 0) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->s.id, len, nread, *err); + goto out; + } + } + + if(nread > 0) { + h3_drain_stream(cf, data); + } + else { + if(stream->closed) { + nread = recv_closed_stream(cf, data, stream, err); + goto out; + } + *err = CURLE_AGAIN; + nread = -1; + } + +out: + if(cf_progress_egress(cf, data)) { + *err = CURLE_SEND_ERROR; + nread = -1; + } + else { + CURLcode result2 = check_and_set_expiry(cf, data); + if(result2) { + *err = result2; + nread = -1; + } + } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_recv(len=%zu) -> %zd, %d", + stream ? stream->s.id : -1, len, nread, *err); + CF_DATA_RESTORE(cf, save); + return nread; +} + +/* + * Called from transfer.c:data_pending to know if we should keep looping + * to receive more data from the connection. + */ +static bool cf_osslq_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + const struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)cf; + return stream && !Curl_bufq_is_empty(&stream->recvbuf); +} + +static CURLcode cf_osslq_data_event(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_DATA_SETUP: + break; + case CF_CTRL_DATA_PAUSE: + result = h3_data_pause(cf, data, (arg1 != 0)); + break; + case CF_CTRL_DATA_DETACH: + h3_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE: + h3_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE_SEND: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + if(stream && !stream->send_closed) { + stream->send_closed = TRUE; + stream->upload_left = Curl_bufq_len(&stream->sendbuf) - + stream->sendbuf_len_in_flight; + (void)nghttp3_conn_resume_stream(ctx->h3.conn, stream->s.id); + } + break; + } + case CF_CTRL_DATA_IDLE: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURL_TRC_CF(data, cf, "data idle"); + if(stream && !stream->closed) { + result = check_and_set_expiry(cf, data); + } + break; + } + default: + break; + } + CF_DATA_RESTORE(cf, save); + return result; +} + +static bool cf_osslq_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + bool alive = FALSE; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + *input_pending = FALSE; + if(!ctx->tls.ossl.ssl) + goto out; + +#ifdef SSL_VALUE_QUIC_IDLE_TIMEOUT + /* Added in OpenSSL v3.3.x */ + { + timediff_t idletime; + uint64_t idle_ms = ctx->max_idle_ms; + if(!SSL_get_value_uint(ctx->tls.ossl.ssl, + SSL_VALUE_CLASS_FEATURE_NEGOTIATED, + SSL_VALUE_QUIC_IDLE_TIMEOUT, &idle_ms)) { + CURL_TRC_CF(data, cf, "error getting negotiated idle timeout, " + "assume connection is dead."); + goto out; + } + CURL_TRC_CF(data, cf, "negotiated idle timeout: %zums", (size_t)idle_ms); + idletime = Curl_timediff(Curl_now(), ctx->q.last_io); + if(idletime > 0 && (uint64_t)idletime > idle_ms) + goto out; + } + +#endif + + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) + goto out; + + alive = TRUE; + if(*input_pending) { + CURLcode result; + /* This happens before we have sent off a request and the connection is + not in use by any other transfer, there should not be any data here, + only "protocol frames" */ + *input_pending = FALSE; + result = cf_progress_ingress(cf, data); + CURL_TRC_CF(data, cf, "is_alive, progress ingress -> %d", result); + alive = result ? FALSE : TRUE; + } + +out: + CF_DATA_RESTORE(cf, save); + return alive; +} + +static void cf_osslq_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + + if(!ctx->tls.ossl.ssl) { + /* NOP */ + } + else if(!cf->connected) { + /* during handshake, transfer has not started yet. we always + * add our socket for polling if SSL wants to send/recv */ + Curl_pollset_set(data, ps, ctx->q.sockfd, + SSL_net_read_desired(ctx->tls.ossl.ssl), + SSL_net_write_desired(ctx->tls.ossl.ssl)); + } + else { + /* once connected, we only modify the socket if it is present. + * this avoids adding it for paused transfers. */ + bool want_recv, want_send; + Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); + if(want_recv || want_send) { + Curl_pollset_set(data, ps, ctx->q.sockfd, + SSL_net_read_desired(ctx->tls.ossl.ssl), + SSL_net_write_desired(ctx->tls.ossl.ssl)); + } + else if(ctx->need_recv || ctx->need_send) { + Curl_pollset_set(data, ps, ctx->q.sockfd, + ctx->need_recv, ctx->need_send); + } + } +} + +static CURLcode cf_osslq_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + + switch(query) { + case CF_QUERY_MAX_CONCURRENT: { +#ifdef SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL + /* Added in OpenSSL v3.3.x */ + uint64_t v; + if(!SSL_get_value_uint(ctx->tls.ossl.ssl, SSL_VALUE_CLASS_GENERIC, + SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL, &v)) { + CURL_TRC_CF(data, cf, "error getting available local bidi streams"); + return CURLE_HTTP3; + } + /* we report avail + in_use */ + v += CONN_INUSE(cf->conn); + *pres1 = (v > INT_MAX) ? INT_MAX : (int)v; +#else + *pres1 = 100; +#endif + CURL_TRC_CF(data, cf, "query max_conncurrent -> %d", *pres1); + return CURLE_OK; + } + case CF_QUERY_CONNECT_REPLY_MS: + if(ctx->got_first_byte) { + timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at); + *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; + } + else + *pres1 = -1; + return CURLE_OK; + case CF_QUERY_TIMER_CONNECT: { + struct curltime *when = pres2; + if(ctx->got_first_byte) + *when = ctx->first_byte_at; + return CURLE_OK; + } + case CF_QUERY_TIMER_APPCONNECT: { + struct curltime *when = pres2; + if(cf->connected) + *when = ctx->handshake_at; + return CURLE_OK; + } + default: + break; + } + return cf->next ? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +struct Curl_cftype Curl_cft_http3 = { + "HTTP/3", + CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX, + 0, + cf_osslq_destroy, + cf_osslq_connect, + cf_osslq_close, + cf_osslq_shutdown, + Curl_cf_def_get_host, + cf_osslq_adjust_pollset, + cf_osslq_data_pending, + cf_osslq_send, + cf_osslq_recv, + cf_osslq_data_event, + cf_osslq_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_osslq_query, +}; + +CURLcode Curl_cf_osslq_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai) +{ + struct cf_osslq_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL, *udp_cf = NULL; + CURLcode result; + + (void)data; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + cf_osslq_ctx_init(ctx); + + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + if(result) + goto out; + + result = Curl_cf_udp_create(&udp_cf, data, conn, ai, TRNSPRT_QUIC); + if(result) + goto out; + + cf->conn = conn; + udp_cf->conn = cf->conn; + udp_cf->sockindex = cf->sockindex; + cf->next = udp_cf; + +out: + *pcf = (!result) ? cf : NULL; + if(result) { + if(udp_cf) + Curl_conn_cf_discard_sub(cf, udp_cf, data, TRUE); + Curl_safefree(cf); + cf_osslq_ctx_free(ctx); + } + return result; +} + +bool Curl_conn_is_osslq(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf = conn ? conn->cfilter[sockindex] : NULL; + + (void)data; + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_http3) + return TRUE; + if(cf->cft->flags & CF_TYPE_IP_CONNECT) + return FALSE; + } + return FALSE; +} + +/* + * Store ngtcp2 version info in this buffer. + */ +void Curl_osslq_ver(char *p, size_t len) +{ + const nghttp3_info *ht3 = nghttp3_version(0); + (void)msnprintf(p, len, "nghttp3/%s", ht3->version_str); +} + +#endif /* USE_OPENSSL_QUIC && USE_NGHTTP3 */ diff --git a/deps/curl/lib/vquic/curl_osslq.h b/deps/curl/lib/vquic/curl_osslq.h new file mode 100644 index 00000000000..0e12d7023e0 --- /dev/null +++ b/deps/curl/lib/vquic/curl_osslq.h @@ -0,0 +1,51 @@ +#ifndef HEADER_CURL_VQUIC_CURL_OSSLQ_H +#define HEADER_CURL_VQUIC_CURL_OSSLQ_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 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.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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + +#ifdef HAVE_NETINET_UDP_H +#include +#endif + +struct Curl_cfilter; + +#include "urldata.h" + +void Curl_osslq_ver(char *p, size_t len); + +CURLcode Curl_cf_osslq_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai); + +bool Curl_conn_is_osslq(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex); +#endif + +#endif /* HEADER_CURL_VQUIC_CURL_OSSLQ_H */ diff --git a/deps/curl/lib/vquic/curl_quiche.c b/deps/curl/lib/vquic/curl_quiche.c index 3598de1c7aa..304849892ce 100644 --- a/deps/curl/lib/vquic/curl_quiche.c +++ b/deps/curl/lib/vquic/curl_quiche.c @@ -29,6 +29,7 @@ #include #include #include "bufq.h" +#include "hash.h" #include "urldata.h" #include "cfilters.h" #include "cf-socket.h" @@ -43,6 +44,7 @@ #include "http1.h" #include "vquic.h" #include "vquic_int.h" +#include "vquic-tls.h" #include "curl_quiche.h" #include "transfer.h" #include "inet_pton.h" @@ -55,18 +57,17 @@ #include "curl_memory.h" #include "memdebug.h" -/* #define DEBUG_QUICHE */ +/* HTTP/3 error values defined in RFC 9114, ch. 8.1 */ +#define CURL_H3_NO_ERROR (0x0100) #define QUIC_MAX_STREAMS (100) -#define QUIC_IDLE_TIMEOUT (60 * 1000) /* milliseconds */ #define H3_STREAM_WINDOW_SIZE (128 * 1024) #define H3_STREAM_CHUNK_SIZE (16 * 1024) -/* The pool keeps spares around and half of a full stream windows - * seems good. More does not seem to improve performance. - * The benefit of the pool is that stream buffer to not keep - * spares. So memory consumption goes down when streams run empty, - * have a large upload done, etc. */ +/* The pool keeps spares around and half of a full stream windows seems good. + * More does not seem to improve performance. The benefit of the pool is that + * stream buffer to not keep spares. Memory consumption goes down when streams + * run empty, have a large upload done, etc. */ #define H3_STREAM_POOL_SPARES \ (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE ) / 2 /* Receive and Send max number of chunks just follows from the @@ -84,34 +85,29 @@ void Curl_quiche_ver(char *p, size_t len) (void)msnprintf(p, len, "quiche/%s", quiche_version()); } -static void keylog_callback(const SSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} - struct cf_quiche_ctx { struct cf_quic_ctx q; + struct ssl_peer peer; + struct curl_tls_ctx tls; quiche_conn *qconn; quiche_config *cfg; quiche_h3_conn *h3c; quiche_h3_config *h3config; uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; - SSL_CTX *sslctx; - SSL *ssl; struct curltime started_at; /* time the current attempt started */ struct curltime handshake_at; /* time connect handshake finished */ - struct curltime first_byte_at; /* when first byte was recvd */ - struct curltime reconnect_at; /* time the next attempt should start */ struct bufc_pool stream_bufcp; /* chunk pool for streams */ + struct Curl_hash streams; /* hash `data->mid` to `stream_ctx` */ curl_off_t data_recvd; - size_t sends_on_hold; /* # of streams with SEND_HOLD set */ + BIT(initialized); BIT(goaway); /* got GOAWAY from server */ - BIT(got_first_byte); /* if first byte was received */ BIT(x509_store_setup); /* if x509 store has been set up */ + BIT(shutdown_started); /* queued shutdown packets */ }; #ifdef DEBUG_QUICHE +/* initialize debug log callback only once */ +static int debug_log_init = 0; static void quiche_debug_log(const char *line, void *argp) { (void)argp; @@ -119,179 +115,102 @@ static void quiche_debug_log(const char *line, void *argp) } #endif -static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx) +static void h3_stream_hash_free(void *stream); + +static void cf_quiche_ctx_init(struct cf_quiche_ctx *ctx) { - if(ctx) { - vquic_ctx_free(&ctx->q); - if(ctx->qconn) - quiche_conn_free(ctx->qconn); - if(ctx->h3config) - quiche_h3_config_free(ctx->h3config); - if(ctx->h3c) - quiche_h3_conn_free(ctx->h3c); - if(ctx->cfg) - quiche_config_free(ctx->cfg); - Curl_bufcp_free(&ctx->stream_bufcp); - memset(ctx, 0, sizeof(*ctx)); + DEBUGASSERT(!ctx->initialized); +#ifdef DEBUG_QUICHE + if(!debug_log_init) { + quiche_enable_debug_logging(quiche_debug_log, NULL); + debug_log_init = 1; } +#endif + Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, + H3_STREAM_POOL_SPARES); + Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free); + ctx->data_recvd = 0; + ctx->initialized = TRUE; } -static CURLcode quic_x509_store_setup(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void cf_quiche_ctx_free(struct cf_quiche_ctx *ctx) { - struct cf_quiche_ctx *ctx = cf->ctx; - - if(!ctx->x509_store_setup) { - if(cf->conn->ssl_config.verifypeer) { - const char * const ssl_cafile = cf->conn->ssl_config.CAfile; - const char * const ssl_capath = cf->conn->ssl_config.CApath; - if(ssl_cafile || ssl_capath) { - SSL_CTX_set_verify(ctx->sslctx, SSL_VERIFY_PEER, NULL); - /* tell OpenSSL where to find CA certificates that are used to verify - the server's certificate. */ - if(!SSL_CTX_load_verify_locations( - ctx->sslctx, ssl_cafile, ssl_capath)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:" - " CAfile: %s CApath: %s", - ssl_cafile ? ssl_cafile : "none", - ssl_capath ? ssl_capath : "none"); - return CURLE_SSL_CACERT_BADFILE; - } - infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); - infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); - } -#ifdef CURL_CA_FALLBACK - else { - /* verifying the peer without any CA certificates won't work so - use openssl's built-in default as fallback */ - SSL_CTX_set_default_verify_paths(ssl_ctx); - } -#endif - } - ctx->x509_store_setup = TRUE; + if(ctx && ctx->initialized) { + /* quiche just freed it */ + ctx->tls.ossl.ssl = NULL; + Curl_vquic_tls_cleanup(&ctx->tls); + Curl_ssl_peer_cleanup(&ctx->peer); + vquic_ctx_free(&ctx->q); + Curl_bufcp_free(&ctx->stream_bufcp); + Curl_hash_clean(&ctx->streams); + Curl_hash_destroy(&ctx->streams); } - return CURLE_OK; + free(ctx); } -static CURLcode quic_ssl_setup(struct Curl_cfilter *cf, struct Curl_easy *data) +static void cf_quiche_ctx_close(struct cf_quiche_ctx *ctx) { - struct cf_quiche_ctx *ctx = cf->ctx; - unsigned char checkip[16]; - - DEBUGASSERT(!ctx->sslctx); - ctx->sslctx = SSL_CTX_new(TLS_method()); - if(!ctx->sslctx) - return CURLE_OUT_OF_MEMORY; - - SSL_CTX_set_alpn_protos(ctx->sslctx, - (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL, - sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1); - - SSL_CTX_set_default_verify_paths(ctx->sslctx); - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - SSL_CTX_set_keylog_callback(ctx->sslctx, keylog_callback); - } - - ctx->ssl = SSL_new(ctx->sslctx); - if(!ctx->ssl) - return CURLE_QUIC_CONNECT_ERROR; - - SSL_set_app_data(ctx->ssl, cf); - - if((0 == Curl_inet_pton(AF_INET, cf->conn->host.name, checkip)) -#ifdef ENABLE_IPV6 - && (0 == Curl_inet_pton(AF_INET6, cf->conn->host.name, checkip)) -#endif - ) { - char *snihost = Curl_ssl_snihost(data, cf->conn->host.name, NULL); - if(!snihost || !SSL_set_tlsext_host_name(ctx->ssl, snihost)) { - failf(data, "Failed set SNI"); - SSL_free(ctx->ssl); - ctx->ssl = NULL; - return CURLE_QUIC_CONNECT_ERROR; - } - } - - return CURLE_OK; + if(ctx->h3c) + quiche_h3_conn_free(ctx->h3c); + if(ctx->h3config) + quiche_h3_config_free(ctx->h3config); + if(ctx->qconn) + quiche_conn_free(ctx->qconn); + if(ctx->cfg) + quiche_config_free(ctx->cfg); } +static CURLcode cf_flush_egress(struct Curl_cfilter *cf, + struct Curl_easy *data); + /** * All about the H3 internals of a stream */ struct stream_ctx { - int64_t id; /* HTTP/3 protocol stream identifier */ + curl_uint64_t id; /* HTTP/3 protocol stream identifier */ struct bufq recvbuf; /* h3 response */ struct h1_req_parser h1; /* h1 request parsing */ - uint64_t error3; /* HTTP/3 stream error code */ - curl_off_t upload_left; /* number of request bytes left to upload */ - bool closed; /* TRUE on stream close */ - bool reset; /* TRUE on stream reset */ - bool send_closed; /* stream is locally closed */ - bool resp_hds_complete; /* complete, final response has been received */ - bool resp_got_header; /* TRUE when h3 stream has recvd some HEADER */ + curl_uint64_t error3; /* HTTP/3 stream error code */ + BIT(opened); /* TRUE after stream has been opened */ + BIT(closed); /* TRUE on stream close */ + BIT(reset); /* TRUE on stream reset */ + BIT(send_closed); /* stream is locally closed */ + BIT(resp_hds_complete); /* final response has been received */ + BIT(resp_got_header); /* TRUE when h3 stream has recvd some HEADER */ + BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ }; -#define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->h3_ctx \ - : NULL)) -#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx -#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ - H3_STREAM_CTX(d)->id : -2) +#define H3_STREAM_CTX(ctx,data) ((struct stream_ctx *)(\ + data? Curl_hash_offt_get(&(ctx)->streams, (data)->mid) : NULL)) -static bool stream_send_is_suspended(struct Curl_easy *data) -{ - return (data->req.keepon & KEEP_SEND_HOLD); -} - -static void stream_send_suspend(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void h3_stream_ctx_free(struct stream_ctx *stream) { - struct cf_quiche_ctx *ctx = cf->ctx; - - if((data->req.keepon & KEEP_SENDBITS) == KEEP_SEND) { - data->req.keepon |= KEEP_SEND_HOLD; - ++ctx->sends_on_hold; - if(H3_STREAM_ID(data) >= 0) - CURL_TRC_CF(data, cf, "[%"PRId64"] suspend sending", - H3_STREAM_ID(data)); - else - CURL_TRC_CF(data, cf, "[%s] suspend sending", data->state.url); - } + Curl_bufq_free(&stream->recvbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); } -static void stream_send_resume(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void h3_stream_hash_free(void *stream) { - struct cf_quiche_ctx *ctx = cf->ctx; - - if(stream_send_is_suspended(data)) { - data->req.keepon &= ~KEEP_SEND_HOLD; - --ctx->sends_on_hold; - if(H3_STREAM_ID(data) >= 0) - CURL_TRC_CF(data, cf, "[%"PRId64"] resume sending", - H3_STREAM_ID(data)); - else - CURL_TRC_CF(data, cf, "[%s] resume sending", data->state.url); - Curl_expire(data, 0, EXPIRE_RUN_NOW); - } + DEBUGASSERT(stream); + h3_stream_ctx_free((struct stream_ctx *)stream); } static void check_resumes(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct Curl_easy *sdata; - - if(ctx->sends_on_hold) { - DEBUGASSERT(data->multi); - for(sdata = data->multi->easyp; - sdata && ctx->sends_on_hold; sdata = sdata->next) { - if(stream_send_is_suspended(sdata)) { - stream_send_resume(cf, sdata); + struct Curl_llist_node *e; + + DEBUGASSERT(data->multi); + for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *sdata = Curl_node_elem(e); + if(sdata->conn == data->conn) { + struct stream_ctx *stream = H3_STREAM_CTX(ctx, sdata); + if(stream && stream->quic_flow_blocked) { + stream->quic_flow_blocked = FALSE; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] unblock", stream->id); } } } @@ -301,7 +220,7 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream) return CURLE_OK; @@ -310,70 +229,110 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, if(!stream) return CURLE_OUT_OF_MEMORY; - H3_STREAM_LCTX(data) = stream; stream->id = -1; Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + + if(!Curl_hash_offt_set(&ctx->streams, data->mid, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURLcode result; (void)cf; if(stream) { - CURL_TRC_CF(data, cf, "[%"PRId64"] easy handle is done", stream->id); - if(stream_send_is_suspended(data)) { - data->req.keepon &= ~KEEP_SEND_HOLD; - --ctx->sends_on_hold; + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] easy handle is done", stream->id); + if(ctx->qconn && !stream->closed) { + quiche_conn_stream_shutdown(ctx->qconn, stream->id, + QUICHE_SHUTDOWN_READ, CURL_H3_NO_ERROR); + if(!stream->send_closed) { + quiche_conn_stream_shutdown(ctx->qconn, stream->id, + QUICHE_SHUTDOWN_WRITE, CURL_H3_NO_ERROR); + stream->send_closed = TRUE; + } + stream->closed = TRUE; + result = cf_flush_egress(cf, data); + if(result) + CURL_TRC_CF(data, cf, "data_done, flush egress -> %d", result); } - Curl_bufq_free(&stream->recvbuf); - Curl_h1_req_parse_free(&stream->h1); - free(stream); - H3_STREAM_LCTX(data) = NULL; + Curl_hash_offt_remove(&ctx->streams, data->mid); } } -static void drain_stream(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); unsigned char bits; (void)cf; bits = CURL_CSELECT_IN; - if(stream && !stream->send_closed && stream->upload_left) + if(stream && !stream->send_closed) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - data->state.dselect_bits = bits; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; Curl_expire(data, 0, EXPIRE_RUN_NOW); } } static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf, struct Curl_easy *data, - int64_t stream3_id) + curl_uint64_t stream_id, + struct stream_ctx **pstream) { - struct Curl_easy *sdata; + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream; (void)cf; - if(H3_STREAM_ID(data) == stream3_id) { + stream = H3_STREAM_CTX(ctx, data); + if(stream && stream->id == stream_id) { + *pstream = stream; return data; } else { + struct Curl_llist_node *e; DEBUGASSERT(data->multi); - for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { - if((sdata->conn == data->conn) && H3_STREAM_ID(sdata) == stream3_id) { + for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *sdata = Curl_node_elem(e); + if(sdata->conn != data->conn) + continue; + stream = H3_STREAM_CTX(ctx, sdata); + if(stream && stream->id == stream_id) { + *pstream = stream; return sdata; } } } + *pstream = NULL; return NULL; } +static void cf_quiche_expire_conn_closed(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct Curl_llist_node *e; + + DEBUGASSERT(data->multi); + CURL_TRC_CF(data, cf, "conn closed, expire all transfers"); + for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *sdata = Curl_node_elem(e); + if(sdata == data || sdata->conn != data->conn) + continue; + CURL_TRC_CF(sdata, cf, "conn closed, expire transfer"); + Curl_expire(sdata, 0, EXPIRE_RUN_NOW); + } +} + /* * write_resp_raw() copies response data in raw format to the `data`'s * receive buffer. If not enough space is available, it appends to the @@ -383,7 +342,8 @@ static CURLcode write_resp_raw(struct Curl_cfilter *cf, struct Curl_easy *data, const void *mem, size_t memlen) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result = CURLE_OK; ssize_t nwritten; @@ -413,14 +373,15 @@ static int cb_each_header(uint8_t *name, size_t name_len, void *argp) { struct cb_ctx *x = argp; - struct stream_ctx *stream = H3_STREAM_CTX(x->data); + struct cf_quiche_ctx *ctx = x->cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, x->data); CURLcode result; if(!stream) return CURLE_OK; if((name_len == 7) && !strncmp(HTTP_PSEUDO_STATUS, (char *)name, 7)) { - CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] status: %.*s", + CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRIu64 "] status: %.*s", stream->id, (int)value_len, value); result = write_resp_raw(x->cf, x->data, "HTTP/3 ", sizeof("HTTP/3 ") - 1); if(!result) @@ -429,7 +390,7 @@ static int cb_each_header(uint8_t *name, size_t name_len, result = write_resp_raw(x->cf, x->data, " \r\n", 3); } else { - CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] header: %.*s: %.*s", + CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRIu64 "] header: %.*s: %.*s", stream->id, (int)name_len, name, (int)value_len, value); result = write_resp_raw(x->cf, x->data, name, name_len); @@ -441,7 +402,7 @@ static int cb_each_header(uint8_t *name, size_t name_len, result = write_resp_raw(x->cf, x->data, "\r\n", 2); } if(result) { - CURL_TRC_CF(x->data, x->cf, "[%"PRId64"] on header error %d", + CURL_TRC_CF(x->data, x->cf, "[%"FMT_PRIu64"] on header error %d", stream->id, result); } return result; @@ -453,7 +414,7 @@ static ssize_t stream_resp_read(void *reader_ctx, { struct cb_ctx *x = reader_ctx; struct cf_quiche_ctx *ctx = x->cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(x->data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, x->data); ssize_t nread; if(!stream) { @@ -476,7 +437,8 @@ static ssize_t stream_resp_read(void *reader_ctx, static CURLcode cf_recv_body(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nwritten; struct cb_ctx cb_ctx; CURLcode result = CURLE_OK; @@ -497,9 +459,9 @@ static CURLcode cf_recv_body(struct Curl_cfilter *cf, stream_resp_read, &cb_ctx, &result); if(nwritten < 0 && result != CURLE_AGAIN) { - CURL_TRC_CF(data, cf, "[%"PRId64"] recv_body error %zd", + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] recv_body error %zd", stream->id, nwritten); - failf(data, "Error %d in HTTP/3 response body for stream[%"PRId64"]", + failf(data, "Error %d in HTTP/3 response body for stream[%"FMT_PRIu64"]", result, stream->id); stream->closed = TRUE; stream->reset = TRUE; @@ -534,17 +496,15 @@ static const char *cf_ev_name(quiche_h3_event *ev) static CURLcode h3_process_event(struct Curl_cfilter *cf, struct Curl_easy *data, - int64_t stream3_id, + struct stream_ctx *stream, quiche_h3_event *ev) { - struct stream_ctx *stream = H3_STREAM_CTX(data); struct cb_ctx cb_ctx; CURLcode result = CURLE_OK; int rc; if(!stream) return CURLE_OK; - DEBUGASSERT(stream3_id == stream->id); switch(quiche_h3_event_type(ev)) { case QUICHE_H3_EVENT_HEADERS: stream->resp_got_header = TRUE; @@ -552,11 +512,11 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf, cb_ctx.data = data; rc = quiche_h3_event_for_each_header(ev, cb_each_header, &cb_ctx); if(rc) { - failf(data, "Error %d in HTTP/3 response header for stream[%"PRId64"]", - rc, stream3_id); + failf(data, "Error %d in HTTP/3 response header for stream[%" + FMT_PRIu64"]", rc, stream->id); return CURLE_RECV_ERROR; } - CURL_TRC_CF(data, cf, "[%"PRId64"] <- [HEADERS]", stream3_id); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] <- [HEADERS]", stream->id); break; case QUICHE_H3_EVENT_DATA: @@ -566,7 +526,7 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf, break; case QUICHE_H3_EVENT_RESET: - CURL_TRC_CF(data, cf, "[%"PRId64"] RESET", stream3_id); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] RESET", stream->id); stream->closed = TRUE; stream->reset = TRUE; stream->send_closed = TRUE; @@ -574,7 +534,7 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf, break; case QUICHE_H3_EVENT_FINISHED: - CURL_TRC_CF(data, cf, "[%"PRId64"] CLOSED", stream3_id); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] CLOSED", stream->id); if(!stream->resp_hds_complete) { result = write_resp_raw(cf, data, "\r\n", 2); if(result) @@ -583,16 +543,15 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf, } stream->closed = TRUE; streamclose(cf->conn, "End of stream"); - data->req.keepon &= ~KEEP_SEND_HOLD; break; case QUICHE_H3_EVENT_GOAWAY: - CURL_TRC_CF(data, cf, "[%"PRId64"] <- [GOAWAY]", stream3_id); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] <- [GOAWAY]", stream->id); break; default: - CURL_TRC_CF(data, cf, "[%"PRId64"] recv, unhandled event %d", - stream3_id, quiche_h3_event_type(ev)); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] recv, unhandled event %d", + stream->id, quiche_h3_event_type(ev)); break; } return result; @@ -602,36 +561,33 @@ static CURLcode cf_poll_events(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = NULL; struct Curl_easy *sdata; quiche_h3_event *ev; CURLcode result; /* Take in the events and distribute them to the transfers. */ while(ctx->h3c) { - int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev); + curl_int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev); if(stream3_id == QUICHE_H3_ERR_DONE) { break; } else if(stream3_id < 0) { - CURL_TRC_CF(data, cf, "[%"PRId64"] error poll: %"PRId64, - stream? stream->id : -1, stream3_id); + CURL_TRC_CF(data, cf, "error poll: %"FMT_PRId64, stream3_id); return CURLE_HTTP3; } - sdata = get_stream_easy(cf, data, stream3_id); - if(!sdata) { - CURL_TRC_CF(data, cf, "[%"PRId64"] discard event %s for " - "unknown [%"PRId64"]", - stream? stream->id : -1, cf_ev_name(ev), stream3_id); + sdata = get_stream_easy(cf, data, stream3_id, &stream); + if(!sdata || !stream) { + CURL_TRC_CF(data, cf, "discard event %s for unknown [%"FMT_PRId64"]", + cf_ev_name(ev), stream3_id); } else { - result = h3_process_event(cf, sdata, stream3_id, ev); - drain_stream(cf, sdata); + result = h3_process_event(cf, sdata, stream, ev); + h3_drain_stream(cf, sdata); if(result) { - CURL_TRC_CF(data, cf, "[%"PRId64"] error processing event %s " - "for [%"PRId64"] -> %d", - stream? stream->id : -1, cf_ev_name(ev), + CURL_TRC_CF(data, cf, "error processing event %s " + "for [%"FMT_PRIu64"] -> %d", cf_ev_name(ev), stream3_id, result); if(data == sdata) { /* Only report this error to the caller if it is about the @@ -675,11 +631,19 @@ static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, &recv_info); if(nread < 0) { if(QUICHE_ERR_DONE == nread) { + if(quiche_conn_is_draining(ctx->qconn)) { + CURL_TRC_CF(r->data, r->cf, "ingress, connection is draining"); + return CURLE_RECV_ERROR; + } + if(quiche_conn_is_closed(ctx->qconn)) { + CURL_TRC_CF(r->data, r->cf, "ingress, connection is closed"); + return CURLE_RECV_ERROR; + } CURL_TRC_CF(r->data, r->cf, "ingress, quiche is DONE"); return CURLE_OK; } else if(QUICHE_ERR_TLS_FAIL == nread) { - long verify_ok = SSL_get_verify_result(ctx->ssl); + long verify_ok = SSL_get_verify_result(ctx->tls.ossl.ssl); if(verify_ok != X509_V_OK) { failf(r->data, "SSL certificate problem: %s", X509_verify_cert_error_string(verify_ok)); @@ -707,7 +671,7 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf, CURLcode result; DEBUGASSERT(ctx->qconn); - result = quic_x509_store_setup(cf, data); + result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); if(result) return result; @@ -766,8 +730,8 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, struct cf_quiche_ctx *ctx = cf->ctx; ssize_t nread; CURLcode result; - int64_t expiry_ns; - int64_t timeout_ns; + curl_int64_t expiry_ns; + curl_int64_t timeout_ns; struct read_ctx readx; size_t pkt_count, gsolen; @@ -775,7 +739,13 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, if(!expiry_ns) { quiche_conn_on_timeout(ctx->qconn); if(quiche_conn_is_closed(ctx->qconn)) { - failf(data, "quiche_conn_on_timeout closed the connection"); + if(quiche_conn_is_timed_out(ctx->qconn)) + failf(data, "connection closed by idle timeout"); + else + failf(data, "connection closed by server"); + /* Connection timed out, expire all transfers belonging to it + * as will not get any more POLL events here. */ + cf_quiche_expire_conn_closed(cf, data); return CURLE_SEND_ERROR; } } @@ -840,25 +810,26 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode *err) { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; DEBUGASSERT(stream); if(stream->reset) { failf(data, - "HTTP/3 stream %" PRId64 " reset by server", stream->id); - *err = stream->resp_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv, was reset -> %d", + "HTTP/3 stream %" FMT_PRIu64 " reset by server", stream->id); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] cf_recv, was reset -> %d", stream->id, *err); } else if(!stream->resp_got_header) { failf(data, - "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" - " all response header fields, treated as error", + "HTTP/3 stream %" FMT_PRIu64 " was closed cleanly, but before " + "getting all response header fields, treated as error", stream->id); /* *err = CURLE_PARTIAL_FILE; */ - *err = CURLE_RECV_ERROR; - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv, closed incomplete" + *err = CURLE_HTTP3; + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] cf_recv, closed incomplete" " -> %d", stream->id, *err); } else { @@ -872,10 +843,12 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; CURLcode result; + vquic_ctx_update_time(&ctx->q); + if(!stream) { *err = CURLE_RECV_ERROR; return -1; @@ -884,7 +857,7 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(!Curl_bufq_is_empty(&stream->recvbuf)) { nread = Curl_bufq_read(&stream->recvbuf, (unsigned char *)buf, len, err); - CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) " + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] read recvbuf(len=%zu) " "-> %zd, %d", stream->id, len, nread, *err); if(nread < 0) goto out; @@ -901,7 +874,7 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(nread < 0 && !Curl_bufq_is_empty(&stream->recvbuf)) { nread = Curl_bufq_read(&stream->recvbuf, (unsigned char *)buf, len, err); - CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) " + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] read recvbuf(len=%zu) " "-> %zd, %d", stream->id, len, nread, *err); if(nread < 0) goto out; @@ -909,7 +882,7 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(nread > 0) { if(stream->closed) - drain_stream(cf, data); + h3_drain_stream(cf, data); } else { if(stream->closed) { @@ -935,25 +908,76 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } if(nread > 0) ctx->data_recvd += nread; - CURL_TRC_CF(data, cf, "[%"PRId64"] cf_recv(total=%" - CURL_FORMAT_CURL_OFF_T ") -> %zd, %d", + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] cf_recv(total=%" + FMT_OFF_T ") -> %zd, %d", stream->id, ctx->data_recvd, nread, *err); return nread; } +static ssize_t cf_quiche_send_body(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct stream_ctx *stream, + const void *buf, size_t len, bool eos, + CURLcode *err) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + ssize_t nwritten; + + nwritten = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->id, + (uint8_t *)buf, len, eos); + if(nwritten == QUICHE_H3_ERR_DONE || (nwritten == 0 && len > 0)) { + /* TODO: we seem to be blocked on flow control and should HOLD + * sending. But when do we open again? */ + if(!quiche_conn_stream_writable(ctx->qconn, stream->id, len)) { + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> window exhausted", stream->id, len); + stream->quic_flow_blocked = TRUE; + } + *err = CURLE_AGAIN; + return -1; + } + else if(nwritten == QUICHE_H3_TRANSPORT_ERR_INVALID_STREAM_STATE) { + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> invalid stream state", stream->id, len); + *err = CURLE_HTTP3; + return -1; + } + else if(nwritten == QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE) { + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> exceeds size", stream->id, len); + *err = CURLE_SEND_ERROR; + return -1; + } + else if(nwritten < 0) { + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> quiche err %zd", stream->id, len, nwritten); + *err = CURLE_SEND_ERROR; + return -1; + } + else { + if(eos && (len == (size_t)nwritten)) + stream->send_closed = TRUE; + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send body(len=%zu, " + "eos=%d) -> %zd", + stream->id, len, stream->send_closed, nwritten); + *err = CURLE_OK; + return nwritten; + } +} + /* Index where :authority header field will appear in request header field list. */ #define AUTHORITY_DST_IDX 3 static ssize_t h3_open_stream(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, + const char *buf, size_t len, bool eos, CURLcode *err) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); size_t nheader, i; - int64_t stream3_id; + curl_int64_t stream3_id; struct dynhds h2_headers; quiche_h3_header *nva = NULL; ssize_t nwritten; @@ -963,7 +987,7 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf, if(*err) { return -1; } - stream = H3_STREAM_CTX(data); + stream = H3_STREAM_CTX(ctx, data); DEBUGASSERT(stream); } @@ -1003,23 +1027,7 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf, nva[i].value_len = e->valuelen; } - switch(data->state.httpreq) { - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - case HTTPREQ_PUT: - if(data->state.infilesize != -1) - stream->upload_left = data->state.infilesize; - else - /* data sending without specifying the data amount up front */ - stream->upload_left = -1; /* unknown */ - break; - default: - stream->upload_left = 0; /* no request body */ - break; - } - - if(stream->upload_left == 0) + if(eos && ((size_t)nwritten == len)) stream->send_closed = TRUE; stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader, @@ -1028,15 +1036,14 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf, if(QUICHE_H3_ERR_STREAM_BLOCKED == stream3_id) { /* quiche seems to report this error if the connection window is * exhausted. Which happens frequently and intermittent. */ - CURL_TRC_CF(data, cf, "send_request(%s) rejected with BLOCKED", - data->state.url); - stream_send_suspend(cf, data); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] blocked", stream->id); + stream->quic_flow_blocked = TRUE; *err = CURLE_AGAIN; nwritten = -1; goto out; } else { - CURL_TRC_CF(data, cf, "send_request(%s) -> %" PRId64, + CURL_TRC_CF(data, cf, "send_request(%s) -> %" FMT_PRIu64, data->state.url, stream3_id); } *err = CURLE_SEND_ERROR; @@ -1044,22 +1051,39 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf, goto out; } - DEBUGASSERT(stream->id == -1); + DEBUGASSERT(!stream->opened); *err = CURLE_OK; stream->id = stream3_id; + stream->opened = TRUE; stream->closed = FALSE; stream->reset = FALSE; if(Curl_trc_is_verbose(data)) { - infof(data, "[HTTP/3] [%" PRId64 "] OPENED stream for %s", + infof(data, "[HTTP/3] [%" FMT_PRIu64 "] OPENED stream for %s", stream->id, data->state.url); for(i = 0; i < nheader; ++i) { - infof(data, "[HTTP/3] [%" PRId64 "] [%.*s: %.*s]", stream->id, + infof(data, "[HTTP/3] [%" FMT_PRIu64 "] [%.*s: %.*s]", stream->id, (int)nva[i].name_len, nva[i].name, (int)nva[i].value_len, nva[i].value); } } + if(nwritten > 0 && ((size_t)nwritten < len)) { + /* after the headers, there was request BODY data */ + size_t hds_len = (size_t)nwritten; + ssize_t bwritten; + + bwritten = cf_quiche_send_body(cf, data, stream, + buf + hds_len, len - hds_len, eos, err); + if((bwritten < 0) && (CURLE_AGAIN != *err)) { + /* real error, fail */ + nwritten = -1; + } + else if(bwritten > 0) { + nwritten += bwritten; + } + } + out: free(nva); Curl_dynhds_free(&h2_headers); @@ -1067,44 +1091,30 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf, } static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) + const void *buf, size_t len, bool eos, + CURLcode *err) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result; ssize_t nwritten; + vquic_ctx_update_time(&ctx->q); + *err = cf_process_ingress(cf, data); if(*err) { nwritten = -1; goto out; } - if(!stream || stream->id < 0) { - nwritten = h3_open_stream(cf, data, buf, len, err); + if(!stream || !stream->opened) { + nwritten = h3_open_stream(cf, data, buf, len, eos, err); if(nwritten < 0) goto out; - stream = H3_STREAM_CTX(data); + stream = H3_STREAM_CTX(ctx, data); } - else { - bool eof = (stream->upload_left >= 0 && - (curl_off_t)len >= stream->upload_left); - nwritten = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->id, - (uint8_t *)buf, len, eof); - if(nwritten == QUICHE_H3_ERR_DONE || (nwritten == 0 && len > 0)) { - /* TODO: we seem to be blocked on flow control and should HOLD - * sending. But when do we open again? */ - if(!quiche_conn_stream_writable(ctx->qconn, stream->id, len)) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " - "-> window exhausted", stream->id, len); - stream_send_suspend(cf, data); - } - *err = CURLE_AGAIN; - nwritten = -1; - goto out; - } - else if(nwritten == QUICHE_H3_TRANSPORT_ERR_INVALID_STREAM_STATE && - stream->closed && stream->resp_hds_complete) { + else if(stream->closed) { + if(stream->resp_hds_complete) { /* sending request body on a stream that has been closed by the * server. If the server has send us a final response, we should * silently discard the send data. @@ -1113,40 +1123,20 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, * sending the 30x response. * This is sort of a race: had the transfer loop called recv first, * it would see the response and stop/discard sending on its own- */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] discarding data" + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] discarding data" "on closed stream with response", stream->id); *err = CURLE_OK; nwritten = (ssize_t)len; goto out; } - else if(nwritten == QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " - "-> exceeds size", stream->id, len); - *err = CURLE_SEND_ERROR; - nwritten = -1; - goto out; - } - else if(nwritten < 0) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " - "-> quiche err %zd", stream->id, len, nwritten); - *err = CURLE_SEND_ERROR; - nwritten = -1; - goto out; - } - else { - /* quiche accepted all or at least a part of the buf */ - if(stream->upload_left > 0) { - stream->upload_left = (nwritten < stream->upload_left)? - (stream->upload_left - nwritten) : 0; - } - if(stream->upload_left == 0) - stream->send_closed = TRUE; - - CURL_TRC_CF(data, cf, "[%" PRId64 "] send body(len=%zu, " - "left=%" CURL_FORMAT_CURL_OFF_T ") -> %zd", - stream->id, len, stream->upload_left, nwritten); - *err = CURLE_OK; - } + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> stream closed", stream->id, len); + *err = CURLE_HTTP3; + nwritten = -1; + goto out; + } + else { + nwritten = cf_quiche_send_body(cf, data, stream, buf, len, eos, err); } out: @@ -1155,8 +1145,8 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, *err = result; nwritten = -1; } - CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %zd, %d", - stream? stream->id : -1, len, nwritten, *err); + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] cf_send(len=%zu) -> %zd, %d", + stream ? stream->id : (curl_uint64_t)~0, len, nwritten, *err); return nwritten; } @@ -1164,32 +1154,37 @@ static bool stream_is_writeable(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); - return stream && - quiche_conn_stream_writable(ctx->qconn, (uint64_t)stream->id, 1); + return stream && (quiche_conn_stream_writable( + ctx->qconn, (curl_uint64_t)stream->id, 1) > 0); } -static int cf_quiche_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) +static void cf_quiche_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) { struct cf_quiche_ctx *ctx = cf->ctx; - struct SingleRequest *k = &data->req; - int rv = GETSOCK_BLANK; + bool want_recv, want_send; - socks[0] = ctx->q.sockfd; + if(!ctx->qconn) + return; - /* in an HTTP/3 connection we can basically always get a frame so we should - always be ready for one */ - rv |= GETSOCK_READSOCK(0); + Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); + if(want_recv || want_send) { + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + bool c_exhaust, s_exhaust; - /* we're still uploading or the HTTP/3 layer wants to send data */ - if(((k->keepon & KEEP_SENDBITS) == KEEP_SEND) - && stream_is_writeable(cf, data)) - rv |= GETSOCK_WRITESOCK(0); + c_exhaust = FALSE; /* Have not found any call in quiche that tells + us if the connection itself is blocked */ + s_exhaust = want_send && stream && stream->opened && + (stream->quic_flow_blocked || !stream_is_writeable(cf, data)); + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + !Curl_bufq_is_empty(&ctx->q.sendbuf); - return rv; + Curl_pollset_set(data, ps, ctx->q.sockfd, want_recv, want_send); + } } /* @@ -1199,7 +1194,8 @@ static int cf_quiche_get_select_socks(struct Curl_cfilter *cf, static bool cf_quiche_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - const struct stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_quiche_ctx *ctx = cf->ctx; + const struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)cf; return stream && !Curl_bufq_is_empty(&stream->recvbuf); } @@ -1211,7 +1207,7 @@ static CURLcode h3_data_pause(struct Curl_cfilter *cf, /* TODO: there seems right now no API in quiche to shrink/enlarge * the streams windows. As we do in HTTP/2. */ if(!pause) { - drain_stream(cf, data); + h3_drain_stream(cf, data); Curl_expire(data, 0, EXPIRE_RUN_NOW); } return CURLE_OK; @@ -1221,6 +1217,7 @@ static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf, struct Curl_easy *data, int event, int arg1, void *arg2) { + struct cf_quiche_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; (void)arg1; @@ -1231,27 +1228,28 @@ static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf, case CF_CTRL_DATA_PAUSE: result = h3_data_pause(cf, data, (arg1 != 0)); break; - case CF_CTRL_DATA_DONE: { + case CF_CTRL_DATA_DETACH: + h3_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE: h3_data_done(cf, data); break; - } case CF_CTRL_DATA_DONE_SEND: { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream && !stream->send_closed) { unsigned char body[1]; ssize_t sent; stream->send_closed = TRUE; - stream->upload_left = 0; body[0] = 'X'; - sent = cf_quiche_send(cf, data, body, 0, &result); - CURL_TRC_CF(data, cf, "[%"PRId64"] DONE_SEND -> %zd, %d", + sent = cf_quiche_send(cf, data, body, 0, TRUE, &result); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] DONE_SEND -> %zd, %d", stream->id, sent, result); } break; } case CF_CTRL_DATA_IDLE: { - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream && !stream->closed) { result = cf_flush_egress(cf, data); if(result) @@ -1265,63 +1263,8 @@ static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf, return result; } -static CURLcode cf_verify_peer(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_quiche_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - - cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - cf->conn->httpversion = 30; - cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; - - if(cf->conn->ssl_config.verifyhost) { - X509 *server_cert; - server_cert = SSL_get_peer_certificate(ctx->ssl); - if(!server_cert) { - result = CURLE_PEER_FAILED_VERIFICATION; - goto out; - } - result = Curl_ossl_verifyhost(data, cf->conn, server_cert); - X509_free(server_cert); - if(result) - goto out; - } - else - CURL_TRC_CF(data, cf, "Skipped certificate verification"); - - ctx->h3config = quiche_h3_config_new(); - if(!ctx->h3config) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - /* Create a new HTTP/3 connection on the QUIC connection. */ - ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config); - if(!ctx->h3c) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - if(data->set.ssl.certinfo) - /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, ctx->ssl); - -out: - if(result) { - if(ctx->h3config) { - quiche_h3_config_free(ctx->h3config); - ctx->h3config = NULL; - } - if(ctx->h3c) { - quiche_h3_conn_free(ctx->h3c); - ctx->h3c = NULL; - } - } - return result; -} - -static CURLcode cf_connect_start(struct Curl_cfilter *cf, - struct Curl_easy *data) +static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; int rv; @@ -1329,30 +1272,23 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, const struct Curl_sockaddr_ex *sockaddr; DEBUGASSERT(ctx->q.sockfd != CURL_SOCKET_BAD); - -#ifdef DEBUG_QUICHE - /* initialize debug log callback only once */ - static int debug_log_init = 0; - if(!debug_log_init) { - quiche_enable_debug_logging(quiche_debug_log, NULL); - debug_log_init = 1; - } -#endif - Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, - H3_STREAM_POOL_SPARES); - ctx->data_recvd = 0; + DEBUGASSERT(ctx->initialized); result = vquic_ctx_init(&ctx->q); if(result) return result; + result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC); + if(result) + return result; + ctx->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION); if(!ctx->cfg) { - failf(data, "can't create quiche config"); + failf(data, "cannot create quiche config"); return CURLE_FAILED_INIT; } - quiche_config_enable_pacing(ctx->cfg, false); - quiche_config_set_max_idle_timeout(ctx->cfg, QUIC_IDLE_TIMEOUT); + quiche_config_enable_pacing(ctx->cfg, FALSE); + quiche_config_set_max_idle_timeout(ctx->cfg, CURL_QUIC_MAX_IDLE_MS); quiche_config_set_initial_max_data(ctx->cfg, (1 * 1024 * 1024) /* (QUIC_MAX_STREAMS/2) * H3_STREAM_WINDOW_SIZE */); quiche_config_set_initial_max_streams_bidi(ctx->cfg, QUIC_MAX_STREAMS); @@ -1374,9 +1310,10 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1); - DEBUGASSERT(!ctx->ssl); - DEBUGASSERT(!ctx->sslctx); - result = quic_ssl_setup(cf, data); + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, + QUICHE_H3_APPLICATION_PROTOCOL, + sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1, + NULL, NULL, cf); if(result) return result; @@ -1384,8 +1321,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, if(result) return result; - Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, - &sockaddr, NULL, NULL, NULL, NULL); + Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &sockaddr, NULL); ctx->q.local_addrlen = sizeof(ctx->q.local_addr); rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr, &ctx->q.local_addrlen); @@ -1393,18 +1329,19 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, return CURLE_QUIC_CONNECT_ERROR; ctx->qconn = quiche_conn_new_with_tls((const uint8_t *)ctx->scid, - sizeof(ctx->scid), NULL, 0, - (struct sockaddr *)&ctx->q.local_addr, - ctx->q.local_addrlen, - &sockaddr->sa_addr, sockaddr->addrlen, - ctx->cfg, ctx->ssl, false); + sizeof(ctx->scid), NULL, 0, + (struct sockaddr *)&ctx->q.local_addr, + ctx->q.local_addrlen, + &sockaddr->curl_sa_addr, + sockaddr->addrlen, + ctx->cfg, ctx->tls.ossl.ssl, FALSE); if(!ctx->qconn) { - failf(data, "can't create quiche connection"); + failf(data, "cannot create quiche connection"); return CURLE_OUT_OF_MEMORY; } /* Known to not work on Windows */ -#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD) +#if !defined(_WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD) { int qfd; (void)Curl_qlogdir(data, ctx->scid, sizeof(ctx->scid), &qfd); @@ -1436,13 +1373,23 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, return CURLE_OK; } +static CURLcode cf_quiche_verify_peer(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->httpversion = 30; + + return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); +} + static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool blocking, bool *done) { struct cf_quiche_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; - struct curltime now; if(cf->connected) { *done = TRUE; @@ -1457,19 +1404,13 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, } *done = FALSE; - now = Curl_now(); - - if(ctx->reconnect_at.tv_sec && Curl_timediff(now, ctx->reconnect_at) < 0) { - /* Not time yet to attempt the next connect */ - CURL_TRC_CF(data, cf, "waiting for reconnect time"); - goto out; - } + vquic_ctx_update_time(&ctx->q); if(!ctx->qconn) { - result = cf_connect_start(cf, data); + result = cf_quiche_ctx_open(cf, data); if(result) goto out; - ctx->started_at = now; + ctx->started_at = ctx->q.last_op; result = cf_flush_egress(cf, data); /* we do not expect to be able to recv anything yet */ goto out; @@ -1484,12 +1425,24 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, goto out; if(quiche_conn_is_established(ctx->qconn)) { + ctx->handshake_at = ctx->q.last_op; CURL_TRC_CF(data, cf, "handshake complete after %dms", - (int)Curl_timediff(now, ctx->started_at)); - ctx->handshake_at = now; - result = cf_verify_peer(cf, data); + (int)Curl_timediff(ctx->handshake_at, ctx->started_at)); + result = cf_quiche_verify_peer(cf, data); if(!result) { CURL_TRC_CF(data, cf, "peer verified"); + ctx->h3config = quiche_h3_config_new(); + if(!ctx->h3config) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + /* Create a new HTTP/3 connection on the QUIC connection. */ + ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config); + if(!ctx->h3c) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } cf->connected = TRUE; cf->conn->alpn = CURL_HTTP_VERSION_3; *done = TRUE; @@ -1499,67 +1452,87 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, else if(quiche_conn_is_draining(ctx->qconn)) { /* When a QUIC server instance is shutting down, it may send us a * CONNECTION_CLOSE right away. Our connection then enters the DRAINING - * state. - * This may be a stopping of the service or it may be that the server - * is reloading and a new instance will start serving soon. - * In any case, we tear down our socket and start over with a new one. - * We re-open the underlying UDP cf right now, but do not start - * connecting until called again. - */ - int reconn_delay_ms = 200; - - CURL_TRC_CF(data, cf, "connect, remote closed, reconnect after %dms", - reconn_delay_ms); - Curl_conn_cf_close(cf->next, data); - cf_quiche_ctx_clear(ctx); - result = Curl_conn_cf_connect(cf->next, data, FALSE, done); - if(!result && *done) { - *done = FALSE; - ctx->reconnect_at = Curl_now(); - ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000; - Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC); - result = CURLE_OK; - } + * state. The CONNECT may work in the near future again. Indicate + * that as a "weird" reply. */ + result = CURLE_WEIRD_SERVER_REPLY; } out: #ifndef CURL_DISABLE_VERBOSE_STRINGS if(result && result != CURLE_AGAIN) { - const char *r_ip; - int r_port; + struct ip_quadruple ip; - Curl_cf_socket_peek(cf->next, data, NULL, NULL, - &r_ip, &r_port, NULL, NULL); + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); infof(data, "connect to %s port %u failed: %s", - r_ip, r_port, curl_easy_strerror(result)); + ip.remote_ip, ip.remote_port, curl_easy_strerror(result)); } #endif return result; } -static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data) +static CURLcode cf_quiche_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) { struct cf_quiche_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(cf->shutdown || !ctx || !ctx->qconn) { + *done = TRUE; + return CURLE_OK; + } - if(ctx) { - if(ctx->qconn) { - (void)quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0); - /* flushing the egress is not a failsafe way to deliver all the - outstanding packets, but we also don't want to get stuck here... */ - (void)cf_flush_egress(cf, data); + *done = FALSE; + if(!ctx->shutdown_started) { + int err; + + ctx->shutdown_started = TRUE; + vquic_ctx_update_time(&ctx->q); + err = quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0); + if(err) { + CURL_TRC_CF(data, cf, "error %d adding shutdown packet, " + "aborting shutdown", err); + result = CURLE_SEND_ERROR; + goto out; } - cf_quiche_ctx_clear(ctx); } + + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "shutdown, flushing sendbuf"); + result = cf_flush_egress(cf, data); + if(result) + goto out; + } + + if(Curl_bufq_is_empty(&ctx->q.sendbuf)) { + /* sent everything, quiche does not seem to support a graceful + * shutdown waiting for a reply, so ware done. */ + CURL_TRC_CF(data, cf, "shutdown completely sent off, done"); + *done = TRUE; + } + else { + CURL_TRC_CF(data, cf, "shutdown sending blocked"); + } + +out: + return result; } -static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct cf_quiche_ctx *ctx = cf->ctx; + if(cf->ctx) { + bool done; + (void)cf_quiche_shutdown(cf, data, &done); + cf_quiche_ctx_close(cf->ctx); + } +} +static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ (void)data; - cf_quiche_ctx_clear(ctx); - free(ctx); - cf->ctx = NULL; + if(cf->ctx) { + cf_quiche_ctx_free(cf->ctx); + cf->ctx = NULL; + } } static CURLcode cf_quiche_query(struct Curl_cfilter *cf, @@ -1570,26 +1543,28 @@ static CURLcode cf_quiche_query(struct Curl_cfilter *cf, switch(query) { case CF_QUERY_MAX_CONCURRENT: { - uint64_t max_streams = CONN_INUSE(cf->conn); + curl_uint64_t max_streams = CONN_INUSE(cf->conn); if(!ctx->goaway) { max_streams += quiche_conn_peer_streams_left_bidi(ctx->qconn); } - *pres1 = (max_streams > INT_MAX)? INT_MAX : (int)max_streams; - CURL_TRC_CF(data, cf, "query: MAX_CONCURRENT -> %d", *pres1); + *pres1 = (max_streams > INT_MAX) ? INT_MAX : (int)max_streams; + CURL_TRC_CF(data, cf, "query conn[%" FMT_OFF_T "]: " + "MAX_CONCURRENT -> %d (%zu in use)", + cf->conn->connection_id, *pres1, CONN_INUSE(cf->conn)); return CURLE_OK; } case CF_QUERY_CONNECT_REPLY_MS: - if(ctx->got_first_byte) { - timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at); - *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX; + if(ctx->q.got_first_byte) { + timediff_t ms = Curl_timediff(ctx->q.first_byte_at, ctx->started_at); + *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; } else *pres1 = -1; return CURLE_OK; case CF_QUERY_TIMER_CONNECT: { struct curltime *when = pres2; - if(ctx->got_first_byte) - *when = ctx->first_byte_at; + if(ctx->q.got_first_byte) + *when = ctx->q.first_byte_at; return CURLE_OK; } case CF_QUERY_TIMER_APPCONNECT: { @@ -1601,7 +1576,7 @@ static CURLcode cf_quiche_query(struct Curl_cfilter *cf, default: break; } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -1610,15 +1585,27 @@ static bool cf_quiche_conn_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data, bool *input_pending) { + struct cf_quiche_ctx *ctx = cf->ctx; bool alive = TRUE; *input_pending = FALSE; + if(!ctx->qconn) + return FALSE; + + if(quiche_conn_is_closed(ctx->qconn)) { + if(quiche_conn_is_timed_out(ctx->qconn)) + CURL_TRC_CF(data, cf, "connection was closed due to idle timeout"); + else + CURL_TRC_CF(data, cf, "connection is closed"); + return FALSE; + } + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) return FALSE; if(*input_pending) { - /* This happens before we've sent off a request and the connection is - not in use by any other transfer, there shouldn't be any data here, + /* This happens before we have sent off a request and the connection is + not in use by any other transfer, there should not be any data here, only "protocol frames" */ *input_pending = FALSE; if(cf_process_ingress(cf, data)) @@ -1638,8 +1625,9 @@ struct Curl_cftype Curl_cft_http3 = { cf_quiche_destroy, cf_quiche_connect, cf_quiche_close, + cf_quiche_shutdown, Curl_cf_def_get_host, - cf_quiche_get_select_socks, + cf_quiche_adjust_pollset, cf_quiche_data_pending, cf_quiche_send, cf_quiche_recv, @@ -1660,11 +1648,12 @@ CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, (void)data; (void)conn; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } + cf_quiche_ctx_init(ctx); result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); if(result) @@ -1679,12 +1668,12 @@ CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, cf->next = udp_cf; out: - *pcf = (!result)? cf : NULL; + *pcf = (!result) ? cf : NULL; if(result) { if(udp_cf) Curl_conn_cf_discard_sub(cf, udp_cf, data, TRUE); Curl_safefree(cf); - Curl_safefree(ctx); + cf_quiche_ctx_free(ctx); } return result; @@ -1694,7 +1683,7 @@ bool Curl_conn_is_quiche(const struct Curl_easy *data, const struct connectdata *conn, int sockindex) { - struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + struct Curl_cfilter *cf = conn ? conn->cfilter[sockindex] : NULL; (void)data; for(; cf; cf = cf->next) { diff --git a/deps/curl/lib/vquic/vquic-tls.c b/deps/curl/lib/vquic/vquic-tls.c new file mode 100644 index 00000000000..580e5707a8d --- /dev/null +++ b/deps/curl/lib/vquic/vquic-tls.c @@ -0,0 +1,353 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 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.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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_HTTP3) && \ + (defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL)) + +#ifdef USE_OPENSSL +#include +#include "vtls/openssl.h" +#elif defined(USE_GNUTLS) +#include +#include +#include +#include +#include +#include "vtls/gtls.h" +#elif defined(USE_WOLFSSL) +#include +#include +#include +#include "vtls/wolfssl.h" +#endif + +#include "urldata.h" +#include "curl_trc.h" +#include "cfilters.h" +#include "multiif.h" +#include "vtls/keylog.h" +#include "vtls/vtls.h" +#include "vquic-tls.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +#if defined(USE_WOLFSSL) + +#define QUIC_CIPHERS \ + "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ + "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" +#define QUIC_GROUPS "P-256:P-384:P-521" + +#if defined(HAVE_SECRET_CALLBACK) +static void keylog_callback(const WOLFSSL *ssl, const char *line) +{ + (void)ssl; + Curl_tls_keylog_write_line(line); +} +#endif + +static CURLcode wssl_init_ctx(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + Curl_vquic_tls_ctx_setup *cb_setup, + void *cb_user_data) +{ + struct ssl_primary_config *conn_config; + CURLcode result = CURLE_FAILED_INIT; + + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) { + result = CURLE_FAILED_INIT; + goto out; + } + + ctx->wssl.ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + if(!ctx->wssl.ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(cb_setup) { + result = cb_setup(cf, data, cb_user_data); + if(result) + goto out; + } + + wolfSSL_CTX_set_default_verify_paths(ctx->wssl.ctx); + + if(wolfSSL_CTX_set_cipher_list(ctx->wssl.ctx, conn_config->cipher_list13 ? + conn_config->cipher_list13 : + QUIC_CIPHERS) != 1) { + char error_buffer[256]; + ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); + failf(data, "wolfSSL failed to set ciphers: %s", error_buffer); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + + if(wolfSSL_CTX_set1_groups_list(ctx->wssl.ctx, conn_config->curves ? + conn_config->curves : + (char *)QUIC_GROUPS) != 1) { + failf(data, "wolfSSL failed to set curves"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { +#if defined(HAVE_SECRET_CALLBACK) + wolfSSL_CTX_set_keylog_callback(ctx->wssl.ctx, keylog_callback); +#else + failf(data, "wolfSSL was built without keylog callback"); + result = CURLE_NOT_BUILT_IN; + goto out; +#endif + } + + if(conn_config->verifypeer) { + const char * const ssl_cafile = conn_config->CAfile; + const char * const ssl_capath = conn_config->CApath; + + wolfSSL_CTX_set_verify(ctx->wssl.ctx, SSL_VERIFY_PEER, NULL); + if(ssl_cafile || ssl_capath) { + /* tell wolfSSL where to find CA certificates that are used to verify + the server's certificate. */ + int rc = + wolfSSL_CTX_load_verify_locations_ex(ctx->wssl.ctx, ssl_cafile, + ssl_capath, + WOLFSSL_LOAD_FLAG_IGNORE_ERR); + if(SSL_SUCCESS != rc) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate verify locations:" + " CAfile: %s CApath: %s", + ssl_cafile ? ssl_cafile : "none", + ssl_capath ? ssl_capath : "none"); + result = CURLE_SSL_CACERT_BADFILE; + goto out; + } + infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); + infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); + } +#ifdef CURL_CA_FALLBACK + else { + /* verifying the peer without any CA certificates will not work so + use wolfSSL's built-in default as fallback */ + wolfSSL_CTX_set_default_verify_paths(ctx->wssl.ctx); + } +#endif + } + else { + wolfSSL_CTX_set_verify(ctx->wssl.ctx, SSL_VERIFY_NONE, NULL); + } + + /* give application a chance to interfere with SSL set up. */ + if(data->set.ssl.fsslctx) { + Curl_set_in_callback(data, TRUE); + result = (*data->set.ssl.fsslctx)(data, ctx->wssl.ctx, + data->set.ssl.fsslctxp); + Curl_set_in_callback(data, FALSE); + if(result) { + failf(data, "error signaled by ssl ctx callback"); + goto out; + } + } + result = CURLE_OK; + +out: + if(result && ctx->wssl.ctx) { + SSL_CTX_free(ctx->wssl.ctx); + ctx->wssl.ctx = NULL; + } + return result; +} + +/** SSL callbacks ***/ + +static CURLcode wssl_init_ssl(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const char *alpn, size_t alpn_len, + void *user_data) +{ + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + + DEBUGASSERT(!ctx->wssl.handle); + DEBUGASSERT(ctx->wssl.ctx); + ctx->wssl.handle = wolfSSL_new(ctx->wssl.ctx); + + wolfSSL_set_app_data(ctx->wssl.handle, user_data); + wolfSSL_set_connect_state(ctx->wssl.handle); + wolfSSL_set_quic_use_legacy_codepoint(ctx->wssl.handle, 0); + + if(alpn) + wolfSSL_set_alpn_protos(ctx->wssl.handle, (const unsigned char *)alpn, + (unsigned int)alpn_len); + + if(peer->sni) { + wolfSSL_UseSNI(ctx->wssl.handle, WOLFSSL_SNI_HOST_NAME, + peer->sni, (unsigned short)strlen(peer->sni)); + } + + if(ssl_config->primary.cache_session) { + (void)wssl_setup_session(cf, data, &ctx->wssl, peer); + } + + return CURLE_OK; +} +#endif /* defined(USE_WOLFSSL) */ + +CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const char *alpn, size_t alpn_len, + Curl_vquic_tls_ctx_setup *cb_setup, + void *cb_user_data, void *ssl_user_data) +{ + CURLcode result; + +#ifdef USE_OPENSSL + (void)result; + return Curl_ossl_ctx_init(&ctx->ossl, cf, data, peer, TRNSPRT_QUIC, + (const unsigned char *)alpn, alpn_len, + cb_setup, cb_user_data, NULL, ssl_user_data); +#elif defined(USE_GNUTLS) + (void)result; + return Curl_gtls_ctx_init(&ctx->gtls, cf, data, peer, + (const unsigned char *)alpn, alpn_len, NULL, + cb_setup, cb_user_data, ssl_user_data); +#elif defined(USE_WOLFSSL) + result = wssl_init_ctx(ctx, cf, data, cb_setup, cb_user_data); + if(result) + return result; + + return wssl_init_ssl(ctx, cf, data, peer, alpn, alpn_len, ssl_user_data); +#else +#error "no TLS lib in used, should not happen" + return CURLE_FAILED_INIT; +#endif +} + +void Curl_vquic_tls_cleanup(struct curl_tls_ctx *ctx) +{ +#ifdef USE_OPENSSL + if(ctx->ossl.ssl) + SSL_free(ctx->ossl.ssl); + if(ctx->ossl.ssl_ctx) + SSL_CTX_free(ctx->ossl.ssl_ctx); +#elif defined(USE_GNUTLS) + if(ctx->gtls.session) + gnutls_deinit(ctx->gtls.session); + Curl_gtls_shared_creds_free(&ctx->gtls.shared_creds); +#elif defined(USE_WOLFSSL) + if(ctx->wssl.handle) + wolfSSL_free(ctx->wssl.handle); + if(ctx->wssl.ctx) + wolfSSL_CTX_free(ctx->wssl.ctx); +#endif + memset(ctx, 0, sizeof(*ctx)); +} + +CURLcode Curl_vquic_tls_before_recv(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ +#ifdef USE_OPENSSL + if(!ctx->ossl.x509_store_setup) { + CURLcode result = Curl_ssl_setup_x509_store(cf, data, ctx->ossl.ssl_ctx); + if(result) + return result; + ctx->ossl.x509_store_setup = TRUE; + } +#elif defined(USE_WOLFSSL) + if(!ctx->wssl.x509_store_setup) { + CURLcode result = Curl_wssl_setup_x509_store(cf, data, &ctx->wssl); + if(result) + return result; + } +#elif defined(USE_GNUTLS) + if(!ctx->gtls.shared_creds->trust_setup) { + CURLcode result = Curl_gtls_client_trust_setup(cf, data, &ctx->gtls); + if(result) + return result; + } +#else + (void)ctx; (void)cf; (void)data; +#endif + return CURLE_OK; +} + +CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer) +{ + struct ssl_primary_config *conn_config; + CURLcode result = CURLE_OK; + + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) + return CURLE_FAILED_INIT; + +#ifdef USE_OPENSSL + (void)conn_config; + result = Curl_oss_check_peer_cert(cf, data, &ctx->ossl, peer); +#elif defined(USE_GNUTLS) + if(conn_config->verifyhost) { + result = Curl_gtls_verifyserver(data, ctx->gtls.session, + conn_config, &data->set.ssl, peer, + data->set.str[STRING_SSL_PINNEDPUBLICKEY]); + if(result) + return result; + } +#elif defined(USE_WOLFSSL) + (void)data; + if(conn_config->verifyhost) { + if(peer->sni) { + WOLFSSL_X509* cert = wolfSSL_get_peer_certificate(ctx->wssl.handle); + if(wolfSSL_X509_check_host(cert, peer->sni, strlen(peer->sni), 0, NULL) + == WOLFSSL_FAILURE) { + result = CURLE_PEER_FAILED_VERIFICATION; + } + wolfSSL_X509_free(cert); + } + + } +#endif + return result; +} + + +#endif /* !USE_HTTP3 && (USE_OPENSSL || USE_GNUTLS || USE_WOLFSSL) */ diff --git a/deps/curl/lib/vquic/vquic-tls.h b/deps/curl/lib/vquic/vquic-tls.h new file mode 100644 index 00000000000..969acad41ef --- /dev/null +++ b/deps/curl/lib/vquic/vquic-tls.h @@ -0,0 +1,100 @@ +#ifndef HEADER_CURL_VQUIC_TLS_H +#define HEADER_CURL_VQUIC_TLS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 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.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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "bufq.h" +#include "vtls/openssl.h" + +#if defined(USE_HTTP3) && \ + (defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL)) + +#include "vtls/wolfssl.h" + +struct curl_tls_ctx { +#ifdef USE_OPENSSL + struct ossl_ctx ossl; +#elif defined(USE_GNUTLS) + struct gtls_ctx gtls; +#elif defined(USE_WOLFSSL) + struct wolfssl_ctx wssl; +#endif +}; + +/** + * Callback passed to `Curl_vquic_tls_init()` that can + * do early initializations on the not otherwise configured TLS + * instances created. This varies by TLS backend: + * - openssl/wolfssl: SSL_CTX* has just been created + * - gnutls: gtls_client_init() has run + */ +typedef CURLcode Curl_vquic_tls_ctx_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *cb_user_data); + +/** + * Initialize the QUIC TLS instances based of the SSL configurations + * for the connection filter, transfer and peer. + * @param ctx the TLS context to initialize + * @param cf the connection filter involved + * @param data the transfer involved + * @param peer the peer that will be connected to + * @param alpn the ALPN string in protocol format ((len+bytes+)+), + * may be NULL + * @param alpn_len the overall number of bytes in `alpn` + * @param cb_setup optional callback for early TLS config + ± @param cb_user_data user_data param for callback + * @param ssl_user_data optional pointer to set in TLS application context + */ +CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const char *alpn, size_t alpn_len, + Curl_vquic_tls_ctx_setup *cb_setup, + void *cb_user_data, + void *ssl_user_data); + +/** + * Cleanup all data that has been initialized. + */ +void Curl_vquic_tls_cleanup(struct curl_tls_ctx *ctx); + +CURLcode Curl_vquic_tls_before_recv(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data); + +/** + * After the QUIC basic handshake has been, verify that the peer + * (and its certificate) fulfill our requirements. + */ +CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer); + +#endif /* !USE_HTTP3 && (USE_OPENSSL || USE_GNUTLS || USE_WOLFSSL) */ + +#endif /* HEADER_CURL_VQUIC_TLS_H */ diff --git a/deps/curl/lib/vquic/vquic.c b/deps/curl/lib/vquic/vquic.c index 9a1a1bbb3c3..20ff6a640b7 100644 --- a/deps/curl/lib/vquic/vquic.c +++ b/deps/curl/lib/vquic/vquic.c @@ -22,20 +22,11 @@ * ***************************************************************************/ -/* WIP, experimental: use recvmmsg() on linux - * we have no configure check, yet - * and also it is only available for _GNU_SOURCE, which - * we do not use otherwise. -#define HAVE_SENDMMSG - */ -#if defined(HAVE_SENDMMSG) -#define _GNU_SOURCE -#include -#undef _GNU_SOURCE -#endif - #include "curl_setup.h" +#ifdef HAVE_NETINET_UDP_H +#include +#endif #ifdef HAVE_FCNTL_H #include #endif @@ -46,7 +37,9 @@ #include "curl_trc.h" #include "curl_msh3.h" #include "curl_ngtcp2.h" +#include "curl_osslq.h" #include "curl_quiche.h" +#include "multiif.h" #include "rand.h" #include "vquic.h" #include "vquic_int.h" @@ -58,7 +51,7 @@ #include "memdebug.h" -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 #ifdef O_BINARY #define QLOGMODE O_WRONLY|O_CREAT|O_BINARY @@ -74,6 +67,8 @@ void Curl_quic_ver(char *p, size_t len) { #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) Curl_ngtcp2_ver(p, len); +#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + Curl_osslq_ver(p, len); #elif defined(USE_QUICHE) Curl_quiche_ver(p, len); #elif defined(USE_MSH3) @@ -100,6 +95,7 @@ CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx) } } #endif + vquic_ctx_update_time(qctx); return CURLE_OK; } @@ -109,6 +105,11 @@ void vquic_ctx_free(struct cf_quic_ctx *qctx) Curl_bufq_free(&qctx->sendbuf); } +void vquic_ctx_update_time(struct cf_quic_ctx *qctx) +{ + qctx->last_op = Curl_now(); +} + static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, @@ -141,8 +142,8 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, /* Only set this, when we need it. macOS, for example, * does not seem to like a msg_control of length 0. */ msg.msg_control = msg_ctrl; - assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(uint16_t))); - msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t)); + assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(int))); + msg.msg_controllen = CMSG_SPACE(sizeof(int)); cm = CMSG_FIRSTHDR(&msg); cm->cmsg_level = SOL_UDP; cm->cmsg_type = UDP_SEGMENT; @@ -173,7 +174,7 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, qctx->no_gso = TRUE; return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); } - /* FALLTHROUGH */ + FALLTHROUGH(); default: failf(data, "sendmsg() returned %zd (errno %d)", sent, SOCKERRNO); return CURLE_SEND_ERROR; @@ -242,6 +243,7 @@ static CURLcode vquic_send_packets(struct Curl_cfilter *cf, const uint8_t *pkt, size_t pktlen, size_t gsolen, size_t *psent) { + CURLcode result; #ifdef DEBUGBUILD /* simulate network blocking/partial writes */ if(qctx->wblock_percent > 0) { @@ -254,10 +256,14 @@ static CURLcode vquic_send_packets(struct Curl_cfilter *cf, } #endif if(qctx->no_gso && pktlen > gsolen) { - return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); + result = send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); } - - return do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent); + else { + result = do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent); + } + if(!result) + qctx->last_io = qctx->last_op; + return result; } CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -315,6 +321,36 @@ CURLcode vquic_send_tail_split(struct Curl_cfilter *cf, struct Curl_easy *data, return vquic_flush(cf, data, qctx); } +#if defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG) +static size_t vquic_msghdr_get_udp_gro(struct msghdr *msg) +{ + int gso_size = 0; +#if defined(__linux__) && defined(UDP_GRO) + struct cmsghdr *cmsg; + + /* Workaround musl CMSG_NXTHDR issue */ +#if defined(__clang__) && !defined(__GLIBC__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-compare" +#pragma clang diagnostic ignored "-Wcast-align" +#endif + for(cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { +#if defined(__clang__) && !defined(__GLIBC__) +#pragma clang diagnostic pop +#endif + if(cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) { + memcpy(&gso_size, CMSG_DATA(cmsg), sizeof(gso_size)); + + break; + } + } +#endif + (void)msg; + + return (size_t)gso_size; +} +#endif + #ifdef HAVE_SENDMMSG static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -322,17 +358,28 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, size_t max_pkts, vquic_recv_pkt_cb *recv_cb, void *userp) { -#define MMSG_NUM 64 +#define MMSG_NUM 16 struct iovec msg_iov[MMSG_NUM]; struct mmsghdr mmsg[MMSG_NUM]; - uint8_t bufs[MMSG_NUM][2*1024]; + uint8_t msg_ctrl[MMSG_NUM * CMSG_SPACE(sizeof(int))]; struct sockaddr_storage remote_addr[MMSG_NUM]; - size_t total_nread, pkts; + size_t total_nread = 0, pkts; int mcount, i, n; char errstr[STRERROR_LEN]; CURLcode result = CURLE_OK; + size_t gso_size; + size_t pktlen; + size_t offset, to; + char *sockbuf = NULL; + uint8_t (*bufs)[64*1024] = NULL; DEBUGASSERT(max_pkts > 0); + result = Curl_multi_xfer_sockbuf_borrow(data, MMSG_NUM * sizeof(bufs[0]), + &sockbuf); + if(result) + goto out; + bufs = (uint8_t (*)[64*1024])sockbuf; + pkts = 0; total_nread = 0; while(pkts < max_pkts) { @@ -345,6 +392,8 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, mmsg[i].msg_hdr.msg_iovlen = 1; mmsg[i].msg_hdr.msg_name = &remote_addr[i]; mmsg[i].msg_hdr.msg_namelen = sizeof(remote_addr[i]); + mmsg[i].msg_hdr.msg_control = &msg_ctrl[i * CMSG_SPACE(sizeof(int))]; + mmsg[i].msg_hdr.msg_controllen = CMSG_SPACE(sizeof(int)); } while((mcount = recvmmsg(qctx->sockfd, mmsg, n, 0, NULL)) == -1 && @@ -356,12 +405,10 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, goto out; } if(!cf->connected && SOCKERRNO == ECONNREFUSED) { - const char *r_ip = NULL; - int r_port = 0; - Curl_cf_socket_peek(cf->next, data, NULL, NULL, - &r_ip, &r_port, NULL, NULL); + struct ip_quadruple ip; + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); failf(data, "QUIC: connection to %s port %u refused", - r_ip, r_port); + ip.remote_ip, ip.remote_port); result = CURLE_COULDNT_CONNECT; goto out; } @@ -373,14 +420,30 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, } CURL_TRC_CF(data, cf, "recvmmsg() -> %d packets", mcount); - pkts += mcount; for(i = 0; i < mcount; ++i) { total_nread += mmsg[i].msg_len; - result = recv_cb(bufs[i], mmsg[i].msg_len, - mmsg[i].msg_hdr.msg_name, mmsg[i].msg_hdr.msg_namelen, - 0, userp); - if(result) - goto out; + + gso_size = vquic_msghdr_get_udp_gro(&mmsg[i].msg_hdr); + if(gso_size == 0) { + gso_size = mmsg[i].msg_len; + } + + for(offset = 0; offset < mmsg[i].msg_len; offset = to) { + ++pkts; + + to = offset + gso_size; + if(to > mmsg[i].msg_len) { + pktlen = mmsg[i].msg_len - offset; + } + else { + pktlen = gso_size; + } + + result = recv_cb(bufs[i] + offset, pktlen, mmsg[i].msg_hdr.msg_name, + mmsg[i].msg_hdr.msg_namelen, 0, userp); + if(result) + goto out; + } } } @@ -388,6 +451,7 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, if(total_nread || result) CURL_TRC_CF(data, cf, "recvd %zu packets with %zu bytes -> %d", pkts, total_nread, result); + Curl_multi_xfer_sockbuf_release(data, sockbuf); return result; } @@ -406,6 +470,10 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, ssize_t nread; char errstr[STRERROR_LEN]; CURLcode result = CURLE_OK; + uint8_t msg_ctrl[CMSG_SPACE(sizeof(int))]; + size_t gso_size; + size_t pktlen; + size_t offset, to; msg_iov.iov_base = buf; msg_iov.iov_len = (int)sizeof(buf); @@ -413,11 +481,13 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, memset(&msg, 0, sizeof(msg)); msg.msg_iov = &msg_iov; msg.msg_iovlen = 1; + msg.msg_control = msg_ctrl; DEBUGASSERT(max_pkts > 0); for(pkts = 0, total_nread = 0; pkts < max_pkts;) { msg.msg_name = &remote_addr; msg.msg_namelen = sizeof(remote_addr); + msg.msg_controllen = sizeof(msg_ctrl); while((nread = recvmsg(qctx->sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR) ; @@ -426,12 +496,10 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, goto out; } if(!cf->connected && SOCKERRNO == ECONNREFUSED) { - const char *r_ip = NULL; - int r_port = 0; - Curl_cf_socket_peek(cf->next, data, NULL, NULL, - &r_ip, &r_port, NULL, NULL); + struct ip_quadruple ip; + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); failf(data, "QUIC: connection to %s port %u refused", - r_ip, r_port); + ip.remote_ip, ip.remote_port); result = CURLE_COULDNT_CONNECT; goto out; } @@ -442,12 +510,29 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, goto out; } - ++pkts; total_nread += (size_t)nread; - result = recv_cb(buf, (size_t)nread, msg.msg_name, msg.msg_namelen, - 0, userp); - if(result) - goto out; + + gso_size = vquic_msghdr_get_udp_gro(&msg); + if(gso_size == 0) { + gso_size = (size_t)nread; + } + + for(offset = 0; offset < (size_t)nread; offset = to) { + ++pkts; + + to = offset + gso_size; + if(to > (size_t)nread) { + pktlen = (size_t)nread - offset; + } + else { + pktlen = gso_size; + } + + result = + recv_cb(buf + offset, pktlen, msg.msg_name, msg.msg_namelen, 0, userp); + if(result) + goto out; + } } out: @@ -486,12 +571,10 @@ static CURLcode recvfrom_packets(struct Curl_cfilter *cf, goto out; } if(!cf->connected && SOCKERRNO == ECONNREFUSED) { - const char *r_ip = NULL; - int r_port = 0; - Curl_cf_socket_peek(cf->next, data, NULL, NULL, - &r_ip, &r_port, NULL, NULL); + struct ip_quadruple ip; + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); failf(data, "QUIC: connection to %s port %u refused", - r_ip, r_port); + ip.remote_ip, ip.remote_port); result = CURLE_COULDNT_CONNECT; goto out; } @@ -524,13 +607,22 @@ CURLcode vquic_recv_packets(struct Curl_cfilter *cf, size_t max_pkts, vquic_recv_pkt_cb *recv_cb, void *userp) { + CURLcode result; #if defined(HAVE_SENDMMSG) - return recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); + result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); #elif defined(HAVE_SENDMSG) - return recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); + result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); #else - return recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp); + result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp); #endif + if(!result) { + if(!qctx->got_first_byte) { + qctx->got_first_byte = TRUE; + qctx->first_byte_at = qctx->last_op; + } + qctx->last_io = qctx->last_op; + } + return result; } /* @@ -588,6 +680,8 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, DEBUGASSERT(transport == TRNSPRT_QUIC); #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) return Curl_cf_ngtcp2_create(pcf, data, conn, ai); +#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + return Curl_cf_osslq_create(pcf, data, conn, ai); #elif defined(USE_QUICHE) return Curl_cf_quiche_create(pcf, data, conn, ai); #elif defined(USE_MSH3) @@ -607,6 +701,8 @@ bool Curl_conn_is_http3(const struct Curl_easy *data, { #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) return Curl_conn_is_ngtcp2(data, conn, sockindex); +#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + return Curl_conn_is_osslq(data, conn, sockindex); #elif defined(USE_QUICHE) return Curl_conn_is_quiche(data, conn, sockindex); #elif defined(USE_MSH3) @@ -621,7 +717,7 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data, const struct connectdata *conn) { if(conn->transport == TRNSPRT_UNIX) { - /* cannot do QUIC over a unix domain socket */ + /* cannot do QUIC over a Unix domain socket */ return CURLE_QUIC_CONNECT_ERROR; } if(!(conn->handler->flags & PROTOPT_SSL)) { @@ -634,7 +730,7 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data, return CURLE_URL_MALFORMAT; } if(conn->bits.httpproxy && conn->bits.tunnel_proxy) { - failf(data, "HTTP/3 is not supported over a HTTP proxy"); + failf(data, "HTTP/3 is not supported over an HTTP proxy"); return CURLE_URL_MALFORMAT; } #endif @@ -642,7 +738,7 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data, return CURLE_OK; } -#else /* ENABLE_QUIC */ +#else /* USE_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, const struct connectdata *conn) @@ -653,4 +749,4 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data, return CURLE_NOT_BUILT_IN; } -#endif /* !ENABLE_QUIC */ +#endif /* !USE_HTTP3 */ diff --git a/deps/curl/lib/vquic/vquic.h b/deps/curl/lib/vquic/vquic.h index dc73957aaf6..c1ca1df6aa0 100644 --- a/deps/curl/lib/vquic/vquic.h +++ b/deps/curl/lib/vquic/vquic.h @@ -26,7 +26,7 @@ #include "curl_setup.h" -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 struct Curl_cfilter; struct Curl_easy; struct connectdata; @@ -52,11 +52,11 @@ bool Curl_conn_is_http3(const struct Curl_easy *data, extern struct Curl_cftype Curl_cft_http3; -#else /* ENABLE_QUIC */ +#else /* USE_HTTP3 */ #define Curl_conn_is_http3(a,b,c) FALSE -#endif /* !ENABLE_QUIC */ +#endif /* !USE_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, const struct connectdata *conn); diff --git a/deps/curl/lib/vquic/vquic_int.h b/deps/curl/lib/vquic/vquic_int.h index dbcd009d721..754e1f5910f 100644 --- a/deps/curl/lib/vquic/vquic_int.h +++ b/deps/curl/lib/vquic/vquic_int.h @@ -27,10 +27,12 @@ #include "curl_setup.h" #include "bufq.h" -#ifdef ENABLE_QUIC +#ifdef USE_HTTP3 #define MAX_PKT_BURST 10 #define MAX_UDP_PAYLOAD_SIZE 1452 +/* Default QUIC connection timeout we announce from our side */ +#define CURL_QUIC_MAX_IDLE_MS (120 * 1000) struct cf_quic_ctx { curl_socket_t sockfd; /* connected UDP socket */ @@ -38,18 +40,24 @@ struct cf_quic_ctx { socklen_t local_addrlen; /* length of local address */ struct bufq sendbuf; /* buffer for sending one or more packets */ + struct curltime first_byte_at; /* when first byte was recvd */ + struct curltime last_op; /* last (attempted) send/recv operation */ + struct curltime last_io; /* last successful socket IO */ size_t gsolen; /* length of individual packets in send buf */ size_t split_len; /* if != 0, buffer length after which GSO differs */ size_t split_gsolen; /* length of individual packets after split_len */ #ifdef DEBUGBUILD int wblock_percent; /* percent of writes doing EAGAIN */ #endif - bool no_gso; /* do not use gso on sending */ + BIT(got_first_byte); /* if first byte was received */ + BIT(no_gso); /* do not use gso on sending */ }; CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx); void vquic_ctx_free(struct cf_quic_ctx *qctx); +void vquic_ctx_update_time(struct cf_quic_ctx *qctx); + void vquic_push_blocked_pkt(struct Curl_cfilter *cf, struct cf_quic_ctx *qctx, const uint8_t *pkt, size_t pktlen, size_t gsolen); @@ -80,6 +88,6 @@ CURLcode vquic_recv_packets(struct Curl_cfilter *cf, size_t max_pkts, vquic_recv_pkt_cb *recv_cb, void *userp); -#endif /* !ENABLE_QUIC */ +#endif /* !USE_HTTP3 */ #endif /* HEADER_CURL_VQUIC_QUIC_INT_H */ diff --git a/deps/curl/lib/vssh/.checksrc b/deps/curl/lib/vssh/.checksrc new file mode 100644 index 00000000000..9066946c890 --- /dev/null +++ b/deps/curl/lib/vssh/.checksrc @@ -0,0 +1,2 @@ +enable STRERROR +enable STRNCPY diff --git a/deps/curl/lib/curl_path.c b/deps/curl/lib/vssh/curl_path.c similarity index 83% rename from deps/curl/lib/curl_path.c rename to deps/curl/lib/vssh/curl_path.c index 856423db997..61452a42527 100644 --- a/deps/curl/lib/curl_path.c +++ b/deps/curl/lib/vssh/curl_path.c @@ -26,9 +26,9 @@ #if defined(USE_SSH) +#include "curl_path.h" #include #include "curl_memory.h" -#include "curl_path.h" #include "escape.h" #include "memdebug.h" @@ -98,8 +98,8 @@ CURLcode Curl_getworkingpath(struct Curl_easy *data, return CURLE_OK; } -/* The get_pathname() function is being borrowed from OpenSSH sftp.c - version 4.6p1. */ +/* The original get_pathname() function came from OpenSSH sftp.c version + 4.6p1. */ /* * Copyright (c) 2001-2004 Damien Miller * @@ -115,38 +115,37 @@ CURLcode Curl_getworkingpath(struct Curl_easy *data, * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -CURLcode Curl_get_pathname(const char **cpp, char **path, char *homedir) + +#define MAX_PATHLENGTH 65535 /* arbitrary long */ + +CURLcode Curl_get_pathname(const char **cpp, char **path, const char *homedir) { const char *cp = *cpp, *end; char quot; - unsigned int i, j; - size_t fullPathLength, pathLength; - bool relativePath = false; + unsigned int i; static const char WHITESPACE[] = " \t\r\n"; + struct dynbuf out; + CURLcode result; DEBUGASSERT(homedir); - if(!*cp || !homedir) { - *cpp = NULL; - *path = NULL; + *path = NULL; + *cpp = NULL; + if(!*cp || !homedir) return CURLE_QUOTE_ERROR; - } + + Curl_dyn_init(&out, MAX_PATHLENGTH); + /* Ignore leading whitespace */ cp += strspn(cp, WHITESPACE); - /* Allocate enough space for home directory and filename + separator */ - fullPathLength = strlen(cp) + strlen(homedir) + 2; - *path = malloc(fullPathLength); - if(!*path) - return CURLE_OUT_OF_MEMORY; /* Check for quoted filenames */ if(*cp == '\"' || *cp == '\'') { quot = *cp++; /* Search for terminating quote, unescape some chars */ - for(i = j = 0; i <= strlen(cp); i++) { + for(i = 0; i <= strlen(cp); i++) { if(cp[i] == quot) { /* Found quote */ i++; - (*path)[j] = '\0'; break; } if(cp[i] == '\0') { /* End of string */ @@ -159,40 +158,45 @@ CURLcode Curl_get_pathname(const char **cpp, char **path, char *homedir) goto fail; } } - (*path)[j++] = cp[i]; + result = Curl_dyn_addn(&out, &cp[i], 1); + if(result) + return result; } - if(j == 0) { + if(!Curl_dyn_len(&out)) goto fail; - } - *cpp = cp + i + strspn(cp + i, WHITESPACE); + + /* return pointer to second parameter if it exists */ + *cpp = &cp[i] + strspn(&cp[i], WHITESPACE); } else { /* Read to end of filename - either to whitespace or terminator */ end = strpbrk(cp, WHITESPACE); if(!end) end = strchr(cp, '\0'); + /* return pointer to second parameter if it exists */ *cpp = end + strspn(end, WHITESPACE); - pathLength = 0; - relativePath = (cp[0] == '/' && cp[1] == '~' && cp[2] == '/'); + /* Handling for relative path - prepend home directory */ - if(relativePath) { - strcpy(*path, homedir); - pathLength = strlen(homedir); - (*path)[pathLength++] = '/'; - (*path)[pathLength] = '\0'; + if(cp[0] == '/' && cp[1] == '~' && cp[2] == '/') { + result = Curl_dyn_add(&out, homedir); + if(!result) + result = Curl_dyn_addn(&out, "/", 1); + if(result) + return result; cp += 3; } /* Copy path name up until first "whitespace" */ - memcpy(&(*path)[pathLength], cp, (int)(end - cp)); - pathLength += (int)(end - cp); - (*path)[pathLength] = '\0'; + result = Curl_dyn_addn(&out, cp, (end - cp)); + if(result) + return result; } + *path = Curl_dyn_ptr(&out); return CURLE_OK; fail: - Curl_safefree(*path); + Curl_dyn_free(&out); return CURLE_QUOTE_ERROR; } diff --git a/deps/curl/lib/curl_path.h b/deps/curl/lib/vssh/curl_path.h similarity index 79% rename from deps/curl/lib/curl_path.h rename to deps/curl/lib/vssh/curl_path.h index 9ed09dea835..8e984174d79 100644 --- a/deps/curl/lib/curl_path.h +++ b/deps/curl/lib/vssh/curl_path.h @@ -28,22 +28,9 @@ #include #include "urldata.h" -#ifdef WIN32 -# undef PATH_MAX -# define PATH_MAX MAX_PATH -# ifndef R_OK -# define R_OK 4 -# endif -#endif - -#ifndef PATH_MAX -#define PATH_MAX 1024 /* just an extra precaution since there are systems that - have their definition hidden well */ -#endif - CURLcode Curl_getworkingpath(struct Curl_easy *data, char *homedir, char **path); -CURLcode Curl_get_pathname(const char **cpp, char **path, char *homedir); +CURLcode Curl_get_pathname(const char **cpp, char **path, const char *homedir); #endif /* HEADER_CURL_PATH_H */ diff --git a/deps/curl/lib/vssh/libssh.c b/deps/curl/lib/vssh/libssh.c index dea00845758..2781365bf48 100644 --- a/deps/curl/lib/vssh/libssh.c +++ b/deps/curl/lib/vssh/libssh.c @@ -31,9 +31,6 @@ #include -#include -#include - #ifdef HAVE_NETINET_IN_H #include #endif @@ -89,13 +86,6 @@ #include "curl_memory.h" #include "memdebug.h" -/* in 0.10.0 or later, ignore deprecated warnings */ -#if defined(__GNUC__) && \ - (LIBSSH_VERSION_MINOR >= 10) || \ - (LIBSSH_VERSION_MAJOR > 0) -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - /* A recent macro provided by libssh. Or make our own. */ #ifndef SSH_STRING_FREE_CHAR #define SSH_STRING_FREE_CHAR(x) \ @@ -144,6 +134,7 @@ static void sftp_quote(struct Curl_easy *data); static void sftp_quote_stat(struct Curl_easy *data); static int myssh_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *sock); +static void myssh_block2waitfor(struct connectdata *conn, bool block); static CURLcode myssh_setup_connection(struct Curl_easy *data, struct connectdata *conn); @@ -166,7 +157,8 @@ const struct Curl_handler Curl_handler_scp = { ZERO_NULL, /* domore_getsock */ myssh_getsock, /* perform_getsock */ scp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ @@ -193,7 +185,8 @@ const struct Curl_handler Curl_handler_sftp = { ZERO_NULL, /* domore_getsock */ myssh_getsock, /* perform_getsock */ sftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ @@ -391,28 +384,25 @@ static int myssh_is_known(struct Curl_easy *data) goto cleanup; } - if(data->set.ssl.primary.verifyhost != TRUE) { - rc = SSH_OK; - goto cleanup; - } + if(data->set.str[STRING_SSH_KNOWNHOSTS]) { #if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,9,0) - /* Get the known_key from the known hosts file */ - vstate = ssh_session_get_known_hosts_entry(sshc->ssh_session, - &knownhostsentry); - - /* Case an entry was found in a known hosts file */ - if(knownhostsentry) { - if(knownhostsentry->publickey) { - rc = ssh_pki_export_pubkey_base64(knownhostsentry->publickey, - &known_base64); - if(rc != SSH_OK) { - goto cleanup; - } - knownkey.key = known_base64; - knownkey.len = strlen(known_base64); + /* Get the known_key from the known hosts file */ + vstate = ssh_session_get_known_hosts_entry(sshc->ssh_session, + &knownhostsentry); + + /* Case an entry was found in a known hosts file */ + if(knownhostsentry) { + if(knownhostsentry->publickey) { + rc = ssh_pki_export_pubkey_base64(knownhostsentry->publickey, + &known_base64); + if(rc != SSH_OK) { + goto cleanup; + } + knownkey.key = known_base64; + knownkey.len = strlen(known_base64); - switch(ssh_key_type(knownhostsentry->publickey)) { + switch(ssh_key_type(knownhostsentry->publickey)) { case SSH_KEYTYPE_RSA: knownkey.keytype = CURLKHTYPE_RSA; break; @@ -434,55 +424,51 @@ static int myssh_is_known(struct Curl_easy *data) default: rc = SSH_ERROR; goto cleanup; + } + knownkeyp = &knownkey; } - knownkeyp = &knownkey; } - } - switch(vstate) { + switch(vstate) { case SSH_KNOWN_HOSTS_OK: keymatch = CURLKHMATCH_OK; break; case SSH_KNOWN_HOSTS_OTHER: - /* fallthrough */ case SSH_KNOWN_HOSTS_NOT_FOUND: - /* fallthrough */ case SSH_KNOWN_HOSTS_UNKNOWN: - /* fallthrough */ case SSH_KNOWN_HOSTS_ERROR: keymatch = CURLKHMATCH_MISSING; break; - default: + default: keymatch = CURLKHMATCH_MISMATCH; break; - } + } #else - vstate = ssh_is_server_known(sshc->ssh_session); - switch(vstate) { + vstate = ssh_is_server_known(sshc->ssh_session); + switch(vstate) { case SSH_SERVER_KNOWN_OK: keymatch = CURLKHMATCH_OK; break; case SSH_SERVER_FILE_NOT_FOUND: - /* fallthrough */ case SSH_SERVER_NOT_KNOWN: keymatch = CURLKHMATCH_MISSING; break; - default: + default: keymatch = CURLKHMATCH_MISMATCH; break; - } + } #endif - if(func) { /* use callback to determine action */ - rc = ssh_pki_export_pubkey_base64(pubkey, &found_base64); - if(rc != SSH_OK) - goto cleanup; + if(func) { /* use callback to determine action */ + rc = ssh_pki_export_pubkey_base64(pubkey, &found_base64); + if(rc != SSH_OK) + goto cleanup; - foundkey.key = found_base64; - foundkey.len = strlen(found_base64); + foundkey.key = found_base64; + foundkey.len = strlen(found_base64); - switch(ssh_key_type(pubkey)) { + switch(ssh_key_type(pubkey)) { case SSH_KEYTYPE_RSA: foundkey.keytype = CURLKHTYPE_RSA; break; @@ -508,15 +494,15 @@ static int myssh_is_known(struct Curl_easy *data) default: rc = SSH_ERROR; goto cleanup; - } + } - Curl_set_in_callback(data, true); - rc = func(data, knownkeyp, /* from the knownhosts file */ - &foundkey, /* from the remote host */ - keymatch, data->set.ssh_keyfunc_userp); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, TRUE); + rc = func(data, knownkeyp, /* from the knownhosts file */ + &foundkey, /* from the remote host */ + keymatch, data->set.ssh_keyfunc_userp); + Curl_set_in_callback(data, FALSE); - switch(rc) { + switch(rc) { case CURLKHSTAT_FINE_ADD_TO_FILE: #if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,8,0) rc = ssh_session_update_known_hosts(sshc->ssh_session); @@ -532,12 +518,13 @@ static int myssh_is_known(struct Curl_easy *data) default: /* REJECT/DEFER */ rc = SSH_ERROR; goto cleanup; + } } - } - else { - if(keymatch != CURLKHMATCH_OK) { - rc = SSH_ERROR; - goto cleanup; + else { + if(keymatch != CURLKHMATCH_OK) { + rc = SSH_ERROR; + goto cleanup; + } } } rc = SSH_OK; @@ -628,7 +615,7 @@ int myssh_auth_interactive(struct connectdata *conn) if(rc < 0) return SSH_ERROR; - /* FALLTHROUGH */ + FALLTHROUGH(); case 1: sshc->kbd_state = 1; @@ -670,7 +657,7 @@ int myssh_auth_interactive(struct connectdata *conn) /* * ssh_statemach_act() runs the SSH state machine as far as it can without - * blocking and without reaching the end. The data the pointer 'block' points + * blocking and without reaching the end. The data the pointer 'block' points * to will be set to TRUE if the libssh function returns SSH_AGAIN * meaning it wants to be called again when the socket is ready */ @@ -684,7 +671,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) int rc = SSH_NO_ERROR, err; int seekerr = CURL_SEEKFUNC_OK; const char *err_msg; - *block = 0; /* we're not blocking by default */ + *block = 0; /* we are not blocking by default */ do { @@ -703,12 +690,16 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) ssh_set_blocking(sshc->ssh_session, 0); state(data, SSH_S_STARTUP); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_S_STARTUP: rc = ssh_connect(sshc->ssh_session); - if(rc == SSH_AGAIN) + + myssh_block2waitfor(conn, (rc == SSH_AGAIN)); + if(rc == SSH_AGAIN) { + DEBUGF(infof(data, "ssh_connect -> EAGAIN")); break; + } if(rc != SSH_OK) { failf(data, "Failure establishing ssh session"); @@ -718,7 +709,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) state(data, SSH_HOSTKEY); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_HOSTKEY: rc = myssh_is_known(data); @@ -728,7 +719,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } state(data, SSH_AUTHLIST); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_AUTHLIST:{ sshc->authed = FALSE; @@ -749,7 +740,8 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) break; } - sshc->auth_methods = ssh_userauth_list(sshc->ssh_session, NULL); + sshc->auth_methods = + (unsigned int)ssh_userauth_list(sshc->ssh_session, NULL); if(sshc->auth_methods) infof(data, "SSH authentication methods available: %s%s%s%s", sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY ? @@ -909,7 +901,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) break; } state(data, SSH_AUTH_PASS); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_AUTH_PASS: rc = ssh_userauth_password(sshc->ssh_session, NULL, conn->passwd); @@ -972,7 +964,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) break; } state(data, SSH_SFTP_REALPATH); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_SFTP_REALPATH: /* * Get the "home" directory @@ -1159,13 +1151,23 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) break; } else if(statvfs) { + #ifdef _MSC_VER + #define CURL_LIBSSH_VFS_SIZE_MASK "I64u" + #else + #define CURL_LIBSSH_VFS_SIZE_MASK PRIu64 + #endif char *tmp = aprintf("statvfs:\n" - "f_bsize: %llu\n" "f_frsize: %llu\n" - "f_blocks: %llu\n" "f_bfree: %llu\n" - "f_bavail: %llu\n" "f_files: %llu\n" - "f_ffree: %llu\n" "f_favail: %llu\n" - "f_fsid: %llu\n" "f_flag: %llu\n" - "f_namemax: %llu\n", + "f_bsize: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_frsize: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_blocks: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_bfree: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_bavail: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_files: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_ffree: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_favail: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_fsid: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_flag: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_namemax: %" CURL_LIBSSH_VFS_SIZE_MASK "\n", statvfs->f_bsize, statvfs->f_frsize, statvfs->f_blocks, statvfs->f_bfree, statvfs->f_bavail, statvfs->f_files, @@ -1239,7 +1241,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) if(attrs) { curl_off_t size = attrs->size; if(size < 0) { - failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); + failf(data, "Bad file size (%" FMT_OFF_T ")", size); MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); break; } @@ -1291,11 +1293,11 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) position. */ if(data->state.resume_from > 0) { /* Let's read off the proper amount of bytes from the input. */ - if(conn->seek_func) { - Curl_set_in_callback(data, true); - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - Curl_set_in_callback(data, false); + if(data->set.seek_func) { + Curl_set_in_callback(data, TRUE); + seekerr = data->set.seek_func(data->set.seek_client, + data->state.resume_from, SEEK_SET); + Curl_set_in_callback(data, FALSE); } if(seekerr != CURL_SEEKFUNC_OK) { @@ -1305,15 +1307,16 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) failf(data, "Could not seek stream"); return CURLE_FTP_COULDNT_USE_REST; } - /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ + /* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ do { + char scratch[4*1024]; size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : - curlx_sotouz(data->state.resume_from - passed); + (data->state.resume_from - passed > + (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); size_t actuallyread = - data->state.fread_func(data->state.buffer, 1, + data->state.fread_func(scratch, 1, readthisamountnow, data->state.in); passed += actuallyread; @@ -1347,21 +1350,21 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_pgrsSetUploadSize(data, data->state.infilesize); } /* upload data */ - Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); - /* not set by Curl_setup_transfer to preserve keepon bits */ + /* not set by Curl_xfer_setup to preserve keepon bits */ conn->sockfd = conn->writesockfd; - /* store this original bitmask setup to use later on if we can't + /* store this original bitmask setup to use later on if we cannot figure out a "real" bitmask */ sshc->orig_waitfor = data->req.keepon; /* we want to use the _sending_ function even when the socket turns out readable as the underlying libssh sftp send function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; + data->state.select_bits = CURL_CSELECT_OUT; - /* since we don't really wait for anything at this point, we want the + /* since we do not really wait for anything at this point, we want the state machine to move on as soon as possible so we set a very short timeout here */ Curl_expire(data, 0, EXPIRE_RUN_NOW); @@ -1400,7 +1403,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) ++sshc->slash_pos; if(rc < 0) { /* - * Abort if failure wasn't that the dir already exists or the + * Abort if failure was not that the dir already exists or the * permission was denied (creation might succeed further down the * path) - retry on unspecific FAILURE also */ @@ -1466,13 +1469,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) state(data, SSH_STOP); break; } - /* since this counts what we send to the client, we include the - newline in this counter */ - data->req.bytecount += sshc->readdir_len + 1; - /* output debug output if that is requested */ - Curl_debug(data, CURLINFO_DATA_OUT, (char *)sshc->readdir_filename, - sshc->readdir_len); } else { if(Curl_dyn_add(&sshc->readdir_buf, sshc->readdir_longentry)) { @@ -1555,7 +1552,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) sshc->readdir_longentry = NULL; state(data, SSH_SFTP_READDIR_BOTTOM); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_SFTP_READDIR_BOTTOM: if(Curl_dyn_addn(&sshc->readdir_buf, "\n", 1)) result = CURLE_OUT_OF_MEMORY; @@ -1564,12 +1561,6 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_dyn_ptr(&sshc->readdir_buf), Curl_dyn_len(&sshc->readdir_buf)); - if(!result) { - /* output debug output if that is requested */ - Curl_debug(data, CURLINFO_DATA_OUT, Curl_dyn_ptr(&sshc->readdir_buf), - Curl_dyn_len(&sshc->readdir_buf)); - data->req.bytecount += Curl_dyn_len(&sshc->readdir_buf); - } ssh_string_free_char(sshc->readdir_tmp); sshc->readdir_tmp = NULL; @@ -1585,7 +1576,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) sshc->sftp_dir = NULL; /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); state(data, SSH_STOP); break; @@ -1619,9 +1610,9 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) !(attrs->flags & SSH_FILEXFER_ATTR_SIZE) || (attrs->size == 0)) { /* - * sftp_fstat didn't return an error, so maybe the server - * just doesn't support stat() - * OR the server doesn't return a file size with a stat() + * sftp_fstat did not return an error, so maybe the server + * just does not support stat() + * OR the server does not return a file size with a stat() * OR file size is 0 */ data->req.size = -1; @@ -1635,7 +1626,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) sftp_attributes_free(attrs); if(size < 0) { - failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); + failf(data, "Bad file size (%" FMT_OFF_T ")", size); return CURLE_BAD_DOWNLOAD_RESUME; } if(data->state.use_range) { @@ -1665,9 +1656,8 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) to = size - 1; } if(from > size) { - failf(data, "Offset (%" - CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" - CURL_FORMAT_CURL_OFF_T ")", from, size); + failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%" + FMT_OFF_T ")", from, size); return CURLE_BAD_DOWNLOAD_RESUME; } if(from > to) { @@ -1675,6 +1665,8 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) size = 0; } else { + if((to - from) == CURL_OFF_T_MAX) + return CURLE_RANGE_ERROR; size = to - from + 1; } @@ -1692,12 +1684,10 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) /* We can resume if we can seek to the resume position */ if(data->state.resume_from) { if(data->state.resume_from < 0) { - /* We're supposed to download the last abs(from) bytes */ + /* We are supposed to download the last abs(from) bytes */ if((curl_off_t)size < -data->state.resume_from) { - failf(data, "Offset (%" - CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" - CURL_FORMAT_CURL_OFF_T ")", - data->state.resume_from, size); + failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%" + FMT_OFF_T ")", data->state.resume_from, size); return CURLE_BAD_DOWNLOAD_RESUME; } /* download from where? */ @@ -1705,8 +1695,8 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } else { if((curl_off_t)size < data->state.resume_from) { - failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T - ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", + failf(data, "Offset (%" FMT_OFF_T + ") was beyond file size (%" FMT_OFF_T ")", data->state.resume_from, size); return CURLE_BAD_DOWNLOAD_RESUME; } @@ -1728,20 +1718,20 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) /* Setup the actual download */ if(data->req.size == 0) { /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); infof(data, "File already completely downloaded"); state(data, SSH_STOP); break; } - Curl_setup_transfer(data, FIRSTSOCKET, data->req.size, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE); - /* not set by Curl_setup_transfer to preserve keepon bits */ + /* not set by Curl_xfer_setup to preserve keepon bits */ conn->writesockfd = conn->sockfd; /* we want to use the _receiving_ function even when the socket turns out writableable as the underlying libssh recv function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; + data->state.select_bits = CURL_CSELECT_IN; if(result) { /* this should never occur; the close state should be entered @@ -1857,19 +1847,19 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } /* upload data */ - Curl_setup_transfer(data, -1, data->req.size, FALSE, FIRSTSOCKET); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); - /* not set by Curl_setup_transfer to preserve keepon bits */ + /* not set by Curl_xfer_setup to preserve keepon bits */ conn->sockfd = conn->writesockfd; - /* store this original bitmask setup to use later on if we can't + /* store this original bitmask setup to use later on if we cannot figure out a "real" bitmask */ sshc->orig_waitfor = data->req.keepon; /* we want to use the _sending_ function even when the socket turns out readable as the underlying libssh scp send function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; + data->state.select_bits = CURL_CSELECT_OUT; state(data, SSH_STOP); @@ -1885,7 +1875,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) break; } state(data, SSH_SCP_DOWNLOAD); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_SCP_DOWNLOAD:{ curl_off_t bytecount; @@ -1901,15 +1891,15 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) /* download data */ bytecount = ssh_scp_request_get_size(sshc->scp_session); data->req.maxdownload = (curl_off_t) bytecount; - Curl_setup_transfer(data, FIRSTSOCKET, bytecount, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, bytecount, FALSE); - /* not set by Curl_setup_transfer to preserve keepon bits */ + /* not set by Curl_xfer_setup to preserve keepon bits */ conn->writesockfd = conn->sockfd; /* we want to use the _receiving_ function even when the socket turns out writableable as the underlying libssh recv function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; + data->state.select_bits = CURL_CSELECT_IN; state(data, SSH_STOP); break; @@ -1949,10 +1939,10 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) ssh_set_blocking(sshc->ssh_session, 0); state(data, SSH_SESSION_DISCONNECT); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_SESSION_DISCONNECT: - /* during weird times when we've been prematurely aborted, the channel + /* during weird times when we have been prematurely aborted, the channel is still alive when we reach this state and we MUST kill the channel properly first */ if(sshc->scp_session) { @@ -1963,17 +1953,16 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) ssh_disconnect(sshc->ssh_session); if(!ssh_version(SSH_VERSION_INT(0, 10, 0))) { /* conn->sock[FIRSTSOCKET] is closed by ssh_disconnect behind our back, - explicitly mark it as closed with the memdebug macro. This libssh + tell the connection to forget about it. This libssh bug is fixed in 0.10.0. */ - fake_sclose(conn->sock[FIRSTSOCKET]); - conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; + Curl_conn_forget_socket(data, FIRSTSOCKET); } SSH_STRING_FREE_CHAR(sshc->homedir); data->state.most_recent_ftp_entrypath = NULL; state(data, SSH_SESSION_FREE); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_SESSION_FREE: if(sshc->ssh_session) { ssh_free(sshc->ssh_session); @@ -2024,7 +2013,6 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) break; case SSH_QUIT: - /* fallthrough, just stop! */ default: /* internal error */ sshc->nextstate = SSH_NO_STATE; @@ -2064,6 +2052,7 @@ static int myssh_getsock(struct Curl_easy *data, if(!conn->waitfor) bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); + DEBUGF(infof(data, "ssh_getsock -> %x", bitmap)); return bitmap; } @@ -2071,19 +2060,18 @@ static void myssh_block2waitfor(struct connectdata *conn, bool block) { struct ssh_conn *sshc = &conn->proto.sshc; - /* If it didn't block, or nothing was returned by ssh_get_poll_flags + /* If it did not block, or nothing was returned by ssh_get_poll_flags * have the original set */ conn->waitfor = sshc->orig_waitfor; if(block) { int dir = ssh_get_poll_flags(sshc->ssh_session); - if(dir & SSH_READ_PENDING) { - /* translate the libssh define bits into our own bit defines */ - conn->waitfor = KEEP_RECV; - } - else if(dir & SSH_WRITE_PENDING) { - conn->waitfor = KEEP_SEND; - } + conn->waitfor = 0; + /* translate the libssh define bits into our own bit defines */ + if(dir & SSH_READ_PENDING) + conn->waitfor |= KEEP_RECV; + if(dir & SSH_WRITE_PENDING) + conn->waitfor |= KEEP_SEND; } } @@ -2097,7 +2085,7 @@ static CURLcode myssh_multi_statemach(struct Curl_easy *data, implementation */ CURLcode result = myssh_statemach_act(data, &block); - *done = (sshc->state == SSH_STOP) ? TRUE : FALSE; + *done = (sshc->state == SSH_STOP); myssh_block2waitfor(conn, block); return result; @@ -2158,7 +2146,7 @@ static CURLcode myssh_setup_connection(struct Curl_easy *data, data->req.p.ssh = ssh = calloc(1, sizeof(struct SSHPROTO)); if(!ssh) return CURLE_OUT_OF_MEMORY; - Curl_dyn_init(&sshc->readdir_buf, PATH_MAX * 2); + Curl_dyn_init(&sshc->readdir_buf, CURL_PATH_MAX * 2); return CURLE_OK; } @@ -2329,7 +2317,7 @@ CURLcode scp_perform(struct Curl_easy *data, static CURLcode myssh_do_it(struct Curl_easy *data, bool *done) { CURLcode result; - bool connected = 0; + bool connected = FALSE; struct connectdata *conn = data->conn; struct ssh_conn *sshc = &conn->proto.sshc; @@ -2366,7 +2354,7 @@ static CURLcode scp_disconnect(struct Curl_easy *data, (void) dead_connection; if(ssh->ssh_session) { - /* only if there's a session still around to use! */ + /* only if there is a session still around to use! */ state(data, SSH_SESSION_DISCONNECT); @@ -2413,12 +2401,13 @@ static CURLcode scp_done(struct Curl_easy *data, CURLcode status, } static ssize_t scp_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, CURLcode *err) + const void *mem, size_t len, bool eos, CURLcode *err) { int rc; struct connectdata *conn = data->conn; (void) sockindex; /* we only support SCP on the fixed known primary socket */ (void) err; + (void)eos; rc = ssh_scp_write(conn->proto.sshc.scp_session, mem, len); @@ -2426,7 +2415,7 @@ static ssize_t scp_send(struct Curl_easy *data, int sockindex, /* The following code is misleading, mostly added as wishful thinking * that libssh at some point will implement non-blocking ssh_scp_write/read. * Currently rc can only be number of bytes read or SSH_ERROR. */ - myssh_block2waitfor(conn, (rc == SSH_AGAIN) ? TRUE : FALSE); + myssh_block2waitfor(conn, (rc == SSH_AGAIN)); if(rc == SSH_AGAIN) { *err = CURLE_AGAIN; @@ -2458,7 +2447,7 @@ static ssize_t scp_recv(struct Curl_easy *data, int sockindex, * that libssh at some point will implement non-blocking ssh_scp_write/read. * Currently rc can only be SSH_OK or SSH_ERROR. */ - myssh_block2waitfor(conn, (nread == SSH_AGAIN) ? TRUE : FALSE); + myssh_block2waitfor(conn, (nread == SSH_AGAIN)); if(nread == SSH_AGAIN) { *err = CURLE_AGAIN; nread = -1; @@ -2531,7 +2520,7 @@ static CURLcode sftp_disconnect(struct Curl_easy *data, DEBUGF(infof(data, "SSH DISCONNECT starts now")); if(conn->proto.sshc.ssh_session) { - /* only if there's a session still around to use! */ + /* only if there is a session still around to use! */ state(data, SSH_SFTP_SHUTDOWN); result = myssh_block_statemach(data, TRUE); } @@ -2561,11 +2550,19 @@ static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, /* return number of sent bytes */ static ssize_t sftp_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, CURLcode *err) + const void *mem, size_t len, bool eos, + CURLcode *err) { ssize_t nwrite; struct connectdata *conn = data->conn; (void)sockindex; + (void)eos; + + /* limit the writes to the maximum specified in Section 3 of + * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 + */ + if(len > 32768) + len = 32768; nwrite = sftp_write(conn->proto.sshc.sftp_file, mem, len); @@ -2609,15 +2606,15 @@ static ssize_t sftp_recv(struct Curl_easy *data, int sockindex, return -1; } - /* FALLTHROUGH */ + FALLTHROUGH(); case 1: conn->proto.sshc.sftp_recv_state = 1; nread = sftp_async_read(conn->proto.sshc.sftp_file, mem, (uint32_t)len, - conn->proto.sshc.sftp_file_index); + (uint32_t)conn->proto.sshc.sftp_file_index); - myssh_block2waitfor(conn, (nread == SSH_AGAIN)?TRUE:FALSE); + myssh_block2waitfor(conn, (nread == SSH_AGAIN)); if(nread == SSH_AGAIN) { *err = CURLE_AGAIN; @@ -2654,7 +2651,7 @@ static void sftp_quote(struct Curl_easy *data) /* if a command starts with an asterisk, which a legal SFTP command never can, the command will be allowed to fail without it causing any aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server reponds. */ + is successful, whatever the server responds. */ if(cmd[0] == '*') { cmd++; @@ -2719,7 +2716,7 @@ static void sftp_quote(struct Curl_easy *data) } /* - * SFTP is a binary protocol, so we don't send text commands + * SFTP is a binary protocol, so we do not send text commands * to the server. Instead, we scan for commands used by * OpenSSH's sftp program and call the appropriate libssh * functions. @@ -2828,7 +2825,7 @@ static void sftp_quote_stat(struct Curl_easy *data) /* if a command starts with an asterisk, which a legal SFTP command never can, the command will be allowed to fail without it causing any aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server reponds. */ + is successful, whatever the server responds. */ if(cmd[0] == '*') { cmd++; diff --git a/deps/curl/lib/vssh/libssh2.c b/deps/curl/lib/vssh/libssh2.c index 37040b4b77b..e19ffefe8b5 100644 --- a/deps/curl/lib/vssh/libssh2.c +++ b/deps/curl/lib/vssh/libssh2.c @@ -30,9 +30,6 @@ #include -#include -#include - #ifdef HAVE_FCNTL_H #include #endif @@ -138,7 +135,8 @@ const struct Curl_handler Curl_handler_scp = { ZERO_NULL, /* domore_getsock */ ssh_getsock, /* perform_getsock */ scp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ssh_attach, /* attach */ PORT_SSH, /* defport */ @@ -167,7 +165,8 @@ const struct Curl_handler Curl_handler_sftp = { ZERO_NULL, /* domore_getsock */ ssh_getsock, /* perform_getsock */ sftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ssh_attach, /* attach */ PORT_SSH, /* defport */ @@ -201,7 +200,8 @@ kbd_callback(const char *name, int name_len, const char *instruction, if(num_prompts == 1) { struct connectdata *conn = data->conn; responses[0].text = strdup(conn->passwd); - responses[0].length = curlx_uztoui(strlen(conn->passwd)); + responses[0].length = + responses[0].text == NULL ? 0 : curlx_uztoui(strlen(conn->passwd)); } (void)prompts; } /* kbd_callback */ @@ -279,23 +279,27 @@ static CURLcode libssh2_session_error_to_CURLE(int err) return CURLE_SSH; } +/* These functions are made to use the libcurl memory functions - NOT the + debugmem functions, as that leads us to trigger on libssh2 memory leaks + that are not ours to care for */ + static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc) { (void)abstract; /* arg not used */ - return malloc(count); + return Curl_cmalloc(count); } static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc) { (void)abstract; /* arg not used */ - return realloc(ptr, count); + return Curl_crealloc(ptr, count); } static LIBSSH2_FREE_FUNC(my_libssh2_free) { (void)abstract; /* arg not used */ if(ptr) /* ssh2 agent sometimes call free with null ptr */ - free(ptr); + Curl_cfree(ptr); } /* @@ -385,7 +389,7 @@ static void state(struct Curl_easy *data, sshstate nowstate) #ifdef HAVE_LIBSSH2_KNOWNHOST_API -static int sshkeycallback(struct Curl_easy *easy, +static int sshkeycallback(CURL *easy, const struct curl_khkey *knownkey, /* known */ const struct curl_khkey *foundkey, /* found */ enum curl_khmatch match, @@ -397,13 +401,13 @@ static int sshkeycallback(struct Curl_easy *easy, (void)clientp; /* we only allow perfect matches, and we reject everything else */ - return (match != CURLKHMATCH_OK)?CURLKHSTAT_REJECT:CURLKHSTAT_FINE; + return (match != CURLKHMATCH_OK) ? CURLKHSTAT_REJECT : CURLKHSTAT_FINE; } #endif /* - * Earlier libssh2 versions didn't have the ability to seek to 64bit positions - * with 32bit size_t. + * Earlier libssh2 versions did not have the ability to seek to 64-bit + * positions with 32-bit size_t. */ #ifdef HAVE_LIBSSH2_SFTP_SEEK64 #define SFTP_SEEK(x,y) libssh2_sftp_seek64(x, (libssh2_uint64_t)y) @@ -412,27 +416,27 @@ static int sshkeycallback(struct Curl_easy *easy, #endif /* - * Earlier libssh2 versions didn't do SCP properly beyond 32bit sizes on 32bit - * architectures so we check of the necessary function is present. + * Earlier libssh2 versions did not do SCP properly beyond 32-bit sizes on + * 32-bit architectures so we check of the necessary function is present. */ #ifndef HAVE_LIBSSH2_SCP_SEND64 #define SCP_SEND(a,b,c,d) libssh2_scp_send_ex(a, b, (int)(c), (size_t)d, 0, 0) #else #define SCP_SEND(a,b,c,d) libssh2_scp_send64(a, b, (int)(c), \ - (libssh2_uint64_t)d, 0, 0) + (libssh2_int64_t)d, 0, 0) #endif /* - * libssh2 1.2.8 fixed the problem with 32bit ints used for sockets on win64. + * libssh2 1.2.8 fixed the problem with 32-bit ints used for sockets on win64. */ #ifdef HAVE_LIBSSH2_SESSION_HANDSHAKE #define session_startup(x,y) libssh2_session_handshake(x, y) #else #define session_startup(x,y) libssh2_session_startup(x, (int)y) #endif -static int convert_ssh2_keytype(int sshkeytype) +static enum curl_khtype convert_ssh2_keytype(int sshkeytype) { - int keytype = CURLKHTYPE_UNKNOWN; + enum curl_khtype keytype = CURLKHTYPE_UNKNOWN; switch(sshkeytype) { case LIBSSH2_HOSTKEY_TYPE_RSA: keytype = CURLKHTYPE_RSA; @@ -473,7 +477,7 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) #ifdef HAVE_LIBSSH2_KNOWNHOST_API if(data->set.str[STRING_SSH_KNOWNHOSTS]) { - /* we're asked to verify the host against a file */ + /* we are asked to verify the host against a file */ struct connectdata *conn = data->conn; struct ssh_conn *sshc = &conn->proto.sshc; struct libssh2_knownhost *host = NULL; @@ -484,8 +488,8 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) if(remotekey) { /* - * A subject to figure out is what host name we need to pass in here. - * What host name does OpenSSH store in its file if an IDN name is + * A subject to figure out is what hostname we need to pass in here. + * What hostname does OpenSSH store in its file if an IDN name is * used? */ enum curl_khmatch keymatch; @@ -523,7 +527,7 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) break; #endif default: - infof(data, "unsupported key type, can't check knownhosts"); + infof(data, "unsupported key type, cannot check knownhosts"); keybit = 0; break; } @@ -534,8 +538,8 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) #ifdef HAVE_LIBSSH2_KNOWNHOST_CHECKP keycheck = libssh2_knownhost_checkp(sshc->kh, conn->host.name, - (conn->remote_port != PORT_SSH)? - conn->remote_port:-1, + (conn->remote_port != PORT_SSH) ? + conn->remote_port : -1, remotekey, keylen, LIBSSH2_KNOWNHOST_TYPE_PLAIN| LIBSSH2_KNOWNHOST_KEYENC_RAW| @@ -552,8 +556,8 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) #endif infof(data, "SSH host check: %d, key: %s", keycheck, - (keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH)? - host->key:""); + (keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) ? + host->key : ""); /* setup 'knownkey' */ if(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) { @@ -576,11 +580,11 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) keymatch = (enum curl_khmatch)keycheck; /* Ask the callback how to behave */ - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); rc = func(data, knownkeyp, /* from the knownhosts file */ &foundkey, /* from the remote host */ keymatch, data->set.ssh_keyfunc_userp); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); } } else @@ -589,25 +593,23 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) switch(rc) { default: /* unknown return codes will equal reject */ - /* FALLTHROUGH */ case CURLKHSTAT_REJECT: state(data, SSH_SESSION_FREE); - /* FALLTHROUGH */ + FALLTHROUGH(); case CURLKHSTAT_DEFER: /* DEFER means bail out but keep the SSH_HOSTKEY state */ result = sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; break; case CURLKHSTAT_FINE_REPLACE: - /* remove old host+key that doesn't match */ + /* remove old host+key that does not match */ if(host) libssh2_knownhost_del(sshc->kh, host); - /* FALLTHROUGH */ + FALLTHROUGH(); case CURLKHSTAT_FINE: - /* FALLTHROUGH */ case CURLKHSTAT_FINE_ADD_TO_FILE: /* proceed */ if(keycheck != LIBSSH2_KNOWNHOST_CHECK_MATCH) { - /* the found host+key didn't match but has been told to be fine + /* the found host+key did not match but has been told to be fine anyway so we add it in memory */ int addrc = libssh2_knownhost_add(sshc->kh, conn->host.name, NULL, @@ -661,7 +663,7 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) size_t b64_pos = 0; #ifdef LIBSSH2_HOSTKEY_HASH_SHA256 - /* The fingerprint points to static storage (!), don't free() it. */ + /* The fingerprint points to static storage (!), do not free() it. */ fingerprint = libssh2_hostkey_hash(sshc->ssh_session, LIBSSH2_HOSTKEY_HASH_SHA256); #else @@ -741,7 +743,7 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) LIBSSH2_HOSTKEY_HASH_MD5); if(fingerprint) { - /* The fingerprint points to static storage (!), don't free() it. */ + /* The fingerprint points to static storage (!), do not free() it. */ int i; for(i = 0; i < 16; i++) { msnprintf(&md5buffer[i*2], 3, "%02x", (unsigned char) fingerprint[i]); @@ -779,11 +781,11 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) const char *remotekey = libssh2_session_hostkey(sshc->ssh_session, &keylen, &sshkeytype); if(remotekey) { - int keytype = convert_ssh2_keytype(sshkeytype); - Curl_set_in_callback(data, true); + enum curl_khtype keytype = convert_ssh2_keytype(sshkeytype); + Curl_set_in_callback(data, TRUE); rc = data->set.ssh_hostkeyfunc(data->set.ssh_hostkeyfunc_userp, - keytype, remotekey, keylen); - Curl_set_in_callback(data, false); + (int)keytype, remotekey, keylen); + Curl_set_in_callback(data, FALSE); if(rc!= CURLKHMATCH_OK) { state(data, SSH_SESSION_FREE); sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; @@ -847,7 +849,7 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data) const char *kh_name_end = NULL; size_t kh_name_size = 0; int port = 0; - bool found = false; + bool found = FALSE; if(sshc->kh && !data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) { /* lets try to find our host in the known hosts file */ @@ -868,18 +870,18 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data) kh_name_size = strlen(store->name) - 1 - strlen(kh_name_end); if(strncmp(store->name + 1, conn->host.name, kh_name_size) == 0) { - found = true; + found = TRUE; break; } } } else if(strcmp(store->name, conn->host.name) == 0) { - found = true; + found = TRUE; break; } } else { - found = true; + found = TRUE; break; } } @@ -957,26 +959,745 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data) return result; } +static CURLcode sftp_quote(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + const char *cp; + CURLcode result = CURLE_OK; + + /* + * Support some of the "FTP" commands + * + * 'sshc->quote_item' is already verified to be non-NULL before it + * switched to this state. + */ + char *cmd = sshc->quote_item->data; + sshc->acceptfail = FALSE; + + /* if a command starts with an asterisk, which a legal SFTP command never + can, the command will be allowed to fail without it causing any + aborts or cancels etc. It will cause libcurl to act as if the command + is successful, whatever the server responds. */ + + if(cmd[0] == '*') { + cmd++; + sshc->acceptfail = TRUE; + } + + if(strcasecompare("pwd", cmd)) { + /* output debug output if that is requested */ + char *tmp = aprintf("257 \"%s\" is current directory.\n", sshp->path); + if(!tmp) + return CURLE_OUT_OF_MEMORY; + Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"PWD\n", 4); + Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp)); + + /* this sends an FTP-like "header" to the header callback so that the + current directory can be read very similar to how it is read when + using ordinary FTP. */ + result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); + free(tmp); + if(!result) + state(data, SSH_SFTP_NEXT_QUOTE); + return result; + } + + /* + * the arguments following the command must be separated from the + * command with a space so we can check for it unconditionally + */ + cp = strchr(cmd, ' '); + if(!cp) { + failf(data, "Syntax error command '%s', missing parameter", cmd); + return result; + } + + /* + * also, every command takes at least one argument so we get that + * first argument right now + */ + result = Curl_get_pathname(&cp, &sshc->quote_path1, sshc->homedir); + if(result) { + if(result != CURLE_OUT_OF_MEMORY) + failf(data, "Syntax error: Bad first parameter to '%s'", cmd); + return result; + } + + /* + * SFTP is a binary protocol, so we do not send text commands to the server. + * Instead, we scan for commands used by OpenSSH's sftp program and call the + * appropriate libssh2 functions. + */ + if(strncasecompare(cmd, "chgrp ", 6) || + strncasecompare(cmd, "chmod ", 6) || + strncasecompare(cmd, "chown ", 6) || + strncasecompare(cmd, "atime ", 6) || + strncasecompare(cmd, "mtime ", 6)) { + /* attribute change */ + + /* sshc->quote_path1 contains the mode to set */ + /* get the destination */ + result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); + if(result) { + if(result != CURLE_OUT_OF_MEMORY) + failf(data, "Syntax error in %s: Bad second parameter", cmd); + Curl_safefree(sshc->quote_path1); + return result; + } + memset(&sshp->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); + state(data, SSH_SFTP_QUOTE_STAT); + return result; + } + if(strncasecompare(cmd, "ln ", 3) || + strncasecompare(cmd, "symlink ", 8)) { + /* symbolic linking */ + /* sshc->quote_path1 is the source */ + /* get the destination */ + result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); + if(result) { + if(result != CURLE_OUT_OF_MEMORY) + failf(data, "Syntax error in ln/symlink: Bad second parameter"); + Curl_safefree(sshc->quote_path1); + return result; + } + state(data, SSH_SFTP_QUOTE_SYMLINK); + return result; + } + else if(strncasecompare(cmd, "mkdir ", 6)) { + /* create dir */ + state(data, SSH_SFTP_QUOTE_MKDIR); + return result; + } + else if(strncasecompare(cmd, "rename ", 7)) { + /* rename file */ + /* first param is the source path */ + /* second param is the dest. path */ + result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); + if(result) { + if(result != CURLE_OUT_OF_MEMORY) + failf(data, "Syntax error in rename: Bad second parameter"); + Curl_safefree(sshc->quote_path1); + return result; + } + state(data, SSH_SFTP_QUOTE_RENAME); + return result; + } + else if(strncasecompare(cmd, "rmdir ", 6)) { + /* delete dir */ + state(data, SSH_SFTP_QUOTE_RMDIR); + return result; + } + else if(strncasecompare(cmd, "rm ", 3)) { + state(data, SSH_SFTP_QUOTE_UNLINK); + return result; + } +#ifdef HAS_STATVFS_SUPPORT + else if(strncasecompare(cmd, "statvfs ", 8)) { + state(data, SSH_SFTP_QUOTE_STATVFS); + return result; + } +#endif + + failf(data, "Unknown SFTP command"); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + return CURLE_QUOTE_ERROR; +} + +static CURLcode +sftp_upload_init(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, + bool *blockp) +{ + unsigned long flags; + + /* + * NOTE!!! libssh2 requires that the destination path is a full path + * that includes the destination file and name OR ends in a "/" + * If this is not done the destination file will be named the + * same name as the last directory in the path. + */ + + if(data->state.resume_from) { + LIBSSH2_SFTP_ATTRIBUTES attrs; + if(data->state.resume_from < 0) { + int rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + LIBSSH2_SFTP_STAT, &attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + *blockp = TRUE; + return CURLE_OK; + } + if(rc) { + data->state.resume_from = 0; + } + else { + curl_off_t size = attrs.filesize; + if(size < 0) { + failf(data, "Bad file size (%" FMT_OFF_T ")", size); + return CURLE_BAD_DOWNLOAD_RESUME; + } + data->state.resume_from = attrs.filesize; + } + } + } + + if(data->set.remote_append) + /* Try to open for append, but create if nonexisting */ + flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_APPEND; + else if(data->state.resume_from > 0) + /* If we have restart position then open for append */ + flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_APPEND; + else + /* Clear file before writing (normal behavior) */ + flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC; + + sshc->sftp_handle = + libssh2_sftp_open_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + flags, (long)data->set.new_file_perms, + LIBSSH2_SFTP_OPENFILE); + + if(!sshc->sftp_handle) { + unsigned long sftperr; + int rc = libssh2_session_last_errno(sshc->ssh_session); + + if(LIBSSH2_ERROR_EAGAIN == rc) { + *blockp = TRUE; + return CURLE_OK; + } + + if(LIBSSH2_ERROR_SFTP_PROTOCOL == rc) + /* only when there was an SFTP protocol error can we extract + the sftp error! */ + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + else + sftperr = LIBSSH2_FX_OK; /* not an sftp error at all */ + + if(sshc->secondCreateDirs) { + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = sftperr != LIBSSH2_FX_OK ? + sftp_libssh2_error_to_CURLE(sftperr) : CURLE_SSH; + failf(data, "Creating the dir/file failed: %s", + sftp_libssh2_strerror(sftperr)); + return CURLE_OK; + } + if(((sftperr == LIBSSH2_FX_NO_SUCH_FILE) || + (sftperr == LIBSSH2_FX_FAILURE) || + (sftperr == LIBSSH2_FX_NO_SUCH_PATH)) && + (data->set.ftp_create_missing_dirs && + (strlen(sshp->path) > 1))) { + /* try to create the path remotely */ + sshc->secondCreateDirs = 1; + state(data, SSH_SFTP_CREATE_DIRS_INIT); + return CURLE_OK; + } + state(data, SSH_SFTP_CLOSE); + sshc->actualcode = sftperr != LIBSSH2_FX_OK ? + sftp_libssh2_error_to_CURLE(sftperr) : CURLE_SSH; + if(!sshc->actualcode) { + /* Sometimes, for some reason libssh2_sftp_last_error() returns zero + even though libssh2_sftp_open() failed previously! We need to + work around that! */ + sshc->actualcode = CURLE_SSH; + sftperr = LIBSSH2_FX_OK; + } + failf(data, "Upload failed: %s (%lu/%d)", + sftperr != LIBSSH2_FX_OK ? + sftp_libssh2_strerror(sftperr) : "ssh error", + sftperr, rc); + return sshc->actualcode; + } + + /* If we have a restart point then we need to seek to the correct + position. */ + if(data->state.resume_from > 0) { + int seekerr = CURL_SEEKFUNC_OK; + /* Let's read off the proper amount of bytes from the input. */ + if(data->set.seek_func) { + Curl_set_in_callback(data, TRUE); + seekerr = data->set.seek_func(data->set.seek_client, + data->state.resume_from, SEEK_SET); + Curl_set_in_callback(data, FALSE); + } + + if(seekerr != CURL_SEEKFUNC_OK) { + curl_off_t passed = 0; + + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + return CURLE_FTP_COULDNT_USE_REST; + } + /* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ + do { + char scratch[4*1024]; + size_t readthisamountnow = + (data->state.resume_from - passed > + (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread; + Curl_set_in_callback(data, TRUE); + actuallyread = data->state.fread_func(scratch, 1, + readthisamountnow, + data->state.in); + Curl_set_in_callback(data, FALSE); + + passed += actuallyread; + if((actuallyread == 0) || (actuallyread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Failed to read data"); + return CURLE_FTP_COULDNT_USE_REST; + } + } while(passed < data->state.resume_from); + } + + /* now, decrease the size of the read */ + if(data->state.infilesize > 0) { + data->state.infilesize -= data->state.resume_from; + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + } + + SFTP_SEEK(sshc->sftp_handle, data->state.resume_from); + } + if(data->state.infilesize > 0) { + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + } + /* upload data */ + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); + + /* not set by Curl_xfer_setup to preserve keepon bits */ + data->conn->sockfd = data->conn->writesockfd; + + /* store this original bitmask setup to use later on if we cannot + figure out a "real" bitmask */ + sshc->orig_waitfor = data->req.keepon; + + /* we want to use the _sending_ function even when the socket turns + out readable as the underlying libssh2 sftp send function will deal + with both accordingly */ + data->state.select_bits = CURL_CSELECT_OUT; + + /* since we do not really wait for anything at this point, we want the + state machine to move on as soon as possible so we set a very short + timeout here */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); + + state(data, SSH_STOP); + return CURLE_OK; +} + +static CURLcode +sftp_pkey_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* + * Check the supported auth types in the order I feel is most secure + * with the requested type of authentication + */ + sshc->authed = FALSE; + + if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) && + (strstr(sshc->authlist, "publickey") != NULL)) { + bool out_of_memory = FALSE; + + sshc->rsa_pub = sshc->rsa = NULL; + + if(data->set.str[STRING_SSH_PRIVATE_KEY]) + sshc->rsa = strdup(data->set.str[STRING_SSH_PRIVATE_KEY]); + else { + /* To ponder about: should really the lib be messing about with the + HOME environment variable etc? */ + char *home = curl_getenv("HOME"); + struct_stat sbuf; + + /* If no private key file is specified, try some common paths. */ + if(home) { + /* Try ~/.ssh first. */ + sshc->rsa = aprintf("%s/.ssh/id_rsa", home); + if(!sshc->rsa) + out_of_memory = TRUE; + else if(stat(sshc->rsa, &sbuf)) { + Curl_safefree(sshc->rsa); + sshc->rsa = aprintf("%s/.ssh/id_dsa", home); + if(!sshc->rsa) + out_of_memory = TRUE; + else if(stat(sshc->rsa, &sbuf)) { + Curl_safefree(sshc->rsa); + } + } + free(home); + } + if(!out_of_memory && !sshc->rsa) { + /* Nothing found; try the current dir. */ + sshc->rsa = strdup("id_rsa"); + if(sshc->rsa && stat(sshc->rsa, &sbuf)) { + Curl_safefree(sshc->rsa); + sshc->rsa = strdup("id_dsa"); + if(sshc->rsa && stat(sshc->rsa, &sbuf)) { + Curl_safefree(sshc->rsa); + /* Out of guesses. Set to the empty string to avoid + * surprising info messages. */ + sshc->rsa = strdup(""); + } + } + } + } + + /* + * Unless the user explicitly specifies a public key file, let + * libssh2 extract the public key from the private key file. + * This is done by simply passing sshc->rsa_pub = NULL. + */ + if(data->set.str[STRING_SSH_PUBLIC_KEY] + /* treat empty string the same way as NULL */ + && data->set.str[STRING_SSH_PUBLIC_KEY][0]) { + sshc->rsa_pub = strdup(data->set.str[STRING_SSH_PUBLIC_KEY]); + if(!sshc->rsa_pub) + out_of_memory = TRUE; + } + + if(out_of_memory || !sshc->rsa) { + Curl_safefree(sshc->rsa); + Curl_safefree(sshc->rsa_pub); + state(data, SSH_SESSION_FREE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + return CURLE_OUT_OF_MEMORY; + } + + sshc->passphrase = data->set.ssl.key_passwd; + if(!sshc->passphrase) + sshc->passphrase = ""; + + if(sshc->rsa_pub) + infof(data, "Using SSH public key file '%s'", sshc->rsa_pub); + infof(data, "Using SSH private key file '%s'", sshc->rsa); + + state(data, SSH_AUTH_PKEY); + } + else { + state(data, SSH_AUTH_PASS_INIT); + } + return CURLE_OK; +} + +static CURLcode +sftp_quote_stat(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, + bool *blockp) +{ + char *cmd = sshc->quote_item->data; + sshc->acceptfail = FALSE; + + /* if a command starts with an asterisk, which a legal SFTP command never + can, the command will be allowed to fail without it causing any aborts or + cancels etc. It will cause libcurl to act as if the command is + successful, whatever the server responds. */ + + if(cmd[0] == '*') { + cmd++; + sshc->acceptfail = TRUE; + } + + if(!strncasecompare(cmd, "chmod", 5)) { + /* Since chown and chgrp only set owner OR group but libssh2 wants to set + * them both at once, we need to obtain the current ownership first. This + * takes an extra protocol round trip. + */ + int rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_STAT, + &sshp->quote_attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + *blockp = TRUE; + return CURLE_OK; + } + if(rc && !sshc->acceptfail) { /* get those attributes */ + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); + failf(data, "Attempt to get SFTP stats failed: %s", + sftp_libssh2_strerror(sftperr)); + goto fail; + } + } + + /* Now set the new attributes... */ + if(strncasecompare(cmd, "chgrp", 5)) { + sshp->quote_attrs.gid = strtoul(sshc->quote_path1, NULL, 10); + sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; + if(sshp->quote_attrs.gid == 0 && !ISDIGIT(sshc->quote_path1[0]) && + !sshc->acceptfail) { + failf(data, "Syntax error: chgrp gid not a number"); + goto fail; + } + } + else if(strncasecompare(cmd, "chmod", 5)) { + sshp->quote_attrs.permissions = strtoul(sshc->quote_path1, NULL, 8); + sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS; + /* permissions are octal */ + if(sshp->quote_attrs.permissions == 0 && + !ISDIGIT(sshc->quote_path1[0])) { + failf(data, "Syntax error: chmod permissions not a number"); + goto fail; + } + } + else if(strncasecompare(cmd, "chown", 5)) { + sshp->quote_attrs.uid = strtoul(sshc->quote_path1, NULL, 10); + sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; + if(sshp->quote_attrs.uid == 0 && !ISDIGIT(sshc->quote_path1[0]) && + !sshc->acceptfail) { + failf(data, "Syntax error: chown uid not a number"); + goto fail; + } + } + else if(strncasecompare(cmd, "atime", 5) || + strncasecompare(cmd, "mtime", 5)) { + time_t date = Curl_getdate_capped(sshc->quote_path1); + bool fail = FALSE; + + if(date == -1) { + failf(data, "incorrect date format for %.*s", 5, cmd); + fail = TRUE; + } +#if SIZEOF_TIME_T > SIZEOF_LONG + if(date > 0xffffffff) { + /* if 'long' cannot old >32-bit, this date cannot be sent */ + failf(data, "date overflow"); + fail = TRUE; + } +#endif + if(fail) + goto fail; + if(strncasecompare(cmd, "atime", 5)) + sshp->quote_attrs.atime = (unsigned long)date; + else /* mtime */ + sshp->quote_attrs.mtime = (unsigned long)date; + + sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_ACMODTIME; + } + + /* Now send the completed structure... */ + state(data, SSH_SFTP_QUOTE_SETSTAT); + return CURLE_OK; +fail: + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + return CURLE_QUOTE_ERROR; +} + +static CURLcode +sftp_download_stat(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, + bool *blockp) +{ + LIBSSH2_SFTP_ATTRIBUTES attrs; + int rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + LIBSSH2_SFTP_STAT, &attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + *blockp = TRUE; + return CURLE_OK; + } + if(rc || + !(attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) || + (attrs.filesize == 0)) { + /* + * libssh2_sftp_open() did not return an error, so maybe the server + * just does not support stat() + * OR the server does not return a file size with a stat() + * OR file size is 0 + */ + data->req.size = -1; + data->req.maxdownload = -1; + Curl_pgrsSetDownloadSize(data, -1); + } + else { + curl_off_t size = attrs.filesize; + + if(size < 0) { + failf(data, "Bad file size (%" FMT_OFF_T ")", size); + return CURLE_BAD_DOWNLOAD_RESUME; + } + if(data->state.use_range) { + curl_off_t from, to; + char *ptr; + char *ptr2; + CURLofft to_t; + CURLofft from_t; + + from_t = curlx_strtoofft(data->state.range, &ptr, 10, &from); + if(from_t == CURL_OFFT_FLOW) + return CURLE_RANGE_ERROR; + while(*ptr && (ISBLANK(*ptr) || (*ptr == '-'))) + ptr++; + to_t = curlx_strtoofft(ptr, &ptr2, 10, &to); + if(to_t == CURL_OFFT_FLOW) + return CURLE_RANGE_ERROR; + if((to_t == CURL_OFFT_INVAL) /* no "to" value given */ + || (to >= size)) { + to = size - 1; + } + if(from_t) { + /* from is relative to end of file */ + from = size - to; + to = size - 1; + } + if(from > size) { + failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%" + FMT_OFF_T ")", from, (curl_off_t)attrs.filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + if(from > to) { + from = to; + size = 0; + } + else { + if((to - from) == CURL_OFF_T_MAX) + return CURLE_RANGE_ERROR; + size = to - from + 1; + } + + SFTP_SEEK(sshc->sftp_handle, from); + } + data->req.size = size; + data->req.maxdownload = size; + Curl_pgrsSetDownloadSize(data, size); + } + + /* We can resume if we can seek to the resume position */ + if(data->state.resume_from) { + if(data->state.resume_from < 0) { + /* We are supposed to download the last abs(from) bytes */ + if((curl_off_t)attrs.filesize < -data->state.resume_from) { + failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%" + FMT_OFF_T ")", + data->state.resume_from, (curl_off_t)attrs.filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* download from where? */ + data->state.resume_from += attrs.filesize; + } + else { + if((curl_off_t)attrs.filesize < data->state.resume_from) { + failf(data, "Offset (%" FMT_OFF_T + ") was beyond file size (%" FMT_OFF_T ")", + data->state.resume_from, (curl_off_t)attrs.filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + } + /* Now store the number of bytes we are expected to download */ + data->req.size = attrs.filesize - data->state.resume_from; + data->req.maxdownload = attrs.filesize - data->state.resume_from; + Curl_pgrsSetDownloadSize(data, + attrs.filesize - data->state.resume_from); + SFTP_SEEK(sshc->sftp_handle, data->state.resume_from); + } + + /* Setup the actual download */ + if(data->req.size == 0) { + /* no data to transfer */ + Curl_xfer_setup_nop(data); + infof(data, "File already completely downloaded"); + state(data, SSH_STOP); + return CURLE_OK; + } + Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE); + + /* not set by Curl_xfer_setup to preserve keepon bits */ + data->conn->writesockfd = data->conn->sockfd; + + /* we want to use the _receiving_ function even when the socket turns + out writableable as the underlying libssh2 recv function will deal + with both accordingly */ + data->state.select_bits = CURL_CSELECT_IN; + state(data, SSH_STOP); + + return CURLE_OK; +} + +static CURLcode sftp_readdir(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, + bool *blockp) +{ + CURLcode result = CURLE_OK; + int rc = libssh2_sftp_readdir_ex(sshc->sftp_handle, + sshp->readdir_filename, CURL_PATH_MAX, + sshp->readdir_longentry, CURL_PATH_MAX, + &sshp->readdir_attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + *blockp = TRUE; + return result; + } + if(rc > 0) { + size_t readdir_len = (size_t) rc; + sshp->readdir_filename[readdir_len] = '\0'; + + if(data->set.list_only) { + result = Curl_client_write(data, CLIENTWRITE_BODY, + sshp->readdir_filename, + readdir_len); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, + (char *)"\n", 1); + if(result) + return result; + } + else { + result = Curl_dyn_add(&sshp->readdir, sshp->readdir_longentry); + + if(!result) { + if((sshp->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) && + ((sshp->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == + LIBSSH2_SFTP_S_IFLNK)) { + Curl_dyn_init(&sshp->readdir_link, CURL_PATH_MAX); + result = Curl_dyn_addf(&sshp->readdir_link, "%s%s", sshp->path, + sshp->readdir_filename); + state(data, SSH_SFTP_READDIR_LINK); + } + else { + state(data, SSH_SFTP_READDIR_BOTTOM); + } + } + return result; + } + } + else if(rc == 0) { + state(data, SSH_SFTP_READDIR_DONE); + } + else if(rc < 0) { + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); + result = sftp_libssh2_error_to_CURLE(sftperr); + sshc->actualcode = result ? result : CURLE_SSH; + failf(data, "Could not open remote file for reading: %s :: %d", + sftp_libssh2_strerror(sftperr), + libssh2_session_last_errno(sshc->ssh_session)); + state(data, SSH_SFTP_CLOSE); + } + return result; +} /* - * ssh_statemach_act() runs the SSH state machine as far as it can without - * blocking and without reaching the end. The data the pointer 'block' points + * ssh_statemachine() runs the SSH state machine as far as it can without + * blocking and without reaching the end. The data the pointer 'block' points * to will be set to TRUE if the libssh2 function returns LIBSSH2_ERROR_EAGAIN * meaning it wants to be called again when the socket is ready */ -static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) +static CURLcode ssh_statemachine(struct Curl_easy *data, bool *block) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; struct SSHPROTO *sshp = data->req.p.ssh; struct ssh_conn *sshc = &conn->proto.sshc; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int rc = LIBSSH2_ERROR_NONE; - int ssherr; - unsigned long sftperr; - int seekerr = CURL_SEEKFUNC_OK; - size_t readdir_len; - *block = 0; /* we're not blocking by default */ + *block = 0; /* we are not blocking by default */ do { switch(sshc->state) { @@ -997,10 +1718,10 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) } state(data, SSH_S_STARTUP); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_S_STARTUP: - rc = session_startup(sshc->ssh_session, sock); + rc = session_startup(sshc->ssh_session, conn->sock[FIRSTSOCKET]); if(rc == LIBSSH2_ERROR_EAGAIN) { break; } @@ -1016,7 +1737,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) state(data, SSH_HOSTKEY); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_HOSTKEY: /* * Before we authenticate we should check the hostkey's fingerprint @@ -1036,7 +1757,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) * must never change it later. Thus, always specify the correct username * here, even though the libssh2 docs kind of indicate that it should be * possible to get a 'generic' list (not user-specific) of authentication - * methods, presumably with a blank username. That won't work in my + * methods, presumably with a blank username. That will not work in my * experience. * So always specify it here. */ @@ -1051,12 +1772,12 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) state(data, SSH_AUTH_DONE); break; } - ssherr = libssh2_session_last_errno(sshc->ssh_session); - if(ssherr == LIBSSH2_ERROR_EAGAIN) + rc = libssh2_session_last_errno(sshc->ssh_session); + if(rc == LIBSSH2_ERROR_EAGAIN) rc = LIBSSH2_ERROR_EAGAIN; else { state(data, SSH_SESSION_FREE); - sshc->actualcode = libssh2_session_error_to_CURLE(ssherr); + sshc->actualcode = libssh2_session_error_to_CURLE(rc); } break; } @@ -1067,92 +1788,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; case SSH_AUTH_PKEY_INIT: - /* - * Check the supported auth types in the order I feel is most secure - * with the requested type of authentication - */ - sshc->authed = FALSE; - - if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) && - (strstr(sshc->authlist, "publickey") != NULL)) { - bool out_of_memory = FALSE; - - sshc->rsa_pub = sshc->rsa = NULL; - - if(data->set.str[STRING_SSH_PRIVATE_KEY]) - sshc->rsa = strdup(data->set.str[STRING_SSH_PRIVATE_KEY]); - else { - /* To ponder about: should really the lib be messing about with the - HOME environment variable etc? */ - char *home = curl_getenv("HOME"); - - /* If no private key file is specified, try some common paths. */ - if(home) { - /* Try ~/.ssh first. */ - sshc->rsa = aprintf("%s/.ssh/id_rsa", home); - if(!sshc->rsa) - out_of_memory = TRUE; - else if(access(sshc->rsa, R_OK) != 0) { - Curl_safefree(sshc->rsa); - sshc->rsa = aprintf("%s/.ssh/id_dsa", home); - if(!sshc->rsa) - out_of_memory = TRUE; - else if(access(sshc->rsa, R_OK) != 0) { - Curl_safefree(sshc->rsa); - } - } - free(home); - } - if(!out_of_memory && !sshc->rsa) { - /* Nothing found; try the current dir. */ - sshc->rsa = strdup("id_rsa"); - if(sshc->rsa && access(sshc->rsa, R_OK) != 0) { - Curl_safefree(sshc->rsa); - sshc->rsa = strdup("id_dsa"); - if(sshc->rsa && access(sshc->rsa, R_OK) != 0) { - Curl_safefree(sshc->rsa); - /* Out of guesses. Set to the empty string to avoid - * surprising info messages. */ - sshc->rsa = strdup(""); - } - } - } - } - - /* - * Unless the user explicitly specifies a public key file, let - * libssh2 extract the public key from the private key file. - * This is done by simply passing sshc->rsa_pub = NULL. - */ - if(data->set.str[STRING_SSH_PUBLIC_KEY] - /* treat empty string the same way as NULL */ - && data->set.str[STRING_SSH_PUBLIC_KEY][0]) { - sshc->rsa_pub = strdup(data->set.str[STRING_SSH_PUBLIC_KEY]); - if(!sshc->rsa_pub) - out_of_memory = TRUE; - } - - if(out_of_memory || !sshc->rsa) { - Curl_safefree(sshc->rsa); - Curl_safefree(sshc->rsa_pub); - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - - sshc->passphrase = data->set.ssl.key_passwd; - if(!sshc->passphrase) - sshc->passphrase = ""; - - if(sshc->rsa_pub) - infof(data, "Using SSH public key file '%s'", sshc->rsa_pub); - infof(data, "Using SSH private key file '%s'", sshc->rsa); - - state(data, SSH_AUTH_PKEY); - } - else { - state(data, SSH_AUTH_PASS_INIT); - } + result = sftp_pkey_init(data, sshc); break; case SSH_AUTH_PKEY: @@ -1178,8 +1814,16 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) } else { char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); + char unknown[] = "Reason unknown (-1)"; + if(rc == -1) { + /* No error message has been set and the last set error message, if + any, is from a previous error so ignore it. #11837 */ + err_msg = unknown; + } + else { + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + } infof(data, "SSH public key authentication failed: %s", err_msg); state(data, SSH_AUTH_PASS_INIT); rc = 0; /* clear rc and continue */ @@ -1367,7 +2011,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSH is connected */ - conn->sockfd = sock; + conn->sockfd = conn->sock[FIRSTSOCKET]; conn->writesockfd = CURL_SOCKET_BAD; if(conn->handler->protocol == CURLPROTO_SFTP) { @@ -1402,21 +2046,18 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; case SSH_SFTP_REALPATH: - { - char tempHome[PATH_MAX]; - /* * Get the "home" directory */ rc = sftp_libssh2_realpath(sshc->sftp_session, ".", - tempHome, PATH_MAX-1); + sshp->readdir_filename, CURL_PATH_MAX); if(rc == LIBSSH2_ERROR_EAGAIN) { break; } if(rc > 0) { /* It seems that this string is not always NULL terminated */ - tempHome[rc] = '\0'; - sshc->homedir = strdup(tempHome); + sshp->readdir_filename[rc] = '\0'; + sshc->homedir = strdup(sshp->readdir_filename); if(!sshc->homedir) { state(data, SSH_SFTP_CLOSE); sshc->actualcode = CURLE_OUT_OF_MEMORY; @@ -1426,11 +2067,11 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) } else { /* Return the error type */ - sftperr = libssh2_sftp_last_error(sshc->sftp_session); + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); if(sftperr) result = sftp_libssh2_error_to_CURLE(sftperr); else - /* in this case, the error wasn't in the SFTP level but for example + /* in this case, the error was not in the SFTP level but for example a time-out or similar */ result = CURLE_SSH; sshc->actualcode = result; @@ -1439,7 +2080,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) state(data, SSH_STOP); break; } - } + /* This is the last step in the SFTP connect phase. Do note that while we get the homedir here, we get the "workingpath" in the DO action since the homedir will remain the same between request but the @@ -1479,189 +2120,14 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; case SSH_SFTP_QUOTE: - /* Send any quote commands */ - { - const char *cp; - - /* - * Support some of the "FTP" commands - * - * 'sshc->quote_item' is already verified to be non-NULL before it - * switched to this state. - */ - char *cmd = sshc->quote_item->data; - sshc->acceptfail = FALSE; - - /* if a command starts with an asterisk, which a legal SFTP command never - can, the command will be allowed to fail without it causing any - aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server reponds. */ - - if(cmd[0] == '*') { - cmd++; - sshc->acceptfail = TRUE; - } - - if(strcasecompare("pwd", cmd)) { - /* output debug output if that is requested */ - char *tmp = aprintf("257 \"%s\" is current directory.\n", - sshp->path); - if(!tmp) { - result = CURLE_OUT_OF_MEMORY; - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - break; - } - Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"PWD\n", 4); - Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp)); - - /* this sends an FTP-like "header" to the header callback so that the - current directory can be read very similar to how it is read when - using ordinary FTP. */ - result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); - free(tmp); - if(result) { - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - } - else - state(data, SSH_SFTP_NEXT_QUOTE); - break; - } - { - /* - * the arguments following the command must be separated from the - * command with a space so we can check for it unconditionally - */ - cp = strchr(cmd, ' '); - if(!cp) { - failf(data, "Syntax error command '%s', missing parameter", - cmd); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - - /* - * also, every command takes at least one argument so we get that - * first argument right now - */ - result = Curl_get_pathname(&cp, &sshc->quote_path1, sshc->homedir); - if(result) { - if(result == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, "Syntax error: Bad first parameter to '%s'", cmd); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } - - /* - * SFTP is a binary protocol, so we don't send text commands - * to the server. Instead, we scan for commands used by - * OpenSSH's sftp program and call the appropriate libssh2 - * functions. - */ - if(strncasecompare(cmd, "chgrp ", 6) || - strncasecompare(cmd, "chmod ", 6) || - strncasecompare(cmd, "chown ", 6) || - strncasecompare(cmd, "atime ", 6) || - strncasecompare(cmd, "mtime ", 6)) { - /* attribute change */ - - /* sshc->quote_path1 contains the mode to set */ - /* get the destination */ - result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); - if(result) { - if(result == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, "Syntax error in %s: Bad second parameter", cmd); - Curl_safefree(sshc->quote_path1); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } - memset(&sshp->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); - state(data, SSH_SFTP_QUOTE_STAT); - break; - } - if(strncasecompare(cmd, "ln ", 3) || - strncasecompare(cmd, "symlink ", 8)) { - /* symbolic linking */ - /* sshc->quote_path1 is the source */ - /* get the destination */ - result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); - if(result) { - if(result == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, - "Syntax error in ln/symlink: Bad second parameter"); - Curl_safefree(sshc->quote_path1); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } - state(data, SSH_SFTP_QUOTE_SYMLINK); - break; - } - else if(strncasecompare(cmd, "mkdir ", 6)) { - /* create dir */ - state(data, SSH_SFTP_QUOTE_MKDIR); - break; - } - else if(strncasecompare(cmd, "rename ", 7)) { - /* rename file */ - /* first param is the source path */ - /* second param is the dest. path */ - result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir); - if(result) { - if(result == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, "Syntax error in rename: Bad second parameter"); - Curl_safefree(sshc->quote_path1); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } - state(data, SSH_SFTP_QUOTE_RENAME); - break; - } - else if(strncasecompare(cmd, "rmdir ", 6)) { - /* delete dir */ - state(data, SSH_SFTP_QUOTE_RMDIR); - break; - } - else if(strncasecompare(cmd, "rm ", 3)) { - state(data, SSH_SFTP_QUOTE_UNLINK); - break; - } -#ifdef HAS_STATVFS_SUPPORT - else if(strncasecompare(cmd, "statvfs ", 8)) { - state(data, SSH_SFTP_QUOTE_STATVFS); - break; - } -#endif - - failf(data, "Unknown SFTP command"); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); + /* Send quote commands */ + result = sftp_quote(data, sshc, sshp); + if(result) { state(data, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; + sshc->actualcode = result; } - } - break; + break; case SSH_SFTP_NEXT_QUOTE: Curl_safefree(sshc->quote_path1); @@ -1684,125 +2150,13 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; case SSH_SFTP_QUOTE_STAT: - { - char *cmd = sshc->quote_item->data; - sshc->acceptfail = FALSE; - - /* if a command starts with an asterisk, which a legal SFTP command never - can, the command will be allowed to fail without it causing any - aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server reponds. */ - - if(cmd[0] == '*') { - cmd++; - sshc->acceptfail = TRUE; - } - - if(!strncasecompare(cmd, "chmod", 5)) { - /* Since chown and chgrp only set owner OR group but libssh2 wants to - * set them both at once, we need to obtain the current ownership - * first. This takes an extra protocol round trip. - */ - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, - curlx_uztoui(strlen(sshc->quote_path2)), - LIBSSH2_SFTP_STAT, - &sshp->quote_attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { /* get those attributes */ - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "Attempt to get SFTP stats failed: %s", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - - /* Now set the new attributes... */ - if(strncasecompare(cmd, "chgrp", 5)) { - sshp->quote_attrs.gid = strtoul(sshc->quote_path1, NULL, 10); - sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; - if(sshp->quote_attrs.gid == 0 && !ISDIGIT(sshc->quote_path1[0]) && - !sshc->acceptfail) { - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "Syntax error: chgrp gid not a number"); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - else if(strncasecompare(cmd, "chmod", 5)) { - sshp->quote_attrs.permissions = strtoul(sshc->quote_path1, NULL, 8); - sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS; - /* permissions are octal */ - if(sshp->quote_attrs.permissions == 0 && - !ISDIGIT(sshc->quote_path1[0])) { - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "Syntax error: chmod permissions not a number"); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - else if(strncasecompare(cmd, "chown", 5)) { - sshp->quote_attrs.uid = strtoul(sshc->quote_path1, NULL, 10); - sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; - if(sshp->quote_attrs.uid == 0 && !ISDIGIT(sshc->quote_path1[0]) && - !sshc->acceptfail) { - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "Syntax error: chown uid not a number"); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - else if(strncasecompare(cmd, "atime", 5) || - strncasecompare(cmd, "mtime", 5)) { - time_t date = Curl_getdate_capped(sshc->quote_path1); - bool fail = FALSE; - - if(date == -1) { - failf(data, "incorrect date format for %.*s", 5, cmd); - fail = TRUE; - } -#if SIZEOF_TIME_T > SIZEOF_LONG - if(date > 0xffffffff) { - /* if 'long' can't old >32bit, this date cannot be sent */ - failf(data, "date overflow"); - fail = TRUE; - } -#endif - if(fail) { - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - if(strncasecompare(cmd, "atime", 5)) - sshp->quote_attrs.atime = (unsigned long)date; - else /* mtime */ - sshp->quote_attrs.mtime = (unsigned long)date; - - sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_ACMODTIME; + result = sftp_quote_stat(data, sshc, sshp, block); + if(result) { + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; } - - /* Now send the completed structure... */ - state(data, SSH_SFTP_QUOTE_SETSTAT); break; - } case SSH_SFTP_QUOTE_SETSTAT: rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, @@ -1813,7 +2167,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; } if(rc && !sshc->acceptfail) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); Curl_safefree(sshc->quote_path1); Curl_safefree(sshc->quote_path2); failf(data, "Attempt to set SFTP stats failed: %s", @@ -1836,7 +2190,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; } if(rc && !sshc->acceptfail) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); Curl_safefree(sshc->quote_path1); Curl_safefree(sshc->quote_path2); failf(data, "symlink command failed: %s", @@ -1852,12 +2206,12 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) case SSH_SFTP_QUOTE_MKDIR: rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshc->quote_path1, curlx_uztoui(strlen(sshc->quote_path1)), - data->set.new_directory_perms); + (long)data->set.new_directory_perms); if(rc == LIBSSH2_ERROR_EAGAIN) { break; } if(rc && !sshc->acceptfail) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); Curl_safefree(sshc->quote_path1); failf(data, "mkdir command failed: %s", sftp_libssh2_strerror(sftperr)); @@ -1882,7 +2236,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; } if(rc && !sshc->acceptfail) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); Curl_safefree(sshc->quote_path1); Curl_safefree(sshc->quote_path2); failf(data, "rename command failed: %s", @@ -1902,7 +2256,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; } if(rc && !sshc->acceptfail) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); Curl_safefree(sshc->quote_path1); failf(data, "rmdir command failed: %s", sftp_libssh2_strerror(sftperr)); @@ -1921,7 +2275,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; } if(rc && !sshc->acceptfail) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); Curl_safefree(sshc->quote_path1); failf(data, "rm command failed: %s", sftp_libssh2_strerror(sftperr)); state(data, SSH_SFTP_CLOSE); @@ -1944,7 +2298,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; } if(rc && !sshc->acceptfail) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); Curl_safefree(sshc->quote_path1); failf(data, "statvfs command failed: %s", sftp_libssh2_strerror(sftperr)); @@ -1954,13 +2308,23 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; } else if(rc == 0) { +#ifdef _MSC_VER +#define CURL_LIBSSH2_VFS_SIZE_MASK "I64u" +#else +#define CURL_LIBSSH2_VFS_SIZE_MASK "llu" +#endif char *tmp = aprintf("statvfs:\n" - "f_bsize: %llu\n" "f_frsize: %llu\n" - "f_blocks: %llu\n" "f_bfree: %llu\n" - "f_bavail: %llu\n" "f_files: %llu\n" - "f_ffree: %llu\n" "f_favail: %llu\n" - "f_fsid: %llu\n" "f_flag: %llu\n" - "f_namemax: %llu\n", + "f_bsize: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_frsize: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_blocks: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_bfree: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_bavail: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_files: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_ffree: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_favail: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_fsid: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_flag: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_namemax: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n", statvfs.f_bsize, statvfs.f_frsize, statvfs.f_blocks, statvfs.f_bfree, statvfs.f_bavail, statvfs.f_files, @@ -2008,7 +2372,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; } if(rc == 0) { - data->info.filetime = attrs.mtime; + data->info.filetime = (time_t)attrs.mtime; } state(data, SSH_SFTP_TRANS_INIT); @@ -2027,187 +2391,13 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; case SSH_SFTP_UPLOAD_INIT: - { - unsigned long flags; - /* - * NOTE!!! libssh2 requires that the destination path is a full path - * that includes the destination file and name OR ends in a "/" - * If this is not done the destination file will be named the - * same name as the last directory in the path. - */ - - if(data->state.resume_from) { - LIBSSH2_SFTP_ATTRIBUTES attrs; - if(data->state.resume_from < 0) { - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, - curlx_uztoui(strlen(sshp->path)), - LIBSSH2_SFTP_STAT, &attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc) { - data->state.resume_from = 0; - } - else { - curl_off_t size = attrs.filesize; - if(size < 0) { - failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); - return CURLE_BAD_DOWNLOAD_RESUME; - } - data->state.resume_from = attrs.filesize; - } - } - } - - if(data->set.remote_append) - /* Try to open for append, but create if nonexisting */ - flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_APPEND; - else if(data->state.resume_from > 0) - /* If we have restart position then open for append */ - flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_APPEND; - else - /* Clear file before writing (normal behavior) */ - flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC; - - sshc->sftp_handle = - libssh2_sftp_open_ex(sshc->sftp_session, sshp->path, - curlx_uztoui(strlen(sshp->path)), - flags, data->set.new_file_perms, - LIBSSH2_SFTP_OPENFILE); - - if(!sshc->sftp_handle) { - rc = libssh2_session_last_errno(sshc->ssh_session); - - if(LIBSSH2_ERROR_EAGAIN == rc) - break; - - if(LIBSSH2_ERROR_SFTP_PROTOCOL == rc) - /* only when there was an SFTP protocol error can we extract - the sftp error! */ - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - else - sftperr = LIBSSH2_FX_OK; /* not an sftp error at all */ - - if(sshc->secondCreateDirs) { - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = sftperr != LIBSSH2_FX_OK ? - sftp_libssh2_error_to_CURLE(sftperr):CURLE_SSH; - failf(data, "Creating the dir/file failed: %s", - sftp_libssh2_strerror(sftperr)); - break; - } - if(((sftperr == LIBSSH2_FX_NO_SUCH_FILE) || - (sftperr == LIBSSH2_FX_FAILURE) || - (sftperr == LIBSSH2_FX_NO_SUCH_PATH)) && - (data->set.ftp_create_missing_dirs && - (strlen(sshp->path) > 1))) { - /* try to create the path remotely */ - rc = 0; /* clear rc and continue */ - sshc->secondCreateDirs = 1; - state(data, SSH_SFTP_CREATE_DIRS_INIT); - break; - } - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = sftperr != LIBSSH2_FX_OK ? - sftp_libssh2_error_to_CURLE(sftperr):CURLE_SSH; - if(!sshc->actualcode) { - /* Sometimes, for some reason libssh2_sftp_last_error() returns zero - even though libssh2_sftp_open() failed previously! We need to - work around that! */ - sshc->actualcode = CURLE_SSH; - sftperr = LIBSSH2_FX_OK; - } - failf(data, "Upload failed: %s (%lu/%d)", - sftperr != LIBSSH2_FX_OK ? - sftp_libssh2_strerror(sftperr):"ssh error", - sftperr, rc); - break; - } - - /* If we have a restart point then we need to seek to the correct - position. */ - if(data->state.resume_from > 0) { - /* Let's read off the proper amount of bytes from the input. */ - if(conn->seek_func) { - Curl_set_in_callback(data, true); - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - Curl_set_in_callback(data, false); - } - - if(seekerr != CURL_SEEKFUNC_OK) { - curl_off_t passed = 0; - - if(seekerr != CURL_SEEKFUNC_CANTSEEK) { - failf(data, "Could not seek stream"); - return CURLE_FTP_COULDNT_USE_REST; - } - /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ - do { - size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : - curlx_sotouz(data->state.resume_from - passed); - - size_t actuallyread; - Curl_set_in_callback(data, true); - actuallyread = data->state.fread_func(data->state.buffer, 1, - readthisamountnow, - data->state.in); - Curl_set_in_callback(data, false); - - passed += actuallyread; - if((actuallyread == 0) || (actuallyread > readthisamountnow)) { - /* this checks for greater-than only to make sure that the - CURL_READFUNC_ABORT return code still aborts */ - failf(data, "Failed to read data"); - return CURLE_FTP_COULDNT_USE_REST; - } - } while(passed < data->state.resume_from); - } - - /* now, decrease the size of the read */ - if(data->state.infilesize > 0) { - data->state.infilesize -= data->state.resume_from; - data->req.size = data->state.infilesize; - Curl_pgrsSetUploadSize(data, data->state.infilesize); - } - - SFTP_SEEK(sshc->sftp_handle, data->state.resume_from); - } - if(data->state.infilesize > 0) { - data->req.size = data->state.infilesize; - Curl_pgrsSetUploadSize(data, data->state.infilesize); - } - /* upload data */ - Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); - - /* not set by Curl_setup_transfer to preserve keepon bits */ - conn->sockfd = conn->writesockfd; - + result = sftp_upload_init(data, sshc, sshp, block); if(result) { state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; sshc->actualcode = result; } - else { - /* store this original bitmask setup to use later on if we can't - figure out a "real" bitmask */ - sshc->orig_waitfor = data->req.keepon; - - /* we want to use the _sending_ function even when the socket turns - out readable as the underlying libssh2 sftp send function will deal - with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; - - /* since we don't really wait for anything at this point, we want the - state machine to move on as soon as possible so we set a very short - timeout here */ - Curl_expire(data, 0, EXPIRE_RUN_NOW); - - state(data, SSH_STOP); - } break; - } case SSH_SFTP_CREATE_DIRS_INIT: if(strlen(sshp->path) > 1) { @@ -2235,7 +2425,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) /* 'mode' - parameter is preliminary - default to 0644 */ rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshp->path, curlx_uztoui(strlen(sshp->path)), - data->set.new_directory_perms); + (long)data->set.new_directory_perms); if(rc == LIBSSH2_ERROR_EAGAIN) { break; } @@ -2243,17 +2433,17 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) ++sshc->slash_pos; if(rc < 0) { /* - * Abort if failure wasn't that the dir already exists or the + * Abort if failure was not that the dir already exists or the * permission was denied (creation might succeed further down the * path) - retry on unspecific FAILURE also */ - sftperr = libssh2_sftp_last_error(sshc->sftp_session); + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); if((sftperr != LIBSSH2_FX_FILE_ALREADY_EXISTS) && (sftperr != LIBSSH2_FX_FAILURE) && (sftperr != LIBSSH2_FX_PERMISSION_DENIED)) { result = sftp_libssh2_error_to_CURLE(sftperr); state(data, SSH_SFTP_CLOSE); - sshc->actualcode = result?result:CURLE_SSH; + sshc->actualcode = result ? result : CURLE_SSH; break; } rc = 0; /* clear rc and continue */ @@ -2272,12 +2462,12 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) * This is a directory that we are trying to get, so produce a directory * listing */ - sshc->sftp_handle = libssh2_sftp_open_ex(sshc->sftp_session, - sshp->path, - curlx_uztoui( - strlen(sshp->path)), - 0, 0, LIBSSH2_SFTP_OPENDIR); + sshc->sftp_handle = + libssh2_sftp_open_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + 0, 0, LIBSSH2_SFTP_OPENDIR); if(!sshc->sftp_handle) { + unsigned long sftperr; if(libssh2_session_last_errno(sshc->ssh_session) == LIBSSH2_ERROR_EAGAIN) { rc = LIBSSH2_ERROR_EAGAIN; @@ -2288,101 +2478,18 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) sftp_libssh2_strerror(sftperr)); state(data, SSH_SFTP_CLOSE); result = sftp_libssh2_error_to_CURLE(sftperr); - sshc->actualcode = result?result:CURLE_SSH; - break; - } - sshp->readdir_filename = malloc(PATH_MAX + 1); - if(!sshp->readdir_filename) { - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - sshp->readdir_longentry = malloc(PATH_MAX + 1); - if(!sshp->readdir_longentry) { - Curl_safefree(sshp->readdir_filename); - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; + sshc->actualcode = result ? result : CURLE_SSH; break; } - Curl_dyn_init(&sshp->readdir, PATH_MAX * 2); + Curl_dyn_init(&sshp->readdir, CURL_PATH_MAX * 2); state(data, SSH_SFTP_READDIR); break; case SSH_SFTP_READDIR: - rc = libssh2_sftp_readdir_ex(sshc->sftp_handle, - sshp->readdir_filename, - PATH_MAX, - sshp->readdir_longentry, - PATH_MAX, - &sshp->readdir_attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc > 0) { - readdir_len = (size_t) rc; - sshp->readdir_filename[readdir_len] = '\0'; - - if(data->set.list_only) { - result = Curl_client_write(data, CLIENTWRITE_BODY, - sshp->readdir_filename, - readdir_len); - if(!result) - result = Curl_client_write(data, CLIENTWRITE_BODY, - (char *)"\n", 1); - if(result) { - state(data, SSH_STOP); - break; - } - /* since this counts what we send to the client, we include the - newline in this counter */ - data->req.bytecount += readdir_len + 1; - - /* output debug output if that is requested */ - Curl_debug(data, CURLINFO_DATA_IN, sshp->readdir_filename, - readdir_len); - Curl_debug(data, CURLINFO_DATA_IN, (char *)"\n", 1); - } - else { - result = Curl_dyn_add(&sshp->readdir, sshp->readdir_longentry); - - if(!result) { - if((sshp->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) && - ((sshp->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == - LIBSSH2_SFTP_S_IFLNK)) { - Curl_dyn_init(&sshp->readdir_link, PATH_MAX); - result = Curl_dyn_addf(&sshp->readdir_link, "%s%s", sshp->path, - sshp->readdir_filename); - state(data, SSH_SFTP_READDIR_LINK); - if(!result) - break; - } - else { - state(data, SSH_SFTP_READDIR_BOTTOM); - break; - } - } - sshc->actualcode = result; - state(data, SSH_SFTP_CLOSE); - break; - } - } - else if(rc == 0) { - Curl_safefree(sshp->readdir_filename); - Curl_safefree(sshp->readdir_longentry); - state(data, SSH_SFTP_READDIR_DONE); - break; - } - else if(rc < 0) { - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - result = sftp_libssh2_error_to_CURLE(sftperr); - sshc->actualcode = result?result:CURLE_SSH; - failf(data, "Could not open remote file for reading: %s :: %d", - sftp_libssh2_strerror(sftperr), - libssh2_session_last_errno(sshc->ssh_session)); - Curl_safefree(sshp->readdir_filename); - Curl_safefree(sshp->readdir_longentry); + result = sftp_readdir(data, sshc, sshp, block); + if(result) { + sshc->actualcode = result; state(data, SSH_SFTP_CLOSE); - break; } break; @@ -2390,9 +2497,10 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) rc = libssh2_sftp_symlink_ex(sshc->sftp_session, Curl_dyn_ptr(&sshp->readdir_link), - (int)Curl_dyn_len(&sshp->readdir_link), + (unsigned int) + Curl_dyn_len(&sshp->readdir_link), sshp->readdir_filename, - PATH_MAX, LIBSSH2_SFTP_READLINK); + CURL_PATH_MAX, LIBSSH2_SFTP_READLINK); if(rc == LIBSSH2_ERROR_EAGAIN) { break; } @@ -2402,8 +2510,6 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) result = Curl_dyn_addf(&sshp->readdir, " -> %s", sshp->readdir_filename); if(result) { - Curl_safefree(sshp->readdir_filename); - Curl_safefree(sshp->readdir_longentry); state(data, SSH_SFTP_CLOSE); sshc->actualcode = result; break; @@ -2419,13 +2525,6 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) Curl_dyn_ptr(&sshp->readdir), Curl_dyn_len(&sshp->readdir)); - if(!result) { - /* output debug output if that is requested */ - Curl_debug(data, CURLINFO_DATA_IN, - Curl_dyn_ptr(&sshp->readdir), - Curl_dyn_len(&sshp->readdir)); - data->req.bytecount += Curl_dyn_len(&sshp->readdir); - } if(result) { Curl_dyn_free(&sshp->readdir); state(data, SSH_STOP); @@ -2443,11 +2542,9 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; } sshc->sftp_handle = NULL; - Curl_safefree(sshp->readdir_filename); - Curl_safefree(sshp->readdir_longentry); /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); state(data, SSH_STOP); break; @@ -2458,9 +2555,10 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) sshc->sftp_handle = libssh2_sftp_open_ex(sshc->sftp_session, sshp->path, curlx_uztoui(strlen(sshp->path)), - LIBSSH2_FXF_READ, data->set.new_file_perms, + LIBSSH2_FXF_READ, (long)data->set.new_file_perms, LIBSSH2_SFTP_OPENFILE); if(!sshc->sftp_handle) { + unsigned long sftperr; if(libssh2_session_last_errno(sshc->ssh_session) == LIBSSH2_ERROR_EAGAIN) { rc = LIBSSH2_ERROR_EAGAIN; @@ -2471,147 +2569,20 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) sftp_libssh2_strerror(sftperr)); state(data, SSH_SFTP_CLOSE); result = sftp_libssh2_error_to_CURLE(sftperr); - sshc->actualcode = result?result:CURLE_SSH; + sshc->actualcode = result ? result : CURLE_SSH; break; } state(data, SSH_SFTP_DOWNLOAD_STAT); break; case SSH_SFTP_DOWNLOAD_STAT: - { - LIBSSH2_SFTP_ATTRIBUTES attrs; - - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, - curlx_uztoui(strlen(sshp->path)), - LIBSSH2_SFTP_STAT, &attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc || - !(attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) || - (attrs.filesize == 0)) { - /* - * libssh2_sftp_open() didn't return an error, so maybe the server - * just doesn't support stat() - * OR the server doesn't return a file size with a stat() - * OR file size is 0 - */ - data->req.size = -1; - data->req.maxdownload = -1; - Curl_pgrsSetDownloadSize(data, -1); - } - else { - curl_off_t size = attrs.filesize; - - if(size < 0) { - failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); - return CURLE_BAD_DOWNLOAD_RESUME; - } - if(data->state.use_range) { - curl_off_t from, to; - char *ptr; - char *ptr2; - CURLofft to_t; - CURLofft from_t; - - from_t = curlx_strtoofft(data->state.range, &ptr, 10, &from); - if(from_t == CURL_OFFT_FLOW) - return CURLE_RANGE_ERROR; - while(*ptr && (ISBLANK(*ptr) || (*ptr == '-'))) - ptr++; - to_t = curlx_strtoofft(ptr, &ptr2, 10, &to); - if(to_t == CURL_OFFT_FLOW) - return CURLE_RANGE_ERROR; - if((to_t == CURL_OFFT_INVAL) /* no "to" value given */ - || (to >= size)) { - to = size - 1; - } - if(from_t) { - /* from is relative to end of file */ - from = size - to; - to = size - 1; - } - if(from > size) { - failf(data, "Offset (%" - CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" - CURL_FORMAT_CURL_OFF_T ")", from, - (curl_off_t)attrs.filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - if(from > to) { - from = to; - size = 0; - } - else { - size = to - from + 1; - } - - SFTP_SEEK(sshc->sftp_handle, from); - } - data->req.size = size; - data->req.maxdownload = size; - Curl_pgrsSetDownloadSize(data, size); - } - - /* We can resume if we can seek to the resume position */ - if(data->state.resume_from) { - if(data->state.resume_from < 0) { - /* We're supposed to download the last abs(from) bytes */ - if((curl_off_t)attrs.filesize < -data->state.resume_from) { - failf(data, "Offset (%" - CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" - CURL_FORMAT_CURL_OFF_T ")", - data->state.resume_from, (curl_off_t)attrs.filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - /* download from where? */ - data->state.resume_from += attrs.filesize; - } - else { - if((curl_off_t)attrs.filesize < data->state.resume_from) { - failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T - ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", - data->state.resume_from, (curl_off_t)attrs.filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - } - /* Now store the number of bytes we are expected to download */ - data->req.size = attrs.filesize - data->state.resume_from; - data->req.maxdownload = attrs.filesize - data->state.resume_from; - Curl_pgrsSetDownloadSize(data, - attrs.filesize - data->state.resume_from); - SFTP_SEEK(sshc->sftp_handle, data->state.resume_from); + result = sftp_download_stat(data, sshc, sshp, block); + if(result) { + state(data, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; } - } - - /* Setup the actual download */ - if(data->req.size == 0) { - /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); - infof(data, "File already completely downloaded"); - state(data, SSH_STOP); break; - } - Curl_setup_transfer(data, FIRSTSOCKET, data->req.size, FALSE, -1); - - /* not set by Curl_setup_transfer to preserve keepon bits */ - conn->writesockfd = conn->sockfd; - - /* we want to use the _receiving_ function even when the socket turns - out writableable as the underlying libssh2 recv function will deal - with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; - - if(result) { - /* this should never occur; the close state should be entered - at the time the error occurs */ - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = result; - } - else { - state(data, SSH_STOP); - } - break; case SSH_SFTP_CLOSE: if(sshc->sftp_handle) { @@ -2706,7 +2677,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) case SSH_SCP_UPLOAD_INIT: /* * libssh2 requires that the destination path is a full path that - * includes the destination file and name OR ends in a "/" . If this is + * includes the destination file and name OR ends in a "/" . If this is * not done the destination file will be named the same name as the last * directory in the path. */ @@ -2738,9 +2709,9 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) /* upload data */ data->req.size = data->state.infilesize; Curl_pgrsSetUploadSize(data, data->state.infilesize); - Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); - /* not set by Curl_setup_transfer to preserve keepon bits */ + /* not set by Curl_xfer_setup to preserve keepon bits */ conn->sockfd = conn->writesockfd; if(result) { @@ -2748,14 +2719,14 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) sshc->actualcode = result; } else { - /* store this original bitmask setup to use later on if we can't + /* store this original bitmask setup to use later on if we cannot figure out a "real" bitmask */ sshc->orig_waitfor = data->req.keepon; /* we want to use the _sending_ function even when the socket turns out readable as the underlying libssh2 scp send function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; + data->state.select_bits = CURL_CSELECT_OUT; state(data, SSH_STOP); } @@ -2809,15 +2780,15 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) /* download data */ bytecount = (curl_off_t)sb.st_size; data->req.maxdownload = (curl_off_t)sb.st_size; - Curl_setup_transfer(data, FIRSTSOCKET, bytecount, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, bytecount, FALSE); - /* not set by Curl_setup_transfer to preserve keepon bits */ + /* not set by Curl_xfer_setup to preserve keepon bits */ conn->writesockfd = conn->sockfd; /* we want to use the _receiving_ function even when the socket turns out writableable as the underlying libssh2 recv function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; + data->state.select_bits = CURL_CSELECT_IN; if(result) { state(data, SSH_SCP_CHANNEL_FREE); @@ -2908,7 +2879,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; case SSH_SESSION_DISCONNECT: - /* during weird times when we've been prematurely aborted, the channel + /* during weird times when we have been prematurely aborted, the channel is still alive when we reach this state and we MUST kill the channel properly first */ if(sshc->ssh_channel) { @@ -3022,7 +2993,6 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; case SSH_QUIT: - /* fallthrough, just stop! */ default: /* internal error */ sshc->nextstate = SSH_NO_STATE; @@ -3065,7 +3035,7 @@ static int ssh_getsock(struct Curl_easy *data, * When one of the libssh2 functions has returned LIBSSH2_ERROR_EAGAIN this * function is used to figure out in what direction and stores this info so * that the multi interface can take advantage of it. Make sure to call this - * function in all cases so that when it _doesn't_ return EAGAIN we can + * function in all cases so that when it _does not_ return EAGAIN we can * restore the default wait bits. */ static void ssh_block2waitfor(struct Curl_easy *data, bool block) @@ -3077,12 +3047,12 @@ static void ssh_block2waitfor(struct Curl_easy *data, bool block) dir = libssh2_session_block_directions(sshc->ssh_session); if(dir) { /* translate the libssh2 define bits into our own bit defines */ - conn->waitfor = ((dir&LIBSSH2_SESSION_BLOCK_INBOUND)?KEEP_RECV:0) | - ((dir&LIBSSH2_SESSION_BLOCK_OUTBOUND)?KEEP_SEND:0); + conn->waitfor = ((dir&LIBSSH2_SESSION_BLOCK_INBOUND) ? KEEP_RECV : 0) | + ((dir&LIBSSH2_SESSION_BLOCK_OUTBOUND) ? KEEP_SEND : 0); } } if(!dir) - /* It didn't block or libssh2 didn't reveal in which direction, put back + /* It did not block or libssh2 did not reveal in which direction, put back the original set */ conn->waitfor = sshc->orig_waitfor; } @@ -3096,9 +3066,9 @@ static CURLcode ssh_multi_statemach(struct Curl_easy *data, bool *done) bool block; /* we store the status and use that to provide a ssh_getsock() implementation */ do { - result = ssh_statemach_act(data, &block); - *done = (sshc->state == SSH_STOP) ? TRUE : FALSE; - /* if there's no error, it isn't done and it didn't EWOULDBLOCK, then + result = ssh_statemachine(data, &block); + *done = (sshc->state == SSH_STOP); + /* if there is no error, it is not done and it did not EWOULDBLOCK, then try again */ } while(!result && !*done && !block); ssh_block2waitfor(data, block); @@ -3119,7 +3089,7 @@ static CURLcode ssh_block_statemach(struct Curl_easy *data, timediff_t left = 1000; struct curltime now = Curl_now(); - result = ssh_statemach_act(data, &block); + result = ssh_statemachine(data, &block); if(result) break; @@ -3155,7 +3125,7 @@ static CURLcode ssh_block_statemach(struct Curl_easy *data, fd_write = sock; /* wait for the socket to become ready */ (void)Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write, - left>1000?1000:left); + left > 1000 ? 1000 : left); } } @@ -3191,12 +3161,13 @@ static ssize_t ssh_tls_recv(libssh2_socket_t sock, void *buffer, struct connectdata *conn = data->conn; Curl_recv *backup = conn->recv[0]; struct ssh_conn *ssh = &conn->proto.sshc; + int socknum = Curl_conn_sockindex(data, sock); (void)flags; /* swap in the TLS reader function for this call only, and then swap back the SSH one again */ conn->recv[0] = ssh->tls_recv; - result = Curl_read(data, sock, buffer, length, &nread); + result = Curl_conn_recv(data, socknum, buffer, length, &nread); conn->recv[0] = backup; if(result == CURLE_AGAIN) return -EAGAIN; /* magic return code for libssh2 */ @@ -3210,24 +3181,25 @@ static ssize_t ssh_tls_send(libssh2_socket_t sock, const void *buffer, size_t length, int flags, void **abstract) { struct Curl_easy *data = (struct Curl_easy *)*abstract; - ssize_t nwrite; + size_t nwrite; CURLcode result; struct connectdata *conn = data->conn; Curl_send *backup = conn->send[0]; struct ssh_conn *ssh = &conn->proto.sshc; + int socknum = Curl_conn_sockindex(data, sock); (void)flags; /* swap in the TLS writer function for this call only, and then swap back the SSH one again */ conn->send[0] = ssh->tls_send; - result = Curl_write(data, sock, buffer, length, &nwrite); + result = Curl_conn_send(data, socknum, buffer, length, FALSE, &nwrite); conn->send[0] = backup; if(result == CURLE_AGAIN) return -EAGAIN; /* magic return code for libssh2 */ else if(result) return -1; /* error */ - Curl_debug(data, CURLINFO_DATA_OUT, (char *)buffer, (size_t)nwrite); - return nwrite; + Curl_debug(data, CURLINFO_DATA_OUT, (char *)buffer, nwrite); + return (ssize_t)nwrite; } #endif @@ -3268,7 +3240,7 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) #endif /* CURL_LIBSSH2_DEBUG */ /* libcurl MUST to set custom memory functions so that the kbd_callback - funciton's memory allocations can be properled freed */ + function's memory allocations can be properly freed */ sshc->ssh_session = libssh2_session_init_ex(my_libssh2_malloc, my_libssh2_free, my_libssh2_realloc, data); @@ -3278,18 +3250,37 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) return CURLE_FAILED_INIT; } -#ifdef HAVE_LIBSSH2_VERSION /* Set the packet read timeout if the libssh2 version supports it */ #if LIBSSH2_VERSION_NUM >= 0x010B00 if(data->set.server_response_timeout > 0) { libssh2_session_set_read_timeout(sshc->ssh_session, - data->set.server_response_timeout / 1000); + (long)(data->set.server_response_timeout / 1000)); } #endif -#endif #ifndef CURL_DISABLE_PROXY if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) { + /* + Setup libssh2 callbacks to make it read/write TLS from the socket. + + ssize_t + recvcb(libssh2_socket_t sock, void *buffer, size_t length, + int flags, void **abstract); + + ssize_t + sendcb(libssh2_socket_t sock, const void *buffer, size_t length, + int flags, void **abstract); + + */ +#if LIBSSH2_VERSION_NUM >= 0x010b01 + infof(data, "Uses HTTPS proxy"); + libssh2_session_callback_set2(sshc->ssh_session, + LIBSSH2_CALLBACK_RECV, + (libssh2_cb_generic *)ssh_tls_recv); + libssh2_session_callback_set2(sshc->ssh_session, + LIBSSH2_CALLBACK_SEND, + (libssh2_cb_generic *)ssh_tls_send); +#else /* * This crazy union dance is here to avoid assigning a void pointer a * function pointer as it is invalid C. The problem is of course that @@ -3310,22 +3301,11 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) sshsend.sendptr = ssh_tls_send; infof(data, "Uses HTTPS proxy"); - /* - Setup libssh2 callbacks to make it read/write TLS from the socket. - - ssize_t - recvcb(libssh2_socket_t sock, void *buffer, size_t length, - int flags, void **abstract); - - ssize_t - sendcb(libssh2_socket_t sock, const void *buffer, size_t length, - int flags, void **abstract); - - */ libssh2_session_callback_set(sshc->ssh_session, LIBSSH2_CALLBACK_RECV, sshrecv.recvp); libssh2_session_callback_set(sshc->ssh_session, LIBSSH2_CALLBACK_SEND, sshsend.sendp); +#endif /* Store the underlying TLS recv/send function pointers to be used when reading from the proxy */ @@ -3438,7 +3418,7 @@ static CURLcode scp_doing(struct Curl_easy *data, static CURLcode ssh_do(struct Curl_easy *data, bool *done) { CURLcode result; - bool connected = 0; + bool connected = FALSE; struct connectdata *conn = data->conn; struct ssh_conn *sshc = &conn->proto.sshc; @@ -3475,7 +3455,7 @@ static CURLcode scp_disconnect(struct Curl_easy *data, (void) dead_connection; if(sshc->ssh_session) { - /* only if there's a session still around to use! */ + /* only if there is a session still around to use! */ state(data, SSH_SESSION_DISCONNECT); result = ssh_block_statemach(data, conn, TRUE); } @@ -3498,8 +3478,6 @@ static CURLcode ssh_done(struct Curl_easy *data, CURLcode status) result = status; Curl_safefree(sshp->path); - Curl_safefree(sshp->readdir_filename); - Curl_safefree(sshp->readdir_longentry); Curl_dyn_free(&sshp->readdir); if(Curl_pgrsDone(data)) @@ -3523,17 +3501,18 @@ static CURLcode scp_done(struct Curl_easy *data, CURLcode status, } static ssize_t scp_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, CURLcode *err) + const void *mem, size_t len, bool eos, CURLcode *err) { ssize_t nwrite; struct connectdata *conn = data->conn; struct ssh_conn *sshc = &conn->proto.sshc; (void)sockindex; /* we only support SCP on the fixed known primary socket */ + (void)eos; /* libssh2_channel_write() returns int! */ nwrite = (ssize_t) libssh2_channel_write(sshc->ssh_channel, mem, len); - ssh_block2waitfor(data, (nwrite == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + ssh_block2waitfor(data, (nwrite == LIBSSH2_ERROR_EAGAIN)); if(nwrite == LIBSSH2_ERROR_EAGAIN) { *err = CURLE_AGAIN; @@ -3558,7 +3537,7 @@ static ssize_t scp_recv(struct Curl_easy *data, int sockindex, /* libssh2_channel_read() returns int */ nread = (ssize_t) libssh2_channel_read(sshc->ssh_channel, mem, len); - ssh_block2waitfor(data, (nread == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + ssh_block2waitfor(data, (nread == LIBSSH2_ERROR_EAGAIN)); if(nread == LIBSSH2_ERROR_EAGAIN) { *err = CURLE_AGAIN; nread = -1; @@ -3631,7 +3610,7 @@ static CURLcode sftp_disconnect(struct Curl_easy *data, DEBUGF(infof(data, "SSH DISCONNECT starts now")); if(sshc->ssh_session) { - /* only if there's a session still around to use! */ + /* only if there is a session still around to use! */ state(data, SSH_SFTP_SHUTDOWN); result = ssh_block_statemach(data, conn, TRUE); } @@ -3661,16 +3640,17 @@ static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, /* return number of sent bytes */ static ssize_t sftp_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, CURLcode *err) + const void *mem, size_t len, bool eos, CURLcode *err) { ssize_t nwrite; struct connectdata *conn = data->conn; struct ssh_conn *sshc = &conn->proto.sshc; (void)sockindex; + (void)eos; nwrite = libssh2_sftp_write(sshc->sftp_handle, mem, len); - ssh_block2waitfor(data, (nwrite == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + ssh_block2waitfor(data, (nwrite == LIBSSH2_ERROR_EAGAIN)); if(nwrite == LIBSSH2_ERROR_EAGAIN) { *err = CURLE_AGAIN; @@ -3698,7 +3678,7 @@ static ssize_t sftp_recv(struct Curl_easy *data, int sockindex, nread = libssh2_sftp_read(sshc->sftp_handle, mem, len); - ssh_block2waitfor(data, (nread == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + ssh_block2waitfor(data, (nread == LIBSSH2_ERROR_EAGAIN)); if(nread == LIBSSH2_ERROR_EAGAIN) { *err = CURLE_AGAIN; diff --git a/deps/curl/lib/vssh/ssh.h b/deps/curl/lib/vssh/ssh.h index 1e1b1379c27..f2d42a3de2a 100644 --- a/deps/curl/lib/vssh/ssh.h +++ b/deps/curl/lib/vssh/ssh.h @@ -30,6 +30,8 @@ #include #include #elif defined(USE_LIBSSH) +/* in 0.10.0 or later, ignore deprecated warnings */ +#define SSH_SUPPRESS_DEPRECATED #include #include #elif defined(USE_WOLFSSH) @@ -37,6 +39,8 @@ #include #endif +#include "curl_path.h" + /**************************************************************************** * SSH unique setup ***************************************************************************/ @@ -107,6 +111,8 @@ typedef enum { SSH_LAST /* never used */ } sshstate; +#define CURL_PATH_MAX 1024 + /* this struct is used in the HandleData struct which is part of the Curl_easy, which means this is used on a per-easy handle basis. Everything that is strictly related to a connection is banned from this @@ -116,8 +122,8 @@ struct SSHPROTO { #ifdef USE_LIBSSH2 struct dynbuf readdir_link; struct dynbuf readdir; - char *readdir_filename; - char *readdir_longentry; + char readdir_filename[CURL_PATH_MAX + 1]; + char readdir_longentry[CURL_PATH_MAX + 1]; LIBSSH2_SFTP_ATTRIBUTES quote_attrs; /* used by the SFTP_QUOTE state */ @@ -163,7 +169,7 @@ struct ssh_conn { unsigned kbd_state; /* 0 or 1 */ ssh_key privkey; ssh_key pubkey; - int auth_methods; + unsigned int auth_methods; ssh_session ssh_session; ssh_scp scp_session; sftp_session sftp_session; @@ -243,10 +249,10 @@ struct ssh_conn { #endif #ifdef HAVE_LIBSSH2_VERSION -/* get it run-time if possible */ +/* get it runtime if possible */ #define CURL_LIBSSH2_VERSION libssh2_version(0) #else -/* use build-time if run-time not possible */ +/* use build-time if runtime not possible */ #define CURL_LIBSSH2_VERSION LIBSSH2_VERSION #endif @@ -267,6 +273,7 @@ void Curl_ssh_attach(struct Curl_easy *data, /* for non-SSH builds */ #define Curl_ssh_cleanup() #define Curl_ssh_attach(x,y) +#define Curl_ssh_init() 0 #endif #endif /* HEADER_CURL_SSH_H */ diff --git a/deps/curl/lib/vssh/wolfssh.c b/deps/curl/lib/vssh/wolfssh.c index 306d299bcf1..5400821129b 100644 --- a/deps/curl/lib/vssh/wolfssh.c +++ b/deps/curl/lib/vssh/wolfssh.c @@ -28,8 +28,6 @@ #include -#include -#include #include "urldata.h" #include "cfilters.h" #include "connect.h" @@ -42,6 +40,7 @@ #include "select.h" #include "multiif.h" #include "warnless.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -92,7 +91,8 @@ const struct Curl_handler Curl_handler_scp = { ZERO_NULL, /* domore_getsock */ wssh_getsock, /* perform_getsock */ wscp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ @@ -121,7 +121,8 @@ const struct Curl_handler Curl_handler_sftp = { ZERO_NULL, /* domore_getsock */ wssh_getsock, /* perform_getsock */ wsftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ @@ -217,13 +218,15 @@ static void state(struct Curl_easy *data, sshstate nowstate) } static ssize_t wscp_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, CURLcode *err) + const void *mem, size_t len, bool eos, + CURLcode *err) { ssize_t nwrite = 0; (void)data; (void)sockindex; /* we only support SCP on the fixed known primary socket */ (void)mem; (void)len; + (void)eos; (void)err; return nwrite; @@ -244,16 +247,17 @@ static ssize_t wscp_recv(struct Curl_easy *data, int sockindex, /* return number of sent bytes */ static ssize_t wsftp_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, CURLcode *err) + const void *mem, size_t len, bool eos, CURLcode *err) { struct connectdata *conn = data->conn; struct ssh_conn *sshc = &conn->proto.sshc; word32 offset[2]; int rc; (void)sockindex; + (void)eos; - offset[0] = (word32)sshc->offset&0xFFFFFFFF; - offset[1] = (word32)(sshc->offset>>32)&0xFFFFFFFF; + offset[0] = (word32)sshc->offset & 0xFFFFFFFF; + offset[1] = (word32)(sshc->offset >> 32) & 0xFFFFFFFF; rc = wolfSSH_SFTP_SendWritePacket(sshc->ssh_session, sshc->handle, sshc->handleSz, @@ -277,7 +281,7 @@ static ssize_t wsftp_send(struct Curl_easy *data, int sockindex, return -1; } DEBUGASSERT(rc == (int)len); - infof(data, "sent %zu bytes SFTP from offset %" CURL_FORMAT_CURL_OFF_T, + infof(data, "sent %zu bytes SFTP from offset %" FMT_OFF_T, len, sshc->offset); sshc->offset += len; return (ssize_t)rc; @@ -296,8 +300,8 @@ static ssize_t wsftp_recv(struct Curl_easy *data, int sockindex, word32 offset[2]; (void)sockindex; - offset[0] = (word32)sshc->offset&0xFFFFFFFF; - offset[1] = (word32)(sshc->offset>>32)&0xFFFFFFFF; + offset[0] = (word32)sshc->offset & 0xFFFFFFFF; + offset[1] = (word32)(sshc->offset >> 32) & 0xFFFFFFFF; rc = wolfSSH_SFTP_SendReadPacket(sshc->ssh_session, sshc->handle, sshc->handleSz, @@ -343,9 +347,6 @@ static CURLcode wssh_setup_connection(struct Curl_easy *data, return CURLE_OK; } -static Curl_recv wscp_recv, wsftp_recv; -static Curl_send wscp_send, wsftp_send; - static int userauth(byte authtype, WS_UserAuthData* authdata, void *ctx) @@ -400,7 +401,7 @@ static CURLcode wssh_connect(struct Curl_easy *data, bool *done) rc = wolfSSH_SetUsername(sshc->ssh_session, conn->user); if(rc != WS_SUCCESS) { - failf(data, "wolfSSH failed to set user name"); + failf(data, "wolfSSH failed to set username"); goto error; } @@ -433,7 +434,7 @@ static CURLcode wssh_connect(struct Curl_easy *data, bool *done) /* * wssh_statemach_act() runs the SSH state machine as far as it can without - * blocking and without reaching the end. The data the pointer 'block' points + * blocking and without reaching the end. The data the pointer 'block' points * to will be set to TRUE if the wolfssh function returns EAGAIN meaning it * wants to be called again when the socket is ready */ @@ -446,7 +447,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) struct SSHPROTO *sftp_scp = data->req.p.ssh; WS_SFTPNAME *name; int rc = 0; - *block = FALSE; /* we're not blocking by default */ + *block = FALSE; /* we are not blocking by default */ do { switch(sshc->state) { @@ -515,15 +516,9 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) return CURLE_OK; } else if(name && (rc == WS_SUCCESS)) { - sshc->homedir = malloc(name->fSz + 1); - if(!sshc->homedir) { + sshc->homedir = Curl_memdup0(name->fName, name->fSz); + if(!sshc->homedir) sshc->actualcode = CURLE_OUT_OF_MEMORY; - } - else { - memcpy(sshc->homedir, name->fName, name->fSz); - sshc->homedir[name->fSz] = 0; - infof(data, "wolfssh SFTP realpath succeeded"); - } wolfSSH_SFTPNAME_list_free(name); state(data, SSH_STOP); return CURLE_OK; @@ -583,7 +578,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) else { curl_off_t size = ((curl_off_t)attrs.sz[1] << 32) | attrs.sz[0]; if(size < 0) { - failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size); + failf(data, "Bad file size (%" FMT_OFF_T ")", size); return CURLE_BAD_DOWNLOAD_RESUME; } data->state.resume_from = size; @@ -633,11 +628,11 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) if(data->state.resume_from > 0) { /* Let's read off the proper amount of bytes from the input. */ int seekerr = CURL_SEEKFUNC_OK; - if(conn->seek_func) { - Curl_set_in_callback(data, true); - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - Curl_set_in_callback(data, false); + if(data->set.seek_func) { + Curl_set_in_callback(data, TRUE); + seekerr = data->set.seek_func(data->set.seek_client, + data->state.resume_from, SEEK_SET); + Curl_set_in_callback(data, FALSE); } if(seekerr != CURL_SEEKFUNC_OK) { @@ -647,19 +642,20 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) failf(data, "Could not seek stream"); return CURLE_FTP_COULDNT_USE_REST; } - /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ + /* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ do { + char scratch[4*1024]; size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : - curlx_sotouz(data->state.resume_from - passed); + (data->state.resume_from - passed > + (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); size_t actuallyread; - Curl_set_in_callback(data, true); - actuallyread = data->state.fread_func(data->state.buffer, 1, + Curl_set_in_callback(data, TRUE); + actuallyread = data->state.fread_func(scratch, 1, readthisamountnow, data->state.in); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); passed += actuallyread; if((actuallyread == 0) || (actuallyread > readthisamountnow)) { @@ -685,9 +681,9 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) Curl_pgrsSetUploadSize(data, data->state.infilesize); } /* upload data */ - Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); - /* not set by Curl_setup_transfer to preserve keepon bits */ + /* not set by Curl_xfer_setup to preserve keepon bits */ conn->sockfd = conn->writesockfd; if(result) { @@ -695,16 +691,16 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) sshc->actualcode = result; } else { - /* store this original bitmask setup to use later on if we can't + /* store this original bitmask setup to use later on if we cannot figure out a "real" bitmask */ sshc->orig_waitfor = data->req.keepon; /* we want to use the _sending_ function even when the socket turns out readable as the underlying libssh2 sftp send function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; + data->state.select_bits = CURL_CSELECT_OUT; - /* since we don't really wait for anything at this point, we want the + /* since we do not really wait for anything at this point, we want the state machine to move on as soon as possible so we set a very short timeout here */ Curl_expire(data, 0, EXPIRE_RUN_NOW); @@ -767,13 +763,13 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) return CURLE_SSH; } - size = ((curl_off_t)attrs.sz[1] <<32) | attrs.sz[0]; + size = ((curl_off_t)attrs.sz[1] << 32) | attrs.sz[0]; data->req.size = size; data->req.maxdownload = size; Curl_pgrsSetDownloadSize(data, size); - infof(data, "SFTP download %" CURL_FORMAT_CURL_OFF_T " bytes", size); + infof(data, "SFTP download %" FMT_OFF_T " bytes", size); /* We cannot seek with wolfSSH so resuming and range requests are not possible */ @@ -785,20 +781,20 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) /* Setup the actual download */ if(data->req.size == 0) { /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); + Curl_xfer_setup_nop(data); infof(data, "File already completely downloaded"); state(data, SSH_STOP); break; } - Curl_setup_transfer(data, FIRSTSOCKET, data->req.size, FALSE, -1); + Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE); - /* not set by Curl_setup_transfer to preserve keepon bits */ + /* not set by Curl_xfer_setup to preserve keepon bits */ conn->writesockfd = conn->sockfd; /* we want to use the _receiving_ function even when the socket turns out writableable as the underlying libssh2 recv function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; + data->state.select_bits = CURL_CSELECT_IN; if(result) { /* this should never occur; the close state should be entered @@ -912,8 +908,8 @@ static CURLcode wssh_multi_statemach(struct Curl_easy *data, bool *done) implementation */ do { result = wssh_statemach_act(data, &block); - *done = (sshc->state == SSH_STOP) ? TRUE : FALSE; - /* if there's no error, it isn't done and it didn't EWOULDBLOCK, then + *done = (sshc->state == SSH_STOP); + /* if there is no error, it is not done and it did not EWOULDBLOCK, then try again */ if(*done) { DEBUGF(infof(data, "wssh_statemach_act says DONE")); @@ -966,7 +962,7 @@ CURLcode wsftp_perform(struct Curl_easy *data, static CURLcode wssh_do(struct Curl_easy *data, bool *done) { CURLcode result; - bool connected = 0; + bool connected = FALSE; struct connectdata *conn = data->conn; struct ssh_conn *sshc = &conn->proto.sshc; @@ -1032,7 +1028,7 @@ static CURLcode wssh_block_statemach(struct Curl_easy *data, /* wait for the socket to become ready */ (void)Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write, - left>1000?1000:left); /* ignore result */ + left > 1000 ? 1000 : left); /* ignore result */ } } @@ -1126,7 +1122,7 @@ static CURLcode wsftp_disconnect(struct Curl_easy *data, DEBUGF(infof(data, "SSH DISCONNECT starts now")); if(conn->proto.sshc.ssh_session) { - /* only if there's a session still around to use! */ + /* only if there is a session still around to use! */ state(data, SSH_SFTP_SHUTDOWN); result = wssh_block_statemach(data, TRUE); } @@ -1168,6 +1164,7 @@ CURLcode Curl_ssh_init(void) } void Curl_ssh_cleanup(void) { + (void)wolfSSH_Cleanup(); } #endif /* USE_WOLFSSH */ diff --git a/deps/curl/lib/vtls/.checksrc b/deps/curl/lib/vtls/.checksrc new file mode 100644 index 00000000000..9066946c890 --- /dev/null +++ b/deps/curl/lib/vtls/.checksrc @@ -0,0 +1,2 @@ +enable STRERROR +enable STRNCPY diff --git a/deps/curl/lib/vtls/bearssl.c b/deps/curl/lib/vtls/bearssl.c index 934149c1b06..53fd4a6bc16 100644 --- a/deps/curl/lib/vtls/bearssl.c +++ b/deps/curl/lib/vtls/bearssl.c @@ -28,6 +28,7 @@ #include #include "bearssl.h" +#include "cipher_suite.h" #include "urldata.h" #include "sendf.h" #include "inet_pton.h" @@ -37,7 +38,6 @@ #include "select.h" #include "multiif.h" #include "curl_printf.h" -#include "strcase.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -63,6 +63,7 @@ struct bearssl_ssl_backend_data { bool active; /* size of pending write, yet to be flushed */ size_t pending_write; + BIT(sent_shutdown); }; struct cafile_parser { @@ -120,9 +121,9 @@ static CURLcode load_cafile(struct cafile_source *source, br_x509_pkey *pkey; FILE *fp = 0; unsigned char buf[BUFSIZ]; - const unsigned char *p; + const unsigned char *p = NULL; const char *name; - size_t n, i, pushed; + size_t n = 0, i, pushed; DEBUGASSERT(source->type == CAFILE_SOURCE_PATH || source->type == CAFILE_SOURCE_BLOB); @@ -327,7 +328,7 @@ static unsigned x509_end_chain(const br_x509_class **ctx) struct x509_context *x509 = (struct x509_context *)ctx; if(!x509->verifypeer) { - return br_x509_decoder_last_error(&x509->decoder); + return (unsigned)br_x509_decoder_last_error(&x509->decoder); } return x509->minimal.vtable->end_chain(&x509->minimal.vtable); @@ -360,213 +361,171 @@ static const br_x509_class x509_vtable = { x509_get_pkey }; -struct st_cipher { - const char *name; /* Cipher suite IANA name. It starts with "TLS_" prefix */ - const char *alias_name; /* Alias name is the same as OpenSSL cipher name */ - uint16_t num; /* BearSSL cipher suite */ -}; +static CURLcode +bearssl_set_ssl_version_min_max(struct Curl_easy *data, + br_ssl_engine_context *ssl_eng, + struct ssl_primary_config *conn_config) +{ + unsigned version_min, version_max; + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + version_min = BR_TLS10; + break; + case CURL_SSLVERSION_TLSv1_1: + version_min = BR_TLS11; + break; + case CURL_SSLVERSION_TLSv1_2: + version_min = BR_TLS12; + break; + case CURL_SSLVERSION_TLSv1_3: + failf(data, "BearSSL: does not support TLS 1.3"); + return CURLE_SSL_CONNECT_ERROR; + default: + failf(data, "BearSSL: unsupported minimum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } + + switch(conn_config->version_max) { + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_TLSv1_3: + case CURL_SSLVERSION_MAX_TLSv1_2: + version_max = BR_TLS12; + break; + case CURL_SSLVERSION_MAX_TLSv1_1: + version_max = BR_TLS11; + break; + case CURL_SSLVERSION_MAX_TLSv1_0: + version_max = BR_TLS10; + break; + default: + failf(data, "BearSSL: unsupported maximum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } -/* Macro to initialize st_cipher data structure */ -#define CIPHER_DEF(num, alias) { #num, alias, BR_##num } + br_ssl_engine_set_versions(ssl_eng, version_min, version_max); -static const struct st_cipher ciphertable[] = { + return CURLE_OK; +} + +static const uint16_t ciphertable[] = { /* RFC 2246 TLS 1.0 */ - CIPHER_DEF(TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ - "DES-CBC3-SHA"), + BR_TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ /* RFC 3268 TLS 1.0 AES */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ - "AES128-SHA"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ - "AES256-SHA"), + BR_TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ + BR_TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ /* RFC 5246 TLS 1.2 */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ - "AES128-SHA256"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ - "AES256-SHA256"), + BR_TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ + BR_TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ /* RFC 5288 TLS 1.2 AES GCM */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ - "AES128-GCM-SHA256"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ - "AES256-GCM-SHA384"), + BR_TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ + BR_TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ /* RFC 4492 TLS 1.0 ECC */ - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */ - "ECDH-ECDSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */ - "ECDH-ECDSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */ - "ECDH-ECDSA-AES256-SHA"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */ - "ECDHE-ECDSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ - "ECDHE-ECDSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ - "ECDHE-ECDSA-AES256-SHA"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */ - "ECDH-RSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */ - "ECDH-RSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */ - "ECDH-RSA-AES256-SHA"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */ - "ECDHE-RSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ - "ECDHE-RSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ - "ECDHE-RSA-AES256-SHA"), + BR_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */ + BR_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */ + BR_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */ + BR_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */ + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ + BR_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */ + BR_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */ + BR_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */ + BR_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */ + BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ + BR_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ /* RFC 5289 TLS 1.2 ECC HMAC SHA256/384 */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ - "ECDHE-ECDSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ - "ECDHE-ECDSA-AES256-SHA384"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */ - "ECDH-ECDSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */ - "ECDH-ECDSA-AES256-SHA384"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ - "ECDHE-RSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ - "ECDHE-RSA-AES256-SHA384"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */ - "ECDH-RSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */ - "ECDH-RSA-AES256-SHA384"), + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ + BR_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */ + BR_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */ + BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ + BR_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ + BR_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */ + BR_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */ /* RFC 5289 TLS 1.2 GCM */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ - "ECDHE-ECDSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ - "ECDHE-ECDSA-AES256-GCM-SHA384"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */ - "ECDH-ECDSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */ - "ECDH-ECDSA-AES256-GCM-SHA384"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ - "ECDHE-RSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ - "ECDHE-RSA-AES256-GCM-SHA384"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */ - "ECDH-RSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */ - "ECDH-RSA-AES256-GCM-SHA384"), -#ifdef BR_TLS_RSA_WITH_AES_128_CCM + BR_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ + BR_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */ + BR_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */ + BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ + BR_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ + BR_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */ + BR_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */ +#ifdef BR_TLS_RSA_WITH_AES_128_CCM /* RFC 6655 TLS 1.2 CCM Supported since BearSSL 0.6 */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CCM, /* 0xC09C */ - "AES128-CCM"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CCM, /* 0xC09D */ - "AES256-CCM"), - CIPHER_DEF(TLS_RSA_WITH_AES_128_CCM_8, /* 0xC0A0 */ - "AES128-CCM8"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CCM_8, /* 0xC0A1 */ - "AES256-CCM8"), + BR_TLS_RSA_WITH_AES_128_CCM, /* 0xC09C */ + BR_TLS_RSA_WITH_AES_256_CCM, /* 0xC09D */ + BR_TLS_RSA_WITH_AES_128_CCM_8, /* 0xC0A0 */ + BR_TLS_RSA_WITH_AES_256_CCM_8, /* 0xC0A1 */ /* RFC 7251 TLS 1.2 ECC CCM Supported since BearSSL 0.6 */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CCM, /* 0xC0AC */ - "ECDHE-ECDSA-AES128-CCM"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CCM, /* 0xC0AD */ - "ECDHE-ECDSA-AES256-CCM"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, /* 0xC0AE */ - "ECDHE-ECDSA-AES128-CCM8"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, /* 0xC0AF */ - "ECDHE-ECDSA-AES256-CCM8"), + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CCM, /* 0xC0AC */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CCM, /* 0xC0AD */ + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, /* 0xC0AE */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, /* 0xC0AF */ #endif /* RFC 7905 TLS 1.2 ChaCha20-Poly1305 Supported since BearSSL 0.2 */ - CIPHER_DEF(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ - "ECDHE-RSA-CHACHA20-POLY1305"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ - "ECDHE-ECDSA-CHACHA20-POLY1305"), + BR_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ + BR_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ }; #define NUM_OF_CIPHERS (sizeof(ciphertable) / sizeof(ciphertable[0])) -#define CIPHER_NAME_BUF_LEN 64 - -static bool is_separator(char c) -{ - /* Return whether character is a cipher list separator. */ - switch(c) { - case ' ': - case '\t': - case ':': - case ',': - case ';': - return true; - } - return false; -} static CURLcode bearssl_set_selected_ciphers(struct Curl_easy *data, br_ssl_engine_context *ssl_eng, const char *ciphers) { - uint16_t selected_ciphers[NUM_OF_CIPHERS]; - size_t selected_count = 0; - char cipher_name[CIPHER_NAME_BUF_LEN]; - const char *cipher_start = ciphers; - const char *cipher_end; - size_t i, j; - - if(!cipher_start) - return CURLE_SSL_CIPHER; - - while(true) { - /* Extract the next cipher name from the ciphers string */ - while(is_separator(*cipher_start)) - ++cipher_start; - if(*cipher_start == '\0') - break; - cipher_end = cipher_start; - while(*cipher_end != '\0' && !is_separator(*cipher_end)) - ++cipher_end; - j = cipher_end - cipher_start < CIPHER_NAME_BUF_LEN - 1 ? - cipher_end - cipher_start : CIPHER_NAME_BUF_LEN - 1; - strncpy(cipher_name, cipher_start, j); - cipher_name[j] = '\0'; - cipher_start = cipher_end; - - /* Lookup the cipher name in the table of available ciphers. If the cipher - name starts with "TLS_" we do the lookup by IANA name. Otherwise, we try - to match cipher name by an (OpenSSL) alias. */ - if(strncasecompare(cipher_name, "TLS_", 4)) { - for(i = 0; i < NUM_OF_CIPHERS && - !strcasecompare(cipher_name, ciphertable[i].name); ++i); + uint16_t selected[NUM_OF_CIPHERS]; + size_t count = 0, i; + const char *ptr, *end; + + for(ptr = ciphers; ptr[0] != '\0' && count < NUM_OF_CIPHERS; ptr = end) { + uint16_t id = Curl_cipher_suite_walk_str(&ptr, &end); + + /* Check if cipher is supported */ + if(id) { + for(i = 0; i < NUM_OF_CIPHERS && ciphertable[i] != id; i++); + if(i == NUM_OF_CIPHERS) + id = 0; } - else { - for(i = 0; i < NUM_OF_CIPHERS && - !strcasecompare(cipher_name, ciphertable[i].alias_name); ++i); - } - if(i == NUM_OF_CIPHERS) { - infof(data, "BearSSL: unknown cipher in list: %s", cipher_name); + if(!id) { + if(ptr[0] != '\0') + infof(data, "BearSSL: unknown cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); continue; } /* No duplicates allowed */ - for(j = 0; j < selected_count && - selected_ciphers[j] != ciphertable[i].num; j++); - if(j < selected_count) { - infof(data, "BearSSL: duplicate cipher in list: %s", cipher_name); + for(i = 0; i < count && selected[i] != id; i++); + if(i < count) { + infof(data, "BearSSL: duplicate cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); continue; } - DEBUGASSERT(selected_count < NUM_OF_CIPHERS); - selected_ciphers[selected_count] = ciphertable[i].num; - ++selected_count; + selected[count++] = id; } - if(selected_count == 0) { + if(count == 0) { failf(data, "BearSSL: no supported cipher in list"); return CURLE_SSL_CIPHER; } - br_ssl_engine_set_suites(ssl_eng, selected_ciphers, selected_count); + br_ssl_engine_set_suites(ssl_eng, selected, count); return CURLE_OK; } @@ -582,50 +541,15 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, const char * const ssl_cafile = /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ (ca_info_blob ? NULL : conn_config->CAfile); - const char *hostname = connssl->hostname; + const char *hostname = connssl->peer.hostname; const bool verifypeer = conn_config->verifypeer; const bool verifyhost = conn_config->verifyhost; CURLcode ret; - unsigned version_min, version_max; int session_set = 0; -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif DEBUGASSERT(backend); CURL_TRC_CF(data, cf, "connect_step1"); - switch(conn_config->version) { - case CURL_SSLVERSION_SSLv2: - failf(data, "BearSSL does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_SSLv3: - failf(data, "BearSSL does not support SSLv3"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_TLSv1_0: - version_min = BR_TLS10; - version_max = BR_TLS10; - break; - case CURL_SSLVERSION_TLSv1_1: - version_min = BR_TLS11; - version_max = BR_TLS11; - break; - case CURL_SSLVERSION_TLSv1_2: - version_min = BR_TLS12; - version_max = BR_TLS12; - break; - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - version_min = BR_TLS10; - version_max = BR_TLS12; - break; - default: - failf(data, "BearSSL: unknown CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - if(verifypeer) { if(ca_info_blob) { struct cafile_source source; @@ -660,7 +584,11 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, /* initialize SSL context */ br_ssl_client_init_full(&backend->ctx, &backend->x509.minimal, backend->anchors, backend->anchors_len); - br_ssl_engine_set_versions(&backend->ctx.eng, version_min, version_max); + + ret = bearssl_set_ssl_version_min_max(data, &backend->ctx.eng, conn_config); + if(ret != CURLE_OK) + return ret; + br_ssl_engine_set_buffer(&backend->ctx.eng, backend->buf, sizeof(backend->buf), 1); @@ -680,12 +608,16 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, backend->x509.verifyhost = verifyhost; br_ssl_engine_set_x509(&backend->ctx.eng, &backend->x509.vtable); - if(ssl_config->primary.sessionid) { - void *session; + if(ssl_config->primary.cache_session) { + void *sdata; + size_t slen; + const br_ssl_session_parameters *session; CURL_TRC_CF(data, cf, "connect_step1, check session cache"); Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &session, NULL)) { + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, &sdata, &slen, NULL) && + slen == sizeof(*session)) { + session = sdata; br_ssl_engine_set_session_parameters(&backend->ctx.eng, session); session_set = 1; infof(data, "BearSSL: reusing session ID"); @@ -706,11 +638,7 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } - if((1 == Curl_inet_pton(AF_INET, hostname, &addr)) -#ifdef ENABLE_IPV6 - || (1 == Curl_inet_pton(AF_INET6, hostname, &addr)) -#endif - ) { + if(connssl->peer.type != CURL_SSL_PEER_DNS) { if(verifyhost) { failf(data, "BearSSL: " "host verification of IP address is not supported"); @@ -719,21 +647,20 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, hostname = NULL; } else { - char *snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost) { + if(!connssl->peer.sni) { failf(data, "Failed to set SNI"); return CURLE_SSL_CONNECT_ERROR; } - hostname = snihost; + hostname = connssl->peer.sni; CURL_TRC_CF(data, cf, "connect_step1, SNI set"); } /* give application a chance to interfere with SSL set up. */ if(data->set.ssl.fsslctx) { - Curl_set_in_callback(data, true); + Curl_set_in_callback(data, TRUE); ret = (*data->set.ssl.fsslctx)(data, &backend->ctx, data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); if(ret) { failf(data, "BearSSL: error signaled by ssl ctx callback"); return ret; @@ -749,28 +676,6 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, return CURLE_OK; } -static int bearssl_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) -{ - struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data); - - if(sock == CURL_SOCKET_BAD) - return GETSOCK_BLANK; - else { - struct bearssl_ssl_backend_data *backend = - (struct bearssl_ssl_backend_data *)connssl->backend; - unsigned state = br_ssl_engine_current_state(&backend->ctx.eng); - if(state & BR_SSL_SENDREC) { - socks[0] = sock; - return GETSOCK_WRITESOCK(0); - } - } - socks[0] = sock; - return GETSOCK_READSOCK(0); -} - static CURLcode bearssl_run_until(struct Curl_cfilter *cf, struct Curl_easy *data, unsigned target) @@ -787,6 +692,7 @@ static CURLcode bearssl_run_until(struct Curl_cfilter *cf, DEBUGASSERT(backend); + connssl->io_need = CURL_SSL_IO_NEED_NONE; for(;;) { state = br_ssl_engine_current_state(&backend->ctx.eng); if(state & BR_SSL_CLOSED) { @@ -811,7 +717,9 @@ static CURLcode bearssl_run_until(struct Curl_cfilter *cf, failf(data, "SSL: X.509 verification: " "chain could not be linked to a trust anchor"); return CURLE_PEER_FAILED_VERIFICATION; + default:; } + failf(data, "BearSSL: connection error 0x%04x", err); /* X.509 errors are documented to have the range 32..63 */ if(err >= 32 && err < 64) return CURLE_PEER_FAILED_VERIFICATION; @@ -821,9 +729,12 @@ static CURLcode bearssl_run_until(struct Curl_cfilter *cf, return CURLE_OK; if(state & BR_SSL_SENDREC) { buf = br_ssl_engine_sendrec_buf(&backend->ctx.eng, &len); - ret = Curl_conn_cf_send(cf->next, data, (char *)buf, len, &result); + ret = Curl_conn_cf_send(cf->next, data, (char *)buf, len, FALSE, + &result); CURL_TRC_CF(data, cf, "ssl_send(len=%zu) -> %zd, %d", len, ret, result); if(ret <= 0) { + if(result == CURLE_AGAIN) + connssl->io_need |= CURL_SSL_IO_NEED_SEND; return result; } br_ssl_engine_sendrec_ack(&backend->ctx.eng, ret); @@ -834,9 +745,11 @@ static CURLcode bearssl_run_until(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "ssl_recv(len=%zu) -> %zd, %d", len, ret, result); if(ret == 0) { failf(data, "SSL: EOF without close notify"); - return CURLE_READ_ERROR; + return CURLE_RECV_ERROR; } if(ret <= 0) { + if(result == CURLE_AGAIN) + connssl->io_need |= CURL_SSL_IO_NEED_RECV; return result; } br_ssl_engine_recvrec_ack(&backend->ctx.eng, ret); @@ -850,6 +763,8 @@ static CURLcode bearssl_connect_step2(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct bearssl_ssl_backend_data *backend = (struct bearssl_ssl_backend_data *)connssl->backend; + br_ssl_session_parameters session; + char cipher_str[64]; CURLcode ret; DEBUGASSERT(backend); @@ -860,6 +775,8 @@ static CURLcode bearssl_connect_step2(struct Curl_cfilter *cf, return CURLE_OK; if(ret == CURLE_OK) { unsigned int tver; + int subver = 0; + if(br_ssl_engine_current_state(&backend->ctx.eng) == BR_SSL_CLOSED) { failf(data, "SSL: connection closed during handshake"); return CURLE_SSL_CONNECT_ERROR; @@ -867,16 +784,32 @@ static CURLcode bearssl_connect_step2(struct Curl_cfilter *cf, connssl->connecting_state = ssl_connect_3; /* Informational message */ tver = br_ssl_engine_get_version(&backend->ctx.eng); - if(tver == 0x0303) - infof(data, "SSL connection using TLSv1.2"); - else if(tver == 0x0304) - infof(data, "SSL connection using TLSv1.3"); - else - infof(data, "SSL connection using TLS 0x%x", tver); + switch(tver) { + case BR_TLS12: + subver = 2; /* 1.2 */ + break; + case BR_TLS11: + subver = 1; /* 1.1 */ + break; + case BR_TLS10: /* 1.0 */ + default: /* unknown, leave it at zero */ + break; + } + br_ssl_engine_get_session_parameters(&backend->ctx.eng, &session); + Curl_cipher_suite_get_str(session.cipher_suite, cipher_str, + sizeof(cipher_str), TRUE); + infof(data, "BearSSL: TLS v1.%d connection using %s", subver, + cipher_str); } return ret; } +static void bearssl_session_free(void *sessionid, size_t idsize) +{ + (void)idsize; + free(sessionid); +} + static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -894,14 +827,11 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf, const char *proto; proto = br_ssl_engine_get_selected_protocol(&backend->ctx.eng); - Curl_alpn_set_negotiated(cf, data, (const unsigned char *)proto, - proto? strlen(proto) : 0); + Curl_alpn_set_negotiated(cf, data, connssl, (const unsigned char *)proto, + proto ? strlen(proto) : 0); } - if(ssl_config->primary.sessionid) { - bool incache; - bool added = FALSE; - void *oldsession; + if(ssl_config->primary.cache_session) { br_ssl_session_parameters *session; session = malloc(sizeof(*session)); @@ -909,16 +839,12 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf, return CURLE_OUT_OF_MEMORY; br_ssl_engine_get_session_parameters(&backend->ctx.eng, session); Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(cf, data, &oldsession, NULL)); - if(incache) - Curl_ssl_delsessionid(data, oldsession); - ret = Curl_ssl_addsessionid(cf, data, session, 0, &added); + ret = Curl_ssl_set_sessionid(cf, data, &connssl->peer, NULL, + session, sizeof(*session), + bearssl_session_free); Curl_ssl_sessionid_unlock(data); - if(!added) - free(session); - if(ret) { - return CURLE_OUT_OF_MEMORY; - } + if(ret) + return ret; } connssl->connecting_state = ssl_connect_done; @@ -1011,9 +937,7 @@ static CURLcode bearssl_connect_common(struct Curl_cfilter *cf, return ret; } - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { + while(ssl_connect_2 == connssl->connecting_state) { /* check allowed time left */ timeout_ms = Curl_timeleft(data, NULL, TRUE); @@ -1023,18 +947,16 @@ static CURLcode bearssl_connect_common(struct Curl_cfilter *cf, return CURLE_OPERATION_TIMEDOUT; } - /* if ssl is expecting something, check if it's available. */ - if(ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + /* if ssl is expecting something, check if it is available. */ + if(connssl->io_need) { + curl_socket_t writefd = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? + sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = (connssl->io_need & CURL_SSL_IO_NEED_RECV) ? + sockfd : CURL_SOCKET_BAD; CURL_TRC_CF(data, cf, "connect_common, check socket"); what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking?0:timeout_ms); + nonblocking ? 0 : timeout_ms); CURL_TRC_CF(data, cf, "connect_common, check socket -> %d", what); if(what < 0) { /* fatal error */ @@ -1061,11 +983,9 @@ static CURLcode bearssl_connect_common(struct Curl_cfilter *cf, * before step2 has completed while ensuring that a client using select() * or epoll() will always have a valid fdset to wait on. */ + connssl->io_need = CURL_SSL_IO_NEED_NONE; ret = bearssl_connect_step2(cf, data); - if(ret || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) + if(ret || (nonblocking && (ssl_connect_2 == connssl->connecting_state))) return ret; } @@ -1156,20 +1076,54 @@ static void *bearssl_get_internals(struct ssl_connect_data *connssl, return &backend->ctx; } -static void bearssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) +static CURLcode bearssl_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { struct ssl_connect_data *connssl = cf->ctx; struct bearssl_ssl_backend_data *backend = (struct bearssl_ssl_backend_data *)connssl->backend; - size_t i; + CURLcode result; DEBUGASSERT(backend); + if(!backend->active || cf->shutdown) { + *done = TRUE; + return CURLE_OK; + } - if(backend->active) { - backend->active = FALSE; + *done = FALSE; + if(!backend->sent_shutdown) { + (void)send_shutdown; /* unknown how to suppress our close notify */ br_ssl_engine_close(&backend->ctx.eng); - (void)bearssl_run_until(cf, data, BR_SSL_CLOSED); + backend->sent_shutdown = TRUE; } + + result = bearssl_run_until(cf, data, BR_SSL_CLOSED); + if(result == CURLE_OK) { + *done = TRUE; + } + else if(result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "shutdown EAGAIN, io_need=%x", connssl->io_need); + result = CURLE_OK; + } + else + CURL_TRC_CF(data, cf, "shutdown error: %d", result); + + cf->shutdown = (result || *done); + return result; +} + +static void bearssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; + size_t i; + + (void)data; + DEBUGASSERT(backend); + + backend->active = FALSE; if(backend->anchors) { for(i = 0; i < backend->anchors_len; ++i) free(backend->anchors[i].dn.data); @@ -1177,11 +1131,6 @@ static void bearssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) } } -static void bearssl_session_free(void *ptr) -{ - free(ptr); -} - static CURLcode bearssl_sha256sum(const unsigned char *input, size_t inputlen, unsigned char *sha256sum, @@ -1197,24 +1146,28 @@ static CURLcode bearssl_sha256sum(const unsigned char *input, const struct Curl_ssl Curl_ssl_bearssl = { { CURLSSLBACKEND_BEARSSL, "bearssl" }, /* info */ - SSLSUPP_CAINFO_BLOB | SSLSUPP_SSL_CTX | SSLSUPP_HTTPS_PROXY, + + SSLSUPP_CAINFO_BLOB | + SSLSUPP_SSL_CTX | + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST, + sizeof(struct bearssl_ssl_backend_data), Curl_none_init, /* init */ Curl_none_cleanup, /* cleanup */ bearssl_version, /* version */ Curl_none_check_cxn, /* check_cxn */ - Curl_none_shutdown, /* shutdown */ + bearssl_shutdown, /* shutdown */ bearssl_data_pending, /* data_pending */ bearssl_random, /* random */ Curl_none_cert_status_request, /* cert_status_request */ bearssl_connect, /* connect */ bearssl_connect_nonblocking, /* connect_nonblocking */ - bearssl_get_select_socks, /* getsock */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ bearssl_get_internals, /* get_internals */ bearssl_close, /* close_one */ Curl_none_close_all, /* close_all */ - bearssl_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ @@ -1222,9 +1175,9 @@ const struct Curl_ssl Curl_ssl_bearssl = { bearssl_sha256sum, /* sha256sum */ NULL, /* associate_connection */ NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ bearssl_recv, /* recv decrypted data */ bearssl_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; #endif /* USE_BEARSSL */ diff --git a/deps/curl/lib/vtls/cipher_suite.c b/deps/curl/lib/vtls/cipher_suite.c new file mode 100644 index 00000000000..a694b146279 --- /dev/null +++ b/deps/curl/lib/vtls/cipher_suite.c @@ -0,0 +1,891 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Jan Venekamp, + * + * 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.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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \ + defined(USE_BEARSSL) || defined(USE_RUSTLS) +#include "cipher_suite.h" +#include "curl_printf.h" +#include "strcase.h" +#include + +/* + * To support the CURLOPT_SSL_CIPHER_LIST option on SSL backends + * that do not support it natively, but do support setting a list of + * IANA ids, we need a list of all supported cipher suite names + * (OpenSSL and IANA) to be able to look up the IANA ids. + * + * To keep the binary size of this list down we compress each entry + * down to 2 + 6 bytes using the C preprocessor. + */ + +/* + * mbedTLS NOTE: mbedTLS has mbedtls_ssl_get_ciphersuite_id() to + * convert a string representation to an IANA id, we do not use that + * because it does not support "standard" OpenSSL cipher suite + * names, nor IANA names. + */ + +/* NOTE: also see tests/unit/unit3205.c */ + +/* Text for cipher suite parts (max 64 entries), + keep indexes below in sync with this! */ +static const char *cs_txt = + "\0" + "TLS" "\0" + "WITH" "\0" + "128" "\0" + "256" "\0" + "3DES" "\0" + "8" "\0" + "AES" "\0" + "AES128" "\0" + "AES256" "\0" + "CBC" "\0" + "CBC3" "\0" + "CCM" "\0" + "CCM8" "\0" + "CHACHA20" "\0" + "DES" "\0" + "DHE" "\0" + "ECDH" "\0" + "ECDHE" "\0" + "ECDSA" "\0" + "EDE" "\0" + "GCM" "\0" + "MD5" "\0" + "NULL" "\0" + "POLY1305" "\0" + "PSK" "\0" + "RSA" "\0" + "SHA" "\0" + "SHA256" "\0" + "SHA384" "\0" +#if defined(USE_MBEDTLS) + "ARIA" "\0" + "ARIA128" "\0" + "ARIA256" "\0" + "CAMELLIA" "\0" + "CAMELLIA128" "\0" + "CAMELLIA256" "\0" +#endif +#if defined(USE_SECTRANSP) + "40" "\0" + "ADH" "\0" + "AECDH" "\0" + "anon" "\0" + "DES40" "\0" + "DH" "\0" + "DSS" "\0" + "EDH" "\0" + "EXP" "\0" + "EXPORT" "\0" + "IDEA" "\0" + "RC2" "\0" + "RC4" "\0" +#endif +; +/* Indexes of above cs_txt */ +enum { + CS_TXT_IDX_, + CS_TXT_IDX_TLS, + CS_TXT_IDX_WITH, + CS_TXT_IDX_128, + CS_TXT_IDX_256, + CS_TXT_IDX_3DES, + CS_TXT_IDX_8, + CS_TXT_IDX_AES, + CS_TXT_IDX_AES128, + CS_TXT_IDX_AES256, + CS_TXT_IDX_CBC, + CS_TXT_IDX_CBC3, + CS_TXT_IDX_CCM, + CS_TXT_IDX_CCM8, + CS_TXT_IDX_CHACHA20, + CS_TXT_IDX_DES, + CS_TXT_IDX_DHE, + CS_TXT_IDX_ECDH, + CS_TXT_IDX_ECDHE, + CS_TXT_IDX_ECDSA, + CS_TXT_IDX_EDE, + CS_TXT_IDX_GCM, + CS_TXT_IDX_MD5, + CS_TXT_IDX_NULL, + CS_TXT_IDX_POLY1305, + CS_TXT_IDX_PSK, + CS_TXT_IDX_RSA, + CS_TXT_IDX_SHA, + CS_TXT_IDX_SHA256, + CS_TXT_IDX_SHA384, +#if defined(USE_MBEDTLS) + CS_TXT_IDX_ARIA, + CS_TXT_IDX_ARIA128, + CS_TXT_IDX_ARIA256, + CS_TXT_IDX_CAMELLIA, + CS_TXT_IDX_CAMELLIA128, + CS_TXT_IDX_CAMELLIA256, +#endif +#if defined(USE_SECTRANSP) + CS_TXT_IDX_40, + CS_TXT_IDX_ADH, + CS_TXT_IDX_AECDH, + CS_TXT_IDX_anon, + CS_TXT_IDX_DES40, + CS_TXT_IDX_DH, + CS_TXT_IDX_DSS, + CS_TXT_IDX_EDH, + CS_TXT_IDX_EXP, + CS_TXT_IDX_EXPORT, + CS_TXT_IDX_IDEA, + CS_TXT_IDX_RC2, + CS_TXT_IDX_RC4, +#endif + CS_TXT_LEN, +}; + +#define CS_ZIP_IDX(a, b, c, d, e, f, g, h) \ +{ \ + (uint8_t) ((((a) << 2) & 0xFF) | ((b) & 0x3F) >> 4), \ + (uint8_t) ((((b) << 4) & 0xFF) | ((c) & 0x3F) >> 2), \ + (uint8_t) ((((c) << 6) & 0xFF) | ((d) & 0x3F)), \ + (uint8_t) ((((e) << 2) & 0xFF) | ((f) & 0x3F) >> 4), \ + (uint8_t) ((((f) << 4) & 0xFF) | ((g) & 0x3F) >> 2), \ + (uint8_t) ((((g) << 6) & 0xFF) | ((h) & 0x3F)) \ +} +#define CS_ENTRY(id, a, b, c, d, e, f, g, h) \ +{ \ + id, \ + CS_ZIP_IDX( \ + CS_TXT_IDX_ ## a, CS_TXT_IDX_ ## b, \ + CS_TXT_IDX_ ## c, CS_TXT_IDX_ ## d, \ + CS_TXT_IDX_ ## e, CS_TXT_IDX_ ## f, \ + CS_TXT_IDX_ ## g, CS_TXT_IDX_ ## h \ + ) \ +} + +struct cs_entry { + uint16_t id; + uint8_t zip[6]; +}; + +/* !checksrc! disable COMMANOSPACE all */ +static const struct cs_entry cs_list [] = { + /* TLS 1.3 ciphers */ +#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || defined(USE_RUSTLS) + CS_ENTRY(0x1301, TLS,AES,128,GCM,SHA256,,,), + CS_ENTRY(0x1302, TLS,AES,256,GCM,SHA384,,,), + CS_ENTRY(0x1303, TLS,CHACHA20,POLY1305,SHA256,,,,), + CS_ENTRY(0x1304, TLS,AES,128,CCM,SHA256,,,), + CS_ENTRY(0x1305, TLS,AES,128,CCM,8,SHA256,,), +#endif + /* TLS 1.2 ciphers */ + CS_ENTRY(0xC02B, TLS,ECDHE,ECDSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC02B, ECDHE,ECDSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC02C, TLS,ECDHE,ECDSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC02C, ECDHE,ECDSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0xC02F, TLS,ECDHE,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC02F, ECDHE,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC030, TLS,ECDHE,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC030, ECDHE,RSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0xCCA8, TLS,ECDHE,RSA,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCA8, ECDHE,RSA,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCA9, TLS,ECDHE,ECDSA,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCA9, ECDHE,ECDSA,CHACHA20,POLY1305,,,,), +#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || defined(USE_BEARSSL) + CS_ENTRY(0x002F, TLS,RSA,WITH,AES,128,CBC,SHA,), + CS_ENTRY(0x002F, AES128,SHA,,,,,,), + CS_ENTRY(0x0035, TLS,RSA,WITH,AES,256,CBC,SHA,), + CS_ENTRY(0x0035, AES256,SHA,,,,,,), + CS_ENTRY(0x003C, TLS,RSA,WITH,AES,128,CBC,SHA256,), + CS_ENTRY(0x003C, AES128,SHA256,,,,,,), + CS_ENTRY(0x003D, TLS,RSA,WITH,AES,256,CBC,SHA256,), + CS_ENTRY(0x003D, AES256,SHA256,,,,,,), + CS_ENTRY(0x009C, TLS,RSA,WITH,AES,128,GCM,SHA256,), + CS_ENTRY(0x009C, AES128,GCM,SHA256,,,,,), + CS_ENTRY(0x009D, TLS,RSA,WITH,AES,256,GCM,SHA384,), + CS_ENTRY(0x009D, AES256,GCM,SHA384,,,,,), + CS_ENTRY(0xC004, TLS,ECDH,ECDSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC004, ECDH,ECDSA,AES128,SHA,,,,), + CS_ENTRY(0xC005, TLS,ECDH,ECDSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC005, ECDH,ECDSA,AES256,SHA,,,,), + CS_ENTRY(0xC009, TLS,ECDHE,ECDSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC009, ECDHE,ECDSA,AES128,SHA,,,,), + CS_ENTRY(0xC00A, TLS,ECDHE,ECDSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC00A, ECDHE,ECDSA,AES256,SHA,,,,), + CS_ENTRY(0xC00E, TLS,ECDH,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC00E, ECDH,RSA,AES128,SHA,,,,), + CS_ENTRY(0xC00F, TLS,ECDH,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC00F, ECDH,RSA,AES256,SHA,,,,), + CS_ENTRY(0xC013, TLS,ECDHE,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC013, ECDHE,RSA,AES128,SHA,,,,), + CS_ENTRY(0xC014, TLS,ECDHE,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC014, ECDHE,RSA,AES256,SHA,,,,), + CS_ENTRY(0xC023, TLS,ECDHE,ECDSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC023, ECDHE,ECDSA,AES128,SHA256,,,,), + CS_ENTRY(0xC024, TLS,ECDHE,ECDSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC024, ECDHE,ECDSA,AES256,SHA384,,,,), + CS_ENTRY(0xC025, TLS,ECDH,ECDSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC025, ECDH,ECDSA,AES128,SHA256,,,,), + CS_ENTRY(0xC026, TLS,ECDH,ECDSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC026, ECDH,ECDSA,AES256,SHA384,,,,), + CS_ENTRY(0xC027, TLS,ECDHE,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC027, ECDHE,RSA,AES128,SHA256,,,,), + CS_ENTRY(0xC028, TLS,ECDHE,RSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC028, ECDHE,RSA,AES256,SHA384,,,,), + CS_ENTRY(0xC029, TLS,ECDH,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC029, ECDH,RSA,AES128,SHA256,,,,), + CS_ENTRY(0xC02A, TLS,ECDH,RSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC02A, ECDH,RSA,AES256,SHA384,,,,), + CS_ENTRY(0xC02D, TLS,ECDH,ECDSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC02D, ECDH,ECDSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC02E, TLS,ECDH,ECDSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC02E, ECDH,ECDSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0xC031, TLS,ECDH,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC031, ECDH,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC032, TLS,ECDH,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC032, ECDH,RSA,AES256,GCM,SHA384,,,), +#endif +#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) + CS_ENTRY(0x0001, TLS,RSA,WITH,NULL,MD5,,,), + CS_ENTRY(0x0001, NULL,MD5,,,,,,), + CS_ENTRY(0x0002, TLS,RSA,WITH,NULL,SHA,,,), + CS_ENTRY(0x0002, NULL,SHA,,,,,,), + CS_ENTRY(0x002C, TLS,PSK,WITH,NULL,SHA,,,), + CS_ENTRY(0x002C, PSK,NULL,SHA,,,,,), + CS_ENTRY(0x002D, TLS,DHE,PSK,WITH,NULL,SHA,,), + CS_ENTRY(0x002D, DHE,PSK,NULL,SHA,,,,), + CS_ENTRY(0x002E, TLS,RSA,PSK,WITH,NULL,SHA,,), + CS_ENTRY(0x002E, RSA,PSK,NULL,SHA,,,,), + CS_ENTRY(0x0033, TLS,DHE,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0033, DHE,RSA,AES128,SHA,,,,), + CS_ENTRY(0x0039, TLS,DHE,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0039, DHE,RSA,AES256,SHA,,,,), + CS_ENTRY(0x003B, TLS,RSA,WITH,NULL,SHA256,,,), + CS_ENTRY(0x003B, NULL,SHA256,,,,,,), + CS_ENTRY(0x0067, TLS,DHE,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x0067, DHE,RSA,AES128,SHA256,,,,), + CS_ENTRY(0x006B, TLS,DHE,RSA,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x006B, DHE,RSA,AES256,SHA256,,,,), + CS_ENTRY(0x008C, TLS,PSK,WITH,AES,128,CBC,SHA,), + CS_ENTRY(0x008C, PSK,AES128,CBC,SHA,,,,), + CS_ENTRY(0x008D, TLS,PSK,WITH,AES,256,CBC,SHA,), + CS_ENTRY(0x008D, PSK,AES256,CBC,SHA,,,,), + CS_ENTRY(0x0090, TLS,DHE,PSK,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0090, DHE,PSK,AES128,CBC,SHA,,,), + CS_ENTRY(0x0091, TLS,DHE,PSK,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0091, DHE,PSK,AES256,CBC,SHA,,,), + CS_ENTRY(0x0094, TLS,RSA,PSK,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0094, RSA,PSK,AES128,CBC,SHA,,,), + CS_ENTRY(0x0095, TLS,RSA,PSK,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0095, RSA,PSK,AES256,CBC,SHA,,,), + CS_ENTRY(0x009E, TLS,DHE,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x009E, DHE,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0x009F, TLS,DHE,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x009F, DHE,RSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00A8, TLS,PSK,WITH,AES,128,GCM,SHA256,), + CS_ENTRY(0x00A8, PSK,AES128,GCM,SHA256,,,,), + CS_ENTRY(0x00A9, TLS,PSK,WITH,AES,256,GCM,SHA384,), + CS_ENTRY(0x00A9, PSK,AES256,GCM,SHA384,,,,), + CS_ENTRY(0x00AA, TLS,DHE,PSK,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00AA, DHE,PSK,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00AB, TLS,DHE,PSK,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00AB, DHE,PSK,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00AC, TLS,RSA,PSK,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00AC, RSA,PSK,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00AD, TLS,RSA,PSK,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00AD, RSA,PSK,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00AE, TLS,PSK,WITH,AES,128,CBC,SHA256,), + CS_ENTRY(0x00AE, PSK,AES128,CBC,SHA256,,,,), + CS_ENTRY(0x00AF, TLS,PSK,WITH,AES,256,CBC,SHA384,), + CS_ENTRY(0x00AF, PSK,AES256,CBC,SHA384,,,,), + CS_ENTRY(0x00B0, TLS,PSK,WITH,NULL,SHA256,,,), + CS_ENTRY(0x00B0, PSK,NULL,SHA256,,,,,), + CS_ENTRY(0x00B1, TLS,PSK,WITH,NULL,SHA384,,,), + CS_ENTRY(0x00B1, PSK,NULL,SHA384,,,,,), + CS_ENTRY(0x00B2, TLS,DHE,PSK,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x00B2, DHE,PSK,AES128,CBC,SHA256,,,), + CS_ENTRY(0x00B3, TLS,DHE,PSK,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0x00B3, DHE,PSK,AES256,CBC,SHA384,,,), + CS_ENTRY(0x00B4, TLS,DHE,PSK,WITH,NULL,SHA256,,), + CS_ENTRY(0x00B4, DHE,PSK,NULL,SHA256,,,,), + CS_ENTRY(0x00B5, TLS,DHE,PSK,WITH,NULL,SHA384,,), + CS_ENTRY(0x00B5, DHE,PSK,NULL,SHA384,,,,), + CS_ENTRY(0x00B6, TLS,RSA,PSK,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x00B6, RSA,PSK,AES128,CBC,SHA256,,,), + CS_ENTRY(0x00B7, TLS,RSA,PSK,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0x00B7, RSA,PSK,AES256,CBC,SHA384,,,), + CS_ENTRY(0x00B8, TLS,RSA,PSK,WITH,NULL,SHA256,,), + CS_ENTRY(0x00B8, RSA,PSK,NULL,SHA256,,,,), + CS_ENTRY(0x00B9, TLS,RSA,PSK,WITH,NULL,SHA384,,), + CS_ENTRY(0x00B9, RSA,PSK,NULL,SHA384,,,,), + CS_ENTRY(0xC001, TLS,ECDH,ECDSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC001, ECDH,ECDSA,NULL,SHA,,,,), + CS_ENTRY(0xC006, TLS,ECDHE,ECDSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC006, ECDHE,ECDSA,NULL,SHA,,,,), + CS_ENTRY(0xC00B, TLS,ECDH,RSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC00B, ECDH,RSA,NULL,SHA,,,,), + CS_ENTRY(0xC010, TLS,ECDHE,RSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC010, ECDHE,RSA,NULL,SHA,,,,), + CS_ENTRY(0xC035, TLS,ECDHE,PSK,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC035, ECDHE,PSK,AES128,CBC,SHA,,,), + CS_ENTRY(0xC036, TLS,ECDHE,PSK,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC036, ECDHE,PSK,AES256,CBC,SHA,,,), + CS_ENTRY(0xCCAB, TLS,PSK,WITH,CHACHA20,POLY1305,SHA256,,), + CS_ENTRY(0xCCAB, PSK,CHACHA20,POLY1305,,,,,), +#endif +#if defined(USE_SECTRANSP) || defined(USE_BEARSSL) + CS_ENTRY(0x000A, TLS,RSA,WITH,3DES,EDE,CBC,SHA,), + CS_ENTRY(0x000A, DES,CBC3,SHA,,,,,), + CS_ENTRY(0xC003, TLS,ECDH,ECDSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC003, ECDH,ECDSA,DES,CBC3,SHA,,,), + CS_ENTRY(0xC008, TLS,ECDHE,ECDSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC008, ECDHE,ECDSA,DES,CBC3,SHA,,,), + CS_ENTRY(0xC00D, TLS,ECDH,RSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC00D, ECDH,RSA,DES,CBC3,SHA,,,), + CS_ENTRY(0xC012, TLS,ECDHE,RSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC012, ECDHE,RSA,DES,CBC3,SHA,,,), +#endif +#if defined(USE_MBEDTLS) || defined(USE_BEARSSL) + CS_ENTRY(0xC09C, TLS,RSA,WITH,AES,128,CCM,,), + CS_ENTRY(0xC09C, AES128,CCM,,,,,,), + CS_ENTRY(0xC09D, TLS,RSA,WITH,AES,256,CCM,,), + CS_ENTRY(0xC09D, AES256,CCM,,,,,,), + CS_ENTRY(0xC0A0, TLS,RSA,WITH,AES,128,CCM,8,), + CS_ENTRY(0xC0A0, AES128,CCM8,,,,,,), + CS_ENTRY(0xC0A1, TLS,RSA,WITH,AES,256,CCM,8,), + CS_ENTRY(0xC0A1, AES256,CCM8,,,,,,), + CS_ENTRY(0xC0AC, TLS,ECDHE,ECDSA,WITH,AES,128,CCM,), + CS_ENTRY(0xC0AC, ECDHE,ECDSA,AES128,CCM,,,,), + CS_ENTRY(0xC0AD, TLS,ECDHE,ECDSA,WITH,AES,256,CCM,), + CS_ENTRY(0xC0AD, ECDHE,ECDSA,AES256,CCM,,,,), + CS_ENTRY(0xC0AE, TLS,ECDHE,ECDSA,WITH,AES,128,CCM,8), + CS_ENTRY(0xC0AE, ECDHE,ECDSA,AES128,CCM8,,,,), + CS_ENTRY(0xC0AF, TLS,ECDHE,ECDSA,WITH,AES,256,CCM,8), + CS_ENTRY(0xC0AF, ECDHE,ECDSA,AES256,CCM8,,,,), +#endif +#if defined(USE_SECTRANSP) + /* entries marked bc are backward compatible aliases for old OpenSSL names */ + CS_ENTRY(0x0003, TLS,RSA,EXPORT,WITH,RC4,40,MD5,), + CS_ENTRY(0x0003, EXP,RC4,MD5,,,,,), + CS_ENTRY(0x0004, TLS,RSA,WITH,RC4,128,MD5,,), + CS_ENTRY(0x0004, RC4,MD5,,,,,,), + CS_ENTRY(0x0005, TLS,RSA,WITH,RC4,128,SHA,,), + CS_ENTRY(0x0005, RC4,SHA,,,,,,), + CS_ENTRY(0x0006, TLS,RSA,EXPORT,WITH,RC2,CBC,40,MD5), + CS_ENTRY(0x0006, EXP,RC2,CBC,MD5,,,,), + CS_ENTRY(0x0007, TLS,RSA,WITH,IDEA,CBC,SHA,,), + CS_ENTRY(0x0007, IDEA,CBC,SHA,,,,,), + CS_ENTRY(0x0008, TLS,RSA,EXPORT,WITH,DES40,CBC,SHA,), + CS_ENTRY(0x0008, EXP,DES,CBC,SHA,,,,), + CS_ENTRY(0x0009, TLS,RSA,WITH,DES,CBC,SHA,,), + CS_ENTRY(0x0009, DES,CBC,SHA,,,,,), + CS_ENTRY(0x000B, TLS,DH,DSS,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x000B, EXP,DH,DSS,DES,CBC,SHA,,), + CS_ENTRY(0x000C, TLS,DH,DSS,WITH,DES,CBC,SHA,), + CS_ENTRY(0x000C, DH,DSS,DES,CBC,SHA,,,), + CS_ENTRY(0x000D, TLS,DH,DSS,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x000D, DH,DSS,DES,CBC3,SHA,,,), + CS_ENTRY(0x000E, TLS,DH,RSA,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x000E, EXP,DH,RSA,DES,CBC,SHA,,), + CS_ENTRY(0x000F, TLS,DH,RSA,WITH,DES,CBC,SHA,), + CS_ENTRY(0x000F, DH,RSA,DES,CBC,SHA,,,), + CS_ENTRY(0x0010, TLS,DH,RSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x0010, DH,RSA,DES,CBC3,SHA,,,), + CS_ENTRY(0x0011, TLS,DHE,DSS,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x0011, EXP,DHE,DSS,DES,CBC,SHA,,), + CS_ENTRY(0x0011, EXP,EDH,DSS,DES,CBC,SHA,,), /* bc */ + CS_ENTRY(0x0012, TLS,DHE,DSS,WITH,DES,CBC,SHA,), + CS_ENTRY(0x0012, DHE,DSS,DES,CBC,SHA,,,), + CS_ENTRY(0x0012, EDH,DSS,DES,CBC,SHA,,,), /* bc */ + CS_ENTRY(0x0013, TLS,DHE,DSS,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x0013, DHE,DSS,DES,CBC3,SHA,,,), + CS_ENTRY(0x0013, EDH,DSS,DES,CBC3,SHA,,,), /* bc */ + CS_ENTRY(0x0014, TLS,DHE,RSA,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x0014, EXP,DHE,RSA,DES,CBC,SHA,,), + CS_ENTRY(0x0014, EXP,EDH,RSA,DES,CBC,SHA,,), /* bc */ + CS_ENTRY(0x0015, TLS,DHE,RSA,WITH,DES,CBC,SHA,), + CS_ENTRY(0x0015, DHE,RSA,DES,CBC,SHA,,,), + CS_ENTRY(0x0015, EDH,RSA,DES,CBC,SHA,,,), /* bc */ + CS_ENTRY(0x0016, TLS,DHE,RSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x0016, DHE,RSA,DES,CBC3,SHA,,,), + CS_ENTRY(0x0016, EDH,RSA,DES,CBC3,SHA,,,), /* bc */ + CS_ENTRY(0x0017, TLS,DH,anon,EXPORT,WITH,RC4,40,MD5), + CS_ENTRY(0x0017, EXP,ADH,RC4,MD5,,,,), + CS_ENTRY(0x0018, TLS,DH,anon,WITH,RC4,128,MD5,), + CS_ENTRY(0x0018, ADH,RC4,MD5,,,,,), + CS_ENTRY(0x0019, TLS,DH,anon,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x0019, EXP,ADH,DES,CBC,SHA,,,), + CS_ENTRY(0x001A, TLS,DH,anon,WITH,DES,CBC,SHA,), + CS_ENTRY(0x001A, ADH,DES,CBC,SHA,,,,), + CS_ENTRY(0x001B, TLS,DH,anon,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x001B, ADH,DES,CBC3,SHA,,,,), + CS_ENTRY(0x0030, TLS,DH,DSS,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0030, DH,DSS,AES128,SHA,,,,), + CS_ENTRY(0x0031, TLS,DH,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0031, DH,RSA,AES128,SHA,,,,), + CS_ENTRY(0x0032, TLS,DHE,DSS,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0032, DHE,DSS,AES128,SHA,,,,), + CS_ENTRY(0x0034, TLS,DH,anon,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0034, ADH,AES128,SHA,,,,,), + CS_ENTRY(0x0036, TLS,DH,DSS,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0036, DH,DSS,AES256,SHA,,,,), + CS_ENTRY(0x0037, TLS,DH,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0037, DH,RSA,AES256,SHA,,,,), + CS_ENTRY(0x0038, TLS,DHE,DSS,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0038, DHE,DSS,AES256,SHA,,,,), + CS_ENTRY(0x003A, TLS,DH,anon,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x003A, ADH,AES256,SHA,,,,,), + CS_ENTRY(0x003E, TLS,DH,DSS,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x003E, DH,DSS,AES128,SHA256,,,,), + CS_ENTRY(0x003F, TLS,DH,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x003F, DH,RSA,AES128,SHA256,,,,), + CS_ENTRY(0x0040, TLS,DHE,DSS,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x0040, DHE,DSS,AES128,SHA256,,,,), + CS_ENTRY(0x0068, TLS,DH,DSS,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x0068, DH,DSS,AES256,SHA256,,,,), + CS_ENTRY(0x0069, TLS,DH,RSA,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x0069, DH,RSA,AES256,SHA256,,,,), + CS_ENTRY(0x006A, TLS,DHE,DSS,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x006A, DHE,DSS,AES256,SHA256,,,,), + CS_ENTRY(0x006C, TLS,DH,anon,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x006C, ADH,AES128,SHA256,,,,,), + CS_ENTRY(0x006D, TLS,DH,anon,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x006D, ADH,AES256,SHA256,,,,,), + CS_ENTRY(0x008A, TLS,PSK,WITH,RC4,128,SHA,,), + CS_ENTRY(0x008A, PSK,RC4,SHA,,,,,), + CS_ENTRY(0x008B, TLS,PSK,WITH,3DES,EDE,CBC,SHA,), + CS_ENTRY(0x008B, PSK,3DES,EDE,CBC,SHA,,,), + CS_ENTRY(0x008E, TLS,DHE,PSK,WITH,RC4,128,SHA,), + CS_ENTRY(0x008E, DHE,PSK,RC4,SHA,,,,), + CS_ENTRY(0x008F, TLS,DHE,PSK,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x008F, DHE,PSK,3DES,EDE,CBC,SHA,,), + CS_ENTRY(0x0092, TLS,RSA,PSK,WITH,RC4,128,SHA,), + CS_ENTRY(0x0092, RSA,PSK,RC4,SHA,,,,), + CS_ENTRY(0x0093, TLS,RSA,PSK,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x0093, RSA,PSK,3DES,EDE,CBC,SHA,,), + CS_ENTRY(0x00A0, TLS,DH,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00A0, DH,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00A1, TLS,DH,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00A1, DH,RSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00A2, TLS,DHE,DSS,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00A2, DHE,DSS,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00A3, TLS,DHE,DSS,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00A3, DHE,DSS,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00A4, TLS,DH,DSS,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00A4, DH,DSS,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00A5, TLS,DH,DSS,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00A5, DH,DSS,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00A6, TLS,DH,anon,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00A6, ADH,AES128,GCM,SHA256,,,,), + CS_ENTRY(0x00A7, TLS,DH,anon,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00A7, ADH,AES256,GCM,SHA384,,,,), + CS_ENTRY(0xC002, TLS,ECDH,ECDSA,WITH,RC4,128,SHA,), + CS_ENTRY(0xC002, ECDH,ECDSA,RC4,SHA,,,,), + CS_ENTRY(0xC007, TLS,ECDHE,ECDSA,WITH,RC4,128,SHA,), + CS_ENTRY(0xC007, ECDHE,ECDSA,RC4,SHA,,,,), + CS_ENTRY(0xC00C, TLS,ECDH,RSA,WITH,RC4,128,SHA,), + CS_ENTRY(0xC00C, ECDH,RSA,RC4,SHA,,,,), + CS_ENTRY(0xC011, TLS,ECDHE,RSA,WITH,RC4,128,SHA,), + CS_ENTRY(0xC011, ECDHE,RSA,RC4,SHA,,,,), + CS_ENTRY(0xC015, TLS,ECDH,anon,WITH,NULL,SHA,,), + CS_ENTRY(0xC015, AECDH,NULL,SHA,,,,,), + CS_ENTRY(0xC016, TLS,ECDH,anon,WITH,RC4,128,SHA,), + CS_ENTRY(0xC016, AECDH,RC4,SHA,,,,,), + CS_ENTRY(0xC017, TLS,ECDH,anon,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC017, AECDH,DES,CBC3,SHA,,,,), + CS_ENTRY(0xC018, TLS,ECDH,anon,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC018, AECDH,AES128,SHA,,,,,), + CS_ENTRY(0xC019, TLS,ECDH,anon,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC019, AECDH,AES256,SHA,,,,,), +#endif +#if defined(USE_MBEDTLS) + /* entries marked ns are "non-standard", they are not in OpenSSL */ + CS_ENTRY(0x0041, TLS,RSA,WITH,CAMELLIA,128,CBC,SHA,), + CS_ENTRY(0x0041, CAMELLIA128,SHA,,,,,,), + CS_ENTRY(0x0045, TLS,DHE,RSA,WITH,CAMELLIA,128,CBC,SHA), + CS_ENTRY(0x0045, DHE,RSA,CAMELLIA128,SHA,,,,), + CS_ENTRY(0x0084, TLS,RSA,WITH,CAMELLIA,256,CBC,SHA,), + CS_ENTRY(0x0084, CAMELLIA256,SHA,,,,,,), + CS_ENTRY(0x0088, TLS,DHE,RSA,WITH,CAMELLIA,256,CBC,SHA), + CS_ENTRY(0x0088, DHE,RSA,CAMELLIA256,SHA,,,,), + CS_ENTRY(0x00BA, TLS,RSA,WITH,CAMELLIA,128,CBC,SHA256,), + CS_ENTRY(0x00BA, CAMELLIA128,SHA256,,,,,,), + CS_ENTRY(0x00BE, TLS,DHE,RSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0x00BE, DHE,RSA,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0x00C0, TLS,RSA,WITH,CAMELLIA,256,CBC,SHA256,), + CS_ENTRY(0x00C0, CAMELLIA256,SHA256,,,,,,), + CS_ENTRY(0x00C4, TLS,DHE,RSA,WITH,CAMELLIA,256,CBC,SHA256), + CS_ENTRY(0x00C4, DHE,RSA,CAMELLIA256,SHA256,,,,), + CS_ENTRY(0xC037, TLS,ECDHE,PSK,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC037, ECDHE,PSK,AES128,CBC,SHA256,,,), + CS_ENTRY(0xC038, TLS,ECDHE,PSK,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC038, ECDHE,PSK,AES256,CBC,SHA384,,,), + CS_ENTRY(0xC039, TLS,ECDHE,PSK,WITH,NULL,SHA,,), + CS_ENTRY(0xC039, ECDHE,PSK,NULL,SHA,,,,), + CS_ENTRY(0xC03A, TLS,ECDHE,PSK,WITH,NULL,SHA256,,), + CS_ENTRY(0xC03A, ECDHE,PSK,NULL,SHA256,,,,), + CS_ENTRY(0xC03B, TLS,ECDHE,PSK,WITH,NULL,SHA384,,), + CS_ENTRY(0xC03B, ECDHE,PSK,NULL,SHA384,,,,), + CS_ENTRY(0xC03C, TLS,RSA,WITH,ARIA,128,CBC,SHA256,), + CS_ENTRY(0xC03C, ARIA128,SHA256,,,,,,), /* ns */ + CS_ENTRY(0xC03D, TLS,RSA,WITH,ARIA,256,CBC,SHA384,), + CS_ENTRY(0xC03D, ARIA256,SHA384,,,,,,), /* ns */ + CS_ENTRY(0xC044, TLS,DHE,RSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC044, DHE,RSA,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC045, TLS,DHE,RSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC045, DHE,RSA,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC048, TLS,ECDHE,ECDSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC048, ECDHE,ECDSA,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC049, TLS,ECDHE,ECDSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC049, ECDHE,ECDSA,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC04A, TLS,ECDH,ECDSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC04A, ECDH,ECDSA,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC04B, TLS,ECDH,ECDSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC04B, ECDH,ECDSA,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC04C, TLS,ECDHE,RSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC04C, ECDHE,ARIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC04D, TLS,ECDHE,RSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC04D, ECDHE,ARIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC04E, TLS,ECDH,RSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC04E, ECDH,ARIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC04F, TLS,ECDH,RSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC04F, ECDH,ARIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC050, TLS,RSA,WITH,ARIA,128,GCM,SHA256,), + CS_ENTRY(0xC050, ARIA128,GCM,SHA256,,,,,), + CS_ENTRY(0xC051, TLS,RSA,WITH,ARIA,256,GCM,SHA384,), + CS_ENTRY(0xC051, ARIA256,GCM,SHA384,,,,,), + CS_ENTRY(0xC052, TLS,DHE,RSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC052, DHE,RSA,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC053, TLS,DHE,RSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC053, DHE,RSA,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC05C, TLS,ECDHE,ECDSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC05C, ECDHE,ECDSA,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC05D, TLS,ECDHE,ECDSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC05D, ECDHE,ECDSA,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC05E, TLS,ECDH,ECDSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC05E, ECDH,ECDSA,ARIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC05F, TLS,ECDH,ECDSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC05F, ECDH,ECDSA,ARIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC060, TLS,ECDHE,RSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC060, ECDHE,ARIA128,GCM,SHA256,,,,), + CS_ENTRY(0xC061, TLS,ECDHE,RSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC061, ECDHE,ARIA256,GCM,SHA384,,,,), + CS_ENTRY(0xC062, TLS,ECDH,RSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC062, ECDH,ARIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC063, TLS,ECDH,RSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC063, ECDH,ARIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC064, TLS,PSK,WITH,ARIA,128,CBC,SHA256,), + CS_ENTRY(0xC064, PSK,ARIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC065, TLS,PSK,WITH,ARIA,256,CBC,SHA384,), + CS_ENTRY(0xC065, PSK,ARIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC066, TLS,DHE,PSK,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC066, DHE,PSK,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC067, TLS,DHE,PSK,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC067, DHE,PSK,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC068, TLS,RSA,PSK,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC068, RSA,PSK,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC069, TLS,RSA,PSK,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC069, RSA,PSK,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC06A, TLS,PSK,WITH,ARIA,128,GCM,SHA256,), + CS_ENTRY(0xC06A, PSK,ARIA128,GCM,SHA256,,,,), + CS_ENTRY(0xC06B, TLS,PSK,WITH,ARIA,256,GCM,SHA384,), + CS_ENTRY(0xC06B, PSK,ARIA256,GCM,SHA384,,,,), + CS_ENTRY(0xC06C, TLS,DHE,PSK,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC06C, DHE,PSK,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC06D, TLS,DHE,PSK,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC06D, DHE,PSK,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC06E, TLS,RSA,PSK,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC06E, RSA,PSK,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC06F, TLS,RSA,PSK,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC06F, RSA,PSK,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC070, TLS,ECDHE,PSK,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC070, ECDHE,PSK,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC071, TLS,ECDHE,PSK,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC071, ECDHE,PSK,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC072, TLS,ECDHE,ECDSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC072, ECDHE,ECDSA,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC073, TLS,ECDHE,ECDSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC073, ECDHE,ECDSA,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC074, TLS,ECDH,ECDSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC074, ECDH,ECDSA,CAMELLIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC075, TLS,ECDH,ECDSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC075, ECDH,ECDSA,CAMELLIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC076, TLS,ECDHE,RSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC076, ECDHE,RSA,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC077, TLS,ECDHE,RSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC077, ECDHE,RSA,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC078, TLS,ECDH,RSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC078, ECDH,CAMELLIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC079, TLS,ECDH,RSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC079, ECDH,CAMELLIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC07A, TLS,RSA,WITH,CAMELLIA,128,GCM,SHA256,), + CS_ENTRY(0xC07A, CAMELLIA128,GCM,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC07B, TLS,RSA,WITH,CAMELLIA,256,GCM,SHA384,), + CS_ENTRY(0xC07B, CAMELLIA256,GCM,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC07C, TLS,DHE,RSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC07C, DHE,RSA,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC07D, TLS,DHE,RSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC07D, DHE,RSA,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC086, TLS,ECDHE,ECDSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC086, ECDHE,ECDSA,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC087, TLS,ECDHE,ECDSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC087, ECDHE,ECDSA,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC088, TLS,ECDH,ECDSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC088, ECDH,ECDSA,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC089, TLS,ECDH,ECDSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC089, ECDH,ECDSA,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC08A, TLS,ECDHE,RSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC08A, ECDHE,CAMELLIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC08B, TLS,ECDHE,RSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC08B, ECDHE,CAMELLIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC08C, TLS,ECDH,RSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC08C, ECDH,CAMELLIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC08D, TLS,ECDH,RSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC08D, ECDH,CAMELLIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC08E, TLS,PSK,WITH,CAMELLIA,128,GCM,SHA256,), + CS_ENTRY(0xC08E, PSK,CAMELLIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC08F, TLS,PSK,WITH,CAMELLIA,256,GCM,SHA384,), + CS_ENTRY(0xC08F, PSK,CAMELLIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC090, TLS,DHE,PSK,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC090, DHE,PSK,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC091, TLS,DHE,PSK,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC091, DHE,PSK,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC092, TLS,RSA,PSK,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC092, RSA,PSK,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC093, TLS,RSA,PSK,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC093, RSA,PSK,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC094, TLS,PSK,WITH,CAMELLIA,128,CBC,SHA256,), + CS_ENTRY(0xC094, PSK,CAMELLIA128,SHA256,,,,,), + CS_ENTRY(0xC095, TLS,PSK,WITH,CAMELLIA,256,CBC,SHA384,), + CS_ENTRY(0xC095, PSK,CAMELLIA256,SHA384,,,,,), + CS_ENTRY(0xC096, TLS,DHE,PSK,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC096, DHE,PSK,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC097, TLS,DHE,PSK,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC097, DHE,PSK,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC098, TLS,RSA,PSK,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC098, RSA,PSK,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC099, TLS,RSA,PSK,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC099, RSA,PSK,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC09A, TLS,ECDHE,PSK,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC09A, ECDHE,PSK,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC09B, TLS,ECDHE,PSK,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC09B, ECDHE,PSK,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC09E, TLS,DHE,RSA,WITH,AES,128,CCM,), + CS_ENTRY(0xC09E, DHE,RSA,AES128,CCM,,,,), + CS_ENTRY(0xC09F, TLS,DHE,RSA,WITH,AES,256,CCM,), + CS_ENTRY(0xC09F, DHE,RSA,AES256,CCM,,,,), + CS_ENTRY(0xC0A2, TLS,DHE,RSA,WITH,AES,128,CCM,8), + CS_ENTRY(0xC0A2, DHE,RSA,AES128,CCM8,,,,), + CS_ENTRY(0xC0A3, TLS,DHE,RSA,WITH,AES,256,CCM,8), + CS_ENTRY(0xC0A3, DHE,RSA,AES256,CCM8,,,,), + CS_ENTRY(0xC0A4, TLS,PSK,WITH,AES,128,CCM,,), + CS_ENTRY(0xC0A4, PSK,AES128,CCM,,,,,), + CS_ENTRY(0xC0A5, TLS,PSK,WITH,AES,256,CCM,,), + CS_ENTRY(0xC0A5, PSK,AES256,CCM,,,,,), + CS_ENTRY(0xC0A6, TLS,DHE,PSK,WITH,AES,128,CCM,), + CS_ENTRY(0xC0A6, DHE,PSK,AES128,CCM,,,,), + CS_ENTRY(0xC0A7, TLS,DHE,PSK,WITH,AES,256,CCM,), + CS_ENTRY(0xC0A7, DHE,PSK,AES256,CCM,,,,), + CS_ENTRY(0xC0A8, TLS,PSK,WITH,AES,128,CCM,8,), + CS_ENTRY(0xC0A8, PSK,AES128,CCM8,,,,,), + CS_ENTRY(0xC0A9, TLS,PSK,WITH,AES,256,CCM,8,), + CS_ENTRY(0xC0A9, PSK,AES256,CCM8,,,,,), + CS_ENTRY(0xC0AA, TLS,PSK,DHE,WITH,AES,128,CCM,8), + CS_ENTRY(0xC0AA, DHE,PSK,AES128,CCM8,,,,), + CS_ENTRY(0xC0AB, TLS,PSK,DHE,WITH,AES,256,CCM,8), + CS_ENTRY(0xC0AB, DHE,PSK,AES256,CCM8,,,,), + CS_ENTRY(0xCCAA, TLS,DHE,RSA,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAA, DHE,RSA,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCAC, TLS,ECDHE,PSK,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAC, ECDHE,PSK,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCAD, TLS,DHE,PSK,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAD, DHE,PSK,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCAE, TLS,RSA,PSK,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAE, RSA,PSK,CHACHA20,POLY1305,,,,), +#endif +}; +#define CS_LIST_LEN (sizeof(cs_list) / sizeof(cs_list[0])) + +static int cs_str_to_zip(const char *cs_str, size_t cs_len, + uint8_t zip[6]) +{ + uint8_t indexes[8] = {0}; + const char *entry, *cur; + const char *nxt = cs_str; + const char *end = cs_str + cs_len; + char separator = '-'; + int idx, i = 0; + size_t len; + + /* split the cipher string by '-' or '_' */ + if(strncasecompare(cs_str, "TLS", 3)) + separator = '_'; + + do { + if(i == 8) + return -1; + + /* determine the length of the part */ + cur = nxt; + for(; nxt < end && *nxt != '\0' && *nxt != separator; nxt++); + len = nxt - cur; + + /* lookup index for the part (skip empty string at 0) */ + for(idx = 1, entry = cs_txt + 1; idx < CS_TXT_LEN; idx++) { + size_t elen = strlen(entry); + if(elen == len && strncasecompare(entry, cur, len)) + break; + entry += elen + 1; + } + if(idx == CS_TXT_LEN) + return -1; + + indexes[i++] = (uint8_t) idx; + } while(nxt < end && *(nxt++) != '\0'); + + /* zip the 8 indexes into 48 bits */ + zip[0] = (uint8_t) (indexes[0] << 2 | (indexes[1] & 0x3F) >> 4); + zip[1] = (uint8_t) (indexes[1] << 4 | (indexes[2] & 0x3F) >> 2); + zip[2] = (uint8_t) (indexes[2] << 6 | (indexes[3] & 0x3F)); + zip[3] = (uint8_t) (indexes[4] << 2 | (indexes[5] & 0x3F) >> 4); + zip[4] = (uint8_t) (indexes[5] << 4 | (indexes[6] & 0x3F) >> 2); + zip[5] = (uint8_t) (indexes[6] << 6 | (indexes[7] & 0x3F)); + + return 0; +} + +static int cs_zip_to_str(const uint8_t zip[6], + char *buf, size_t buf_size) +{ + uint8_t indexes[8] = {0}; + const char *entry; + char separator = '-'; + int idx, i, r; + size_t len = 0; + + /* unzip the 8 indexes */ + indexes[0] = zip[0] >> 2; + indexes[1] = ((zip[0] << 4) & 0x3F) | zip[1] >> 4; + indexes[2] = ((zip[1] << 2) & 0x3F) | zip[2] >> 6; + indexes[3] = ((zip[2] << 0) & 0x3F); + indexes[4] = zip[3] >> 2; + indexes[5] = ((zip[3] << 4) & 0x3F) | zip[4] >> 4; + indexes[6] = ((zip[4] << 2) & 0x3F) | zip[5] >> 6; + indexes[7] = ((zip[5] << 0) & 0x3F); + + if(indexes[0] == CS_TXT_IDX_TLS) + separator = '_'; + + for(i = 0; i < 8 && indexes[i] != 0 && len < buf_size; i++) { + if(indexes[i] >= CS_TXT_LEN) + return -1; + + /* lookup the part string for the index (skip empty string at 0) */ + for(idx = 1, entry = cs_txt + 1; idx < indexes[i]; idx++) { + size_t elen = strlen(entry); + entry += elen + 1; + } + + /* append the part string to the buffer */ + if(i > 0) + r = msnprintf(&buf[len], buf_size - len, "%c%s", separator, entry); + else + r = msnprintf(&buf[len], buf_size - len, "%s", entry); + + if(r < 0) + return -1; + len += r; + } + + return 0; +} + +uint16_t Curl_cipher_suite_lookup_id(const char *cs_str, size_t cs_len) +{ + size_t i; + uint8_t zip[6]; + + if(cs_len > 0 && cs_str_to_zip(cs_str, cs_len, zip) == 0) { + for(i = 0; i < CS_LIST_LEN; i++) { + if(memcmp(cs_list[i].zip, zip, sizeof(zip)) == 0) + return cs_list[i].id; + } + } + + return 0; +} + +static bool cs_is_separator(char c) +{ + switch(c) { + case ' ': + case '\t': + case ':': + case ',': + case ';': + return TRUE; + default:; + } + return FALSE; +} + +uint16_t Curl_cipher_suite_walk_str(const char **str, const char **end) +{ + /* move string pointer to first non-separator or end of string */ + for(; cs_is_separator(*str[0]); (*str)++); + + /* move end pointer to next separator or end of string */ + for(*end = *str; *end[0] != '\0' && !cs_is_separator(*end[0]); (*end)++); + + return Curl_cipher_suite_lookup_id(*str, *end - *str); +} + +int Curl_cipher_suite_get_str(uint16_t id, char *buf, size_t buf_size, + bool prefer_rfc) +{ + size_t i, j = CS_LIST_LEN; + int r = -1; + + for(i = 0; i < CS_LIST_LEN; i++) { + if(cs_list[i].id != id) + continue; + if((cs_list[i].zip[0] >> 2 != CS_TXT_IDX_TLS) == !prefer_rfc) { + j = i; + break; + } + if(j == CS_LIST_LEN) + j = i; + } + + if(j < CS_LIST_LEN) + r = cs_zip_to_str(cs_list[j].zip, buf, buf_size); + + if(r < 0) + msnprintf(buf, buf_size, "TLS_UNKNOWN_0x%04x", id); + + return r; +} + +#endif /* defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \ + defined(USE_BEARSSL) || defined(USE_RUSTLS) */ diff --git a/deps/curl/lib/vtls/cipher_suite.h b/deps/curl/lib/vtls/cipher_suite.h new file mode 100644 index 00000000000..6d980103a57 --- /dev/null +++ b/deps/curl/lib/vtls/cipher_suite.h @@ -0,0 +1,48 @@ +#ifndef HEADER_CURL_CIPHER_SUITE_H +#define HEADER_CURL_CIPHER_SUITE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Jan Venekamp, + * + * 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.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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \ + defined(USE_BEARSSL) || defined(USE_RUSTLS) +#include + +/* Lookup IANA id for cipher suite string, returns 0 if not recognized */ +uint16_t Curl_cipher_suite_lookup_id(const char *cs_str, size_t cs_len); + +/* Walk over cipher suite string, update str and end pointers to next + cipher suite in string, returns IANA id of that suite if recognized */ +uint16_t Curl_cipher_suite_walk_str(const char **str, const char **end); + +/* Copy openssl or RFC name for cipher suite in supplied buffer. + Caller is responsible to supply sufficiently large buffer (size + of 64 should suffice), excess bytes are silently truncated. */ +int Curl_cipher_suite_get_str(uint16_t id, char *buf, size_t buf_size, + bool prefer_rfc); + +#endif /* defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \ + defined(USE_BEARSSL) || defined(USE_RUSTLS) */ +#endif /* HEADER_CURL_CIPHER_SUITE_H */ diff --git a/deps/curl/lib/vtls/gtls.c b/deps/curl/lib/vtls/gtls.c index e48346903c7..af4f0c349f2 100644 --- a/deps/curl/lib/vtls/gtls.c +++ b/deps/curl/lib/vtls/gtls.c @@ -26,7 +26,7 @@ * Source file for all GnuTLS-specific code for the TLS/SSL layer. No code * but vtls.c should ever call or use these functions. * - * Note: don't use the GnuTLS' *_t variable type names in this source code, + * Note: do not use the GnuTLS' *_t variable type names in this source code, * since they were not present in 1.0.X. */ @@ -43,12 +43,14 @@ #include "urldata.h" #include "sendf.h" #include "inet_pton.h" +#include "keylog.h" #include "gtls.h" #include "vtls.h" #include "vtls_int.h" #include "vauth/vauth.h" #include "parsedate.h" #include "connect.h" /* for the connect timeout */ +#include "progress.h" #include "select.h" #include "strcase.h" #include "warnless.h" @@ -59,6 +61,16 @@ /* The last #include file should be: */ #include "memdebug.h" +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +#define QUIC_PRIORITY \ + "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \ + "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \ + "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \ + "%DISABLE_TLS13_COMPAT_MODE" + /* Enable GnuTLS debugging by defining GTLSDEBUG */ /*#define GTLSDEBUG */ @@ -77,24 +89,27 @@ static bool gtls_inited = FALSE; # include struct gtls_ssl_backend_data { - struct gtls_instance gtls; + struct gtls_ctx gtls; }; static ssize_t gtls_push(void *s, const void *buf, size_t blen) { struct Curl_cfilter *cf = s; struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nwritten; CURLcode result; DEBUGASSERT(data); - nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result); + nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, FALSE, &result); + CURL_TRC_CF(data, cf, "gtls_push(len=%zu) -> %zd, err=%d", + blen, nwritten, result); + backend->gtls.io_result = result; if(nwritten < 0) { - struct gtls_ssl_backend_data *backend = - (struct gtls_ssl_backend_data *)connssl->backend; gnutls_transport_set_errno(backend->gtls.session, - (CURLE_AGAIN == result)? EAGAIN : EINVAL); + (CURLE_AGAIN == result) ? EAGAIN : EINVAL); nwritten = -1; } return nwritten; @@ -104,19 +119,33 @@ static ssize_t gtls_pull(void *s, void *buf, size_t blen) { struct Curl_cfilter *cf = s; struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result; DEBUGASSERT(data); + if(!backend->gtls.shared_creds->trust_setup) { + result = Curl_gtls_client_trust_setup(cf, data, &backend->gtls); + if(result) { + gnutls_transport_set_errno(backend->gtls.session, EINVAL); + backend->gtls.io_result = result; + return -1; + } + } + nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); + CURL_TRC_CF(data, cf, "glts_pull(len=%zu) -> %zd, err=%d", + blen, nread, result); + backend->gtls.io_result = result; if(nread < 0) { - struct gtls_ssl_backend_data *backend = - (struct gtls_ssl_backend_data *)connssl->backend; gnutls_transport_set_errno(backend->gtls.session, - (CURLE_AGAIN == result)? EAGAIN : EINVAL); + (CURLE_AGAIN == result) ? EAGAIN : EINVAL); nread = -1; } + else if(nread == 0) + connssl->peer_closed = TRUE; return nread; } @@ -131,7 +160,7 @@ static int gtls_init(void) { int ret = 1; if(!gtls_inited) { - ret = gnutls_global_init()?0:1; + ret = gnutls_global_init() ? 0 : 1; #ifdef GTLSDEBUG gnutls_global_set_log_function(tls_log_func); gnutls_global_set_log_level(2); @@ -165,7 +194,7 @@ static void showtime(struct Curl_easy *data, sizeof(str), " %s: %s, %02d %s %4d %02d:%02d:%02d GMT", text, - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + Curl_wkday[tm->tm_wday ? tm->tm_wday-1 : 6], tm->tm_mday, Curl_month[tm->tm_mon], tm->tm_year + 1900, @@ -223,6 +252,7 @@ static CURLcode handshake(struct Curl_cfilter *cf, DEBUGASSERT(backend); session = backend->gtls.session; + connssl->connecting_state = ssl_connect_2; for(;;) { timediff_t timeout_ms; @@ -237,18 +267,17 @@ static CURLcode handshake(struct Curl_cfilter *cf, return CURLE_OPERATION_TIMEDOUT; } - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { + /* if ssl is expecting something, check if it is available. */ + if(connssl->io_need) { int what; - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + curl_socket_t writefd = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? + sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = (connssl->io_need & CURL_SSL_IO_NEED_RECV) ? + sockfd : CURL_SOCKET_BAD; what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking?0: - timeout_ms?timeout_ms:1000); + nonblocking ? 0 : + timeout_ms ? timeout_ms : 1000); if(what < 0) { /* fatal error */ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); @@ -256,7 +285,7 @@ static CURLcode handshake(struct Curl_cfilter *cf, } else if(0 == what) { if(nonblocking) - return CURLE_OK; + return CURLE_AGAIN; else if(timeout_ms) { /* timeout */ failf(data, "SSL connection timeout at %ld", (long)timeout_ms); @@ -266,19 +295,29 @@ static CURLcode handshake(struct Curl_cfilter *cf, /* socket is readable or writable */ } + connssl->io_need = CURL_SSL_IO_NEED_NONE; + backend->gtls.io_result = CURLE_OK; rc = gnutls_handshake(session); + if(!backend->gtls.shared_creds->trust_setup) { + /* After having send off the ClientHello, we prepare the trust + * store to verify the coming certificate from the server */ + CURLcode result = Curl_gtls_client_trust_setup(cf, data, &backend->gtls); + if(result) + return result; + } + if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) { - connssl->connecting_state = - gnutls_record_get_direction(session)? - ssl_connect_2_writing:ssl_connect_2_reading; + connssl->io_need = + gnutls_record_get_direction(session) ? + CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV; continue; } else if((rc < 0) && !gnutls_error_is_fatal(rc)) { const char *strerr = NULL; if(rc == GNUTLS_E_WARNING_ALERT_RECEIVED) { - int alert = gnutls_alert_get(session); + gnutls_alert_description_t alert = gnutls_alert_get(session); strerr = gnutls_alert_get_name(alert); } @@ -288,18 +327,21 @@ static CURLcode handshake(struct Curl_cfilter *cf, infof(data, "gnutls_handshake() warning: %s", strerr); continue; } + else if((rc < 0) && backend->gtls.io_result) { + return backend->gtls.io_result; + } else if(rc < 0) { const char *strerr = NULL; if(rc == GNUTLS_E_FATAL_ALERT_RECEIVED) { - int alert = gnutls_alert_get(session); + gnutls_alert_description_t alert = gnutls_alert_get(session); strerr = gnutls_alert_get_name(alert); } if(!strerr) strerr = gnutls_strerror(rc); - failf(data, "gnutls_handshake() failed: %s", strerr); + failf(data, "GnuTLS, handshake failed: %s", strerr); return CURLE_SSL_CONNECT_ERROR; } @@ -309,7 +351,7 @@ static CURLcode handshake(struct Curl_cfilter *cf, } } -static gnutls_x509_crt_fmt_t do_file_type(const char *type) +static gnutls_x509_crt_fmt_t gnutls_do_file_type(const char *type) { if(!type || !type[0]) return GNUTLS_X509_FMT_PEM; @@ -327,10 +369,11 @@ static gnutls_x509_crt_fmt_t do_file_type(const char *type) #define GNUTLS_SRP "+SRP" static CURLcode -set_ssl_version_min_max(struct Curl_easy *data, - struct ssl_primary_config *conn_config, - const char **prioritylist, - const char *tls13support) +gnutls_set_ssl_version_min_max(struct Curl_easy *data, + struct ssl_peer *peer, + struct ssl_primary_config *conn_config, + const char **prioritylist, + const char *tls13support) { long ssl_version = conn_config->version; long ssl_version_max = conn_config->version_max; @@ -340,8 +383,19 @@ set_ssl_version_min_max(struct Curl_easy *data, ssl_version = CURL_SSLVERSION_TLSv1_0; if(ssl_version_max == CURL_SSLVERSION_MAX_NONE) ssl_version_max = CURL_SSLVERSION_MAX_DEFAULT; + + if(peer->transport == TRNSPRT_QUIC) { + if((ssl_version_max != CURL_SSLVERSION_MAX_DEFAULT) && + (ssl_version_max < CURL_SSLVERSION_MAX_TLSv1_3)) { + failf(data, "QUIC needs at least TLS version 1.3"); + return CURLE_SSL_CONNECT_ERROR; + } + *prioritylist = QUIC_PRIORITY; + return CURLE_OK; + } + if(!tls13support) { - /* If the running GnuTLS doesn't support TLS 1.3, we must not specify a + /* If the running GnuTLS does not support TLS 1.3, we must not specify a prioritylist involving that since it will make GnuTLS return an en error back at us */ if((ssl_version_max == CURL_SSLVERSION_MAX_TLSv1_3) || @@ -399,21 +453,360 @@ set_ssl_version_min_max(struct Curl_easy *data, return CURLE_SSL_CONNECT_ERROR; } -CURLcode gtls_client_init(struct Curl_easy *data, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, - const char *hostname, - struct gtls_instance *gtls, - long *pverifyresult) +CURLcode Curl_gtls_shared_creds_create(struct Curl_easy *data, + struct gtls_shared_creds **pcreds) +{ + struct gtls_shared_creds *shared; + int rc; + + *pcreds = NULL; + shared = calloc(1, sizeof(*shared)); + if(!shared) + return CURLE_OUT_OF_MEMORY; + + rc = gnutls_certificate_allocate_credentials(&shared->creds); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc)); + free(shared); + return CURLE_SSL_CONNECT_ERROR; + } + + shared->refcount = 1; + shared->time = Curl_now(); + *pcreds = shared; + return CURLE_OK; +} + +CURLcode Curl_gtls_shared_creds_up_ref(struct gtls_shared_creds *creds) +{ + DEBUGASSERT(creds); + if(creds->refcount < SIZE_T_MAX) { + ++creds->refcount; + return CURLE_OK; + } + return CURLE_BAD_FUNCTION_ARGUMENT; +} + +void Curl_gtls_shared_creds_free(struct gtls_shared_creds **pcreds) +{ + struct gtls_shared_creds *shared = *pcreds; + *pcreds = NULL; + if(shared) { + --shared->refcount; + if(!shared->refcount) { + gnutls_certificate_free_credentials(shared->creds); + free(shared->CAfile); + free(shared); + } + } +} + +static CURLcode gtls_populate_creds(struct Curl_cfilter *cf, + struct Curl_easy *data, + gnutls_certificate_credentials_t creds) +{ + struct ssl_primary_config *config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + int rc; + + if(config->verifypeer) { + bool imported_native_ca = FALSE; + + if(ssl_config->native_ca_store) { + rc = gnutls_certificate_set_x509_system_trust(creds); + if(rc < 0) + infof(data, "error reading native ca store (%s), continuing anyway", + gnutls_strerror(rc)); + else { + infof(data, "found %d certificates in native ca store", rc); + if(rc > 0) + imported_native_ca = TRUE; + } + } + + if(config->CAfile) { + /* set the trusted CA cert bundle file */ + gnutls_certificate_set_verify_flags(creds, + GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); + + rc = gnutls_certificate_set_x509_trust_file(creds, + config->CAfile, + GNUTLS_X509_FMT_PEM); + if(rc < 0) { + infof(data, "error reading ca cert file %s (%s)%s", + config->CAfile, gnutls_strerror(rc), + (imported_native_ca ? ", continuing anyway" : "")); + if(!imported_native_ca) { + ssl_config->certverifyresult = rc; + return CURLE_SSL_CACERT_BADFILE; + } + } + else + infof(data, "found %d certificates in %s", rc, config->CAfile); + } + + if(config->CApath) { + /* set the trusted CA cert directory */ + rc = gnutls_certificate_set_x509_trust_dir(creds, config->CApath, + GNUTLS_X509_FMT_PEM); + if(rc < 0) { + infof(data, "error reading ca cert file %s (%s)%s", + config->CApath, gnutls_strerror(rc), + (imported_native_ca ? ", continuing anyway" : "")); + if(!imported_native_ca) { + ssl_config->certverifyresult = rc; + return CURLE_SSL_CACERT_BADFILE; + } + } + else + infof(data, "found %d certificates in %s", rc, config->CApath); + } + } + + if(config->CRLfile) { + /* set the CRL list file */ + rc = gnutls_certificate_set_x509_crl_file(creds, config->CRLfile, + GNUTLS_X509_FMT_PEM); + if(rc < 0) { + failf(data, "error reading crl file %s (%s)", + config->CRLfile, gnutls_strerror(rc)); + return CURLE_SSL_CRL_BADFILE; + } + else + infof(data, "found %d CRL in %s", rc, config->CRLfile); + } + + return CURLE_OK; +} + +/* key to use at `multi->proto_hash` */ +#define MPROTO_GTLS_X509_KEY "tls:gtls:x509:share" + +static bool gtls_shared_creds_expired(const struct Curl_easy *data, + const struct gtls_shared_creds *sc) +{ + const struct ssl_general_config *cfg = &data->set.general_ssl; + struct curltime now = Curl_now(); + timediff_t elapsed_ms = Curl_timediff(now, sc->time); + timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + + if(timeout_ms < 0) + return FALSE; + + return elapsed_ms >= timeout_ms; +} + +static bool gtls_shared_creds_different(struct Curl_cfilter *cf, + const struct gtls_shared_creds *sc) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!sc->CAfile || !conn_config->CAfile) + return sc->CAfile != conn_config->CAfile; + + return strcmp(sc->CAfile, conn_config->CAfile); +} + +static struct gtls_shared_creds* +gtls_get_cached_creds(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct gtls_shared_creds *shared_creds; + + if(data->multi) { + shared_creds = Curl_hash_pick(&data->multi->proto_hash, + (void *)MPROTO_GTLS_X509_KEY, + sizeof(MPROTO_GTLS_X509_KEY)-1); + if(shared_creds && shared_creds->creds && + !gtls_shared_creds_expired(data, shared_creds) && + !gtls_shared_creds_different(cf, shared_creds)) { + return shared_creds; + } + } + return NULL; +} + +static void gtls_shared_creds_hash_free(void *key, size_t key_len, void *p) +{ + struct gtls_shared_creds *sc = p; + DEBUGASSERT(key_len == (sizeof(MPROTO_GTLS_X509_KEY)-1)); + DEBUGASSERT(!memcmp(MPROTO_GTLS_X509_KEY, key, key_len)); + (void)key; + (void)key_len; + Curl_gtls_shared_creds_free(&sc); /* down reference */ +} + +static void gtls_set_cached_creds(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct gtls_shared_creds *sc) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + + DEBUGASSERT(sc); + DEBUGASSERT(sc->creds); + DEBUGASSERT(!sc->CAfile); + DEBUGASSERT(sc->refcount == 1); + if(!data->multi) + return; + + if(conn_config->CAfile) { + sc->CAfile = strdup(conn_config->CAfile); + if(!sc->CAfile) + return; + } + + if(Curl_gtls_shared_creds_up_ref(sc)) + return; + + if(!Curl_hash_add2(&data->multi->proto_hash, + (void *)MPROTO_GTLS_X509_KEY, + sizeof(MPROTO_GTLS_X509_KEY)-1, + sc, gtls_shared_creds_hash_free)) { + Curl_gtls_shared_creds_free(&sc); /* down reference again */ + return; + } +} + +CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct gtls_ctx *gtls) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct gtls_shared_creds *cached_creds = NULL; + bool cache_criteria_met; + CURLcode result; + int rc; + + + /* Consider the X509 store cacheable if it comes exclusively from a CAfile, + or no source is provided and we are falling back to OpenSSL's built-in + default. */ + cache_criteria_met = (data->set.general_ssl.ca_cache_timeout != 0) && + conn_config->verifypeer && + !conn_config->CApath && + !conn_config->ca_info_blob && + !ssl_config->primary.CRLfile && + !ssl_config->native_ca_store && + !conn_config->clientcert; /* GnuTLS adds client cert to its credentials! */ + + if(cache_criteria_met) + cached_creds = gtls_get_cached_creds(cf, data); + + if(cached_creds && !Curl_gtls_shared_creds_up_ref(cached_creds)) { + CURL_TRC_CF(data, cf, "using shared trust anchors and CRLs"); + Curl_gtls_shared_creds_free(>ls->shared_creds); + gtls->shared_creds = cached_creds; + rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_CERTIFICATE, + gtls->shared_creds->creds); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); + return CURLE_SSL_CONNECT_ERROR; + } + } + else { + CURL_TRC_CF(data, cf, "loading trust anchors and CRLs"); + result = gtls_populate_creds(cf, data, gtls->shared_creds->creds); + if(result) + return result; + gtls->shared_creds->trust_setup = TRUE; + if(cache_criteria_met) + gtls_set_cached_creds(cf, data, gtls->shared_creds); + } + return CURLE_OK; +} + +static void gtls_sessionid_free(void *sessionid, size_t idsize) +{ + (void)idsize; + free(sessionid); +} + +CURLcode Curl_gtls_update_session_id(struct Curl_cfilter *cf, + struct Curl_easy *data, + gnutls_session_t session, + struct ssl_peer *peer, + const char *alpn) +{ + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + void *connect_sessionid; + size_t connect_idsize = 0; + CURLcode result = CURLE_OK; + + if(!ssl_config->primary.cache_session) + return CURLE_OK; + + /* we always unconditionally get the session id here, as even if we + already got it from the cache and asked to use it in the connection, it + might've been rejected and then a new one is in use now and we need to + detect that. */ + + /* get the session ID data size */ + gnutls_session_get_data(session, NULL, &connect_idsize); + if(!connect_idsize) /* gnutls does this for some version combinations */ + return CURLE_OK; + + connect_sessionid = malloc(connect_idsize); /* get a buffer for it */ + if(!connect_sessionid) + return CURLE_OUT_OF_MEMORY; + + /* extract session ID to the allocated buffer */ + gnutls_session_get_data(session, connect_sessionid, &connect_idsize); + + CURL_TRC_CF(data, cf, "get session id (len=%zu, alpn=%s) and store in cache", + connect_idsize, alpn ? alpn : "-"); + Curl_ssl_sessionid_lock(data); + /* store this session id, takes ownership */ + result = Curl_ssl_set_sessionid(cf, data, peer, alpn, + connect_sessionid, connect_idsize, + gtls_sessionid_free); + Curl_ssl_sessionid_unlock(data); + return result; +} + +static CURLcode cf_gtls_update_session_id(struct Curl_cfilter *cf, + struct Curl_easy *data, + gnutls_session_t session) +{ + struct ssl_connect_data *connssl = cf->ctx; + return Curl_gtls_update_session_id(cf, data, session, &connssl->peer, + connssl->alpn_negotiated); +} + +static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, + const gnutls_datum_t *msg) { + struct Curl_cfilter *cf = gnutls_session_get_ptr(session); + + (void)msg; + (void)incoming; + if(when) { /* after message has been processed */ + struct Curl_easy *data = CF_DATA_CURRENT(cf); + if(data) { + CURL_TRC_CF(data, cf, "handshake: %s message type %d", + incoming ? "incoming" : "outgoing", htype); + switch(htype) { + case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: { + cf_gtls_update_session_id(cf, data, session); + break; + } + default: + break; + } + } + } + return 0; +} + +static CURLcode gtls_client_init(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + struct gtls_ctx *gtls) +{ + struct ssl_primary_config *config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); unsigned int init_flags; int rc; bool sni = TRUE; /* default is SNI enabled */ -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif const char *prioritylist; const char *err = NULL; const char *tls13support; @@ -422,8 +815,6 @@ CURLcode gtls_client_init(struct Curl_easy *data, if(!gtls_inited) gtls_init(); - *pverifyresult = 0; - if(config->version == CURL_SSLVERSION_SSLv2) { failf(data, "GnuTLS does not support SSLv2"); return CURLE_SSL_CONNECT_ERROR; @@ -431,12 +822,10 @@ CURLcode gtls_client_init(struct Curl_easy *data, else if(config->version == CURL_SSLVERSION_SSLv3) sni = FALSE; /* SSLv3 has no SNI */ - /* allocate a cred struct */ - rc = gnutls_certificate_allocate_credentials(>ls->cred); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; - } + /* allocate a shared creds struct */ + result = Curl_gtls_shared_creds_create(data, >ls->shared_creds); + if(result) + return result; #ifdef USE_GNUTLS_SRP if(config->username && Curl_auth_allowed_to_host(data)) { @@ -460,64 +849,7 @@ CURLcode gtls_client_init(struct Curl_easy *data, } #endif - if(config->CAfile) { - /* set the trusted CA cert bundle file */ - gnutls_certificate_set_verify_flags(gtls->cred, - GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); - - rc = gnutls_certificate_set_x509_trust_file(gtls->cred, - config->CAfile, - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - infof(data, "error reading ca cert file %s (%s)", - config->CAfile, gnutls_strerror(rc)); - if(config->verifypeer) { - *pverifyresult = rc; - return CURLE_SSL_CACERT_BADFILE; - } - } - else - infof(data, "found %d certificates in %s", rc, config->CAfile); - } - - if(config->CApath) { - /* set the trusted CA cert directory */ - rc = gnutls_certificate_set_x509_trust_dir(gtls->cred, - config->CApath, - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - infof(data, "error reading ca cert file %s (%s)", - config->CApath, gnutls_strerror(rc)); - if(config->verifypeer) { - *pverifyresult = rc; - return CURLE_SSL_CACERT_BADFILE; - } - } - else - infof(data, "found %d certificates in %s", rc, config->CApath); - } - -#ifdef CURL_CA_FALLBACK - /* use system ca certificate store as fallback */ - if(config->verifypeer && !(config->CAfile || config->CApath)) { - /* this ignores errors on purpose */ - gnutls_certificate_set_x509_system_trust(gtls->cred); - } -#endif - - if(config->CRLfile) { - /* set the CRL list file */ - rc = gnutls_certificate_set_x509_crl_file(gtls->cred, - config->CRLfile, - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - failf(data, "error reading crl file %s (%s)", - config->CRLfile, gnutls_strerror(rc)); - return CURLE_SSL_CRL_BADFILE; - } - else - infof(data, "found %d CRL in %s", rc, config->CRLfile); - } + ssl_config->certverifyresult = 0; /* Initialize TLS session as a client */ init_flags = GNUTLS_CLIENT; @@ -526,9 +858,20 @@ CURLcode gtls_client_init(struct Curl_easy *data, init_flags |= GNUTLS_FORCE_CLIENT_CERT; #endif -#if defined(GNUTLS_NO_TICKETS) - /* Disable TLS session tickets */ - init_flags |= GNUTLS_NO_TICKETS; +#if defined(GNUTLS_NO_TICKETS_TLS12) + init_flags |= GNUTLS_NO_TICKETS_TLS12; +#elif defined(GNUTLS_NO_TICKETS) + /* Disable TLS session tickets for non 1.3 connections */ + if((config->version != CURL_SSLVERSION_TLSv1_3) && + (config->version != CURL_SSLVERSION_DEFAULT)) + init_flags |= GNUTLS_NO_TICKETS; +#endif + +#if defined(GNUTLS_NO_STATUS_REQUEST) + if(!config->verifystatus) + /* Disable the "status_request" TLS extension, enabled by default since + GnuTLS 3.8.0. */ + init_flags |= GNUTLS_NO_STATUS_REQUEST; #endif rc = gnutls_init(>ls->session, init_flags); @@ -537,15 +880,9 @@ CURLcode gtls_client_init(struct Curl_easy *data, return CURLE_SSL_CONNECT_ERROR; } - if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) && -#ifdef ENABLE_IPV6 - (0 == Curl_inet_pton(AF_INET6, hostname, &addr)) && -#endif - sni) { - size_t snilen; - char *snihost = Curl_ssl_snihost(data, hostname, &snilen); - if(!snihost || gnutls_server_name_set(gtls->session, GNUTLS_NAME_DNS, - snihost, snilen) < 0) { + if(sni && peer->sni) { + if(gnutls_server_name_set(gtls->session, GNUTLS_NAME_DNS, + peer->sni, strlen(peer->sni)) < 0) { failf(data, "Failed to set SNI"); return CURLE_SSL_CONNECT_ERROR; } @@ -560,7 +897,7 @@ CURLcode gtls_client_init(struct Curl_easy *data, tls13support = gnutls_check_version("3.6.5"); /* Ensure +SRP comes at the *end* of all relevant strings so that it can be - * removed if a run-time error indicates that SRP is not supported by this + * removed if a runtime error indicates that SRP is not supported by this * GnuTLS version */ if(config->version == CURL_SSLVERSION_SSLv2 || @@ -577,7 +914,8 @@ CURLcode gtls_client_init(struct Curl_easy *data, } /* At this point we know we have a supported TLS version, so set it */ - result = set_ssl_version_min_max(data, config, &prioritylist, tls13support); + result = gnutls_set_ssl_version_min_max(data, peer, + config, &prioritylist, tls13support); if(result) return result; @@ -585,13 +923,9 @@ CURLcode gtls_client_init(struct Curl_easy *data, /* Only add SRP to the cipher list if SRP is requested. Otherwise * GnuTLS will disable TLS 1.3 support. */ if(config->username) { - size_t len = strlen(prioritylist); - - char *prioritysrp = malloc(len + sizeof(GNUTLS_SRP) + 1); + char *prioritysrp = aprintf("%s:" GNUTLS_SRP, prioritylist); if(!prioritysrp) return CURLE_OUT_OF_MEMORY; - strcpy(prioritysrp, prioritylist); - strcpy(prioritysrp + len, ":" GNUTLS_SRP); rc = gnutls_priority_set_direct(gtls->session, prioritysrp, &err); free(prioritysrp); @@ -614,17 +948,34 @@ CURLcode gtls_client_init(struct Curl_easy *data, } if(config->clientcert) { - if(ssl_config->key_passwd) { + if(!gtls->shared_creds->trust_setup) { + result = Curl_gtls_client_trust_setup(cf, data, gtls); + if(result) + return result; + } + if(ssl_config->cert_type && strcasecompare(ssl_config->cert_type, "P12")) { + rc = gnutls_certificate_set_x509_simple_pkcs12_file( + gtls->shared_creds->creds, config->clientcert, GNUTLS_X509_FMT_DER, + ssl_config->key_passwd ? ssl_config->key_passwd : ""); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, + "error reading X.509 potentially-encrypted key or certificate " + "file: %s", + gnutls_strerror(rc)); + return CURLE_SSL_CONNECT_ERROR; + } + } + else if(ssl_config->key_passwd) { const unsigned int supported_key_encryption_algorithms = GNUTLS_PKCS_USE_PKCS12_3DES | GNUTLS_PKCS_USE_PKCS12_ARCFOUR | GNUTLS_PKCS_USE_PKCS12_RC2_40 | GNUTLS_PKCS_USE_PBES2_3DES | GNUTLS_PKCS_USE_PBES2_AES_128 | GNUTLS_PKCS_USE_PBES2_AES_192 | GNUTLS_PKCS_USE_PBES2_AES_256; rc = gnutls_certificate_set_x509_key_file2( - gtls->cred, + gtls->shared_creds->creds, config->clientcert, ssl_config->key ? ssl_config->key : config->clientcert, - do_file_type(ssl_config->cert_type), + gnutls_do_file_type(ssl_config->cert_type), ssl_config->key_passwd, supported_key_encryption_algorithms); if(rc != GNUTLS_E_SUCCESS) { @@ -636,10 +987,10 @@ CURLcode gtls_client_init(struct Curl_easy *data, } else { if(gnutls_certificate_set_x509_key_file( - gtls->cred, + gtls->shared_creds->creds, config->clientcert, ssl_config->key ? ssl_config->key : config->clientcert, - do_file_type(ssl_config->cert_type) ) != + gnutls_do_file_type(ssl_config->cert_type) ) != GNUTLS_E_SUCCESS) { failf(data, "error reading X.509 key or certificate file"); return CURLE_SSL_CONNECT_ERROR; @@ -661,7 +1012,7 @@ CURLcode gtls_client_init(struct Curl_easy *data, #endif { rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_CERTIFICATE, - gtls->cred); + gtls->shared_creds->creds); if(rc != GNUTLS_E_SUCCESS) { failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; @@ -680,66 +1031,185 @@ CURLcode gtls_client_init(struct Curl_easy *data, return CURLE_OK; } -static CURLcode -gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +static int keylog_callback(gnutls_session_t session, const char *label, + const gnutls_datum_t *secret) +{ + gnutls_datum_t crandom; + gnutls_datum_t srandom; + + gnutls_session_get_random(session, &crandom, &srandom); + if(crandom.size != 32) { + return -1; + } + + Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); + return 0; +} + +CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const unsigned char *alpn, size_t alpn_len, + struct ssl_connect_data *connssl, + Curl_gtls_ctx_setup_cb *cb_setup, + void *cb_user_data, + void *ssl_user_data) { - struct ssl_connect_data *connssl = cf->ctx; - struct gtls_ssl_backend_data *backend = - (struct gtls_ssl_backend_data *)connssl->backend; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - long * const pverifyresult = &ssl_config->certverifyresult; + gnutls_datum_t gtls_alpns[5]; + size_t gtls_alpns_count = 0; CURLcode result; - DEBUGASSERT(backend); - - if(connssl->state == ssl_connection_complete) - /* to make us tolerant against being called more than once for the - same connection */ - return CURLE_OK; + DEBUGASSERT(gctx); - result = gtls_client_init(data, conn_config, ssl_config, - connssl->hostname, - &backend->gtls, pverifyresult); + result = gtls_client_init(cf, data, peer, gctx); if(result) return result; - if(connssl->alpn) { - struct alpn_proto_buf proto; - gnutls_datum_t alpn[ALPN_ENTRIES_MAX]; - size_t i; + gnutls_session_set_ptr(gctx->session, ssl_user_data); - for(i = 0; i < connssl->alpn->count; ++i) { - alpn[i].data = (unsigned char *)connssl->alpn->entries[i]; - alpn[i].size = (unsigned)strlen(connssl->alpn->entries[i]); - } - if(gnutls_alpn_set_protocols(backend->gtls.session, alpn, - (unsigned)connssl->alpn->count, 0)) { - failf(data, "failed setting ALPN"); - return CURLE_SSL_CONNECT_ERROR; - } - Curl_alpn_to_proto_str(&proto, connssl->alpn); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + if(cb_setup) { + result = cb_setup(cf, data, cb_user_data); + if(result) + return result; + } + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { + gnutls_session_set_keylog_function(gctx->session, keylog_callback); } /* This might be a reconnect, so we check for a session ID in the cache to speed up things */ - if(conn_config->sessionid) { + if(conn_config->cache_session) { void *ssl_sessionid; size_t ssl_idsize; - + char *session_alpn; Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, &ssl_idsize)) { + if(!Curl_ssl_getsessionid(cf, data, peer, + &ssl_sessionid, &ssl_idsize, &session_alpn)) { /* we got a session id, use it! */ - gnutls_session_set_data(backend->gtls.session, - ssl_sessionid, ssl_idsize); + int rc; - /* Informational message */ - infof(data, "SSL reusing session ID"); + rc = gnutls_session_set_data(gctx->session, ssl_sessionid, ssl_idsize); + if(rc < 0) + infof(data, "SSL failed to set session ID"); + else { + infof(data, "SSL reusing session ID (size=%zu, alpn=%s)", + ssl_idsize, session_alpn ? session_alpn : "-"); +#ifdef DEBUGBUILD + if((ssl_config->earlydata || !!getenv("CURL_USE_EARLYDATA")) && +#else + if(ssl_config->earlydata && +#endif + !cf->conn->connect_only && connssl && + (gnutls_protocol_get_version(gctx->session) == GNUTLS_TLS1_3) && + Curl_alpn_contains_proto(connssl->alpn, session_alpn)) { + connssl->earlydata_max = + gnutls_record_get_max_early_data_size(gctx->session); + if((!connssl->earlydata_max || + connssl->earlydata_max == 0xFFFFFFFFUL)) { + /* Seems to be GnuTLS way to signal no EarlyData in session */ + CURL_TRC_CF(data, cf, "TLS session does not allow earlydata"); + } + else { + CURL_TRC_CF(data, cf, "TLS session allows %zu earlydata bytes, " + "reusing ALPN '%s'", + connssl->earlydata_max, session_alpn); + connssl->earlydata_state = ssl_earlydata_use; + connssl->state = ssl_connection_deferred; + result = Curl_alpn_set_negotiated(cf, data, connssl, + (const unsigned char *)session_alpn, + session_alpn ? strlen(session_alpn) : 0); + if(result) + return result; + /* We only try the ALPN protocol the session used before, + * otherwise we might send early data for the wrong protocol */ + gtls_alpns[0].data = (unsigned char *)session_alpn; + gtls_alpns[0].size = (unsigned)strlen(session_alpn); + gtls_alpns_count = 1; + } + } + } } Curl_ssl_sessionid_unlock(data); } + /* convert the ALPN string from our arguments to a list of strings that + * gnutls wants and will convert internally back to this string for sending + * to the server. nice. */ + if(!gtls_alpns_count && alpn && alpn_len) { + size_t i, alen = alpn_len; + unsigned char *s = (unsigned char *)alpn; + unsigned char slen; + for(i = 0; (i < ARRAYSIZE(gtls_alpns)) && alen; ++i) { + slen = s[0]; + if(slen >= alen) + return CURLE_FAILED_INIT; + gtls_alpns[i].data = s + 1; + gtls_alpns[i].size = slen; + s += slen + 1; + alen -= (size_t)slen + 1; + } + if(alen) /* not all alpn chars used, wrong format or too many */ + return CURLE_FAILED_INIT; + gtls_alpns_count = i; + } + + if(gtls_alpns_count && + gnutls_alpn_set_protocols(gctx->session, + gtls_alpns, (unsigned int)gtls_alpns_count, + GNUTLS_ALPN_MANDATORY)) { + failf(data, "failed setting ALPN"); + return CURLE_SSL_CONNECT_ERROR; + } + + return CURLE_OK; +} + +static CURLcode +gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + struct alpn_proto_buf proto; + CURLcode result; + + DEBUGASSERT(backend); + DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); + + if(connssl->state == ssl_connection_complete) + /* to make us tolerant against being called more than once for the + same connection */ + return CURLE_OK; + + memset(&proto, 0, sizeof(proto)); + if(connssl->alpn) { + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result) { + failf(data, "Error determining ALPN"); + return CURLE_SSL_CONNECT_ERROR; + } + } + + result = Curl_gtls_ctx_init(&backend->gtls, cf, data, &connssl->peer, + proto.data, proto.len, connssl, NULL, NULL, cf); + if(result) + return result; + + if(connssl->alpn && (connssl->state != ssl_connection_deferred)) { + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } + + gnutls_handshake_set_hook_function(backend->gtls.session, + GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, + gtls_handshake_cb); + /* register callback functions and handle to send and receive data. */ gnutls_transport_set_ptr(backend->gtls.session, cf); gnutls_transport_set_push_function(backend->gtls.session, gtls_push); @@ -761,7 +1231,7 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, /* Result is returned to caller */ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - /* if a path wasn't specified, don't pin */ + /* if a path was not specified, do not pin */ if(!pinnedpubkey) return CURLE_OK; @@ -811,8 +1281,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, gnutls_session_t session, struct ssl_primary_config *config, struct ssl_config_data *ssl_config, - const char *hostname, - const char *dispname, + struct ssl_peer *peer, const char *pinned_key) { unsigned int cert_list_size; @@ -824,16 +1293,17 @@ Curl_gtls_verifyserver(struct Curl_easy *data, char certname[65] = ""; /* limited to 64 chars by ASN.1 */ size_t size; time_t certclock; - const char *ptr; int rc; CURLcode result = CURLE_OK; #ifndef CURL_DISABLE_VERBOSE_STRINGS - unsigned int algo; + const char *ptr; + int algo; unsigned int bits; gnutls_protocol_t version = gnutls_protocol_get_version(session); #endif long * const certverifyresult = &ssl_config->certverifyresult; +#ifndef CURL_DISABLE_VERBOSE_STRINGS /* the name of the cipher suite used, e.g. ECDHE_RSA_AES_256_GCM_SHA384. */ ptr = gnutls_cipher_suite_get_name(gnutls_kx_get(session), gnutls_cipher_get(session), @@ -841,6 +1311,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, infof(data, "SSL connection using %s / %s", gnutls_protocol_get_name(version), ptr); +#endif /* This function will return the peer's raw certificate (chain) as sent by the peer. These certificates are in raw format (DER encoded for @@ -868,13 +1339,13 @@ Curl_gtls_verifyserver(struct Curl_easy *data, } #endif } - infof(data, " common name: WARNING couldn't obtain"); + infof(data, " common name: WARNING could not obtain"); } if(data->set.ssl.certinfo && chainp) { unsigned int i; - result = Curl_ssl_init_certinfo(data, cert_list_size); + result = Curl_ssl_init_certinfo(data, (int)cert_list_size); if(result) return result; @@ -882,7 +1353,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, const char *beg = (const char *) chainp[i].data; const char *end = beg + chainp[i].size; - result = Curl_extract_certinfo(data, i, beg, end); + result = Curl_extract_certinfo(data, (int)i, beg, end); if(result) return result; } @@ -908,9 +1379,18 @@ Curl_gtls_verifyserver(struct Curl_easy *data, /* verify_status is a bitmask of gnutls_certificate_status bits */ if(verify_status & GNUTLS_CERT_INVALID) { if(config->verifypeer) { - failf(data, "server certificate verification failed. CAfile: %s " - "CRLfile: %s", config->CAfile ? config->CAfile: - "none", + const char *cause = "certificate error, no details available"; + if(verify_status & GNUTLS_CERT_EXPIRED) + cause = "certificate has expired"; + else if(verify_status & GNUTLS_CERT_SIGNER_NOT_FOUND) + cause = "certificate signer not trusted"; + else if(verify_status & GNUTLS_CERT_INSECURE_ALGORITHM) + cause = "certificate uses insecure algorithm"; + else if(verify_status & GNUTLS_CERT_INVALID_OCSP_STATUS) + cause = "attached OCSP status response is invalid"; + failf(data, "server verification failed: %s. (CAfile: %s " + "CRLfile: %s)", cause, + config->CAfile ? config->CAfile : "none", ssl_config->primary.CRLfile ? ssl_config->primary.CRLfile : "none"); return CURLE_PEER_FAILED_VERIFICATION; @@ -925,104 +1405,97 @@ Curl_gtls_verifyserver(struct Curl_easy *data, infof(data, " server certificate verification SKIPPED"); if(config->verifystatus) { - if(gnutls_ocsp_status_request_is_checked(session, 0) == 0) { - gnutls_datum_t status_request; - gnutls_ocsp_resp_t ocsp_resp; + gnutls_datum_t status_request; + gnutls_ocsp_resp_t ocsp_resp; + gnutls_ocsp_cert_status_t status; + gnutls_x509_crl_reason_t reason; - gnutls_ocsp_cert_status_t status; - gnutls_x509_crl_reason_t reason; + rc = gnutls_ocsp_status_request_get(session, &status_request); - rc = gnutls_ocsp_status_request_get(session, &status_request); + if(rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + failf(data, "No OCSP response received"); + return CURLE_SSL_INVALIDCERTSTATUS; + } - infof(data, " server certificate status verification FAILED"); + if(rc < 0) { + failf(data, "Invalid OCSP response received"); + return CURLE_SSL_INVALIDCERTSTATUS; + } - if(rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { - failf(data, "No OCSP response received"); - return CURLE_SSL_INVALIDCERTSTATUS; - } + gnutls_ocsp_resp_init(&ocsp_resp); - if(rc < 0) { - failf(data, "Invalid OCSP response received"); - return CURLE_SSL_INVALIDCERTSTATUS; - } + rc = gnutls_ocsp_resp_import(ocsp_resp, &status_request); + if(rc < 0) { + failf(data, "Invalid OCSP response received"); + return CURLE_SSL_INVALIDCERTSTATUS; + } - gnutls_ocsp_resp_init(&ocsp_resp); + (void)gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL, + &status, NULL, NULL, NULL, &reason); - rc = gnutls_ocsp_resp_import(ocsp_resp, &status_request); - if(rc < 0) { - failf(data, "Invalid OCSP response received"); - return CURLE_SSL_INVALIDCERTSTATUS; - } + switch(status) { + case GNUTLS_OCSP_CERT_GOOD: + break; - (void)gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL, - &status, NULL, NULL, NULL, &reason); + case GNUTLS_OCSP_CERT_REVOKED: { + const char *crl_reason; - switch(status) { - case GNUTLS_OCSP_CERT_GOOD: + switch(reason) { + default: + case GNUTLS_X509_CRLREASON_UNSPECIFIED: + crl_reason = "unspecified reason"; break; - case GNUTLS_OCSP_CERT_REVOKED: { - const char *crl_reason; - - switch(reason) { - default: - case GNUTLS_X509_CRLREASON_UNSPECIFIED: - crl_reason = "unspecified reason"; - break; - - case GNUTLS_X509_CRLREASON_KEYCOMPROMISE: - crl_reason = "private key compromised"; - break; - - case GNUTLS_X509_CRLREASON_CACOMPROMISE: - crl_reason = "CA compromised"; - break; - - case GNUTLS_X509_CRLREASON_AFFILIATIONCHANGED: - crl_reason = "affiliation has changed"; - break; + case GNUTLS_X509_CRLREASON_KEYCOMPROMISE: + crl_reason = "private key compromised"; + break; - case GNUTLS_X509_CRLREASON_SUPERSEDED: - crl_reason = "certificate superseded"; - break; + case GNUTLS_X509_CRLREASON_CACOMPROMISE: + crl_reason = "CA compromised"; + break; - case GNUTLS_X509_CRLREASON_CESSATIONOFOPERATION: - crl_reason = "operation has ceased"; - break; + case GNUTLS_X509_CRLREASON_AFFILIATIONCHANGED: + crl_reason = "affiliation has changed"; + break; - case GNUTLS_X509_CRLREASON_CERTIFICATEHOLD: - crl_reason = "certificate is on hold"; - break; + case GNUTLS_X509_CRLREASON_SUPERSEDED: + crl_reason = "certificate superseded"; + break; - case GNUTLS_X509_CRLREASON_REMOVEFROMCRL: - crl_reason = "will be removed from delta CRL"; - break; + case GNUTLS_X509_CRLREASON_CESSATIONOFOPERATION: + crl_reason = "operation has ceased"; + break; - case GNUTLS_X509_CRLREASON_PRIVILEGEWITHDRAWN: - crl_reason = "privilege withdrawn"; - break; + case GNUTLS_X509_CRLREASON_CERTIFICATEHOLD: + crl_reason = "certificate is on hold"; + break; - case GNUTLS_X509_CRLREASON_AACOMPROMISE: - crl_reason = "AA compromised"; - break; - } + case GNUTLS_X509_CRLREASON_REMOVEFROMCRL: + crl_reason = "will be removed from delta CRL"; + break; - failf(data, "Server certificate was revoked: %s", crl_reason); + case GNUTLS_X509_CRLREASON_PRIVILEGEWITHDRAWN: + crl_reason = "privilege withdrawn"; break; - } - default: - case GNUTLS_OCSP_CERT_UNKNOWN: - failf(data, "Server certificate status is unknown"); + case GNUTLS_X509_CRLREASON_AACOMPROMISE: + crl_reason = "AA compromised"; break; } - gnutls_ocsp_resp_deinit(ocsp_resp); + failf(data, "Server certificate was revoked: %s", crl_reason); + break; + } - return CURLE_SSL_INVALIDCERTSTATUS; + default: + case GNUTLS_OCSP_CERT_UNKNOWN: + failf(data, "Server certificate status is unknown"); + break; } - else - infof(data, " server certificate status verification OK"); + + gnutls_ocsp_resp_deinit(ocsp_resp); + if(status != GNUTLS_OCSP_CERT_GOOD) + return CURLE_SSL_INVALIDCERTSTATUS; } else infof(data, " server certificate status verification SKIPPED"); @@ -1039,17 +1512,17 @@ Curl_gtls_verifyserver(struct Curl_easy *data, gnutls_x509_crt_init(&x509_issuer); issuerp = load_file(config->issuercert); gnutls_x509_crt_import(x509_issuer, &issuerp, GNUTLS_X509_FMT_PEM); - rc = gnutls_x509_crt_check_issuer(x509_cert, x509_issuer); + rc = (int)gnutls_x509_crt_check_issuer(x509_cert, x509_issuer); gnutls_x509_crt_deinit(x509_issuer); unload_file(issuerp); if(rc <= 0) { failf(data, "server certificate issuer check failed (IssuerCert: %s)", - config->issuercert?config->issuercert:"none"); + config->issuercert ? config->issuercert : "none"); gnutls_x509_crt_deinit(x509_cert); return CURLE_SSL_ISSUER_ERROR; } infof(data, " server certificate issuer check OK (Issuer Cert: %s)", - config->issuercert?config->issuercert:"none"); + config->issuercert ? config->issuercert : "none"); } size = sizeof(certname); @@ -1068,12 +1541,18 @@ Curl_gtls_verifyserver(struct Curl_easy *data, in RFC2818 (HTTPS), which takes into account wildcards, and the subject alternative name PKIX extension. Returns non zero on success, and zero on failure. */ - rc = gnutls_x509_crt_check_hostname(x509_cert, hostname); + + /* This function does not handle trailing dots, so if we have an SNI name + use that and fallback to the hostname only if there is no SNI (like for + IP addresses) */ + rc = (int)gnutls_x509_crt_check_hostname(x509_cert, + peer->sni ? peer->sni : + peer->hostname); #if GNUTLS_VERSION_NUMBER < 0x030306 - /* Before 3.3.6, gnutls_x509_crt_check_hostname() didn't check IP + /* Before 3.3.6, gnutls_x509_crt_check_hostname() did not check IP addresses. */ if(!rc) { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 #define use_addr in6_addr #else #define use_addr in_addr @@ -1081,10 +1560,10 @@ Curl_gtls_verifyserver(struct Curl_easy *data, unsigned char addrbuf[sizeof(struct use_addr)]; size_t addrlen = 0; - if(Curl_inet_pton(AF_INET, hostname, addrbuf) > 0) + if(Curl_inet_pton(AF_INET, peer->hostname, addrbuf) > 0) addrlen = 4; -#ifdef ENABLE_IPV6 - else if(Curl_inet_pton(AF_INET6, hostname, addrbuf) > 0) +#ifdef USE_IPV6 + else if(Curl_inet_pton(AF_INET6, peer->hostname, addrbuf) > 0) addrlen = 16; #endif @@ -1096,7 +1575,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, size_t certaddrlen = sizeof(certaddr); int ret = gnutls_x509_crt_get_subject_alt_name(x509_cert, i, certaddr, &certaddrlen, NULL); - /* If this happens, it wasn't an IP address. */ + /* If this happens, it was not an IP address. */ if(ret == GNUTLS_E_SHORT_MEMORY_BUFFER) continue; if(ret < 0) @@ -1114,13 +1593,13 @@ Curl_gtls_verifyserver(struct Curl_easy *data, if(!rc) { if(config->verifyhost) { failf(data, "SSL: certificate subject name (%s) does not match " - "target host name '%s'", certname, dispname); + "target hostname '%s'", certname, peer->dispname); gnutls_x509_crt_deinit(x509_cert); return CURLE_PEER_FAILED_VERIFICATION; } else infof(data, " common name: %s (does not match '%s')", - certname, dispname); + certname, peer->dispname); } else infof(data, " common name: %s (matched)", certname); @@ -1203,7 +1682,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, /* public key algorithm's parameters */ algo = gnutls_x509_crt_get_pk_algorithm(x509_cert, &bits); infof(data, " certificate public key: %s", - gnutls_pk_algorithm_get_name(algo)); + gnutls_pk_algorithm_get_name((gnutls_pk_algorithm_t)algo)); /* version of the X.509 certificate. */ infof(data, " certificate version: #%d", @@ -1247,70 +1726,89 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - const char *pinned_key = Curl_ssl_cf_is_proxy(cf)? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: +#ifndef CURL_DISABLE_PROXY + const char *pinned_key = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + const char *pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif CURLcode result; result = Curl_gtls_verifyserver(data, session, conn_config, ssl_config, - connssl->hostname, connssl->dispname, - pinned_key); + &connssl->peer, pinned_key); if(result) goto out; - if(connssl->alpn) { - gnutls_datum_t proto; - int rc; + /* Only on TLSv1.2 or lower do we have the session id now. For + * TLSv1.3 we get it via a SESSION_TICKET message that arrives later. */ + if(gnutls_protocol_get_version(session) < GNUTLS_TLS1_3) + result = cf_gtls_update_session_id(cf, data, session); - rc = gnutls_alpn_get_selected_protocol(session, &proto); - if(rc == 0) - Curl_alpn_set_negotiated(cf, data, proto.data, proto.size); - else - Curl_alpn_set_negotiated(cf, data, NULL, 0); - } - - if(ssl_config->primary.sessionid) { - /* we always unconditionally get the session id here, as even if we - already got it from the cache and asked to use it in the connection, it - might've been rejected and then a new one is in use now and we need to - detect that. */ - void *connect_sessionid; - size_t connect_idsize = 0; - - /* get the session ID data size */ - gnutls_session_get_data(session, NULL, &connect_idsize); - connect_sessionid = malloc(connect_idsize); /* get a buffer for it */ - - if(connect_sessionid) { - bool incache; - bool added = FALSE; - void *ssl_sessionid; - - /* extract session ID to the allocated buffer */ - gnutls_session_get_data(session, connect_sessionid, &connect_idsize); - - Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL)); - if(incache) { - /* there was one before in the cache, so instead of risking that the - previous one was rejected, we just kill that and store the new */ - Curl_ssl_delsessionid(data, ssl_sessionid); - } +out: + return result; +} - /* store this session id */ - result = Curl_ssl_addsessionid(cf, data, connect_sessionid, - connect_idsize, &added); - Curl_ssl_sessionid_unlock(data); - if(!added) - free(connect_sessionid); - if(result) { - result = CURLE_OUT_OF_MEMORY; - } - } - else - result = CURLE_OUT_OF_MEMORY; +static CURLcode gtls_set_earlydata(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *buf, size_t blen) +{ + struct ssl_connect_data *connssl = cf->ctx; + ssize_t nwritten = 0; + CURLcode result = CURLE_OK; + + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_use); + DEBUGASSERT(Curl_bufq_is_empty(&connssl->earlydata)); + if(blen) { + if(blen > connssl->earlydata_max) + blen = connssl->earlydata_max; + nwritten = Curl_bufq_write(&connssl->earlydata, buf, blen, &result); + CURL_TRC_CF(data, cf, "gtls_set_earlydata(len=%zu) -> %zd", + blen, nwritten); + if(nwritten < 0) + return result; } + connssl->earlydata_state = ssl_earlydata_sending; + connssl->earlydata_skip = Curl_bufq_len(&connssl->earlydata); + return CURLE_OK; +} + +static CURLcode gtls_send_earlydata(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + CURLcode result = CURLE_OK; + const unsigned char *buf; + size_t blen; + ssize_t n; + + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sending); + backend->gtls.io_result = CURLE_OK; + while(Curl_bufq_peek(&connssl->earlydata, &buf, &blen)) { + n = gnutls_record_send_early_data(backend->gtls.session, buf, blen); + CURL_TRC_CF(data, cf, "gtls_send_earlydata(len=%zu) -> %zd", + blen, n); + if(n < 0) { + if(n == GNUTLS_E_AGAIN) + result = CURLE_AGAIN; + else + result = backend->gtls.io_result ? + backend->gtls.io_result : CURLE_SEND_ERROR; + goto out; + } + else if(!n) { + /* gnutls is buggy, it *SHOULD* return the amount of bytes it took in. + * Instead it returns 0 if everything was written. */ + n = (ssize_t)blen; + } + Curl_bufq_skip(&connssl->earlydata, (size_t)n); + } + /* sent everything there was */ + infof(data, "SSL sending %" FMT_OFF_T " bytes of early data", + connssl->earlydata_skip); out: return result; } @@ -1321,53 +1819,96 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, */ /* We use connssl->connecting_state to keep track of the connection status; there are three states: 'ssl_connect_1' (not started yet or complete), - 'ssl_connect_2_reading' (waiting for data from server), and - 'ssl_connect_2_writing' (waiting to be able to write). + 'ssl_connect_2' (doing handshake with the server), and + 'ssl_connect_3' (verifying and getting stats). */ static CURLcode gtls_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, bool nonblocking, - bool *done) -{ + bool *done) { struct ssl_connect_data *connssl = cf->ctx; - int rc; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; CURLcode result = CURLE_OK; + DEBUGASSERT(backend); + /* Initiate the connection, if not already done */ - if(ssl_connect_1 == connssl->connecting_state) { - rc = gtls_connect_step1(cf, data); - if(rc) { - result = rc; + if(connssl->connecting_state == ssl_connect_1) { + result = gtls_connect_step1(cf, data); + if(result) goto out; - } + connssl->connecting_state = ssl_connect_2; } - rc = handshake(cf, data, TRUE, nonblocking); - if(rc) { - /* handshake() sets its own error message with failf() */ - result = rc; - goto out; + if(connssl->connecting_state == ssl_connect_2) { + if(connssl->earlydata_state == ssl_earlydata_use) { + goto out; + } + else if(connssl->earlydata_state == ssl_earlydata_sending) { + result = gtls_send_earlydata(cf, data); + if(result) + goto out; + connssl->earlydata_state = ssl_earlydata_sent; + if(!Curl_ssl_cf_is_proxy(cf)) + Curl_pgrsEarlyData(data, (curl_off_t)connssl->earlydata_skip); + } + DEBUGASSERT((connssl->earlydata_state == ssl_earlydata_none) || + (connssl->earlydata_state == ssl_earlydata_sent)); + + result = handshake(cf, data, TRUE, nonblocking); + if(result) + goto out; + connssl->connecting_state = ssl_connect_3; } /* Finish connecting once the handshake is done */ - if(ssl_connect_1 == connssl->connecting_state) { - struct gtls_ssl_backend_data *backend = - (struct gtls_ssl_backend_data *)connssl->backend; - gnutls_session_t session; - DEBUGASSERT(backend); - session = backend->gtls.session; - rc = gtls_verifyserver(cf, data, session); - if(rc) { - result = rc; + if(connssl->connecting_state == ssl_connect_3) { + gnutls_datum_t proto; + int rc; + result = gtls_verifyserver(cf, data, backend->gtls.session); + if(result) goto out; - } + connssl->state = ssl_connection_complete; + connssl->connecting_state = ssl_connect_1; + + rc = gnutls_alpn_get_selected_protocol(backend->gtls.session, &proto); + if(rc) { /* No ALPN from server */ + proto.data = NULL; + proto.size = 0; + } + + result = Curl_alpn_set_negotiated(cf, data, connssl, + proto.data, proto.size); + if(result) + goto out; + + if(connssl->earlydata_state == ssl_earlydata_sent) { + if(gnutls_session_get_flags(backend->gtls.session) & + GNUTLS_SFLAGS_EARLY_DATA) { + connssl->earlydata_state = ssl_earlydata_accepted; + infof(data, "Server accepted %zu bytes of TLS early data.", + connssl->earlydata_skip); + } + else { + connssl->earlydata_state = ssl_earlydata_rejected; + if(!Curl_ssl_cf_is_proxy(cf)) + Curl_pgrsEarlyData(data, -(curl_off_t)connssl->earlydata_skip); + infof(data, "Server rejected TLS early data."); + connssl->earlydata_skip = 0; + } + } } out: - *done = ssl_connect_1 == connssl->connecting_state; - + if(result == CURLE_AGAIN) { + *done = FALSE; + return CURLE_OK; + } + *done = ((connssl->connecting_state == ssl_connect_1) || + (connssl->state == ssl_connection_deferred)); return result; } @@ -1375,6 +1916,12 @@ static CURLcode gtls_connect_nonblocking(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) { + struct ssl_connect_data *connssl = cf->ctx; + if(connssl->state == ssl_connection_deferred) { + /* We refuse to be pushed, we are waiting for someone to send/recv. */ + *done = TRUE; + return CURLE_OK; + } return gtls_connect_common(cf, data, TRUE, done); } @@ -1393,6 +1940,26 @@ static CURLcode gtls_connect(struct Curl_cfilter *cf, return CURLE_OK; } +static CURLcode gtls_connect_deferred(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *buf, + size_t blen, + bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + CURLcode result = CURLE_OK; + + DEBUGASSERT(connssl->state == ssl_connection_deferred); + *done = FALSE; + if(connssl->earlydata_state == ssl_earlydata_use) { + result = gtls_set_earlydata(cf, data, buf, blen); + if(result) + return result; + } + + return gtls_connect_common(cf, data, TRUE, done); +} + static bool gtls_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { @@ -1410,140 +1977,178 @@ static bool gtls_data_pending(struct Curl_cfilter *cf, static ssize_t gtls_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *mem, - size_t len, + const void *buf, + size_t blen, CURLcode *curlcode) { struct ssl_connect_data *connssl = cf->ctx; struct gtls_ssl_backend_data *backend = (struct gtls_ssl_backend_data *)connssl->backend; ssize_t rc; + size_t nwritten, total_written = 0; - (void)data; DEBUGASSERT(backend); - rc = gnutls_record_send(backend->gtls.session, mem, len); - if(rc < 0) { - *curlcode = (rc == GNUTLS_E_AGAIN) - ? CURLE_AGAIN - : CURLE_SEND_ERROR; + if(connssl->state == ssl_connection_deferred) { + bool done = FALSE; + *curlcode = gtls_connect_deferred(cf, data, buf, blen, &done); + if(*curlcode) { + rc = -1; + goto out; + } + else if(!done) { + *curlcode = CURLE_AGAIN; + rc = -1; + goto out; + } + DEBUGASSERT(connssl->state == ssl_connection_complete); + } - rc = -1; + if(connssl->earlydata_skip) { + if(connssl->earlydata_skip >= blen) { + connssl->earlydata_skip -= blen; + *curlcode = CURLE_OK; + rc = (ssize_t)blen; + goto out; + } + else { + total_written += connssl->earlydata_skip; + buf = ((const char *)buf) + connssl->earlydata_skip; + blen -= connssl->earlydata_skip; + connssl->earlydata_skip = 0; + } } + while(blen) { + backend->gtls.io_result = CURLE_OK; + rc = gnutls_record_send(backend->gtls.session, buf, blen); + + if(rc < 0) { + if(total_written && (rc == GNUTLS_E_AGAIN)) { + *curlcode = CURLE_OK; + rc = (ssize_t)total_written; + goto out; + } + *curlcode = (rc == GNUTLS_E_AGAIN) ? + CURLE_AGAIN : + (backend->gtls.io_result ? backend->gtls.io_result : CURLE_SEND_ERROR); + + rc = -1; + goto out; + } + nwritten = (size_t)rc; + total_written += nwritten; + DEBUGASSERT(nwritten <= blen); + buf = (char *)buf + nwritten; + blen -= nwritten; + } + rc = total_written; + +out: return rc; } -static void gtls_close(struct Curl_cfilter *cf, - struct Curl_easy *data) +/* + * This function is called to shut down the SSL layer but keep the + * socket open (CCC - Clear Command Channel) + */ +static CURLcode gtls_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { struct ssl_connect_data *connssl = cf->ctx; struct gtls_ssl_backend_data *backend = (struct gtls_ssl_backend_data *)connssl->backend; + char buf[1024]; + CURLcode result = CURLE_OK; + ssize_t nread; + size_t i; - (void) data; DEBUGASSERT(backend); + /* If we have no handshaked connection or already shut down */ + if(!backend->gtls.session || cf->shutdown || + connssl->state != ssl_connection_complete) { + *done = TRUE; + goto out; + } - if(backend->gtls.session) { - char buf[32]; - /* Maybe the server has already sent a close notify alert. - Read it to avoid an RST on the TCP connection. */ - (void)gnutls_record_recv(backend->gtls.session, buf, sizeof(buf)); - gnutls_bye(backend->gtls.session, GNUTLS_SHUT_WR); - gnutls_deinit(backend->gtls.session); - backend->gtls.session = NULL; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + + if(!backend->gtls.sent_shutdown) { + /* do this only once */ + backend->gtls.sent_shutdown = TRUE; + if(send_shutdown) { + int ret = gnutls_bye(backend->gtls.session, GNUTLS_SHUT_RDWR); + if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) { + CURL_TRC_CF(data, cf, "SSL shutdown, gnutls_bye EAGAIN"); + connssl->io_need = gnutls_record_get_direction(backend->gtls.session) ? + CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV; + backend->gtls.sent_shutdown = FALSE; + result = CURLE_OK; + goto out; + } + if(ret != GNUTLS_E_SUCCESS) { + CURL_TRC_CF(data, cf, "SSL shutdown, gnutls_bye error: '%s'(%d)", + gnutls_strerror((int)ret), (int)ret); + result = CURLE_RECV_ERROR; + goto out; + } + } } - if(backend->gtls.cred) { - gnutls_certificate_free_credentials(backend->gtls.cred); - backend->gtls.cred = NULL; + + /* SSL should now have started the shutdown from our side. Since it + * was not complete, we are lacking the close notify from the server. */ + for(i = 0; i < 10; ++i) { + nread = gnutls_record_recv(backend->gtls.session, buf, sizeof(buf)); + if(nread <= 0) + break; } -#ifdef USE_GNUTLS_SRP - if(backend->gtls.srp_client_cred) { - gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred); - backend->gtls.srp_client_cred = NULL; + if(nread > 0) { + /* still data coming in? */ } -#endif + else if(nread == 0) { + /* We got the close notify alert and are done. */ + *done = TRUE; + } + else if((nread == GNUTLS_E_AGAIN) || (nread == GNUTLS_E_INTERRUPTED)) { + connssl->io_need = gnutls_record_get_direction(backend->gtls.session) ? + CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV; + } + else { + CURL_TRC_CF(data, cf, "SSL shutdown, error: '%s'(%d)", + gnutls_strerror((int)nread), (int)nread); + result = CURLE_RECV_ERROR; + } + +out: + cf->shutdown = (result || *done); + return result; } -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -static int gtls_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void gtls_close(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); struct gtls_ssl_backend_data *backend = (struct gtls_ssl_backend_data *)connssl->backend; - int retval = 0; + (void) data; DEBUGASSERT(backend); - -#ifndef CURL_DISABLE_FTP - /* This has only been tested on the proftpd server, and the mod_tls code - sends a close notify alert without waiting for a close notify alert in - response. Thus we wait for a close notify alert from the server, but - we do not send one. Let's hope other servers do the same... */ - - if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) - gnutls_bye(backend->gtls.session, GNUTLS_SHUT_WR); -#endif - + CURL_TRC_CF(data, cf, "close"); if(backend->gtls.session) { - ssize_t result; - bool done = FALSE; - char buf[120]; - - while(!done) { - int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), - SSL_SHUTDOWN_TIMEOUT); - if(what > 0) { - /* Something to read, let's do it and hope that it is the close - notify alert from the server */ - result = gnutls_record_recv(backend->gtls.session, - buf, sizeof(buf)); - switch(result) { - case 0: - /* This is the expected response. There was no data but only - the close notify alert */ - done = TRUE; - break; - case GNUTLS_E_AGAIN: - case GNUTLS_E_INTERRUPTED: - infof(data, "GNUTLS_E_AGAIN || GNUTLS_E_INTERRUPTED"); - break; - default: - retval = -1; - done = TRUE; - break; - } - } - else if(0 == what) { - /* timeout */ - failf(data, "SSL shutdown timeout"); - done = TRUE; - } - else { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - retval = -1; - done = TRUE; - } - } gnutls_deinit(backend->gtls.session); + backend->gtls.session = NULL; + } + if(backend->gtls.shared_creds) { + Curl_gtls_shared_creds_free(&backend->gtls.shared_creds); } - gnutls_certificate_free_credentials(backend->gtls.cred); - #ifdef USE_GNUTLS_SRP - if(ssl_config->primary.username) + if(backend->gtls.srp_client_cred) { gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred); + backend->gtls.srp_client_cred = NULL; + } #endif - - backend->gtls.cred = NULL; - backend->gtls.session = NULL; - - return retval; } static ssize_t gtls_recv(struct Curl_cfilter *cf, @@ -1560,6 +2165,21 @@ static ssize_t gtls_recv(struct Curl_cfilter *cf, (void)data; DEBUGASSERT(backend); + if(connssl->state == ssl_connection_deferred) { + bool done = FALSE; + *curlcode = gtls_connect_deferred(cf, data, NULL, 0, &done); + if(*curlcode) { + ret = -1; + goto out; + } + else if(!done) { + *curlcode = CURLE_AGAIN; + ret = -1; + goto out; + } + DEBUGASSERT(connssl->state == ssl_connection_complete); + } + ret = gnutls_record_recv(backend->gtls.session, buf, buffersize); if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) { *curlcode = CURLE_AGAIN; @@ -1582,9 +2202,9 @@ static ssize_t gtls_recv(struct Curl_cfilter *cf, if(ret < 0) { failf(data, "GnuTLS recv error (%d): %s", - (int)ret, gnutls_strerror((int)ret)); - *curlcode = CURLE_RECV_ERROR; + *curlcode = backend->gtls.io_result ? + backend->gtls.io_result : CURLE_RECV_ERROR; ret = -1; goto out; } @@ -1593,11 +2213,6 @@ static ssize_t gtls_recv(struct Curl_cfilter *cf, return ret; } -static void gtls_session_free(void *ptr) -{ - free(ptr); -} - static size_t gtls_version(char *buffer, size_t size) { return msnprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL)); @@ -1610,7 +2225,7 @@ static CURLcode gtls_random(struct Curl_easy *data, int rc; (void)data; rc = gnutls_rnd(GNUTLS_RND_RANDOM, entropy, length); - return rc?CURLE_FAILED_INIT:CURLE_OK; + return rc ? CURLE_FAILED_INIT : CURLE_OK; } static CURLcode gtls_sha256sum(const unsigned char *tmp, /* input */ @@ -1646,7 +2261,8 @@ const struct Curl_ssl Curl_ssl_gnutls = { SSLSUPP_CA_PATH | SSLSUPP_CERTINFO | SSLSUPP_PINNEDPUBKEY | - SSLSUPP_HTTPS_PROXY, + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CA_CACHE, sizeof(struct gtls_ssl_backend_data), @@ -1660,11 +2276,10 @@ const struct Curl_ssl Curl_ssl_gnutls = { gtls_cert_status_request, /* cert_status_request */ gtls_connect, /* connect */ gtls_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks, /* getsock */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ gtls_get_internals, /* get_internals */ gtls_close, /* close_one */ Curl_none_close_all, /* close_all */ - gtls_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ @@ -1672,9 +2287,9 @@ const struct Curl_ssl Curl_ssl_gnutls = { gtls_sha256sum, /* sha256sum */ NULL, /* associate_connection */ NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ gtls_recv, /* recv decrypted data */ gtls_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; #endif /* USE_GNUTLS */ diff --git a/deps/curl/lib/vtls/gtls.h b/deps/curl/lib/vtls/gtls.h index ac141e1c61b..4f9c540ed27 100644 --- a/deps/curl/lib/vtls/gtls.h +++ b/deps/curl/lib/vtls/gtls.h @@ -30,6 +30,7 @@ #ifdef USE_GNUTLS #include +#include "timeval.h" #ifdef HAVE_GNUTLS_SRP /* the function exists */ @@ -43,31 +44,63 @@ struct Curl_easy; struct Curl_cfilter; struct ssl_primary_config; struct ssl_config_data; +struct ssl_peer; +struct ssl_connect_data; -struct gtls_instance { +struct gtls_shared_creds { + gnutls_certificate_credentials_t creds; + char *CAfile; /* CAfile path used to generate X509 store */ + struct curltime time; /* when the shared creds was created */ + size_t refcount; + BIT(trust_setup); /* x509 anchors + CRLs have been set up */ +}; + +CURLcode Curl_gtls_shared_creds_create(struct Curl_easy *data, + struct gtls_shared_creds **pcreds); +CURLcode Curl_gtls_shared_creds_up_ref(struct gtls_shared_creds *creds); +void Curl_gtls_shared_creds_free(struct gtls_shared_creds **pcreds); + +struct gtls_ctx { gnutls_session_t session; - gnutls_certificate_credentials_t cred; + struct gtls_shared_creds *shared_creds; #ifdef USE_GNUTLS_SRP gnutls_srp_client_credentials_t srp_client_cred; #endif + CURLcode io_result; /* result of last IO cfilter operation */ + BIT(sent_shutdown); }; -CURLcode -gtls_client_init(struct Curl_easy *data, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, - const char *hostname, - struct gtls_instance *gtls, - long *pverifyresult); +typedef CURLcode Curl_gtls_ctx_setup_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data); + +CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const unsigned char *alpn, size_t alpn_len, + struct ssl_connect_data *connssl, + Curl_gtls_ctx_setup_cb *cb_setup, + void *cb_user_data, + void *ssl_user_data); + +CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct gtls_ctx *gtls); + +CURLcode Curl_gtls_verifyserver(struct Curl_easy *data, + gnutls_session_t session, + struct ssl_primary_config *config, + struct ssl_config_data *ssl_config, + struct ssl_peer *peer, + const char *pinned_key); -CURLcode -Curl_gtls_verifyserver(struct Curl_easy *data, - gnutls_session_t session, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, - const char *hostname, - const char *dispname, - const char *pinned_key); +/* Extract TLS session and place in cache, if configured. */ +CURLcode Curl_gtls_update_session_id(struct Curl_cfilter *cf, + struct Curl_easy *data, + gnutls_session_t session, + struct ssl_peer *peer, + const char *alpn); extern const struct Curl_ssl Curl_ssl_gnutls; diff --git a/deps/curl/lib/vtls/hostcheck.c b/deps/curl/lib/vtls/hostcheck.c index 2726dca7f22..e46439a5ecc 100644 --- a/deps/curl/lib/vtls/hostcheck.c +++ b/deps/curl/lib/vtls/hostcheck.c @@ -62,7 +62,7 @@ static bool pmatch(const char *hostname, size_t hostlen, * We use the matching rule described in RFC6125, section 6.4.3. * https://datatracker.ietf.org/doc/html/rfc6125#section-6.4.3 * - * In addition: ignore trailing dots in the host names and wildcards, so that + * In addition: ignore trailing dots in the hostnames and wildcards, so that * the names are used normalized. This is what the browsers do. * * Do not allow wildcard matching on IP numbers. There are apparently diff --git a/deps/curl/lib/vtls/hostcheck.h b/deps/curl/lib/vtls/hostcheck.h index 22a1ac2e563..6b4e3796443 100644 --- a/deps/curl/lib/vtls/hostcheck.h +++ b/deps/curl/lib/vtls/hostcheck.h @@ -26,7 +26,7 @@ #include -/* returns TRUE if there's a match */ +/* returns TRUE if there is a match */ bool Curl_cert_hostcheck(const char *match_pattern, size_t matchlen, const char *hostname, size_t hostlen); diff --git a/deps/curl/lib/vtls/keylog.c b/deps/curl/lib/vtls/keylog.c index d37bb183e7c..ca86c156082 100644 --- a/deps/curl/lib/vtls/keylog.c +++ b/deps/curl/lib/vtls/keylog.c @@ -23,6 +23,12 @@ ***************************************************************************/ #include "curl_setup.h" +#if defined(USE_OPENSSL) || \ + defined(USE_GNUTLS) || \ + defined(USE_WOLFSSL) || \ + (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || \ + defined(USE_QUICHE) + #include "keylog.h" #include @@ -55,7 +61,7 @@ Curl_tls_keylog_open(void) if(keylog_file_name) { keylog_file_fp = fopen(keylog_file_name, FOPEN_APPENDTEXT); if(keylog_file_fp) { -#ifdef WIN32 +#ifdef _WIN32 if(setvbuf(keylog_file_fp, NULL, _IONBF, 0)) #else if(setvbuf(keylog_file_fp, NULL, _IOLBF, 4096)) @@ -93,13 +99,13 @@ Curl_tls_keylog_write_line(const char *line) char buf[256]; if(!keylog_file_fp || !line) { - return false; + return FALSE; } linelen = strlen(line); if(linelen == 0 || linelen > sizeof(buf) - 2) { /* Empty line or too big to fit in a LF and NUL. */ - return false; + return FALSE; } memcpy(buf, line, linelen); @@ -111,7 +117,7 @@ Curl_tls_keylog_write_line(const char *line) /* Using fputs here instead of fprintf since libcurl's fprintf replacement may not be thread-safe. */ fputs(buf, keylog_file_fp); - return true; + return TRUE; } bool @@ -125,13 +131,13 @@ Curl_tls_keylog_write(const char *label, 2 * SECRET_MAXLEN + 1 + 1]; if(!keylog_file_fp) { - return false; + return FALSE; } pos = strlen(label); if(pos > KEYLOG_LABEL_MAXLEN || !secretlen || secretlen > SECRET_MAXLEN) { /* Should never happen - sanity check anyway. */ - return false; + return FALSE; } memcpy(line, label, pos); @@ -155,5 +161,7 @@ Curl_tls_keylog_write(const char *label, /* Using fputs here instead of fprintf since libcurl's fprintf replacement may not be thread-safe. */ fputs(line, keylog_file_fp); - return true; + return TRUE; } + +#endif /* TLS or QUIC backend */ diff --git a/deps/curl/lib/vtls/mbedtls.c b/deps/curl/lib/vtls/mbedtls.c index f45636e57e0..7a34e9c183b 100644 --- a/deps/curl/lib/vtls/mbedtls.c +++ b/deps/curl/lib/vtls/mbedtls.c @@ -56,17 +56,21 @@ # endif #endif +#include "cipher_suite.h" +#include "strcase.h" #include "urldata.h" #include "sendf.h" #include "inet_pton.h" #include "mbedtls.h" #include "vtls.h" #include "vtls_int.h" +#include "x509asn1.h" #include "parsedate.h" #include "connect.h" /* for the connect timeout */ #include "select.h" #include "multiif.h" #include "mbedtls_threadlock.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -95,10 +99,14 @@ struct mbed_ssl_backend_data { #ifdef HAS_ALPN const char *protocols[3]; #endif + int *ciphersuites; + BIT(initialized); /* mbedtls_ssl_context is initialized */ + BIT(sent_shutdown); }; /* apply threading? */ -#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) +#if (defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)) || \ + defined(_WIN32) #define THREADING_SUPPORT #endif @@ -106,12 +114,19 @@ struct mbed_ssl_backend_data { #define mbedtls_strerror(a,b,c) b[0] = 0 #endif +#if defined(MBEDTLS_SSL_PROTO_TLS1_3) && MBEDTLS_VERSION_NUMBER >= 0x03060000 +#define TLS13_SUPPORT +#endif + +#if defined(TLS13_SUPPORT) && defined(MBEDTLS_SSL_SESSION_TICKETS) +#define HAS_SESSION_TICKETS +#endif + #if defined(THREADING_SUPPORT) static mbedtls_entropy_context ts_entropy; static int entropy_init_initialized = 0; -/* start of entropy_init_mutex() */ static void entropy_init_mutex(mbedtls_entropy_context *ctx) { /* lock 0 = entropy_init_mutex() */ @@ -122,9 +137,18 @@ static void entropy_init_mutex(mbedtls_entropy_context *ctx) } Curl_mbedtlsthreadlock_unlock_function(0); } -/* end of entropy_init_mutex() */ -/* start of entropy_func_mutex() */ +static void entropy_cleanup_mutex(mbedtls_entropy_context *ctx) +{ + /* lock 0 = use same lock as init */ + Curl_mbedtlsthreadlock_lock_function(0); + if(entropy_init_initialized == 1) { + mbedtls_entropy_free(ctx); + entropy_init_initialized = 0; + } + Curl_mbedtlsthreadlock_unlock_function(0); +} + static int entropy_func_mutex(void *data, unsigned char *output, size_t len) { int ret; @@ -135,7 +159,6 @@ static int entropy_func_mutex(void *data, unsigned char *output, size_t len) return ret; } -/* end of entropy_func_mutex() */ #endif /* THREADING_SUPPORT */ @@ -143,20 +166,23 @@ static int entropy_func_mutex(void *data, unsigned char *output, size_t len) static void mbed_debug(void *context, int level, const char *f_name, int line_nb, const char *line) { - struct Curl_easy *data = NULL; - - if(!context) - return; - - data = (struct Curl_easy *)context; - - infof(data, "%s", line); + struct Curl_easy *data = (struct Curl_easy *)context; (void) level; + (void) line_nb; + (void) f_name; + + if(data) { + size_t len = strlen(line); + if(len && (line[len - 1] == '\n')) + /* discount any trailing newline */ + len--; + infof(data, "%.*s", (int)len, line); + } } -#else #endif -static int bio_cf_write(void *bio, const unsigned char *buf, size_t blen) +static int mbedtls_bio_cf_write(void *bio, + const unsigned char *buf, size_t blen) { struct Curl_cfilter *cf = bio; struct Curl_easy *data = CF_DATA_CURRENT(cf); @@ -164,8 +190,12 @@ static int bio_cf_write(void *bio, const unsigned char *buf, size_t blen) CURLcode result; DEBUGASSERT(data); - nwritten = Curl_conn_cf_send(cf->next, data, (char *)buf, blen, &result); - CURL_TRC_CF(data, cf, "bio_cf_out_write(len=%zu) -> %zd, err=%d", + if(!data) + return 0; + + nwritten = Curl_conn_cf_send(cf->next, data, (char *)buf, blen, FALSE, + &result); + CURL_TRC_CF(data, cf, "mbedtls_bio_cf_out_write(len=%zu) -> %zd, err=%d", blen, nwritten, result); if(nwritten < 0 && CURLE_AGAIN == result) { nwritten = MBEDTLS_ERR_SSL_WANT_WRITE; @@ -173,7 +203,7 @@ static int bio_cf_write(void *bio, const unsigned char *buf, size_t blen) return (int)nwritten; } -static int bio_cf_read(void *bio, unsigned char *buf, size_t blen) +static int mbedtls_bio_cf_read(void *bio, unsigned char *buf, size_t blen) { struct Curl_cfilter *cf = bio; struct Curl_easy *data = CF_DATA_CURRENT(cf); @@ -181,12 +211,14 @@ static int bio_cf_read(void *bio, unsigned char *buf, size_t blen) CURLcode result; DEBUGASSERT(data); + if(!data) + return 0; /* OpenSSL catches this case, so should we. */ if(!buf) return 0; nread = Curl_conn_cf_recv(cf->next, data, (char *)buf, blen, &result); - CURL_TRC_CF(data, cf, "bio_cf_in_read(len=%zu) -> %zd, err=%d", + CURL_TRC_CF(data, cf, "mbedtls_bio_cf_in_read(len=%zu) -> %zd, err=%d", blen, nread, result); if(nread < 0 && CURLE_AGAIN == result) { nread = MBEDTLS_ERR_SSL_WANT_READ; @@ -211,8 +243,8 @@ static const mbedtls_x509_crt_profile mbedtls_x509_crt_profile_fr = 1024, /* RSA min key len */ }; -/* See https://tls.mbed.org/discussions/generic/ - howto-determine-exact-buffer-len-for-mbedtls_pk_write_pubkey_der +/* See https://web.archive.org/web/20200921194007/tls.mbed.org/discussions/ + generic/howto-determine-exact-buffer-len-for-mbedtls_pk_write_pubkey_der */ #define RSA_PUB_DER_MAX_BYTES (38 + 2 * MBEDTLS_MPI_MAX_SIZE) #define ECP_PUB_DER_MAX_BYTES (30 + 2 * MBEDTLS_ECP_MAX_BYTES) @@ -220,88 +252,307 @@ static const mbedtls_x509_crt_profile mbedtls_x509_crt_profile_fr = #define PUB_DER_MAX_BYTES (RSA_PUB_DER_MAX_BYTES > ECP_PUB_DER_MAX_BYTES ? \ RSA_PUB_DER_MAX_BYTES : ECP_PUB_DER_MAX_BYTES) -static CURLcode mbedtls_version_from_curl(int *mbedver, long version) +static CURLcode +mbed_set_ssl_version_min_max(struct Curl_easy *data, + struct mbed_ssl_backend_data *backend, + struct ssl_primary_config *conn_config) { -#if MBEDTLS_VERSION_NUMBER >= 0x03000000 - switch(version) { - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - *mbedver = MBEDTLS_SSL_MINOR_VERSION_3; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_3: - break; + /* TLS 1.0 and TLS 1.1 were dropped with mbedTLS 3.0.0 (2021). So, since + * then, and before the introduction of TLS 1.3 in 3.6.0 (2024), this + * function basically always sets TLS 1.2 as min/max, unless given + * unsupported option values. */ + +#if MBEDTLS_VERSION_NUMBER < 0x03020000 + int ver_min = MBEDTLS_SSL_MINOR_VERSION_3; /* TLS 1.2 */ + int ver_max = MBEDTLS_SSL_MINOR_VERSION_3; /* TLS 1.2 */ +#else + /* mbedTLS 3.2.0 (2022) introduced new methods for setting TLS version */ + mbedtls_ssl_protocol_version ver_min = MBEDTLS_SSL_VERSION_TLS1_2; + mbedtls_ssl_protocol_version ver_max = MBEDTLS_SSL_VERSION_TLS1_2; +#endif + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: +#if MBEDTLS_VERSION_NUMBER < 0x03000000 + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + ver_min = MBEDTLS_SSL_MINOR_VERSION_1; + break; + case CURL_SSLVERSION_TLSv1_1: + ver_min = MBEDTLS_SSL_MINOR_VERSION_2; + break; +#else + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: +#endif + case CURL_SSLVERSION_TLSv1_2: + /* ver_min = MBEDTLS_SSL_VERSION_TLS1_2; */ + break; + case CURL_SSLVERSION_TLSv1_3: +#ifdef TLS13_SUPPORT + ver_min = MBEDTLS_SSL_VERSION_TLS1_3; + break; +#endif + default: + failf(data, "mbedTLS: unsupported minimum TLS version value: %x", + conn_config->version); + return CURLE_SSL_CONNECT_ERROR; } + + switch(conn_config->version_max) { + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_TLSv1_3: +#ifdef TLS13_SUPPORT + ver_max = MBEDTLS_SSL_VERSION_TLS1_3; + break; +#endif + case CURL_SSLVERSION_MAX_TLSv1_2: + /* ver_max = MBEDTLS_SSL_VERSION_TLS1_2; */ + break; +#if MBEDTLS_VERSION_NUMBER < 0x03000000 + case CURL_SSLVERSION_MAX_TLSv1_1: + ver_max = MBEDTLS_SSL_MINOR_VERSION_2; + break; + case CURL_SSLVERSION_MAX_TLSv1_0: + ver_max = MBEDTLS_SSL_MINOR_VERSION_1; + break; #else - switch(version) { - case CURL_SSLVERSION_TLSv1_0: - *mbedver = MBEDTLS_SSL_MINOR_VERSION_1; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_1: - *mbedver = MBEDTLS_SSL_MINOR_VERSION_2; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_2: - *mbedver = MBEDTLS_SSL_MINOR_VERSION_3; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_3: - break; + case CURL_SSLVERSION_MAX_TLSv1_1: + case CURL_SSLVERSION_MAX_TLSv1_0: +#endif + default: + failf(data, "mbedTLS: unsupported maximum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; } + +#if MBEDTLS_VERSION_NUMBER < 0x03020000 + mbedtls_ssl_conf_min_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, + ver_min); + mbedtls_ssl_conf_max_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, + ver_max); +#else + mbedtls_ssl_conf_min_tls_version(&backend->config, ver_min); + mbedtls_ssl_conf_max_tls_version(&backend->config, ver_max); #endif - return CURLE_SSL_CONNECT_ERROR; + return CURLE_OK; +} + +/* TLS_ECJPAKE_WITH_AES_128_CCM_8 (0xC0FF) is marked experimental + in mbedTLS. The number is not reserved by IANA nor is the + cipher suite present in other SSL implementations. Provide + provisional support for specifying the cipher suite here. */ +#ifdef MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8 +#if MBEDTLS_VERSION_NUMBER >= 0x03020000 +static int +mbed_cipher_suite_get_str(uint16_t id, char *buf, size_t buf_size, + bool prefer_rfc) +{ + if(id == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8) + msnprintf(buf, buf_size, "%s", "TLS_ECJPAKE_WITH_AES_128_CCM_8"); + else + return Curl_cipher_suite_get_str(id, buf, buf_size, prefer_rfc); + return 0; } +#endif + +static uint16_t +mbed_cipher_suite_walk_str(const char **str, const char **end) +{ + uint16_t id = Curl_cipher_suite_walk_str(str, end); + size_t len = *end - *str; + + if(!id) { + if(strncasecompare("TLS_ECJPAKE_WITH_AES_128_CCM_8", *str, len)) + id = MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8; + } + return id; +} +#else +#define mbed_cipher_suite_get_str Curl_cipher_suite_get_str +#define mbed_cipher_suite_walk_str Curl_cipher_suite_walk_str +#endif static CURLcode -set_ssl_version_min_max(struct Curl_cfilter *cf, struct Curl_easy *data) +mbed_set_selected_ciphers(struct Curl_easy *data, + struct mbed_ssl_backend_data *backend, + const char *ciphers12, + const char *ciphers13) { - struct ssl_connect_data *connssl = cf->ctx; - struct mbed_ssl_backend_data *backend = - (struct mbed_ssl_backend_data *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); -#if MBEDTLS_VERSION_NUMBER >= 0x03000000 - int mbedtls_ver_min = MBEDTLS_SSL_MINOR_VERSION_3; - int mbedtls_ver_max = MBEDTLS_SSL_MINOR_VERSION_3; + const char *ciphers = ciphers12; + const int *supported; + int *selected; + size_t supported_len, count = 0, default13_count = 0, i, j; + const char *ptr, *end; + + supported = mbedtls_ssl_list_ciphersuites(); + for(i = 0; supported[i] != 0; i++); + supported_len = i; + + selected = malloc(sizeof(int) * (supported_len + 1)); + if(!selected) + return CURLE_OUT_OF_MEMORY; + +#ifndef TLS13_SUPPORT + (void) ciphers13, (void) j; #else - int mbedtls_ver_min = MBEDTLS_SSL_MINOR_VERSION_1; - int mbedtls_ver_max = MBEDTLS_SSL_MINOR_VERSION_1; + if(!ciphers13) { + /* Add default TLSv1.3 ciphers to selection */ + for(j = 0; j < supported_len; j++) { + uint16_t id = (uint16_t) supported[j]; + if(strncmp(mbedtls_ssl_get_ciphersuite_name(id), "TLS1-3", 6) != 0) + continue; + + selected[count++] = id; + } + + default13_count = count; + } + else + ciphers = ciphers13; + +add_ciphers: #endif - long ssl_version = conn_config->version; - long ssl_version_max = conn_config->version_max; - CURLcode result = CURLE_OK; + for(ptr = ciphers; ptr[0] != '\0' && count < supported_len; ptr = end) { + uint16_t id = mbed_cipher_suite_walk_str(&ptr, &end); + + /* Check if cipher is supported */ + if(id) { + for(i = 0; i < supported_len && supported[i] != id; i++); + if(i == supported_len) + id = 0; + } + if(!id) { + if(ptr[0] != '\0') + infof(data, "mbedTLS: unknown cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } - DEBUGASSERT(backend); + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != id; i++); + if(i < count) { + if(i >= default13_count) + infof(data, "mbedTLS: duplicate cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } - switch(ssl_version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - ssl_version = CURL_SSLVERSION_TLSv1_0; - break; + selected[count++] = id; } - switch(ssl_version_max) { - case CURL_SSLVERSION_MAX_NONE: - case CURL_SSLVERSION_MAX_DEFAULT: - ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2; - break; +#ifdef TLS13_SUPPORT + if(ciphers == ciphers13 && ciphers12) { + ciphers = ciphers12; + goto add_ciphers; } - result = mbedtls_version_from_curl(&mbedtls_ver_min, ssl_version); - if(result) { - failf(data, "unsupported min version passed via CURLOPT_SSLVERSION"); - return result; + if(!ciphers12) { + /* Add default TLSv1.2 ciphers to selection */ + for(j = 0; j < supported_len; j++) { + uint16_t id = (uint16_t) supported[j]; + if(strncmp(mbedtls_ssl_get_ciphersuite_name(id), "TLS1-3", 6) == 0) + continue; + + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != id; i++); + if(i < count) + continue; + + selected[count++] = id; + } } - result = mbedtls_version_from_curl(&mbedtls_ver_max, ssl_version_max >> 16); - if(result) { - failf(data, "unsupported max version passed via CURLOPT_SSLVERSION"); - return result; +#endif + + selected[count] = 0; + + if(count == 0) { + free(selected); + failf(data, "mbedTLS: no supported cipher in list"); + return CURLE_SSL_CIPHER; } - mbedtls_ssl_conf_min_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, - mbedtls_ver_min); - mbedtls_ssl_conf_max_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, - mbedtls_ver_max); + /* mbedtls_ssl_conf_ciphersuites(): The ciphersuites array is not copied. + It must remain valid for the lifetime of the SSL configuration */ + backend->ciphersuites = selected; + mbedtls_ssl_conf_ciphersuites(&backend->config, backend->ciphersuites); + return CURLE_OK; +} - return result; +static void +mbed_dump_cert_info(struct Curl_easy *data, const mbedtls_x509_crt *crt) +{ +#if defined(CURL_DISABLE_VERBOSE_STRINGS) || \ + (MBEDTLS_VERSION_NUMBER >= 0x03000000 && defined(MBEDTLS_X509_REMOVE_INFO)) + (void) data, (void) crt; +#else + const size_t bufsize = 16384; + char *p, *buffer = malloc(bufsize); + + if(buffer && mbedtls_x509_crt_info(buffer, bufsize, " ", crt) > 0) { + infof(data, "Server certificate:"); + for(p = buffer; *p; p += *p != '\0') { + size_t s = strcspn(p, "\n"); + infof(data, "%.*s", (int) s, p); + p += s; + } + } + else + infof(data, "Unable to dump certificate information"); + + free(buffer); +#endif +} + +static void +mbed_extract_certinfo(struct Curl_easy *data, const mbedtls_x509_crt *crt) +{ + CURLcode result; + const mbedtls_x509_crt *cur; + int i; + + for(i = 0, cur = crt; cur; ++i, cur = cur->next); + result = Curl_ssl_init_certinfo(data, i); + + for(i = 0, cur = crt; result == CURLE_OK && cur; ++i, cur = cur->next) { + const char *beg = (const char *) cur->raw.p; + const char *end = beg + cur->raw.len; + result = Curl_extract_certinfo(data, i, beg, end); + } +} + +static int mbed_verify_cb(void *ptr, mbedtls_x509_crt *crt, + int depth, uint32_t *flags) +{ + struct Curl_cfilter *cf = (struct Curl_cfilter *) ptr; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct Curl_easy *data = CF_DATA_CURRENT(cf); + + if(depth == 0) { + if(data->set.verbose) + mbed_dump_cert_info(data, crt); + if(data->set.ssl.certinfo) + mbed_extract_certinfo(data, crt); + } + + if(!conn_config->verifypeer) + *flags = 0; + else if(!conn_config->verifyhost) + *flags &= ~MBEDTLS_X509_BADCERT_CN_MISMATCH; + + if(*flags) { +#if MBEDTLS_VERSION_NUMBER < 0x03000000 || !defined(MBEDTLS_X509_REMOVE_INFO) + char buf[128]; + mbedtls_x509_crt_verify_info(buf, sizeof(buf), "", *flags); + failf(data, "mbedTLS: %s", buf); +#else + failf(data, "mbedTLS: certificate verification error 0x%08x", *flags); +#endif + } + + return 0; } static CURLcode @@ -321,11 +572,12 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) char * const ssl_cert = ssl_config->primary.clientcert; const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; const char * const ssl_crlfile = ssl_config->primary.CRLfile; - const char *hostname = connssl->hostname; + const char *hostname = connssl->peer.hostname; int ret = -1; char errorbuf[128]; DEBUGASSERT(backend); + DEBUGASSERT(!backend->initialized); if((conn_config->version == CURL_SSLVERSION_SSLv2) || (conn_config->version == CURL_SSLVERSION_SSLv3)) { @@ -333,8 +585,17 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_NOT_BUILT_IN; } +#ifdef TLS13_SUPPORT + ret = psa_crypto_init(); + if(ret != PSA_SUCCESS) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "mbedTLS psa_crypto_init returned (-0x%04X) %s", + -ret, errorbuf); + return CURLE_SSL_CONNECT_ERROR; + } +#endif /* TLS13_SUPPORT */ + #ifdef THREADING_SUPPORT - entropy_init_mutex(&ts_entropy); mbedtls_ctr_drbg_init(&backend->ctr_drbg); ret = mbedtls_ctr_drbg_seed(&backend->ctr_drbg, entropy_func_mutex, @@ -366,15 +627,14 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* Unfortunately, mbedtls_x509_crt_parse() requires the data to be null terminated even when provided the exact length, forcing us to waste extra memory here. */ - unsigned char *newblob = malloc(ca_info_blob->len + 1); + unsigned char *newblob = Curl_memdup0(ca_info_blob->data, + ca_info_blob->len); if(!newblob) return CURLE_OUT_OF_MEMORY; - memcpy(newblob, ca_info_blob->data, ca_info_blob->len); - newblob[ca_info_blob->len] = 0; /* null terminate */ ret = mbedtls_x509_crt_parse(&backend->cacert, newblob, ca_info_blob->len + 1); free(newblob); - if(ret<0) { + if(ret < 0) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "Error importing ca cert blob - mbedTLS: (-0x%04X) %s", -ret, errorbuf); @@ -386,7 +646,7 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) #ifdef MBEDTLS_FS_IO ret = mbedtls_x509_crt_parse_file(&backend->cacert, ssl_cafile); - if(ret<0) { + if(ret < 0) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "Error reading ca cert file %s - mbedTLS: (-0x%04X) %s", ssl_cafile, -ret, errorbuf); @@ -402,7 +662,7 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) #ifdef MBEDTLS_FS_IO ret = mbedtls_x509_crt_parse_path(&backend->cacert, ssl_capath); - if(ret<0) { + if(ret < 0) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "Error reading ca cert path %s - mbedTLS: (-0x%04X) %s", ssl_capath, -ret, errorbuf); @@ -440,18 +700,17 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* Unfortunately, mbedtls_x509_crt_parse() requires the data to be null terminated even when provided the exact length, forcing us to waste extra memory here. */ - unsigned char *newblob = malloc(ssl_cert_blob->len + 1); + unsigned char *newblob = Curl_memdup0(ssl_cert_blob->data, + ssl_cert_blob->len); if(!newblob) return CURLE_OUT_OF_MEMORY; - memcpy(newblob, ssl_cert_blob->data, ssl_cert_blob->len); - newblob[ssl_cert_blob->len] = 0; /* null terminate */ ret = mbedtls_x509_crt_parse(&backend->clicert, newblob, ssl_cert_blob->len + 1); free(newblob); if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error reading private key %s - mbedTLS: (-0x%04X) %s", + failf(data, "Error reading client cert data %s - mbedTLS: (-0x%04X) %s", ssl_config->key, -ret, errorbuf); return CURLE_SSL_CERTPROBLEM; } @@ -541,7 +800,7 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } #endif - infof(data, "mbedTLS: Connecting to %s:%d", hostname, connssl->port); + infof(data, "mbedTLS: Connecting to %s:%d", hostname, connssl->peer.port); mbedtls_ssl_config_init(&backend->config); ret = mbedtls_ssl_config_defaults(&backend->config, @@ -553,49 +812,66 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_SSL_CONNECT_ERROR; } +#ifdef MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED + /* New in mbedTLS 3.6.1, need to enable, default is now disabled */ + mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(&backend->config, + MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED); +#endif + + /* Always let mbedTLS verify certificates, if verifypeer or verifyhost are + * disabled we clear the corresponding error flags in the verify callback + * function. That is also where we log verification errors. */ + mbedtls_ssl_conf_verify(&backend->config, mbed_verify_cb, cf); + mbedtls_ssl_conf_authmode(&backend->config, MBEDTLS_SSL_VERIFY_REQUIRED); + mbedtls_ssl_init(&backend->ssl); - if(mbedtls_ssl_setup(&backend->ssl, &backend->config)) { - failf(data, "mbedTLS: ssl_init failed"); - return CURLE_SSL_CONNECT_ERROR; - } + backend->initialized = TRUE; /* new profile with RSA min key len = 1024 ... */ mbedtls_ssl_conf_cert_profile(&backend->config, &mbedtls_x509_crt_profile_fr); - switch(conn_config->version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: -#if MBEDTLS_VERSION_NUMBER < 0x03000000 - mbedtls_ssl_conf_min_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, - MBEDTLS_SSL_MINOR_VERSION_1); - infof(data, "mbedTLS: Set min SSL version to TLS 1.0"); - break; -#endif - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - { - CURLcode result = set_ssl_version_min_max(cf, data); - if(result != CURLE_OK) - return result; - break; - } - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - mbedtls_ssl_conf_authmode(&backend->config, MBEDTLS_SSL_VERIFY_OPTIONAL); + ret = mbed_set_ssl_version_min_max(data, backend, conn_config); + if(ret != CURLE_OK) + return ret; mbedtls_ssl_conf_rng(&backend->config, mbedtls_ctr_drbg_random, &backend->ctr_drbg); - mbedtls_ssl_set_bio(&backend->ssl, cf, bio_cf_write, bio_cf_read, + + ret = mbedtls_ssl_setup(&backend->ssl, &backend->config); + if(ret) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "ssl_setup failed - mbedTLS: (-0x%04X) %s", + -ret, errorbuf); + return CURLE_SSL_CONNECT_ERROR; + } + + mbedtls_ssl_set_bio(&backend->ssl, cf, + mbedtls_bio_cf_write, + mbedtls_bio_cf_read, NULL /* rev_timeout() */); - mbedtls_ssl_conf_ciphersuites(&backend->config, - mbedtls_ssl_list_ciphersuites()); +#ifndef TLS13_SUPPORT + if(conn_config->cipher_list) { + CURLcode result = mbed_set_selected_ciphers(data, backend, + conn_config->cipher_list, + NULL); +#else + if(conn_config->cipher_list || conn_config->cipher_list13) { + CURLcode result = mbed_set_selected_ciphers(data, backend, + conn_config->cipher_list, + conn_config->cipher_list13); +#endif + if(result != CURLE_OK) { + failf(data, "mbedTLS: failed to set cipher suites"); + return result; + } + } + else { + mbedtls_ssl_conf_ciphersuites(&backend->config, + mbedtls_ssl_list_ciphersuites()); + } + #if defined(MBEDTLS_SSL_RENEGOTIATION) mbedtls_ssl_conf_renegotiation(&backend->config, @@ -607,19 +883,29 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) MBEDTLS_SSL_SESSION_TICKETS_DISABLED); #endif - /* Check if there's a cached ID we can/should use here! */ - if(ssl_config->primary.sessionid) { - void *old_session = NULL; + /* Check if there is a cached ID we can/should use here! */ + if(ssl_config->primary.cache_session) { + void *sdata = NULL; + size_t slen = 0; Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &old_session, NULL)) { - ret = mbedtls_ssl_set_session(&backend->ssl, old_session); + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, + &sdata, &slen, NULL) && slen) { + mbedtls_ssl_session session; + + mbedtls_ssl_session_init(&session); + ret = mbedtls_ssl_session_load(&session, sdata, slen); if(ret) { - Curl_ssl_sessionid_unlock(data); - failf(data, "mbedtls_ssl_set_session returned -0x%x", -ret); - return CURLE_SSL_CONNECT_ERROR; + failf(data, "error loading cached session: -0x%x", -ret); + } + else { + ret = mbedtls_ssl_set_session(&backend->ssl, &session); + if(ret) + failf(data, "error setting session: -0x%x", -ret); + else + infof(data, "SSL reusing session ID"); } - infof(data, "mbedTLS reusing session"); + mbedtls_ssl_session_free(&session); } Curl_ssl_sessionid_unlock(data); } @@ -636,15 +922,14 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) mbedtls_ssl_conf_own_cert(&backend->config, &backend->clicert, &backend->pk); } - { - char *snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost || mbedtls_ssl_set_hostname(&backend->ssl, snihost)) { - /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks and - the name to set in the SNI extension. So even if curl connects to a - host specified as an IP address, this function must be used. */ - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } + + if(mbedtls_ssl_set_hostname(&backend->ssl, connssl->peer.sni ? + connssl->peer.sni : connssl->peer.hostname)) { + /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks and + the name to set in the SNI extension. So even if curl connects to a + host specified as an IP address, this function must be used. */ + failf(data, "Failed to set SNI"); + return CURLE_SSL_CONNECT_ERROR; } #ifdef HAS_ALPN @@ -655,7 +940,7 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) for(i = 0; i < connssl->alpn->count; ++i) { backend->protocols[i] = connssl->alpn->entries[i]; } - /* this function doesn't clone the protocols array, which is why we need + /* this function does not clone the protocols array, which is why we need to keep it around */ if(mbedtls_ssl_conf_alpn_protocols(&backend->config, &backend->protocols[0])) { @@ -681,11 +966,11 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* give application a chance to interfere with mbedTLS set up. */ if(data->set.ssl.fsslctx) { - ret = (*data->set.ssl.fsslctx)(data, &backend->config, - data->set.ssl.fsslctxp); - if(ret) { + CURLcode result = (*data->set.ssl.fsslctx)(data, &backend->config, + data->set.ssl.fsslctxp); + if(result != CURLE_OK) { failf(data, "error signaled by ssl ctx callback"); - return ret; + return result; } } @@ -701,83 +986,65 @@ mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) struct ssl_connect_data *connssl = cf->ctx; struct mbed_ssl_backend_data *backend = (struct mbed_ssl_backend_data *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - const mbedtls_x509_crt *peercert; - const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf)? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: +#ifndef CURL_DISABLE_PROXY + const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + const char * const pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif DEBUGASSERT(backend); ret = mbedtls_ssl_handshake(&backend->ssl); if(ret == MBEDTLS_ERR_SSL_WANT_READ) { - connssl->connecting_state = ssl_connect_2_reading; + connssl->io_need = CURL_SSL_IO_NEED_RECV; return CURLE_OK; } else if(ret == MBEDTLS_ERR_SSL_WANT_WRITE) { - connssl->connecting_state = ssl_connect_2_writing; + connssl->io_need = CURL_SSL_IO_NEED_SEND; return CURLE_OK; } + else if(ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) { + failf(data, "peer certificate could not be verified"); + return CURLE_PEER_FAILED_VERIFICATION; + } else if(ret) { char errorbuf[128]; +#if MBEDTLS_VERSION_NUMBER >= 0x03020000 + CURL_TRC_CF(data, cf, "TLS version %04X", + mbedtls_ssl_get_version_number(&backend->ssl)); +#endif mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "ssl_handshake returned - mbedTLS: (-0x%04X) %s", + failf(data, "ssl_handshake returned: (-0x%04X) %s", -ret, errorbuf); return CURLE_SSL_CONNECT_ERROR; } - infof(data, "mbedTLS: Handshake complete, cipher is %s", - mbedtls_ssl_get_ciphersuite(&backend->ssl)); - - ret = mbedtls_ssl_get_verify_result(&backend->ssl); - - if(!conn_config->verifyhost) - /* Ignore hostname errors if verifyhost is disabled */ - ret &= ~MBEDTLS_X509_BADCERT_CN_MISMATCH; - - if(ret && conn_config->verifypeer) { - if(ret & MBEDTLS_X509_BADCERT_EXPIRED) - failf(data, "Cert verify failed: BADCERT_EXPIRED"); - - else if(ret & MBEDTLS_X509_BADCERT_REVOKED) - failf(data, "Cert verify failed: BADCERT_REVOKED"); - - else if(ret & MBEDTLS_X509_BADCERT_CN_MISMATCH) - failf(data, "Cert verify failed: BADCERT_CN_MISMATCH"); - - else if(ret & MBEDTLS_X509_BADCERT_NOT_TRUSTED) - failf(data, "Cert verify failed: BADCERT_NOT_TRUSTED"); - - else if(ret & MBEDTLS_X509_BADCERT_FUTURE) - failf(data, "Cert verify failed: BADCERT_FUTURE"); - - return CURLE_PEER_FAILED_VERIFICATION; - } - - peercert = mbedtls_ssl_get_peer_cert(&backend->ssl); - - if(peercert && data->set.verbose) { - const size_t bufsize = 16384; - char *buffer = malloc(bufsize); - - if(!buffer) - return CURLE_OUT_OF_MEMORY; - - if(mbedtls_x509_crt_info(buffer, bufsize, "* ", peercert) > 0) - infof(data, "Dumping cert info: %s", buffer); - else - infof(data, "Unable to dump certificate information"); - - free(buffer); +#if MBEDTLS_VERSION_NUMBER >= 0x03020000 + { + char cipher_str[64]; + uint16_t cipher_id; + cipher_id = (uint16_t) + mbedtls_ssl_get_ciphersuite_id_from_ssl(&backend->ssl); + mbed_cipher_suite_get_str(cipher_id, cipher_str, sizeof(cipher_str), TRUE); + infof(data, "mbedTLS: %s Handshake complete, cipher is %s", + mbedtls_ssl_get_version(&backend->ssl), cipher_str); } +#else + infof(data, "mbedTLS: %s Handshake complete", + mbedtls_ssl_get_version(&backend->ssl)); +#endif if(pinnedpubkey) { int size; CURLcode result; + const mbedtls_x509_crt *peercert; mbedtls_x509_crt *p = NULL; unsigned char *pubkey = NULL; + peercert = mbedtls_ssl_get_peer_cert(&backend->ssl); #if MBEDTLS_VERSION_NUMBER == 0x03000000 if(!peercert || !peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(p) || !peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len)) { @@ -804,7 +1071,7 @@ mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* Make a copy of our const peercert because mbedtls_pk_write_pubkey_der needs a non-const key, for now. - https://github.com/ARMmbed/mbedtls/issues/396 */ + https://github.com/Mbed-TLS/mbedtls/issues/396 */ #if MBEDTLS_VERSION_NUMBER == 0x03000000 if(mbedtls_x509_crt_parse_der(p, peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(p), @@ -847,8 +1114,8 @@ mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) if(connssl->alpn) { const char *proto = mbedtls_ssl_get_alpn_protocol(&backend->ssl); - Curl_alpn_set_negotiated(cf, data, (const unsigned char *)proto, - proto? strlen(proto) : 0); + Curl_alpn_set_negotiated(cf, data, connssl, (const unsigned char *)proto, + proto ? strlen(proto) : 0); } #endif @@ -858,60 +1125,62 @@ mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_OK; } +static void mbedtls_session_free(void *session, size_t slen) +{ + (void)slen; + free(session); +} + static CURLcode -mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) +mbed_new_session(struct Curl_cfilter *cf, struct Curl_easy *data) { - CURLcode retcode = CURLE_OK; struct ssl_connect_data *connssl = cf->ctx; struct mbed_ssl_backend_data *backend = (struct mbed_ssl_backend_data *)connssl->backend; struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + CURLcode result = CURLE_OK; - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); DEBUGASSERT(backend); - - if(ssl_config->primary.sessionid) { + if(ssl_config->primary.cache_session) { int ret; - mbedtls_ssl_session *our_ssl_sessionid; - void *old_ssl_sessionid = NULL; - bool added = FALSE; - - our_ssl_sessionid = malloc(sizeof(mbedtls_ssl_session)); - if(!our_ssl_sessionid) - return CURLE_OUT_OF_MEMORY; - - mbedtls_ssl_session_init(our_ssl_sessionid); + mbedtls_ssl_session session; + unsigned char *sdata = NULL; + size_t slen = 0; - ret = mbedtls_ssl_get_session(&backend->ssl, our_ssl_sessionid); + mbedtls_ssl_session_init(&session); + ret = mbedtls_ssl_get_session(&backend->ssl, &session); if(ret) { if(ret != MBEDTLS_ERR_SSL_ALLOC_FAILED) - mbedtls_ssl_session_free(our_ssl_sessionid); - free(our_ssl_sessionid); + mbedtls_ssl_session_free(&session); failf(data, "mbedtls_ssl_get_session returned -0x%x", -ret); return CURLE_SSL_CONNECT_ERROR; } - /* If there's already a matching session in the cache, delete it */ - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL)) - Curl_ssl_delsessionid(data, old_ssl_sessionid); - - retcode = Curl_ssl_addsessionid(cf, data, our_ssl_sessionid, - 0, &added); - Curl_ssl_sessionid_unlock(data); - if(!added) { - mbedtls_ssl_session_free(our_ssl_sessionid); - free(our_ssl_sessionid); + mbedtls_ssl_session_save(&session, NULL, 0, &slen); + if(!slen) { + failf(data, "failed to serialize session: length is 0"); } - if(retcode) { - failf(data, "failed to store ssl session"); - return retcode; + else { + sdata = malloc(slen); + if(sdata) { + ret = mbedtls_ssl_session_save(&session, sdata, slen, &slen); + if(ret) { + failf(data, "failed to serialize session: -0x%x", -ret); + } + else { + Curl_ssl_sessionid_lock(data); + result = Curl_ssl_set_sessionid(cf, data, &connssl->peer, NULL, + sdata, slen, mbedtls_session_free); + Curl_ssl_sessionid_unlock(data); + if(!result) + sdata = NULL; + } + } } + mbedtls_ssl_session_free(&session); + free(sdata); } - - connssl->connecting_state = ssl_connect_done; - - return CURLE_OK; + return result; } static ssize_t mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -928,8 +1197,13 @@ static ssize_t mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, ret = mbedtls_ssl_write(&backend->ssl, (unsigned char *)mem, len); if(ret < 0) { - *curlcode = (ret == MBEDTLS_ERR_SSL_WANT_WRITE) ? - CURLE_AGAIN : CURLE_SEND_ERROR; + CURL_TRC_CF(data, cf, "mbedtls_ssl_write(len=%zu) -> -0x%04X", + len, -ret); + *curlcode = ((ret == MBEDTLS_ERR_SSL_WANT_WRITE) +#ifdef TLS13_SUPPORT + || (ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) +#endif + ) ? CURLE_AGAIN : CURLE_SEND_ERROR; ret = -1; } @@ -941,32 +1215,120 @@ static void mbedtls_close_all(struct Curl_easy *data) (void)data; } -static void mbedtls_close(struct Curl_cfilter *cf, struct Curl_easy *data) +static CURLcode mbedtls_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { struct ssl_connect_data *connssl = cf->ctx; struct mbed_ssl_backend_data *backend = (struct mbed_ssl_backend_data *)connssl->backend; - char buf[32]; + unsigned char buf[1024]; + CURLcode result = CURLE_OK; + int ret; + size_t i; - (void)data; DEBUGASSERT(backend); - /* Maybe the server has already sent a close notify alert. - Read it to avoid an RST on the TCP connection. */ - (void)mbedtls_ssl_read(&backend->ssl, (unsigned char *)buf, sizeof(buf)); + if(!backend->initialized || cf->shutdown) { + *done = TRUE; + return CURLE_OK; + } + + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + + if(!backend->sent_shutdown) { + /* do this only once */ + backend->sent_shutdown = TRUE; + if(send_shutdown) { + ret = mbedtls_ssl_close_notify(&backend->ssl); + switch(ret) { + case 0: /* we sent it, receive from the server */ + break; + case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: /* server also closed */ + *done = TRUE; + goto out; + case MBEDTLS_ERR_SSL_WANT_READ: + connssl->io_need = CURL_SSL_IO_NEED_RECV; + goto out; + case MBEDTLS_ERR_SSL_WANT_WRITE: + connssl->io_need = CURL_SSL_IO_NEED_SEND; + goto out; + default: + CURL_TRC_CF(data, cf, "mbedtls_shutdown error -0x%04X", -ret); + result = CURLE_RECV_ERROR; + goto out; + } + } + } + + /* SSL should now have started the shutdown from our side. Since it + * was not complete, we are lacking the close notify from the server. */ + for(i = 0; i < 10; ++i) { + ret = mbedtls_ssl_read(&backend->ssl, buf, sizeof(buf)); + /* This seems to be a bug in mbedTLS TLSv1.3 where it reports + * WANT_READ, but has not encountered an EAGAIN. */ + if(ret == MBEDTLS_ERR_SSL_WANT_READ) + ret = mbedtls_ssl_read(&backend->ssl, buf, sizeof(buf)); +#ifdef TLS13_SUPPORT + if(ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) + continue; +#endif + if(ret <= 0) + break; + } + + if(ret > 0) { + /* still data coming in? */ + CURL_TRC_CF(data, cf, "mbedtls_shutdown, still getting data"); + } + else if(ret == 0 || (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)) { + /* We got the close notify alert and are done. */ + CURL_TRC_CF(data, cf, "mbedtls_shutdown done"); + *done = TRUE; + } + else if(ret == MBEDTLS_ERR_SSL_WANT_READ) { + CURL_TRC_CF(data, cf, "mbedtls_shutdown, need RECV"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + } + else if(ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + CURL_TRC_CF(data, cf, "mbedtls_shutdown, need SEND"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + } + else { + CURL_TRC_CF(data, cf, "mbedtls_shutdown error -0x%04X", -ret); + result = CURLE_RECV_ERROR; + } + +out: + cf->shutdown = (result || *done); + return result; +} - mbedtls_pk_free(&backend->pk); - mbedtls_x509_crt_free(&backend->clicert); - mbedtls_x509_crt_free(&backend->cacert); +static void mbedtls_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; + + (void)data; + DEBUGASSERT(backend); + if(backend->initialized) { + mbedtls_pk_free(&backend->pk); + mbedtls_x509_crt_free(&backend->clicert); + mbedtls_x509_crt_free(&backend->cacert); #ifdef MBEDTLS_X509_CRL_PARSE_C - mbedtls_x509_crl_free(&backend->crl); + mbedtls_x509_crl_free(&backend->crl); #endif - mbedtls_ssl_config_free(&backend->config); - mbedtls_ssl_free(&backend->ssl); - mbedtls_ctr_drbg_free(&backend->ctr_drbg); + Curl_safefree(backend->ciphersuites); + mbedtls_ssl_config_free(&backend->config); + mbedtls_ssl_free(&backend->ssl); + mbedtls_ctr_drbg_free(&backend->ctr_drbg); #ifndef THREADING_SUPPORT - mbedtls_entropy_free(&backend->entropy); + mbedtls_entropy_free(&backend->entropy); #endif /* THREADING_SUPPORT */ + backend->initialized = FALSE; + } } static ssize_t mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -977,32 +1339,40 @@ static ssize_t mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data, struct mbed_ssl_backend_data *backend = (struct mbed_ssl_backend_data *)connssl->backend; int ret = -1; - ssize_t len = -1; (void)data; DEBUGASSERT(backend); ret = mbedtls_ssl_read(&backend->ssl, (unsigned char *)buf, buffersize); - if(ret <= 0) { - if(ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) - return 0; - - *curlcode = (ret == MBEDTLS_ERR_SSL_WANT_READ) ? - CURLE_AGAIN : CURLE_RECV_ERROR; - return -1; + CURL_TRC_CF(data, cf, "mbedtls_ssl_read(len=%zu) -> -0x%04X", + buffersize, -ret); + switch(ret) { +#ifdef HAS_SESSION_TICKETS + case MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET: + mbed_new_session(cf, data); + FALLTHROUGH(); +#endif + case MBEDTLS_ERR_SSL_WANT_READ: + *curlcode = CURLE_AGAIN; + ret = -1; + break; + case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: + *curlcode = CURLE_OK; + ret = 0; + break; + default: { + char errorbuf[128]; + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "ssl_read returned: (-0x%04X) %s", -ret, errorbuf); + *curlcode = CURLE_RECV_ERROR; + ret = -1; + break; + } + } } - - len = ret; - - return len; -} - -static void mbedtls_session_free(void *ptr) -{ - mbedtls_ssl_session_free(ptr); - free(ptr); + return (ssize_t)ret; } static size_t mbedtls_version(char *buffer, size_t size) @@ -1010,42 +1380,31 @@ static size_t mbedtls_version(char *buffer, size_t size) #ifdef MBEDTLS_VERSION_C /* if mbedtls_version_get_number() is available it is better */ unsigned int version = mbedtls_version_get_number(); - return msnprintf(buffer, size, "mbedTLS/%u.%u.%u", version>>24, - (version>>16)&0xff, (version>>8)&0xff); + return msnprintf(buffer, size, "mbedTLS/%u.%u.%u", version >> 24, + (version >> 16) & 0xff, (version >> 8) & 0xff); #else return msnprintf(buffer, size, "mbedTLS/%s", MBEDTLS_VERSION_STRING); #endif } +/* 'data' might be NULL */ static CURLcode mbedtls_random(struct Curl_easy *data, unsigned char *entropy, size_t length) { #if defined(MBEDTLS_CTR_DRBG_C) - int ret = -1; - char errorbuf[128]; + int ret; mbedtls_entropy_context ctr_entropy; mbedtls_ctr_drbg_context ctr_drbg; mbedtls_entropy_init(&ctr_entropy); mbedtls_ctr_drbg_init(&ctr_drbg); + (void)data; ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &ctr_entropy, NULL, 0); - if(ret) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "mbedtls_ctr_drbg_seed returned (-0x%04X) %s", - -ret, errorbuf); - } - else { + if(!ret) ret = mbedtls_ctr_drbg_random(&ctr_drbg, entropy, length); - if(ret) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "mbedtls_ctr_drbg_random returned (-0x%04X) %s", - -ret, errorbuf); - } - } - mbedtls_ctr_drbg_free(&ctr_drbg); mbedtls_entropy_free(&ctr_entropy); @@ -1079,7 +1438,7 @@ mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, } if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we're allowed */ + /* Find out how much more time we are allowed */ timeout_ms = Curl_timeleft(data, NULL, TRUE); if(timeout_ms < 0) { @@ -1092,9 +1451,7 @@ mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, return retcode; } - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { + while(ssl_connect_2 == connssl->connecting_state) { /* check allowed time left */ timeout_ms = Curl_timeleft(data, NULL, TRUE); @@ -1105,14 +1462,12 @@ mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, return CURLE_OPERATION_TIMEDOUT; } - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + /* if ssl is expecting something, check if it is available. */ + if(connssl->io_need) { + curl_socket_t writefd = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? + sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = (connssl->io_need & CURL_SSL_IO_NEED_RECV) ? + sockfd : CURL_SOCKET_BAD; what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, nonblocking ? 0 : timeout_ms); @@ -1142,19 +1497,31 @@ mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, * ensuring that a client using select() or epoll() will always * have a valid fdset to wait on. */ + connssl->io_need = CURL_SSL_IO_NEED_NONE; retcode = mbed_connect_step2(cf, data); - if(retcode || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) + if(retcode || + (nonblocking && (ssl_connect_2 == connssl->connecting_state))) return retcode; } /* repeat step2 until all transactions are done. */ if(ssl_connect_3 == connssl->connecting_state) { - retcode = mbed_connect_step3(cf, data); - if(retcode) - return retcode; + /* For tls1.3 we get notified about new sessions */ +#if MBEDTLS_VERSION_NUMBER >= 0x03020000 + struct ssl_connect_data *ctx = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)ctx->backend; + + if(mbedtls_ssl_get_version_number(&backend->ssl) <= + MBEDTLS_SSL_VERSION_TLS1_2) { +#else + { /* no TLSv1.3 supported here */ +#endif + retcode = mbed_new_session(cf, data); + if(retcode) + return retcode; + } + connssl->connecting_state = ssl_connect_done; } if(ssl_connect_done == connssl->connecting_state) { @@ -1199,11 +1566,19 @@ static CURLcode mbedtls_connect(struct Curl_cfilter *cf, */ static int mbedtls_init(void) { - return Curl_mbedtlsthreadlock_thread_setup(); + if(!Curl_mbedtlsthreadlock_thread_setup()) + return 0; +#ifdef THREADING_SUPPORT + entropy_init_mutex(&ts_entropy); +#endif + return 1; } static void mbedtls_cleanup(void) { +#ifdef THREADING_SUPPORT + entropy_cleanup_mutex(&ts_entropy); +#endif (void)Curl_mbedtlsthreadlock_thread_cleanup(); } @@ -1255,9 +1630,14 @@ const struct Curl_ssl Curl_ssl_mbedtls = { SSLSUPP_CA_PATH | SSLSUPP_CAINFO_BLOB | + SSLSUPP_CERTINFO | SSLSUPP_PINNEDPUBKEY | SSLSUPP_SSL_CTX | - SSLSUPP_HTTPS_PROXY, +#ifdef TLS13_SUPPORT + SSLSUPP_TLS13_CIPHERSUITES | +#endif + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST, sizeof(struct mbed_ssl_backend_data), @@ -1265,17 +1645,16 @@ const struct Curl_ssl Curl_ssl_mbedtls = { mbedtls_cleanup, /* cleanup */ mbedtls_version, /* version */ Curl_none_check_cxn, /* check_cxn */ - Curl_none_shutdown, /* shutdown */ + mbedtls_shutdown, /* shutdown */ mbedtls_data_pending, /* data_pending */ mbedtls_random, /* random */ Curl_none_cert_status_request, /* cert_status_request */ mbedtls_connect, /* connect */ mbedtls_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks, /* getsock */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ mbedtls_get_internals, /* get_internals */ mbedtls_close, /* close_one */ mbedtls_close_all, /* close_all */ - mbedtls_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ @@ -1283,9 +1662,9 @@ const struct Curl_ssl Curl_ssl_mbedtls = { mbedtls_sha256sum, /* sha256sum */ NULL, /* associate_connection */ NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ mbed_recv, /* recv decrypted data */ mbed_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; #endif /* USE_MBEDTLS */ diff --git a/deps/curl/lib/vtls/mbedtls_threadlock.c b/deps/curl/lib/vtls/mbedtls_threadlock.c index bcb7106a636..b96a904fcb1 100644 --- a/deps/curl/lib/vtls/mbedtls_threadlock.c +++ b/deps/curl/lib/vtls/mbedtls_threadlock.c @@ -26,12 +26,12 @@ #if defined(USE_MBEDTLS) && \ ((defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)) || \ - defined(USE_THREADS_WIN32)) + defined(_WIN32)) #if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) # include # define MBEDTLS_MUTEX_T pthread_mutex_t -#elif defined(USE_THREADS_WIN32) +#elif defined(_WIN32) # define MBEDTLS_MUTEX_T HANDLE #endif @@ -51,7 +51,7 @@ int Curl_mbedtlsthreadlock_thread_setup(void) { int i; - mutex_buf = calloc(NUMT * sizeof(MBEDTLS_MUTEX_T), 1); + mutex_buf = calloc(1, NUMT * sizeof(MBEDTLS_MUTEX_T)); if(!mutex_buf) return 0; /* error, no number of threads defined */ @@ -59,7 +59,7 @@ int Curl_mbedtlsthreadlock_thread_setup(void) #if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) if(pthread_mutex_init(&mutex_buf[i], NULL)) return 0; /* pthread_mutex_init failed */ -#elif defined(USE_THREADS_WIN32) +#elif defined(_WIN32) mutex_buf[i] = CreateMutex(0, FALSE, 0); if(mutex_buf[i] == 0) return 0; /* CreateMutex failed */ @@ -80,7 +80,7 @@ int Curl_mbedtlsthreadlock_thread_cleanup(void) #if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) if(pthread_mutex_destroy(&mutex_buf[i])) return 0; /* pthread_mutex_destroy failed */ -#elif defined(USE_THREADS_WIN32) +#elif defined(_WIN32) if(!CloseHandle(mutex_buf[i])) return 0; /* CloseHandle failed */ #endif /* USE_THREADS_POSIX && HAVE_PTHREAD_H */ @@ -100,7 +100,7 @@ int Curl_mbedtlsthreadlock_lock_function(int n) "Error: mbedtlsthreadlock_lock_function failed\n")); return 0; /* pthread_mutex_lock failed */ } -#elif defined(USE_THREADS_WIN32) +#elif defined(_WIN32) if(WaitForSingleObject(mutex_buf[n], INFINITE) == WAIT_FAILED) { DEBUGF(fprintf(stderr, "Error: mbedtlsthreadlock_lock_function failed\n")); @@ -120,7 +120,7 @@ int Curl_mbedtlsthreadlock_unlock_function(int n) "Error: mbedtlsthreadlock_unlock_function failed\n")); return 0; /* pthread_mutex_unlock failed */ } -#elif defined(USE_THREADS_WIN32) +#elif defined(_WIN32) if(!ReleaseMutex(mutex_buf[n])) { DEBUGF(fprintf(stderr, "Error: mbedtlsthreadlock_unlock_function failed\n")); diff --git a/deps/curl/lib/vtls/mbedtls_threadlock.h b/deps/curl/lib/vtls/mbedtls_threadlock.h index 2b0bd41c8b9..484626852f5 100644 --- a/deps/curl/lib/vtls/mbedtls_threadlock.h +++ b/deps/curl/lib/vtls/mbedtls_threadlock.h @@ -29,7 +29,7 @@ #ifdef USE_MBEDTLS #if (defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)) || \ - defined(USE_THREADS_WIN32) + defined(_WIN32) int Curl_mbedtlsthreadlock_thread_setup(void); int Curl_mbedtlsthreadlock_thread_cleanup(void); @@ -43,7 +43,7 @@ int Curl_mbedtlsthreadlock_unlock_function(int n); #define Curl_mbedtlsthreadlock_lock_function(x) 1 #define Curl_mbedtlsthreadlock_unlock_function(x) 1 -#endif /* USE_THREADS_POSIX || USE_THREADS_WIN32 */ +#endif /* (USE_THREADS_POSIX && HAVE_PTHREAD_H) || _WIN32 */ #endif /* USE_MBEDTLS */ diff --git a/deps/curl/lib/vtls/openssl.c b/deps/curl/lib/vtls/openssl.c index a12e712b16e..e8be5d0ccd9 100644 --- a/deps/curl/lib/vtls/openssl.c +++ b/deps/curl/lib/vtls/openssl.c @@ -79,6 +79,19 @@ #include #include #include +#include +#include + +#ifdef USE_ECH +# ifndef OPENSSL_IS_BORINGSSL +# include +# endif +# include "curl_base64.h" +# define ECH_ENABLED(__data__) \ + (__data__->set.tls_ech && \ + !(__data__->set.tls_ech & CURLECH_DISABLE)\ + ) +#endif /* USE_ECH */ #if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_OCSP) #include @@ -96,6 +109,9 @@ #include "curl_memory.h" #include "memdebug.h" +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif /* Uncomment the ALLOW_RENEG line to a real #define if you want to allow TLS renegotiations when built with BoringSSL. Renegotiating is non-compliant @@ -173,8 +189,6 @@ #if (OPENSSL_VERSION_NUMBER >= 0x30000000L) #define HAVE_EVP_PKEY_GET_PARAMS 1 -#else -#define SSL_get1_peer_certificate SSL_get_peer_certificate #endif #ifdef HAVE_EVP_PKEY_GET_PARAMS @@ -190,12 +204,10 @@ * Whether SSL_CTX_set_keylog_callback is available. * OpenSSL: supported since 1.1.1 https://github.com/openssl/openssl/pull/2287 * BoringSSL: supported since d28f59c27bac (committed 2015-11-19) - * LibreSSL: supported since 3.5.0 (released 2022-02-24) + * LibreSSL: not supported. 3.5.0+ has a stub function that does nothing. */ #if (OPENSSL_VERSION_NUMBER >= 0x10101000L && \ !defined(LIBRESSL_VERSION_NUMBER)) || \ - (defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER >= 0x3050000fL) || \ defined(OPENSSL_IS_BORINGSSL) #define HAVE_KEYLOG_CALLBACK #endif @@ -219,7 +231,7 @@ /* * Whether SSL_CTX_set1_curves_list is available. * OpenSSL: supported since 1.0.2, see - * https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set1_groups.html + * https://docs.openssl.org/master/man3/SSL_CTX_set1_curves/ * BoringSSL: supported since 5fd1807d95f7 (committed 2016-09-30) * LibreSSL: since 2.5.3 (April 12, 2017) */ @@ -235,8 +247,19 @@ #elif defined(OPENSSL_IS_AWSLC) #define OSSL_PACKAGE "AWS-LC" #else -#define OSSL_PACKAGE "OpenSSL" +# if (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || defined(USE_MSH3) +# define OSSL_PACKAGE "quictls" +# else +# define OSSL_PACKAGE "OpenSSL" +#endif +#endif + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +typedef size_t numcert_t; +#else +typedef int numcert_t; #endif +#define ossl_valsize_t numcert_t #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* up2date versions of OpenSSL maintain reasonably secure defaults without @@ -244,7 +267,7 @@ */ #define DEFAULT_CIPHER_SELECTION NULL #else -/* ... but it is not the case with old versions of OpenSSL */ +/* not the case with old versions of OpenSSL */ #define DEFAULT_CIPHER_SELECTION \ "ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH" #endif @@ -291,51 +314,36 @@ typedef unsigned long sslerr_t; #define USE_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L) #endif /* !LIBRESSL_VERSION_NUMBER */ -struct ossl_ssl_backend_data { - /* these ones requires specific SSL-types */ - SSL_CTX* ctx; - SSL* handle; - X509* server_cert; - BIO_METHOD *bio_method; - CURLcode io_result; /* result of last BIO cfilter operation */ -#ifndef HAVE_KEYLOG_CALLBACK - /* Set to true once a valid keylog entry has been created to avoid dupes. */ - bool keylog_done; -#endif - bool x509_store_setup; /* x509 store has been set up */ -}; - -#if defined(HAVE_SSL_X509_STORE_SHARE) -struct multi_ssl_backend_data { - char *CAfile; /* CAfile path used to generate X509 store */ - X509_STORE *store; /* cached X509 store or NULL if none */ - struct curltime time; /* when the cached store was created */ -}; -#endif /* HAVE_SSL_X509_STORE_SHARE */ +static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl); -#define push_certinfo(_label, _num) \ -do { \ - long info_len = BIO_get_mem_data(mem, &ptr); \ - Curl_ssl_push_certinfo_len(data, _num, _label, ptr, info_len); \ - if(1 != BIO_reset(mem)) \ - break; \ -} while(0) +static CURLcode push_certinfo(struct Curl_easy *data, + BIO *mem, const char *label, int num) + WARN_UNUSED_RESULT; -static void pubkey_show(struct Curl_easy *data, - BIO *mem, - int num, - const char *type, - const char *name, - const BIGNUM *bn) +static CURLcode push_certinfo(struct Curl_easy *data, + BIO *mem, const char *label, int num) { char *ptr; + long len = BIO_get_mem_data(mem, &ptr); + CURLcode result = Curl_ssl_push_certinfo_len(data, num, label, ptr, len); + (void)BIO_reset(mem); + return result; +} + +static CURLcode pubkey_show(struct Curl_easy *data, + BIO *mem, + int num, + const char *type, + const char *name, + const BIGNUM *bn) +{ char namebuf[32]; msnprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name); if(bn) BN_print(mem, bn); - push_certinfo(namebuf, num); + return push_certinfo(data, mem, namebuf, num); } #ifdef HAVE_OPAQUE_RSA_DSA_DH @@ -367,25 +375,26 @@ static int asn1_object_dump(ASN1_OBJECT *a, char *buf, size_t len) return 0; } -static void X509V3_ext(struct Curl_easy *data, - int certnum, - CONST_EXTS STACK_OF(X509_EXTENSION) *exts) +static CURLcode X509V3_ext(struct Curl_easy *data, + int certnum, + CONST_EXTS STACK_OF(X509_EXTENSION) *exts) { int i; + CURLcode result = CURLE_OK; if((int)sk_X509_EXTENSION_num(exts) <= 0) /* no extensions, bail out */ - return; + return result; for(i = 0; i < (int)sk_X509_EXTENSION_num(exts); i++) { ASN1_OBJECT *obj; - X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, i); + X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, (ossl_valsize_t)i); BUF_MEM *biomem; char namebuf[128]; BIO *bio_out = BIO_new(BIO_s_mem()); if(!bio_out) - return; + return result; obj = X509_EXTENSION_get_object(ext); @@ -395,19 +404,16 @@ static void X509V3_ext(struct Curl_easy *data, ASN1_STRING_print(bio_out, (ASN1_STRING *)X509_EXTENSION_get_data(ext)); BIO_get_mem_ptr(bio_out, &biomem); - Curl_ssl_push_certinfo_len(data, certnum, namebuf, biomem->data, - biomem->length); + result = Curl_ssl_push_certinfo_len(data, certnum, namebuf, biomem->data, + biomem->length); BIO_free(bio_out); + if(result) + break; } + return result; } -#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) -typedef size_t numcert_t; -#else -typedef int numcert_t; -#endif - -CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) +static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl) { CURLcode result; STACK_OF(X509) *sk; @@ -425,38 +431,43 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) numcerts = sk_X509_num(sk); result = Curl_ssl_init_certinfo(data, (int)numcerts); - if(result) { + if(result) return result; - } mem = BIO_new(BIO_s_mem()); - if(!mem) { - return CURLE_OUT_OF_MEMORY; - } + if(!mem) + result = CURLE_OUT_OF_MEMORY; - for(i = 0; i < (int)numcerts; i++) { + for(i = 0; !result && (i < (int)numcerts); i++) { ASN1_INTEGER *num; - X509 *x = sk_X509_value(sk, i); + X509 *x = sk_X509_value(sk, (ossl_valsize_t)i); EVP_PKEY *pubkey = NULL; int j; - char *ptr; const ASN1_BIT_STRING *psig = NULL; X509_NAME_print_ex(mem, X509_get_subject_name(x), 0, XN_FLAG_ONELINE); - push_certinfo("Subject", i); + result = push_certinfo(data, mem, "Subject", i); + if(result) + break; X509_NAME_print_ex(mem, X509_get_issuer_name(x), 0, XN_FLAG_ONELINE); - push_certinfo("Issuer", i); + result = push_certinfo(data, mem, "Issuer", i); + if(result) + break; BIO_printf(mem, "%lx", X509_get_version(x)); - push_certinfo("Version", i); + result = push_certinfo(data, mem, "Version", i); + if(result) + break; num = X509_get_serialNumber(x); if(num->type == V_ASN1_NEG_INTEGER) BIO_puts(mem, "-"); for(j = 0; j < num->length; j++) BIO_printf(mem, "%02x", num->data[j]); - push_certinfo("Serial Number", i); + result = push_certinfo(data, mem, "Serial Number", i); + if(result) + break; #if defined(HAVE_X509_GET0_SIGNATURE) && defined(HAVE_X509_GET0_EXTENSIONS) { @@ -466,8 +477,12 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) X509_get0_signature(&psig, &sigalg, x); if(sigalg) { - i2a_ASN1_OBJECT(mem, sigalg->algorithm); - push_certinfo("Signature Algorithm", i); + const ASN1_OBJECT *sigalgoid = NULL; + X509_ALGOR_get0(&sigalgoid, NULL, NULL, sigalg); + i2a_ASN1_OBJECT(mem, sigalgoid); + result = push_certinfo(data, mem, "Signature Algorithm", i); + if(result) + break; } xpubkey = X509_get_X509_PUBKEY(x); @@ -475,11 +490,15 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) X509_PUBKEY_get0_param(&pubkeyoid, NULL, NULL, NULL, xpubkey); if(pubkeyoid) { i2a_ASN1_OBJECT(mem, pubkeyoid); - push_certinfo("Public Key Algorithm", i); + result = push_certinfo(data, mem, "Public Key Algorithm", i); + if(result) + break; } } - X509V3_ext(data, i, X509_get0_extensions(x)); + result = X509V3_ext(data, i, X509_get0_extensions(x)); + if(result) + break; } #else { @@ -487,22 +506,32 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) X509_CINF *cinf = x->cert_info; i2a_ASN1_OBJECT(mem, cinf->signature->algorithm); - push_certinfo("Signature Algorithm", i); + result = push_certinfo(data, mem, "Signature Algorithm", i); + + if(!result) { + i2a_ASN1_OBJECT(mem, cinf->key->algor->algorithm); + result = push_certinfo(data, mem, "Public Key Algorithm", i); + } - i2a_ASN1_OBJECT(mem, cinf->key->algor->algorithm); - push_certinfo("Public Key Algorithm", i); + if(!result) + result = X509V3_ext(data, i, cinf->extensions); - X509V3_ext(data, i, cinf->extensions); + if(result) + break; psig = x->signature; } #endif ASN1_TIME_print(mem, X509_get0_notBefore(x)); - push_certinfo("Start date", i); + result = push_certinfo(data, mem, "Start date", i); + if(result) + break; ASN1_TIME_print(mem, X509_get0_notAfter(x)); - push_certinfo("Expire date", i); + result = push_certinfo(data, mem, "Expire date", i); + if(result) + break; pubkey = X509_get_pubkey(x); if(!pubkey) @@ -515,8 +544,7 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) pktype = pubkey->type; #endif switch(pktype) { - case EVP_PKEY_RSA: - { + case EVP_PKEY_RSA: { #ifndef HAVE_EVP_PKEY_GET_PARAMS RSA *rsa; #ifdef HAVE_OPAQUE_EVP_PKEY @@ -536,11 +564,13 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) #else RSA_get0_key(rsa, &n, &e, NULL); #endif /* HAVE_EVP_PKEY_GET_PARAMS */ - BIO_printf(mem, "%d", BN_num_bits(n)); + BIO_printf(mem, "%d", n ? BN_num_bits(n) : 0); #else - BIO_printf(mem, "%d", BN_num_bits(rsa->n)); + BIO_printf(mem, "%d", rsa->n ? BN_num_bits(rsa->n) : 0); #endif /* HAVE_OPAQUE_RSA_DSA_DH */ - push_certinfo("RSA Public Key", i); + result = push_certinfo(data, mem, "RSA Public Key", i); + if(result) + break; print_pubkey_BN(rsa, n, i); print_pubkey_BN(rsa, e, i); FREE_PKEY_PARAM_BIGNUM(n); @@ -588,8 +618,7 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) #endif /* !OPENSSL_NO_DSA */ break; } - case EVP_PKEY_DH: - { + case EVP_PKEY_DH: { #ifndef HAVE_EVP_PKEY_GET_PARAMS DH *dh; #ifdef HAVE_OPAQUE_EVP_PKEY @@ -632,19 +661,25 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) EVP_PKEY_free(pubkey); } - if(psig) { + if(!result && psig) { for(j = 0; j < psig->length; j++) BIO_printf(mem, "%02x:", psig->data[j]); - push_certinfo("Signature", i); + result = push_certinfo(data, mem, "Signature", i); } - PEM_write_bio_X509(mem, x); - push_certinfo("Cert", i); + if(!result) { + PEM_write_bio_X509(mem, x); + result = push_certinfo(data, mem, "Cert", i); + } } BIO_free(mem); - return CURLE_OK; + if(result) + /* cleanup all leftovers */ + Curl_ssl_free_certinfo(data); + + return result; } #endif /* quiche or OpenSSL */ @@ -661,7 +696,7 @@ CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) #define BIO_set_shutdown(x,v) ((x)->shutdown=(v)) #endif /* USE_PRE_1_1_API */ -static int bio_cf_create(BIO *bio) +static int ossl_bio_cf_create(BIO *bio) { BIO_set_shutdown(bio, 1); BIO_set_init(bio, 1); @@ -672,14 +707,14 @@ static int bio_cf_create(BIO *bio) return 1; } -static int bio_cf_destroy(BIO *bio) +static int ossl_bio_cf_destroy(BIO *bio) { if(!bio) return 0; return 1; } -static long bio_cf_ctrl(BIO *bio, int cmd, long num, void *ptr) +static long ossl_bio_cf_ctrl(BIO *bio, int cmd, long num, void *ptr) { struct Curl_cfilter *cf = BIO_get_data(bio); long ret = 1; @@ -713,22 +748,25 @@ static long bio_cf_ctrl(BIO *bio, int cmd, long num, void *ptr) return ret; } -static int bio_cf_out_write(BIO *bio, const char *buf, int blen) +static int ossl_bio_cf_out_write(BIO *bio, const char *buf, int blen) { struct Curl_cfilter *cf = BIO_get_data(bio); struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nwritten; CURLcode result = CURLE_SEND_ERROR; DEBUGASSERT(data); - nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result); - CURL_TRC_CF(data, cf, "bio_cf_out_write(len=%d) -> %d, err=%d", + if(blen < 0) + return 0; + + nwritten = Curl_conn_cf_send(cf->next, data, buf, (size_t)blen, FALSE, + &result); + CURL_TRC_CF(data, cf, "ossl_bio_cf_out_write(len=%d) -> %d, err=%d", blen, (int)nwritten, result); BIO_clear_retry_flags(bio); - backend->io_result = result; + octx->io_result = result; if(nwritten < 0) { if(CURLE_AGAIN == result) BIO_set_retry_write(bio); @@ -736,12 +774,11 @@ static int bio_cf_out_write(BIO *bio, const char *buf, int blen) return (int)nwritten; } -static int bio_cf_in_read(BIO *bio, char *buf, int blen) +static int ossl_bio_cf_in_read(BIO *bio, char *buf, int blen) { struct Curl_cfilter *cf = BIO_get_data(bio); struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result = CURLE_RECV_ERROR; @@ -750,26 +787,31 @@ static int bio_cf_in_read(BIO *bio, char *buf, int blen) /* OpenSSL catches this case, so should we. */ if(!buf) return 0; + if(blen < 0) + return 0; - nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); - CURL_TRC_CF(data, cf, "bio_cf_in_read(len=%d) -> %d, err=%d", + nread = Curl_conn_cf_recv(cf->next, data, buf, (size_t)blen, &result); + CURL_TRC_CF(data, cf, "ossl_bio_cf_in_read(len=%d) -> %d, err=%d", blen, (int)nread, result); BIO_clear_retry_flags(bio); - backend->io_result = result; + octx->io_result = result; if(nread < 0) { if(CURLE_AGAIN == result) BIO_set_retry_read(bio); } + else if(nread == 0) { + connssl->peer_closed = TRUE; + } /* Before returning server replies to the SSL instance, we need * to have setup the x509 store or verification will fail. */ - if(!backend->x509_store_setup) { - result = Curl_ssl_setup_x509_store(cf, data, backend->ctx); + if(!octx->x509_store_setup) { + result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); if(result) { - backend->io_result = result; + octx->io_result = result; return -1; } - backend->x509_store_setup = TRUE; + octx->x509_store_setup = TRUE; } return (int)nread; @@ -777,42 +819,42 @@ static int bio_cf_in_read(BIO *bio, char *buf, int blen) #if USE_PRE_1_1_API -static BIO_METHOD bio_cf_meth_1_0 = { +static BIO_METHOD ossl_bio_cf_meth_1_0 = { BIO_TYPE_MEM, "OpenSSL CF BIO", - bio_cf_out_write, - bio_cf_in_read, + ossl_bio_cf_out_write, + ossl_bio_cf_in_read, NULL, /* puts is never called */ NULL, /* gets is never called */ - bio_cf_ctrl, - bio_cf_create, - bio_cf_destroy, + ossl_bio_cf_ctrl, + ossl_bio_cf_create, + ossl_bio_cf_destroy, NULL }; -static BIO_METHOD *bio_cf_method_create(void) +static BIO_METHOD *ossl_bio_cf_method_create(void) { - return &bio_cf_meth_1_0; + return &ossl_bio_cf_meth_1_0; } -#define bio_cf_method_free(m) Curl_nop_stmt +#define ossl_bio_cf_method_free(m) Curl_nop_stmt #else -static BIO_METHOD *bio_cf_method_create(void) +static BIO_METHOD *ossl_bio_cf_method_create(void) { BIO_METHOD *m = BIO_meth_new(BIO_TYPE_MEM, "OpenSSL CF BIO"); if(m) { - BIO_meth_set_write(m, &bio_cf_out_write); - BIO_meth_set_read(m, &bio_cf_in_read); - BIO_meth_set_ctrl(m, &bio_cf_ctrl); - BIO_meth_set_create(m, &bio_cf_create); - BIO_meth_set_destroy(m, &bio_cf_destroy); + BIO_meth_set_write(m, &ossl_bio_cf_out_write); + BIO_meth_set_read(m, &ossl_bio_cf_in_read); + BIO_meth_set_ctrl(m, &ossl_bio_cf_ctrl); + BIO_meth_set_create(m, &ossl_bio_cf_create); + BIO_meth_set_destroy(m, &ossl_bio_cf_destroy); } return m; } -static void bio_cf_method_free(BIO_METHOD *m) +static void ossl_bio_cf_method_free(BIO_METHOD *m) { if(m) BIO_meth_free(m); @@ -839,7 +881,7 @@ static void ossl_keylog_callback(const SSL *ssl, const char *line) #else /* * ossl_log_tls12_secret is called by libcurl to make the CLIENT_RANDOMs if the - * OpenSSL being used doesn't have native support for doing that. + * OpenSSL being used does not have native support for doing that. */ static void ossl_log_tls12_secret(const SSL *ssl, bool *keylog_done) @@ -855,7 +897,7 @@ ossl_log_tls12_secret(const SSL *ssl, bool *keylog_done) #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !(defined(LIBRESSL_VERSION_NUMBER) && \ LIBRESSL_VERSION_NUMBER < 0x20700000L) - /* ssl->s3 is not checked in openssl 1.1.0-pre6, but let's assume that + /* ssl->s3 is not checked in OpenSSL 1.1.0-pre6, but let's assume that * we have a valid SSL context if we have a non-NULL session. */ SSL_get_client_random(ssl, client_random, SSL3_RANDOM_SIZE); master_key_length = (int) @@ -874,7 +916,7 @@ ossl_log_tls12_secret(const SSL *ssl, bool *keylog_done) if(master_key_length <= 0) return; - *keylog_done = true; + *keylog_done = TRUE; Curl_tls_keylog_write("CLIENT_RANDOM", client_random, master_key, master_key_length); } @@ -945,8 +987,9 @@ static char *ossl_strerror(unsigned long error, char *buf, size_t size) #endif if(!*buf) { - strncpy(buf, (error ? "Unknown error" : "No error"), size); - buf[size - 1] = '\0'; + const char *msg = error ? "Unknown error" : "No error"; + if(strlen(msg) < size) + strcpy(buf, msg); } return buf; @@ -957,7 +1000,7 @@ static int passwd_callback(char *buf, int num, int encrypting, { DEBUGASSERT(0 == encrypting); - if(!encrypting) { + if(!encrypting && num >= 0) { int klen = curlx_uztosi(strlen((char *)global_passwd)); if(num > klen) { memcpy(buf, global_passwd, klen + 1); @@ -972,7 +1015,7 @@ static int passwd_callback(char *buf, int num, int encrypting, */ static bool rand_enough(void) { - return (0 != RAND_status()) ? TRUE : FALSE; + return (0 != RAND_status()); } static CURLcode ossl_seed(struct Curl_easy *data) @@ -993,12 +1036,6 @@ static CURLcode ossl_seed(struct Curl_easy *data) return CURLE_SSL_CONNECT_ERROR; #else -#ifdef RANDOM_FILE - RAND_load_file(RANDOM_FILE, RAND_LOAD_LENGTH); - if(rand_enough()) - return CURLE_OK; -#endif - /* fallback to a custom seeding of the PRNG using a hash based on a current time */ do { @@ -1008,13 +1045,12 @@ static CURLcode ossl_seed(struct Curl_easy *data) for(i = 0, i_max = len / sizeof(struct curltime); i < i_max; ++i) { struct curltime tv = Curl_now(); Curl_wait_ms(1); - tv.tv_sec *= i + 1; - tv.tv_usec *= (unsigned int)i + 2; - tv.tv_sec ^= ((Curl_now().tv_sec + Curl_now().tv_usec) * - (i + 3)) << 8; - tv.tv_usec ^= (unsigned int) ((Curl_now().tv_sec + - Curl_now().tv_usec) * - (i + 4)) << 16; + tv.tv_sec *= (time_t)i + 1; + tv.tv_usec *= (int)i + 2; + tv.tv_sec ^= ((Curl_now().tv_sec + (time_t)Curl_now().tv_usec) * + (time_t)(i + 3)) << 8; + tv.tv_usec ^= (int) ((Curl_now().tv_sec + (time_t)Curl_now().tv_usec) * + (time_t)(i + 4)) << 16; memcpy(&randb[i * sizeof(struct curltime)], &tv, sizeof(struct curltime)); } @@ -1027,7 +1063,7 @@ static CURLcode ossl_seed(struct Curl_easy *data) fname[0] = 0; /* blank it first */ RAND_file_name(fname, sizeof(fname)); if(fname[0]) { - /* we got a file name to try */ + /* we got a filename to try */ RAND_load_file(fname, RAND_LOAD_LENGTH); if(rand_enough()) return CURLE_OK; @@ -1046,7 +1082,7 @@ static CURLcode ossl_seed(struct Curl_easy *data) #ifndef SSL_FILETYPE_PKCS12 #define SSL_FILETYPE_PKCS12 43 #endif -static int do_file_type(const char *type) +static int ossl_do_file_type(const char *type) { if(!type || !type[0]) return SSL_FILETYPE_PEM; @@ -1078,6 +1114,7 @@ static int ssl_ui_reader(UI *ui, UI_STRING *uis) UI_set_result(ui, uis, password); return 1; } + FALLTHROUGH(); default: break; } @@ -1096,6 +1133,7 @@ static int ssl_ui_writer(UI *ui, UI_STRING *uis) (UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD)) { return 1; } + FALLTHROUGH(); default: break; } @@ -1266,7 +1304,7 @@ int cert_stuff(struct Curl_easy *data, char error_buffer[256]; bool check_privkey = TRUE; - int file_type = do_file_type(cert_type); + int file_type = ossl_do_file_type(cert_type); if(cert_file || cert_blob || (file_type == SSL_FILETYPE_ENGINE)) { SSL *ssl; @@ -1361,7 +1399,7 @@ int cert_stuff(struct Curl_easy *data, } if(!params.cert) { - failf(data, "ssl engine didn't initialized the certificate " + failf(data, "ssl engine did not initialized the certificate " "properly."); return 0; } @@ -1372,10 +1410,10 @@ int cert_stuff(struct Curl_easy *data, sizeof(error_buffer))); return 0; } - X509_free(params.cert); /* we don't need the handle any more... */ + X509_free(params.cert); /* we do not need the handle any more... */ } else { - failf(data, "crypto engine not set, can't load certificate"); + failf(data, "crypto engine not set, cannot load certificate"); return 0; } } @@ -1471,7 +1509,7 @@ int cert_stuff(struct Curl_easy *data, * Note that sk_X509_pop() is used below to make sure the cert is * removed from the stack properly before getting passed to * SSL_CTX_add_extra_chain_cert(), which takes ownership. Previously - * we used sk_X509_value() instead, but then we'd clean it in the + * we used sk_X509_value() instead, but then we would clean it in the * subsequent sk_X509_pop_free() call. */ X509 *x = sk_X509_pop(ca); @@ -1507,20 +1545,21 @@ int cert_stuff(struct Curl_easy *data, key_blob = cert_blob; } else - file_type = do_file_type(key_type); + file_type = ossl_do_file_type(key_type); switch(file_type) { case SSL_FILETYPE_PEM: if(cert_done) break; - /* FALLTHROUGH */ + FALLTHROUGH(); case SSL_FILETYPE_ASN1: cert_use_result = key_blob ? SSL_CTX_use_PrivateKey_blob(ctx, key_blob, file_type, key_passwd) : SSL_CTX_use_PrivateKey_file(ctx, key_file, file_type); if(cert_use_result != 1) { failf(data, "unable to set private key file: '%s' type %s", - key_file?key_file:"(memory blob)", key_type?key_type:"PEM"); + key_file ? key_file : "(memory blob)", + key_type ? key_type : "PEM"); return 0; } break; @@ -1551,11 +1590,9 @@ int cert_stuff(struct Curl_easy *data, UI_method_set_closer(ui_method, UI_method_get_closer(UI_OpenSSL())); UI_method_set_reader(ui_method, ssl_ui_reader); UI_method_set_writer(ui_method, ssl_ui_writer); - /* the typecast below was added to please mingw32 */ - priv_key = (EVP_PKEY *) - ENGINE_load_private_key(data->state.engine, key_file, - ui_method, - key_passwd); + priv_key = ENGINE_load_private_key(data->state.engine, key_file, + ui_method, + key_passwd); UI_destroy_method(ui_method); if(!priv_key) { failf(data, "failed to load private key from crypto engine"); @@ -1566,10 +1603,10 @@ int cert_stuff(struct Curl_easy *data, EVP_PKEY_free(priv_key); return 0; } - EVP_PKEY_free(priv_key); /* we don't need the handle any more... */ + EVP_PKEY_free(priv_key); /* we do not need the handle any more... */ } else { - failf(data, "crypto engine not set, can't load private key"); + failf(data, "crypto engine not set, cannot load private key"); return 0; } } @@ -1608,8 +1645,8 @@ int cert_stuff(struct Curl_easy *data, #if !defined(OPENSSL_NO_RSA) && !defined(OPENSSL_IS_BORINGSSL) && \ !defined(OPENSSL_NO_DEPRECATED_3_0) { - /* If RSA is used, don't check the private key if its flags indicate - * it doesn't support it. */ + /* If RSA is used, do not check the private key if its flags indicate + * it does not support it. */ EVP_PKEY *priv_key = SSL_get_privatekey(ssl); int pktype; #ifdef HAVE_OPAQUE_EVP_PKEY @@ -1643,46 +1680,24 @@ int cert_stuff(struct Curl_easy *data, return 1; } -CURLcode Curl_ossl_set_client_cert(struct Curl_easy *data, SSL_CTX *ctx, - char *cert_file, - const struct curl_blob *cert_blob, - const char *cert_type, char *key_file, - const struct curl_blob *key_blob, - const char *key_type, char *key_passwd) -{ - int rv = cert_stuff(data, ctx, cert_file, cert_blob, cert_type, key_file, - key_blob, key_type, key_passwd); - if(rv != 1) { - return CURLE_SSL_CERTPROBLEM; - } - - return CURLE_OK; -} - /* returns non-zero on failure */ -static int x509_name_oneline(X509_NAME *a, char *buf, size_t size) +static CURLcode x509_name_oneline(X509_NAME *a, struct dynbuf *d) { BIO *bio_out = BIO_new(BIO_s_mem()); BUF_MEM *biomem; int rc; - - if(!bio_out) - return 1; /* alloc failed! */ - - rc = X509_NAME_print_ex(bio_out, a, 0, XN_FLAG_SEP_SPLUS_SPC); - BIO_get_mem_ptr(bio_out, &biomem); - - if((size_t)biomem->length < size) - size = biomem->length; - else - size--; /* don't overwrite the buffer end */ - - memcpy(buf, biomem->data, size); - buf[size] = 0; - - BIO_free(bio_out); - - return !rc; + CURLcode result = CURLE_OUT_OF_MEMORY; + + if(bio_out) { + Curl_dyn_reset(d); + rc = X509_NAME_print_ex(bio_out, a, 0, XN_FLAG_SEP_SPLUS_SPC); + if(rc != -1) { + BIO_get_mem_ptr(bio_out, &biomem); + result = Curl_dyn_addn(d, biomem->data, biomem->length); + BIO_free(bio_out); + } + } + return result; } /** @@ -1745,7 +1760,7 @@ static int ossl_init(void) static void ossl_cleanup(void) { #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ - !defined(LIBRESSL_VERSION_NUMBER) + (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER >= 0x2070000fL) /* OpenSSL 1.1 deprecates all these cleanup functions and turns them into no-ops in OpenSSL 1.0 compatibility mode */ #else @@ -1867,156 +1882,150 @@ static struct curl_slist *ossl_engines_list(struct Curl_easy *data) return list; } -static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data) +static CURLcode ossl_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; - - (void)data; - DEBUGASSERT(backend); - - if(backend->handle) { - if(cf->next && cf->next->connected) { - char buf[32]; - /* Maybe the server has already sent a close notify alert. - Read it to avoid an RST on the TCP connection. */ - (void)SSL_read(backend->handle, buf, (int)sizeof(buf)); - - (void)SSL_shutdown(backend->handle); + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + CURLcode result = CURLE_OK; + char buf[1024]; + int nread = -1, err; + unsigned long sslerr; + size_t i; - ERR_clear_error(); + DEBUGASSERT(octx); + if(!octx->ssl || cf->shutdown) { + *done = TRUE; + goto out; + } - SSL_set_connect_state(backend->handle); + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + if(!(SSL_get_shutdown(octx->ssl) & SSL_SENT_SHUTDOWN)) { + /* We have not started the shutdown from our side yet. Check + * if the server already sent us one. */ + ERR_clear_error(); + for(i = 0; i < 10; ++i) { + nread = SSL_read(octx->ssl, buf, (int)sizeof(buf)); + CURL_TRC_CF(data, cf, "SSL shutdown not sent, read -> %d", nread); + if(nread <= 0) + break; + } + err = SSL_get_error(octx->ssl, nread); + if(!nread && err == SSL_ERROR_ZERO_RETURN) { + bool input_pending; + /* Yes, it did. */ + if(!send_shutdown) { + CURL_TRC_CF(data, cf, "SSL shutdown received, not sending"); + *done = TRUE; + goto out; + } + else if(!cf->next->cft->is_alive(cf->next, data, &input_pending)) { + /* Server closed the connection after its closy notify. It + * seems not interested to see our close notify, so do not + * send it. We are done. */ + connssl->peer_closed = TRUE; + CURL_TRC_CF(data, cf, "peer closed connection"); + *done = TRUE; + goto out; + } } + } - SSL_free(backend->handle); - backend->handle = NULL; + /* SSL should now have started the shutdown from our side. Since it + * was not complete, we are lacking the close notify from the server. */ + if(send_shutdown && !(SSL_get_shutdown(octx->ssl) & SSL_SENT_SHUTDOWN)) { + ERR_clear_error(); + CURL_TRC_CF(data, cf, "send SSL close notify"); + if(SSL_shutdown(octx->ssl) == 1) { + CURL_TRC_CF(data, cf, "SSL shutdown finished"); + *done = TRUE; + goto out; + } + if(SSL_ERROR_WANT_WRITE == SSL_get_error(octx->ssl, nread)) { + CURL_TRC_CF(data, cf, "SSL shutdown still wants to send"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + goto out; + } + /* Having sent the close notify, we use SSL_read() to get the + * missing close notify from the server. */ } - if(backend->ctx) { - SSL_CTX_free(backend->ctx); - backend->ctx = NULL; - backend->x509_store_setup = FALSE; + + for(i = 0; i < 10; ++i) { + ERR_clear_error(); + nread = SSL_read(octx->ssl, buf, (int)sizeof(buf)); + CURL_TRC_CF(data, cf, "SSL shutdown read -> %d", nread); + if(nread <= 0) + break; } - if(backend->bio_method) { - bio_cf_method_free(backend->bio_method); - backend->bio_method = NULL; + err = SSL_get_error(octx->ssl, nread); + switch(err) { + case SSL_ERROR_ZERO_RETURN: /* no more data */ + if(SSL_shutdown(octx->ssl) == 1) + CURL_TRC_CF(data, cf, "SSL shutdown finished"); + else + CURL_TRC_CF(data, cf, "SSL shutdown not received, but closed"); + *done = TRUE; + break; + case SSL_ERROR_NONE: /* just did not get anything */ + case SSL_ERROR_WANT_READ: + /* SSL has send its notify and now wants to read the reply + * from the server. We are not really interested in that. */ + CURL_TRC_CF(data, cf, "SSL shutdown sent, want receive"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + break; + case SSL_ERROR_WANT_WRITE: + CURL_TRC_CF(data, cf, "SSL shutdown send blocked"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + break; + default: + /* Server seems to have closed the connection without sending us + * a close notify. */ + sslerr = ERR_get_error(); + CURL_TRC_CF(data, cf, "SSL shutdown, ignore recv error: '%s', errno %d", + (sslerr ? + ossl_strerror(sslerr, buf, sizeof(buf)) : + SSL_ERROR_to_str(err)), + SOCKERRNO); + *done = TRUE; + result = CURLE_OK; + break; } + +out: + cf->shutdown = (result || *done); + return result; } -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -static int ossl_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data) +static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data) { - int retval = 0; struct ssl_connect_data *connssl = cf->ctx; - char buf[256]; /* We will use this for the OpenSSL error buffer, so it has - to be at least 256 bytes long. */ - unsigned long sslerror; - int nread; - int buffsize; - int err; - bool done = FALSE; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; - int loop = 10; - - DEBUGASSERT(backend); - -#ifndef CURL_DISABLE_FTP - /* This has only been tested on the proftpd server, and the mod_tls code - sends a close notify alert without waiting for a close notify alert in - response. Thus we wait for a close notify alert from the server, but - we do not send one. Let's hope other servers do the same... */ - - if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) - (void)SSL_shutdown(backend->handle); -#endif - - if(backend->handle) { - buffsize = (int)sizeof(buf); - while(!done && loop--) { - int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), - SSL_SHUTDOWN_TIMEOUT); - if(what > 0) { - ERR_clear_error(); - - /* Something to read, let's do it and hope that it is the close - notify alert from the server */ - nread = SSL_read(backend->handle, buf, buffsize); - err = SSL_get_error(backend->handle, nread); - - switch(err) { - case SSL_ERROR_NONE: /* this is not an error */ - case SSL_ERROR_ZERO_RETURN: /* no more data */ - /* This is the expected response. There was no data but only - the close notify alert */ - done = TRUE; - break; - case SSL_ERROR_WANT_READ: - /* there's data pending, re-invoke SSL_read() */ - infof(data, "SSL_ERROR_WANT_READ"); - break; - case SSL_ERROR_WANT_WRITE: - /* SSL wants a write. Really odd. Let's bail out. */ - infof(data, "SSL_ERROR_WANT_WRITE"); - done = TRUE; - break; - default: - /* openssl/ssl.h says "look at error stack/return value/errno" */ - sslerror = ERR_get_error(); - failf(data, OSSL_PACKAGE " SSL_read on shutdown: %s, errno %d", - (sslerror ? - ossl_strerror(sslerror, buf, sizeof(buf)) : - SSL_ERROR_to_str(err)), - SOCKERRNO); - done = TRUE; - break; - } - } - else if(0 == what) { - /* timeout */ - failf(data, "SSL shutdown timeout"); - done = TRUE; - } - else { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - retval = -1; - done = TRUE; - } - } /* while()-loop for the select() */ + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; - if(data->set.verbose) { -#ifdef HAVE_SSL_GET_SHUTDOWN - switch(SSL_get_shutdown(backend->handle)) { - case SSL_SENT_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN"); - break; - case SSL_RECEIVED_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_RECEIVED_SHUTDOWN"); - break; - case SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN|" - "SSL_RECEIVED__SHUTDOWN"); - break; - } -#endif - } + (void)data; + DEBUGASSERT(octx); - SSL_free(backend->handle); - backend->handle = NULL; + if(octx->ssl) { + SSL_free(octx->ssl); + octx->ssl = NULL; + } + if(octx->ssl_ctx) { + SSL_CTX_free(octx->ssl_ctx); + octx->ssl_ctx = NULL; + octx->x509_store_setup = FALSE; + } + if(octx->bio_method) { + ossl_bio_cf_method_free(octx->bio_method); + octx->bio_method = NULL; } - return retval; } -static void ossl_session_free(void *ptr) +static void ossl_session_free(void *sessionid, size_t idsize) { /* free the ID */ - SSL_SESSION_free(ptr); + (void)idsize; + free(sessionid); } /* @@ -2047,7 +2056,7 @@ static void ossl_close_all(struct Curl_easy *data) /* ====================================================== */ /* - * Match subjectAltName against the host name. + * Match subjectAltName against the hostname. */ static bool subj_alt_hostcheck(struct Curl_easy *data, const char *match_pattern, @@ -2068,22 +2077,6 @@ static bool subj_alt_hostcheck(struct Curl_easy *data, return FALSE; } -static CURLcode -ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, - X509 *server_cert, const char *hostname, - const char *dispname); - -CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, - X509 *server_cert) -{ - const char *hostname, *dispname; - int port; - - (void)conn; - Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &dispname, &port); - return ossl_verifyhost(data, conn, server_cert, hostname, dispname); -} - /* Quote from RFC2818 section 3.1 "Server Identity" If a subjectAltName extension of type dNSName is present, that MUST @@ -2093,7 +2086,7 @@ CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, Certification Authorities are encouraged to use the dNSName instead. Matching is performed using the matching rules specified by - [RFC2459]. If more than one identity of a given type is present in + [RFC2459]. If more than one identity of a given type is present in the certificate (e.g., more than one dNSName name, a match in any one of the set is considered acceptable.) Names may contain the wildcard character * which is considered to match any single domain name @@ -2106,45 +2099,49 @@ CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, This function is now used from ngtcp2 (QUIC) as well. */ -static CURLcode -ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, - X509 *server_cert, const char *hostname, - const char *dispname) +static CURLcode ossl_verifyhost(struct Curl_easy *data, + struct connectdata *conn, + struct ssl_peer *peer, X509 *server_cert) { bool matched = FALSE; - int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */ + int target; /* target type, GEN_DNS or GEN_IPADD */ size_t addrlen = 0; STACK_OF(GENERAL_NAME) *altnames; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct in6_addr addr; #else struct in_addr addr; #endif CURLcode result = CURLE_OK; bool dNSName = FALSE; /* if a dNSName field exists in the cert */ - bool iPAddress = FALSE; /* if a iPAddress field exists in the cert */ + bool iPAddress = FALSE; /* if an iPAddress field exists in the cert */ size_t hostlen; (void)conn; - hostlen = strlen(hostname); - -#ifndef ENABLE_IPV6 - /* Silence compiler warnings for unused params */ - (void) conn; -#endif - -#ifdef ENABLE_IPV6 - if(conn->bits.ipv6_ip && - Curl_inet_pton(AF_INET6, hostname, &addr)) { + hostlen = strlen(peer->hostname); + switch(peer->type) { + case CURL_SSL_PEER_IPV4: + if(!Curl_inet_pton(AF_INET, peer->hostname, &addr)) + return CURLE_PEER_FAILED_VERIFICATION; + target = GEN_IPADD; + addrlen = sizeof(struct in_addr); + break; +#ifdef USE_IPV6 + case CURL_SSL_PEER_IPV6: + if(!Curl_inet_pton(AF_INET6, peer->hostname, &addr)) + return CURLE_PEER_FAILED_VERIFICATION; target = GEN_IPADD; addrlen = sizeof(struct in6_addr); - } - else + break; #endif - if(Curl_inet_pton(AF_INET, hostname, &addr)) { - target = GEN_IPADD; - addrlen = sizeof(struct in_addr); - } + case CURL_SSL_PEER_DNS: + target = GEN_DNS; + break; + default: + DEBUGASSERT(0); + failf(data, "unexpected ssl peer type: %d", peer->type); + return CURLE_PEER_FAILED_VERIFICATION; + } /* get a "list" of alternative names */ altnames = X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL); @@ -2161,7 +2158,7 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, bool ipmatched = FALSE; /* get amount of alternatives, RFC2459 claims there MUST be at least - one, but we don't depend on it... */ + one, but we do not depend on it... */ numalts = sk_GENERAL_NAME_num(altnames); /* loop through all alternatives - until a dnsmatch */ @@ -2182,7 +2179,7 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, switch(target) { case GEN_DNS: /* name/pattern comparison */ - /* The OpenSSL man page explicitly says: "In general it cannot be + /* The OpenSSL manpage explicitly says: "In general it cannot be assumed that the data returned by ASN1_STRING_data() is null terminated or does not contain embedded nulls." But also that "The actual format of the data will depend on the actual string @@ -2192,11 +2189,11 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, is always null-terminated. */ if((altlen == strlen(altptr)) && - /* if this isn't true, there was an embedded zero in the name + /* if this is not true, there was an embedded zero in the name string and we cannot match it. */ - subj_alt_hostcheck(data, - altptr, - altlen, hostname, hostlen, dispname)) { + subj_alt_hostcheck(data, altptr, altlen, + peer->hostname, hostlen, + peer->dispname)) { dnsmatched = TRUE; } break; @@ -2208,7 +2205,7 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, ipmatched = TRUE; infof(data, " subjectAltName: host \"%s\" matched cert's IP address!", - dispname); + peer->dispname); } break; } @@ -2224,17 +2221,21 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, /* an alternative name matched */ ; else if(dNSName || iPAddress) { - infof(data, " subjectAltName does not match %s", dispname); + const char *tname = (peer->type == CURL_SSL_PEER_DNS) ? "hostname" : + (peer->type == CURL_SSL_PEER_IPV4) ? + "ipv4 address" : "ipv6 address"; + infof(data, " subjectAltName does not match %s %s", tname, peer->dispname); failf(data, "SSL: no alternative certificate subject name matches " - "target host name '%s'", dispname); + "target %s '%s'", tname, peer->dispname); result = CURLE_PEER_FAILED_VERIFICATION; } else { /* we have to look to the last occurrence of a commonName in the distinguished one to get the most significant one. */ int i = -1; - unsigned char *peer_CN = NULL; - int peerlen = 0; + unsigned char *cn = NULL; + int cnlen = 0; + bool free_cn = FALSE; /* The following is done because of a bug in 0.9.6b */ X509_NAME *name = X509_get_subject_name(server_cert); @@ -2258,21 +2259,17 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, conditional in the future when OpenSSL has been fixed. */ if(tmp) { if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) { - peerlen = ASN1_STRING_length(tmp); - if(peerlen >= 0) { - peer_CN = OPENSSL_malloc(peerlen + 1); - if(peer_CN) { - memcpy(peer_CN, ASN1_STRING_get0_data(tmp), peerlen); - peer_CN[peerlen] = '\0'; - } - else - result = CURLE_OUT_OF_MEMORY; - } + cnlen = ASN1_STRING_length(tmp); + cn = (unsigned char *) ASN1_STRING_get0_data(tmp); + } + else { /* not a UTF8 name */ + cnlen = ASN1_STRING_to_UTF8(&cn, tmp); + free_cn = TRUE; } - else /* not a UTF8 name */ - peerlen = ASN1_STRING_to_UTF8(&peer_CN, tmp); - if(peer_CN && (curlx_uztosi(strlen((char *)peer_CN)) != peerlen)) { + if((cnlen <= 0) || !cn) + result = CURLE_OUT_OF_MEMORY; + else if((size_t)cnlen != strlen((char *)cn)) { /* there was a terminating zero before the end of string, this cannot match and we return failure! */ failf(data, "SSL: illegal cert name field"); @@ -2284,22 +2281,22 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, if(result) /* error already detected, pass through */ ; - else if(!peer_CN) { + else if(!cn) { failf(data, "SSL: unable to obtain common name from peer certificate"); result = CURLE_PEER_FAILED_VERIFICATION; } - else if(!Curl_cert_hostcheck((const char *)peer_CN, - peerlen, hostname, hostlen)) { + else if(!Curl_cert_hostcheck((const char *)cn, cnlen, + peer->hostname, hostlen)) { failf(data, "SSL: certificate subject name '%s' does not match " - "target host name '%s'", peer_CN, dispname); + "target hostname '%s'", cn, peer->dispname); result = CURLE_PEER_FAILED_VERIFICATION; } else { - infof(data, " common name: %s (matched)", peer_CN); + infof(data, " common name: %s (matched)", cn); } - if(peer_CN) - OPENSSL_free(peer_CN); + if(free_cn) + OPENSSL_free(cn); } return result; @@ -2308,9 +2305,9 @@ ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, #if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ !defined(OPENSSL_NO_OCSP) static CURLcode verifystatus(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + struct ossl_ctx *octx) { - struct ssl_connect_data *connssl = cf->ctx; int i, ocsp_status; #if defined(OPENSSL_IS_AWSLC) const uint8_t *status; @@ -2323,8 +2320,6 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, OCSP_BASICRESP *br = NULL; X509_STORE *st = NULL; STACK_OF(X509) *ch = NULL; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; X509 *cert; OCSP_CERTID *id = NULL; int cert_status, crl_reason; @@ -2332,9 +2327,10 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, int ret; long len; - DEBUGASSERT(backend); + (void)cf; + DEBUGASSERT(octx); - len = SSL_get_tlsext_status_ocsp_resp(backend->handle, &status); + len = (long)SSL_get_tlsext_status_ocsp_resp(octx->ssl, &status); if(!status) { failf(data, "No OCSP response received"); @@ -2364,20 +2360,20 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, goto end; } - ch = SSL_get_peer_cert_chain(backend->handle); + ch = SSL_get_peer_cert_chain(octx->ssl); if(!ch) { failf(data, "Could not get peer certificate chain"); result = CURLE_SSL_INVALIDCERTSTATUS; goto end; } - st = SSL_CTX_get_cert_store(backend->ctx); + st = SSL_CTX_get_cert_store(octx->ssl_ctx); #if ((OPENSSL_VERSION_NUMBER <= 0x1000201fL) /* Fixed after 1.0.2a */ || \ (defined(LIBRESSL_VERSION_NUMBER) && \ LIBRESSL_VERSION_NUMBER <= 0x2040200fL)) /* The authorized responder cert in the OCSP response MUST be signed by the - peer cert's issuer (see RFC6960 section 4.2.2.2). If that's a root cert, - no problem, but if it's an intermediate cert OpenSSL has a bug where it + peer cert's issuer (see RFC6960 section 4.2.2.2). If that is a root cert, + no problem, but if it is an intermediate cert OpenSSL has a bug where it expects this issuer to be present in the chain embedded in the OCSP response. So we add it if necessary. */ @@ -2407,7 +2403,7 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, } /* Compute the certificate's ID */ - cert = SSL_get1_peer_certificate(backend->handle); + cert = SSL_get1_peer_certificate(octx->ssl); if(!cert) { failf(data, "Error getting peer certificate"); result = CURLE_SSL_INVALIDCERTSTATUS; @@ -2415,7 +2411,7 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, } for(i = 0; i < (int)sk_X509_num(ch); i++) { - X509 *issuer = sk_X509_value(ch, i); + X509 *issuer = sk_X509_value(ch, (ossl_valsize_t)i); if(X509_check_issued(issuer, cert) == X509_V_OK) { id = OCSP_cert_to_id(EVP_sha1(), cert, issuer); break; @@ -2476,7 +2472,7 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, #endif /* USE_OPENSSL */ -/* The SSL_CTRL_SET_MSG_CALLBACK doesn't exist in ancient OpenSSL versions +/* The SSL_CTRL_SET_MSG_CALLBACK does not exist in ancient OpenSSL versions and thus this cannot be done there. */ #ifdef SSL_CTRL_SET_MSG_CALLBACK @@ -2661,7 +2657,7 @@ static void ossl_trace(int direction, int ssl_ver, int content_type, ssl_ver >>= 8; /* check the upper 8 bits only below */ - /* SSLv2 doesn't seem to have TLS record-type headers, so OpenSSL + /* SSLv2 does not seem to have TLS record-type headers, so OpenSSL * always pass-up content-type as 0. But the interesting message-type * is at 'buf[0]'. */ @@ -2685,11 +2681,9 @@ static void ossl_trace(int direction, int ssl_ver, int content_type, txt_len = msnprintf(ssl_buf, sizeof(ssl_buf), "%s (%s), %s, %s (%d):\n", - verstr, direction?"OUT":"IN", + verstr, direction ? "OUT" : "IN", tls_rt_name, msg_name, msg_type); - if(0 <= txt_len && (unsigned)txt_len < sizeof(ssl_buf)) { - Curl_debug(data, CURLINFO_TEXT, ssl_buf, (size_t)txt_len); - } + Curl_debug(data, CURLINFO_TEXT, ssl_buf, (size_t)txt_len); } Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT : @@ -2701,12 +2695,6 @@ static void ossl_trace(int direction, int ssl_ver, int content_type, #ifdef USE_OPENSSL /* ====================================================== */ -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME -# define use_sni(x) sni = (x) -#else -# define use_sni(x) Curl_nop_stmt -#endif - /* Check for OpenSSL 1.0.2 which has ALPN support. */ #undef HAS_ALPN #if OPENSSL_VERSION_NUMBER >= 0x10002000L \ @@ -2754,7 +2742,7 @@ ossl_set_ssl_version_min_max(struct Curl_cfilter *cf, SSL_CTX *ctx) } /* CURL_SSLVERSION_DEFAULT means that no option was selected. - We don't want to pass 0 to SSL_CTX_set_min_proto_version as + We do not want to pass 0 to SSL_CTX_set_min_proto_version as it would enable all versions down to the lowest supported by the library. So we skip this, and stay with the library default @@ -2766,7 +2754,7 @@ ossl_set_ssl_version_min_max(struct Curl_cfilter *cf, SSL_CTX *ctx) } /* ... then, TLS max version */ - curl_ssl_version_max = conn_config->version_max; + curl_ssl_version_max = (long)conn_config->version_max; /* convert curl max SSL version option to OpenSSL constant */ switch(curl_ssl_version_max) { @@ -2807,6 +2795,9 @@ ossl_set_ssl_version_min_max(struct Curl_cfilter *cf, SSL_CTX *ctx) typedef uint32_t ctx_option_t; #elif OPENSSL_VERSION_NUMBER >= 0x30000000L typedef uint64_t ctx_option_t; +#elif OPENSSL_VERSION_NUMBER >= 0x10100000L && \ + !defined(LIBRESSL_VERSION_NUMBER) +typedef unsigned long ctx_option_t; #else typedef long ctx_option_t; #endif @@ -2814,24 +2805,23 @@ typedef long ctx_option_t; #if (OPENSSL_VERSION_NUMBER < 0x10100000L) /* 1.1.0 */ static CURLcode ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, - struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_cfilter *cf, + struct Curl_easy *data) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); long ssl_version = conn_config->version; long ssl_version_max = conn_config->version_max; - (void) data; /* In case it's unused. */ + (void) data; /* In case it is unused. */ switch(ssl_version) { case CURL_SSLVERSION_TLSv1_3: #ifdef TLS1_3_VERSION { struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; - DEBUGASSERT(backend); - SSL_CTX_set_max_proto_version(backend->ctx, TLS1_3_VERSION); + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + DEBUGASSERT(octx); + SSL_CTX_set_max_proto_version(octx->ssl_ctx, TLS1_3_VERSION); *ctx_options |= SSL_OP_NO_TLSv1_2; } #else @@ -2839,7 +2829,7 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, failf(data, OSSL_PACKAGE " was built without TLS 1.3 support"); return CURLE_NOT_BUILT_IN; #endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_TLSv1_2: #if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1_1; @@ -2847,7 +2837,7 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, failf(data, OSSL_PACKAGE " was built without TLS 1.2 support"); return CURLE_NOT_BUILT_IN; #endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_TLSv1_1: #if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1; @@ -2855,7 +2845,7 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, failf(data, OSSL_PACKAGE " was built without TLS 1.1 support"); return CURLE_NOT_BUILT_IN; #endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_TLSv1_0: case CURL_SSLVERSION_TLSv1: break; @@ -2866,12 +2856,12 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, #if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1_1; #endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_MAX_TLSv1_1: #if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1_2; #endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_MAX_TLSv1_2: #ifdef TLS1_3_VERSION *ctx_options |= SSL_OP_NO_TLSv1_3; @@ -2889,61 +2879,66 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, } #endif -/* The "new session" callback must return zero if the session can be removed - * or non-zero if the session has been put into the session cache. - */ -static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) +CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct ssl_peer *peer, + SSL_SESSION *session) { - int res = 0; - struct Curl_easy *data; - struct Curl_cfilter *cf; const struct ssl_config_data *config; - struct ssl_connect_data *connssl; - bool isproxy; + CURLcode result = CURLE_OK; + size_t der_session_size; + unsigned char *der_session_buf; + unsigned char *der_session_ptr; - cf = (struct Curl_cfilter*) SSL_get_app_data(ssl); - connssl = cf? cf->ctx : NULL; - data = connssl? CF_DATA_CURRENT(cf) : NULL; - /* The sockindex has been stored as a pointer to an array element */ if(!cf || !data) - return 0; - - isproxy = Curl_ssl_cf_is_proxy(cf); + goto out; config = Curl_ssl_cf_get_config(cf, data); - if(config->primary.sessionid) { - bool incache; - bool added = FALSE; - void *old_ssl_sessionid = NULL; + if(config->primary.cache_session) { - Curl_ssl_sessionid_lock(data); - if(isproxy) - incache = FALSE; - else - incache = !(Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL)); - if(incache) { - if(old_ssl_sessionid != ssl_sessionid) { - infof(data, "old SSL session ID is stale, removing"); - Curl_ssl_delsessionid(data, old_ssl_sessionid); - incache = FALSE; - } + der_session_size = i2d_SSL_SESSION(session, NULL); + if(der_session_size == 0) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - if(!incache) { - if(!Curl_ssl_addsessionid(cf, data, ssl_sessionid, - 0 /* unknown size */, &added)) { - if(added) { - /* the session has been put into the session cache */ - res = 1; - } - } - else - failf(data, "failed to store ssl session"); + der_session_buf = der_session_ptr = malloc(der_session_size); + if(!der_session_buf) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + der_session_size = i2d_SSL_SESSION(session, &der_session_ptr); + if(der_session_size == 0) { + result = CURLE_OUT_OF_MEMORY; + free(der_session_buf); + goto out; } + + Curl_ssl_sessionid_lock(data); + result = Curl_ssl_set_sessionid(cf, data, peer, NULL, der_session_buf, + der_session_size, ossl_session_free); Curl_ssl_sessionid_unlock(data); } - return res; +out: + return result; +} + +/* The "new session" callback must return zero if the session can be removed + * or non-zero if the session has been put into the session cache. + */ +static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) +{ + struct Curl_cfilter *cf; + struct Curl_easy *data; + struct ssl_connect_data *connssl; + + cf = (struct Curl_cfilter*) SSL_get_app_data(ssl); + connssl = cf ? cf->ctx : NULL; + data = connssl ? CF_DATA_CURRENT(cf) : NULL; + Curl_ossl_add_session(cf, data, &connssl->peer, ssl_sessionid); + return 0; } static CURLcode load_cacert_from_memory(X509_STORE *store, @@ -2972,7 +2967,7 @@ static CURLcode load_cacert_from_memory(X509_STORE *store, /* add each entry from PEM file to x509_store */ for(i = 0; i < (int)sk_X509_INFO_num(inf); ++i) { - itmp = sk_X509_INFO_value(inf, i); + itmp = sk_X509_INFO_value(inf, (ossl_valsize_t)i); if(itmp->x509) { if(X509_STORE_add_cert(store, itmp->x509)) { ++count; @@ -2998,173 +2993,204 @@ static CURLcode load_cacert_from_memory(X509_STORE *store, sk_X509_INFO_pop_free(inf, X509_INFO_free); BIO_free(cbio); - /* if we didn't end up importing anything, treat that as an error */ + /* if we did not end up importing anything, treat that as an error */ return (count > 0) ? CURLE_OK : CURLE_SSL_CACERT_BADFILE; } -static CURLcode populate_x509_store(struct Curl_cfilter *cf, - struct Curl_easy *data, - X509_STORE *store) +#if defined(USE_WIN32_CRYPTO) +static CURLcode import_windows_cert_store(struct Curl_easy *data, + const char *name, + X509_STORE *store, + bool *imported) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); CURLcode result = CURLE_OK; - X509_LOOKUP *lookup = NULL; - const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; - const char * const ssl_cafile = - /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ - (ca_info_blob ? NULL : conn_config->CAfile); - const char * const ssl_capath = conn_config->CApath; - const char * const ssl_crlfile = ssl_config->primary.CRLfile; - const bool verifypeer = conn_config->verifypeer; - bool imported_native_ca = false; - bool imported_ca_info_blob = false; + HCERTSTORE hStore; + + *imported = FALSE; + + hStore = CertOpenSystemStoreA(0, name); + if(hStore) { + PCCERT_CONTEXT pContext = NULL; + /* The array of enhanced key usage OIDs will vary per certificate and + is declared outside of the loop so that rather than malloc/free each + iteration we can grow it with realloc, when necessary. */ + CERT_ENHKEY_USAGE *enhkey_usage = NULL; + DWORD enhkey_usage_size = 0; + + /* This loop makes a best effort to import all valid certificates from + the MS root store. If a certificate cannot be imported it is + skipped. 'result' is used to store only hard-fail conditions (such + as out of memory) that cause an early break. */ + result = CURLE_OK; + for(;;) { + X509 *x509; + FILETIME now; + BYTE key_usage[2]; + DWORD req_size; + const unsigned char *encoded_cert; + pContext = CertEnumCertificatesInStore(hStore, pContext); + if(!pContext) + break; - if(!store) - return CURLE_OUT_OF_MEMORY; - - if(verifypeer) { -#if defined(USE_WIN32_CRYPTO) - /* Import certificates from the Windows root certificate store if - requested. - https://stackoverflow.com/questions/9507184/ - https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037 - https://datatracker.ietf.org/doc/html/rfc5280 */ - if(ssl_config->native_ca_store) { - HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT")); - - if(hStore) { - PCCERT_CONTEXT pContext = NULL; - /* The array of enhanced key usage OIDs will vary per certificate and - is declared outside of the loop so that rather than malloc/free each - iteration we can grow it with realloc, when necessary. */ - CERT_ENHKEY_USAGE *enhkey_usage = NULL; - DWORD enhkey_usage_size = 0; - - /* This loop makes a best effort to import all valid certificates from - the MS root store. If a certificate cannot be imported it is - skipped. 'result' is used to store only hard-fail conditions (such - as out of memory) that cause an early break. */ - result = CURLE_OK; - for(;;) { - X509 *x509; - FILETIME now; - BYTE key_usage[2]; - DWORD req_size; - const unsigned char *encoded_cert; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - char cert_name[256]; + else { + char cert_name[256]; + if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, + NULL, cert_name, sizeof(cert_name))) + infof(data, "SSL: unknown cert name"); + else + infof(data, "SSL: Checking cert \"%s\"", cert_name); + } #endif + encoded_cert = (const unsigned char *)pContext->pbCertEncoded; + if(!encoded_cert) + continue; + + GetSystemTimeAsFileTime(&now); + if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 || + CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0) + continue; + + /* If key usage exists check for signing attribute */ + if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType, + pContext->pCertInfo, + key_usage, sizeof(key_usage))) { + if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE)) + continue; + } + else if(GetLastError()) + continue; + + /* If enhanced key usage exists check for server auth attribute. + * + * Note "In a Microsoft environment, a certificate might also have + * EKU extended properties that specify valid uses for the + * certificate." The call below checks both, and behavior varies + * depending on what is found. For more details see + * CertGetEnhancedKeyUsage doc. + */ + if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) { + if(req_size && req_size > enhkey_usage_size) { + void *tmp = realloc(enhkey_usage, req_size); - pContext = CertEnumCertificatesInStore(hStore, pContext); - if(!pContext) + if(!tmp) { + failf(data, "SSL: Out of memory allocating for OID list"); + result = CURLE_OUT_OF_MEMORY; break; - -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, - NULL, cert_name, sizeof(cert_name))) { - strcpy(cert_name, "Unknown"); } - infof(data, "SSL: Checking cert \"%s\"", cert_name); -#endif - encoded_cert = (const unsigned char *)pContext->pbCertEncoded; - if(!encoded_cert) - continue; - GetSystemTimeAsFileTime(&now); - if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 || - CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0) - continue; + enhkey_usage = (CERT_ENHKEY_USAGE *)tmp; + enhkey_usage_size = req_size; + } - /* If key usage exists check for signing attribute */ - if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType, - pContext->pCertInfo, - key_usage, sizeof(key_usage))) { - if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE)) + if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) { + if(!enhkey_usage->cUsageIdentifier) { + /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate + is good for all uses. If it returns zero, the certificate + has no valid uses." */ + if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND) continue; } - else if(GetLastError()) - continue; - - /* If enhanced key usage exists check for server auth attribute. - * - * Note "In a Microsoft environment, a certificate might also have - * EKU extended properties that specify valid uses for the - * certificate." The call below checks both, and behavior varies - * depending on what is found. For more details see - * CertGetEnhancedKeyUsage doc. - */ - if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) { - if(req_size && req_size > enhkey_usage_size) { - void *tmp = realloc(enhkey_usage, req_size); - - if(!tmp) { - failf(data, "SSL: Out of memory allocating for OID list"); - result = CURLE_OUT_OF_MEMORY; + else { + DWORD i; + bool found = FALSE; + + for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) { + if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */, + enhkey_usage->rgpszUsageIdentifier[i])) { + found = TRUE; break; } - - enhkey_usage = (CERT_ENHKEY_USAGE *)tmp; - enhkey_usage_size = req_size; } - if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) { - if(!enhkey_usage->cUsageIdentifier) { - /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate - is good for all uses. If it returns zero, the certificate - has no valid uses." */ - if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND) - continue; - } - else { - DWORD i; - bool found = false; - - for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) { - if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */, - enhkey_usage->rgpszUsageIdentifier[i])) { - found = true; - break; - } - } - - if(!found) - continue; - } - } - else + if(!found) continue; } - else - continue; + } + else + continue; + } + else + continue; - x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); - if(!x509) - continue; + x509 = d2i_X509(NULL, &encoded_cert, (long)pContext->cbCertEncoded); + if(!x509) + continue; - /* Try to import the certificate. This may fail for legitimate - reasons such as duplicate certificate, which is allowed by MS but - not OpenSSL. */ - if(X509_STORE_add_cert(store, x509) == 1) { + /* Try to import the certificate. This may fail for legitimate + reasons such as duplicate certificate, which is allowed by MS but + not OpenSSL. */ + if(X509_STORE_add_cert(store, x509) == 1) { #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - infof(data, "SSL: Imported cert \"%s\"", cert_name); + infof(data, "SSL: Imported cert"); #endif - imported_native_ca = true; - } - X509_free(x509); - } + *imported = TRUE; + } + X509_free(x509); + } + + free(enhkey_usage); + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + if(result) + return result; + } + + return result; +} +#endif + +static CURLcode populate_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + X509_STORE *store) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + CURLcode result = CURLE_OK; + X509_LOOKUP *lookup = NULL; + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + const char * const ssl_cafile = + /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ + (ca_info_blob ? NULL : conn_config->CAfile); + const char * const ssl_capath = conn_config->CApath; + const char * const ssl_crlfile = ssl_config->primary.CRLfile; + const bool verifypeer = conn_config->verifypeer; + bool imported_native_ca = FALSE; + bool imported_ca_info_blob = FALSE; - free(enhkey_usage); - CertFreeCertificateContext(pContext); - CertCloseStore(hStore, 0); + CURL_TRC_CF(data, cf, "populate_x509_store, path=%s, blob=%d", + ssl_cafile ? ssl_cafile : "none", !!ca_info_blob); + if(!store) + return CURLE_OUT_OF_MEMORY; + if(verifypeer) { +#if defined(USE_WIN32_CRYPTO) + /* Import certificates from the Windows root certificate store if + requested. + https://stackoverflow.com/questions/9507184/ + https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037 + https://datatracker.ietf.org/doc/html/rfc5280 */ + if(ssl_config->native_ca_store) { + const char *storeNames[] = { + "ROOT", /* Trusted Root Certification Authorities */ + "CA" /* Intermediate Certification Authorities */ + }; + size_t i; + for(i = 0; i < ARRAYSIZE(storeNames); ++i) { + bool imported = FALSE; + result = import_windows_cert_store(data, storeNames[i], store, + &imported); if(result) return result; + if(imported) { + infof(data, "successfully imported Windows %s store", storeNames[i]); + imported_native_ca = TRUE; + } + else + infof(data, "error importing Windows %s store, continuing anyway", + storeNames[i]); } - if(imported_native_ca) - infof(data, "successfully imported Windows CA store"); - else - infof(data, "error importing Windows CA store, continuing anyway"); } #endif if(ca_info_blob) { @@ -3174,13 +3200,13 @@ static CURLcode populate_x509_store(struct Curl_cfilter *cf, return result; } else { - imported_ca_info_blob = true; + imported_ca_info_blob = TRUE; infof(data, "successfully imported CA certificate blob"); } } if(ssl_cafile || ssl_capath) { -#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */ if(ssl_cafile && !X509_STORE_load_file(store, ssl_cafile)) { if(!imported_native_ca && !imported_ca_info_blob) { @@ -3225,8 +3251,8 @@ static CURLcode populate_x509_store(struct Curl_cfilter *cf, #ifdef CURL_CA_FALLBACK if(!ssl_cafile && !ssl_capath && !imported_native_ca && !imported_ca_info_blob) { - /* verifying the peer without any CA certificates won't - work so use openssl's built-in default as fallback */ + /* verifying the peer without any CA certificates will not + work so use OpenSSL's built-in default as fallback */ X509_STORE_set_default_paths(store); } #endif @@ -3251,10 +3277,11 @@ static CURLcode populate_x509_store(struct Curl_cfilter *cf, if(verifypeer) { /* Try building a chain using issuers in the trusted store first to avoid - problems with server-sent legacy intermediates. Newer versions of + problems with server-sent legacy intermediates. Newer versions of OpenSSL do alternate chain checking by default but we do not know how to determine that in a reliable manner. - https://rt.openssl.org/Ticket/Display.html?id=3621&user=guest&pass=guest + https://web.archive.org/web/20190422050538/ + rt.openssl.org/Ticket/Display.html?id=3621 */ #if defined(X509_V_FLAG_TRUSTED_FIRST) X509_STORE_set_flags(store, X509_V_FLAG_TRUSTED_FIRST); @@ -3278,23 +3305,49 @@ static CURLcode populate_x509_store(struct Curl_cfilter *cf, } #if defined(HAVE_SSL_X509_STORE_SHARE) -static bool cached_x509_store_expired(const struct Curl_easy *data, - const struct multi_ssl_backend_data *mb) + +/* key to use at `multi->proto_hash` */ +#define MPROTO_OSSL_X509_KEY "tls:ossl:x509:share" + +struct ossl_x509_share { + char *CAfile; /* CAfile path used to generate X509 store */ + X509_STORE *store; /* cached X509 store or NULL if none */ + struct curltime time; /* when the cached store was created */ +}; + +static void oss_x509_share_free(void *key, size_t key_len, void *p) { - const struct ssl_general_config *cfg = &data->set.general_ssl; - struct curltime now = Curl_now(); - timediff_t elapsed_ms = Curl_timediff(now, mb->time); - timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + struct ossl_x509_share *share = p; + DEBUGASSERT(key_len == (sizeof(MPROTO_OSSL_X509_KEY)-1)); + DEBUGASSERT(!memcmp(MPROTO_OSSL_X509_KEY, key, key_len)); + (void)key; + (void)key_len; + if(share->store) { + X509_STORE_free(share->store); + } + free(share->CAfile); + free(share); +} - if(timeout_ms < 0) - return false; +static bool +cached_x509_store_expired(const struct Curl_easy *data, + const struct ossl_x509_share *mb) +{ + const struct ssl_general_config *cfg = &data->set.general_ssl; + if(cfg->ca_cache_timeout < 0) + return FALSE; + else { + struct curltime now = Curl_now(); + timediff_t elapsed_ms = Curl_timediff(now, mb->time); + timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; - return elapsed_ms >= timeout_ms; + return elapsed_ms >= timeout_ms; + } } -static bool cached_x509_store_different( - struct Curl_cfilter *cf, - const struct multi_ssl_backend_data *mb) +static bool +cached_x509_store_different(struct Curl_cfilter *cf, + const struct ossl_x509_share *mb) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); if(!mb->CAfile || !conn_config->CAfile) @@ -3306,15 +3359,18 @@ static bool cached_x509_store_different( static X509_STORE *get_cached_x509_store(struct Curl_cfilter *cf, const struct Curl_easy *data) { - struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi; + struct Curl_multi *multi = data->multi; + struct ossl_x509_share *share; X509_STORE *store = NULL; - if(multi && - multi->ssl_backend_data && - multi->ssl_backend_data->store && - !cached_x509_store_expired(data, multi->ssl_backend_data) && - !cached_x509_store_different(cf, multi->ssl_backend_data)) { - store = multi->ssl_backend_data->store; + DEBUGASSERT(multi); + share = multi ? Curl_hash_pick(&multi->proto_hash, + (void *)MPROTO_OSSL_X509_KEY, + sizeof(MPROTO_OSSL_X509_KEY)-1) : NULL; + if(share && share->store && + !cached_x509_store_expired(data, share) && + !cached_x509_store_different(cf, share)) { + store = share->store; } return store; @@ -3325,20 +3381,29 @@ static void set_cached_x509_store(struct Curl_cfilter *cf, X509_STORE *store) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi; - struct multi_ssl_backend_data *mbackend; + struct Curl_multi *multi = data->multi; + struct ossl_x509_share *share; + DEBUGASSERT(multi); if(!multi) return; + share = Curl_hash_pick(&multi->proto_hash, + (void *)MPROTO_OSSL_X509_KEY, + sizeof(MPROTO_OSSL_X509_KEY)-1); - if(!multi->ssl_backend_data) { - multi->ssl_backend_data = calloc(1, sizeof(struct multi_ssl_backend_data)); - if(!multi->ssl_backend_data) + if(!share) { + share = calloc(1, sizeof(*share)); + if(!share) + return; + if(!Curl_hash_add2(&multi->proto_hash, + (void *)MPROTO_OSSL_X509_KEY, + sizeof(MPROTO_OSSL_X509_KEY)-1, + share, oss_x509_share_free)) { + free(share); return; + } } - mbackend = multi->ssl_backend_data; - if(X509_STORE_up_ref(store)) { char *CAfile = NULL; @@ -3350,14 +3415,14 @@ static void set_cached_x509_store(struct Curl_cfilter *cf, } } - if(mbackend->store) { - X509_STORE_free(mbackend->store); - free(mbackend->CAfile); + if(share->store) { + X509_STORE_free(share->store); + free(share->CAfile); } - mbackend->time = Curl_now(); - mbackend->store = store; - mbackend->CAfile = CAfile; + share->time = Curl_now(); + share->store = store; + share->CAfile = CAfile; } } @@ -3372,7 +3437,7 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, bool cache_criteria_met; /* Consider the X509 store cacheable if it comes exclusively from a CAfile, - or no source is provided and we are falling back to openssl's built-in + or no source is provided and we are falling back to OpenSSL's built-in default. */ cache_criteria_met = (data->set.general_ssl.ca_cache_timeout != 0) && conn_config->verifypeer && @@ -3407,40 +3472,32 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, } #endif /* HAVE_SSL_X509_STORE_SHARE */ -static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, - struct Curl_easy *data) +CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + int transport, /* TCP or QUIC */ + const unsigned char *alpn, size_t alpn_len, + Curl_ossl_ctx_setup_cb *cb_setup, + void *cb_user_data, + Curl_ossl_new_session_cb *cb_new_session, + void *ssl_user_data) { CURLcode result = CURLE_OK; - char *ciphers; + const char *ciphers; SSL_METHOD_QUAL SSL_METHOD *req_method = NULL; - struct ssl_connect_data *connssl = cf->ctx; ctx_option_t ctx_options = 0; - void *ssl_sessionid = NULL; + SSL_SESSION *ssl_session = NULL; + const unsigned char *der_sessionid = NULL; + size_t der_sessionid_size = 0; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - BIO *bio; - -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - bool sni; - const char *hostname = connssl->hostname; - -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif -#endif - const long int ssl_version = conn_config->version; + const long int ssl_version_min = conn_config->version; char * const ssl_cert = ssl_config->primary.clientcert; const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; const char * const ssl_cert_type = ssl_config->cert_type; const bool verifypeer = conn_config->verifypeer; char error_buffer[256]; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; - - DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); - DEBUGASSERT(backend); /* Make funny stuff to get random input */ result = ossl_seed(data); @@ -3449,68 +3506,86 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, ssl_config->certverifyresult = !X509_V_OK; - /* check to see if we've been told to use an explicit SSL/TLS version */ + switch(transport) { + case TRNSPRT_TCP: + /* check to see if we have been told to use an explicit SSL/TLS version */ + switch(ssl_version_min) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: + case CURL_SSLVERSION_TLSv1_2: + case CURL_SSLVERSION_TLSv1_3: + /* it will be handled later with the context options */ + #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) + req_method = TLS_client_method(); + #else + req_method = SSLv23_client_method(); + #endif + break; + case CURL_SSLVERSION_SSLv2: + failf(data, "No SSLv2 support"); + return CURLE_NOT_BUILT_IN; + case CURL_SSLVERSION_SSLv3: + failf(data, "No SSLv3 support"); + return CURLE_NOT_BUILT_IN; + default: + failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + return CURLE_SSL_CONNECT_ERROR; + } + break; + case TRNSPRT_QUIC: + if(conn_config->version_max && + (conn_config->version_max != CURL_SSLVERSION_MAX_TLSv1_3)) { + failf(data, "QUIC needs at least TLS version 1.3"); + return CURLE_SSL_CONNECT_ERROR; + } - switch(ssl_version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - /* it will be handled later with the context options */ -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) - req_method = TLS_client_method(); +#ifdef USE_OPENSSL_QUIC + req_method = OSSL_QUIC_client_method(); +#elif (OPENSSL_VERSION_NUMBER >= 0x10100000L) + req_method = TLS_method(); #else req_method = SSLv23_client_method(); #endif - use_sni(TRUE); break; - case CURL_SSLVERSION_SSLv2: - failf(data, "No SSLv2 support"); - return CURLE_NOT_BUILT_IN; - case CURL_SSLVERSION_SSLv3: - failf(data, "No SSLv3 support"); - return CURLE_NOT_BUILT_IN; default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + failf(data, "unsupported transport %d in SSL init", transport); return CURLE_SSL_CONNECT_ERROR; } - if(backend->ctx) { - /* This happens when an error was encountered before in this - * step and we are called to do it again. Get rid of any leftover - * from the previous call. */ - ossl_close(cf, data); - } - backend->ctx = SSL_CTX_new(req_method); - if(!backend->ctx) { - failf(data, "SSL: couldn't create a context: %s", + DEBUGASSERT(!octx->ssl_ctx); + octx->ssl_ctx = SSL_CTX_new(req_method); + + if(!octx->ssl_ctx) { + failf(data, "SSL: could not create a context: %s", ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer))); return CURLE_OUT_OF_MEMORY; } -#ifdef SSL_MODE_RELEASE_BUFFERS - SSL_CTX_set_mode(backend->ctx, SSL_MODE_RELEASE_BUFFERS); -#endif + if(cb_setup) { + result = cb_setup(cf, data, cb_user_data); + if(result) + return result; + } #ifdef SSL_CTRL_SET_MSG_CALLBACK if(data->set.fdebug && data->set.verbose) { /* the SSL trace callback is only used for verbose logging */ - SSL_CTX_set_msg_callback(backend->ctx, ossl_trace); - SSL_CTX_set_msg_callback_arg(backend->ctx, cf); + SSL_CTX_set_msg_callback(octx->ssl_ctx, ossl_trace); + SSL_CTX_set_msg_callback_arg(octx->ssl_ctx, cf); } #endif /* OpenSSL contains code to work around lots of bugs and flaws in various SSL-implementations. SSL_CTX_set_options() is used to enabled those - work-arounds. The man page for this option states that SSL_OP_ALL enables + work-arounds. The manpage for this option states that SSL_OP_ALL enables all the work-arounds and that "It is usually safe to use SSL_OP_ALL to enable the bug workaround options if compatibility with somewhat broken implementations is desired." - The "-no_ticket" option was introduced in OpenSSL 0.9.8j. It's a flag to + The "-no_ticket" option was introduced in OpenSSL 0.9.8j. it is a flag to disable "rfc4507bis session ticket support". rfc4507bis was later turned into the proper RFC5077: https://datatracker.ietf.org/doc/html/rfc5077 @@ -3531,12 +3606,12 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, CVE-2010-4180 when using previous OpenSSL versions we no longer enable this option regardless of OpenSSL version and SSL_OP_ALL definition. - OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability - (https://www.openssl.org/~bodo/tls-cbc.txt). In 0.9.6e they added a bit to - SSL_OP_ALL that _disables_ that work-around despite the fact that - SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to - keep the secure work-around, the SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit - must not be set. + OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability: + https://web.archive.org/web/20240114184648/openssl.org/~bodo/tls-cbc.txt. + In 0.9.6e they added a bit to SSL_OP_ALL that _disables_ that work-around + despite the fact that SSL_OP_ALL is documented to do "rather harmless" + workarounds. In order to keep the secure work-around, the + SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit must not be set. */ ctx_options = SSL_OP_ALL; @@ -3551,17 +3626,17 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, #ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG /* mitigate CVE-2010-4180 */ - ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; + ctx_options &= ~(ctx_option_t)SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; #endif #ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS /* unless the user explicitly asks to allow the protocol vulnerability we use the work-around */ if(!ssl_config->enable_beast) - ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; + ctx_options &= ~(ctx_option_t)SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; #endif - switch(ssl_version) { + switch(ssl_version_min) { case CURL_SSLVERSION_SSLv2: case CURL_SSLVERSION_SSLv3: return CURLE_NOT_BUILT_IN; @@ -3579,7 +3654,7 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, ctx_options |= SSL_OP_NO_SSLv3; #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* 1.1.0 */ - result = ossl_set_ssl_version_min_max(cf, backend->ctx); + result = ossl_set_ssl_version_min_max(cf, octx->ssl_ctx); #else result = ossl_set_ssl_version_min_max_legacy(&ctx_options, cf, data); #endif @@ -3592,26 +3667,25 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } - SSL_CTX_set_options(backend->ctx, ctx_options); + SSL_CTX_set_options(octx->ssl_ctx, ctx_options); -#ifdef HAS_ALPN - if(connssl->alpn) { - struct alpn_proto_buf proto; +#ifdef SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER + /* We do retry writes sometimes from another buffer address */ + SSL_CTX_set_mode(octx->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); +#endif - result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); - if(result || - SSL_CTX_set_alpn_protos(backend->ctx, proto.data, proto.len)) { +#ifdef HAS_ALPN + if(alpn && alpn_len) { + if(SSL_CTX_set_alpn_protos(octx->ssl_ctx, alpn, (int)alpn_len)) { failf(data, "Error setting ALPN"); return CURLE_SSL_CONNECT_ERROR; } - Curl_alpn_to_proto_str(&proto, connssl->alpn); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } #endif if(ssl_cert || ssl_cert_blob || ssl_cert_type) { if(!result && - !cert_stuff(data, backend->ctx, + !cert_stuff(data, octx->ssl_ctx, ssl_cert, ssl_cert_blob, ssl_cert_type, ssl_config->key, ssl_config->key_blob, ssl_config->key_type, ssl_config->key_passwd)) @@ -3622,10 +3696,10 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, } ciphers = conn_config->cipher_list; - if(!ciphers) - ciphers = (char *)DEFAULT_CIPHER_SELECTION; + if(!ciphers && (peer->transport != TRNSPRT_QUIC)) + ciphers = DEFAULT_CIPHER_SELECTION; if(ciphers) { - if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) { + if(!SSL_CTX_set_cipher_list(octx->ssl_ctx, ciphers)) { failf(data, "failed setting cipher list: %s", ciphers); return CURLE_SSL_CIPHER; } @@ -3634,9 +3708,9 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, #ifdef HAVE_SSL_CTX_SET_CIPHERSUITES { - char *ciphers13 = conn_config->cipher_list13; + const char *ciphers13 = conn_config->cipher_list13; if(ciphers13) { - if(!SSL_CTX_set_ciphersuites(backend->ctx, ciphers13)) { + if(!SSL_CTX_set_ciphersuites(octx->ssl_ctx, ciphers13)) { failf(data, "failed setting TLS 1.3 cipher suite: %s", ciphers13); return CURLE_SSL_CIPHER; } @@ -3647,14 +3721,14 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, #ifdef HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH /* OpenSSL 1.1.1 requires clients to opt-in for PHA */ - SSL_CTX_set_post_handshake_auth(backend->ctx, 1); + SSL_CTX_set_post_handshake_auth(octx->ssl_ctx, 1); #endif #ifdef HAVE_SSL_CTX_SET_EC_CURVES { - char *curves = conn_config->curves; + const char *curves = conn_config->curves; if(curves) { - if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) { + if(!SSL_CTX_set1_curves_list(octx->ssl_ctx, curves)) { failf(data, "failed setting curves list: '%s'", curves); return CURLE_SSL_CIPHER; } @@ -3668,18 +3742,18 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, char * const ssl_password = ssl_config->primary.password; infof(data, "Using TLS-SRP username: %s", ssl_username); - if(!SSL_CTX_set_srp_username(backend->ctx, ssl_username)) { - failf(data, "Unable to set SRP user name"); + if(!SSL_CTX_set_srp_username(octx->ssl_ctx, ssl_username)) { + failf(data, "Unable to set SRP username"); return CURLE_BAD_FUNCTION_ARGUMENT; } - if(!SSL_CTX_set_srp_password(backend->ctx, ssl_password)) { + if(!SSL_CTX_set_srp_password(octx->ssl_ctx, ssl_password)) { failf(data, "failed setting SRP password"); return CURLE_BAD_FUNCTION_ARGUMENT; } if(!conn_config->cipher_list) { infof(data, "Setting cipher list SRP"); - if(!SSL_CTX_set_cipher_list(backend->ctx, "SRP")) { + if(!SSL_CTX_set_cipher_list(octx->ssl_ctx, "SRP")) { failf(data, "failed setting SRP cipher list"); return CURLE_SSL_CIPHER; } @@ -3691,40 +3765,42 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, * fail to connect if the verification fails, or if it should continue * anyway. In the latter case the result of the verification is checked with * SSL_get_verify_result() below. */ - SSL_CTX_set_verify(backend->ctx, + SSL_CTX_set_verify(octx->ssl_ctx, verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); /* Enable logging of secrets to the file specified in env SSLKEYLOGFILE. */ #ifdef HAVE_KEYLOG_CALLBACK if(Curl_tls_keylog_enabled()) { - SSL_CTX_set_keylog_callback(backend->ctx, ossl_keylog_callback); + SSL_CTX_set_keylog_callback(octx->ssl_ctx, ossl_keylog_callback); } #endif - /* Enable the session cache because it's a prerequisite for the "new session" - * callback. Use the "external storage" mode to prevent OpenSSL from creating - * an internal session cache. - */ - SSL_CTX_set_session_cache_mode(backend->ctx, - SSL_SESS_CACHE_CLIENT | - SSL_SESS_CACHE_NO_INTERNAL); - SSL_CTX_sess_set_new_cb(backend->ctx, ossl_new_session_cb); + if(cb_new_session) { + /* Enable the session cache because it is a prerequisite for the + * "new session" callback. Use the "external storage" mode to prevent + * OpenSSL from creating an internal session cache. + */ + SSL_CTX_set_session_cache_mode(octx->ssl_ctx, + SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(octx->ssl_ctx, cb_new_session); + } /* give application a chance to interfere with SSL set up. */ if(data->set.ssl.fsslctx) { /* When a user callback is installed to modify the SSL_CTX, * we need to do the full initialization before calling it. * See: #11800 */ - if(!backend->x509_store_setup) { - result = Curl_ssl_setup_x509_store(cf, data, backend->ctx); + if(!octx->x509_store_setup) { + result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); if(result) return result; - backend->x509_store_setup = TRUE; + octx->x509_store_setup = TRUE; } - Curl_set_in_callback(data, true); - result = (*data->set.ssl.fsslctx)(data, backend->ctx, + Curl_set_in_callback(data, TRUE); + result = (*data->set.ssl.fsslctx)(data, octx->ssl_ctx, data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); + Curl_set_in_callback(data, FALSE); if(result) { failf(data, "error signaled by ssl ctx callback"); return result; @@ -3732,67 +3808,234 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, } /* Let's make an SSL structure */ - if(backend->handle) - SSL_free(backend->handle); - backend->handle = SSL_new(backend->ctx); - if(!backend->handle) { - failf(data, "SSL: couldn't create a context (handle)"); + if(octx->ssl) + SSL_free(octx->ssl); + octx->ssl = SSL_new(octx->ssl_ctx); + if(!octx->ssl) { + failf(data, "SSL: could not create a context (handle)"); return CURLE_OUT_OF_MEMORY; } - SSL_set_app_data(backend->handle, cf); + SSL_set_app_data(octx->ssl, ssl_user_data); #if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ !defined(OPENSSL_NO_OCSP) if(conn_config->verifystatus) - SSL_set_tlsext_status_type(backend->handle, TLSEXT_STATUSTYPE_ocsp); + SSL_set_tlsext_status_type(octx->ssl, TLSEXT_STATUSTYPE_ocsp); #endif #if (defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)) && \ defined(ALLOW_RENEG) - SSL_set_renegotiate_mode(backend->handle, ssl_renegotiate_freely); + SSL_set_renegotiate_mode(octx->ssl, ssl_renegotiate_freely); #endif - SSL_set_connect_state(backend->handle); + SSL_set_connect_state(octx->ssl); - backend->server_cert = 0x0; + octx->server_cert = 0x0; #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) && -#ifdef ENABLE_IPV6 - (0 == Curl_inet_pton(AF_INET6, hostname, &addr)) && -#endif - sni) { - char *snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost || !SSL_set_tlsext_host_name(backend->handle, snihost)) { + if(peer->sni) { + if(!SSL_set_tlsext_host_name(octx->ssl, peer->sni)) { failf(data, "Failed set SNI"); return CURLE_SSL_CONNECT_ERROR; } } -#endif - SSL_set_app_data(backend->handle, cf); +#ifdef USE_ECH + if(ECH_ENABLED(data)) { + unsigned char *ech_config = NULL; + size_t ech_config_len = 0; + char *outername = data->set.str[STRING_ECH_PUBLIC]; + int trying_ech_now = 0; + + if(data->set.tls_ech & CURLECH_GREASE) { + infof(data, "ECH: will GREASE ClientHello"); +# ifdef OPENSSL_IS_BORINGSSL + SSL_set_enable_ech_grease(octx->ssl, 1); +# else + SSL_set_options(octx->ssl, SSL_OP_ECH_GREASE); +# endif + } + else if(data->set.tls_ech & CURLECH_CLA_CFG) { +# ifdef OPENSSL_IS_BORINGSSL + /* have to do base64 decode here for boring */ + const char *b64 = data->set.str[STRING_ECH_CONFIG]; + + if(!b64) { + infof(data, "ECH: ECHConfig from command line empty"); + return CURLE_SSL_CONNECT_ERROR; + } + ech_config_len = 2 * strlen(b64); + result = Curl_base64_decode(b64, &ech_config, &ech_config_len); + if(result || !ech_config) { + infof(data, "ECH: cannot base64 decode ECHConfig from command line"); + if(data->set.tls_ech & CURLECH_HARD) + return result; + } + if(SSL_set1_ech_config_list(octx->ssl, ech_config, + ech_config_len) != 1) { + infof(data, "ECH: SSL_ECH_set1_echconfig failed"); + if(data->set.tls_ech & CURLECH_HARD) { + free(ech_config); + return CURLE_SSL_CONNECT_ERROR; + } + } + free(ech_config); + trying_ech_now = 1; +# else + ech_config = (unsigned char *) data->set.str[STRING_ECH_CONFIG]; + if(!ech_config) { + infof(data, "ECH: ECHConfig from command line empty"); + return CURLE_SSL_CONNECT_ERROR; + } + ech_config_len = strlen(data->set.str[STRING_ECH_CONFIG]); + if(SSL_ech_set1_echconfig(octx->ssl, ech_config, ech_config_len) != 1) { + infof(data, "ECH: SSL_ECH_set1_echconfig failed"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else + trying_ech_now = 1; +# endif + infof(data, "ECH: ECHConfig from command line"); + } + else { + struct Curl_dns_entry *dns = NULL; + + if(peer->hostname) + dns = Curl_fetch_addr(data, peer->hostname, peer->port); + if(!dns) { + infof(data, "ECH: requested but no DNS info available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else { + struct Curl_https_rrinfo *rinfo = NULL; + + rinfo = dns->hinfo; + if(rinfo && rinfo->echconfiglist) { + unsigned char *ecl = rinfo->echconfiglist; + size_t elen = rinfo->echconfiglist_len; + + infof(data, "ECH: ECHConfig from DoH HTTPS RR"); +# ifndef OPENSSL_IS_BORINGSSL + if(SSL_ech_set1_echconfig(octx->ssl, ecl, elen) != 1) { + infof(data, "ECH: SSL_ECH_set1_echconfig failed"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } +# else + if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) { + infof(data, "ECH: SSL_set1_ech_config_list failed (boring)"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } +# endif + else { + trying_ech_now = 1; + infof(data, "ECH: imported ECHConfigList of length %zu", elen); + } + } + else { + infof(data, "ECH: requested but no ECHConfig available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + Curl_resolv_unlink(data, &dns); + } + } +# ifdef OPENSSL_IS_BORINGSSL + if(trying_ech_now && outername) { + infof(data, "ECH: setting public_name not supported with BoringSSL"); + return CURLE_SSL_CONNECT_ERROR; + } +# else + if(trying_ech_now && outername) { + infof(data, "ECH: inner: '%s', outer: '%s'", + peer->hostname ? peer->hostname : "NULL", outername); + result = SSL_ech_set_server_names(octx->ssl, + peer->hostname, outername, + 0 /* do send outer */); + if(result != 1) { + infof(data, "ECH: rv failed to set server name(s) %d [ERROR]", result); + return CURLE_SSL_CONNECT_ERROR; + } + } +# endif /* not BORING */ + if(trying_ech_now + && SSL_set_min_proto_version(octx->ssl, TLS1_3_VERSION) != 1) { + infof(data, "ECH: cannot force TLSv1.3 [ERROR]"); + return CURLE_SSL_CONNECT_ERROR; + } + } +#endif /* USE_ECH */ + +#endif - if(ssl_config->primary.sessionid) { + octx->reused_session = FALSE; + if(ssl_config->primary.cache_session) { Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL)) { + if(!Curl_ssl_getsessionid(cf, data, peer, (void **)&der_sessionid, + &der_sessionid_size, NULL)) { /* we got a session id, use it! */ - if(!SSL_set_session(backend->handle, ssl_sessionid)) { + ssl_session = d2i_SSL_SESSION(NULL, &der_sessionid, + (long)der_sessionid_size); + if(ssl_session) { + if(!SSL_set_session(octx->ssl, ssl_session)) { + Curl_ssl_sessionid_unlock(data); + SSL_SESSION_free(ssl_session); + failf(data, "SSL: SSL_set_session failed: %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + return CURLE_SSL_CONNECT_ERROR; + } + SSL_SESSION_free(ssl_session); + /* Informational message */ + infof(data, "SSL reusing session ID"); + octx->reused_session = TRUE; + } + else { Curl_ssl_sessionid_unlock(data); - failf(data, "SSL: SSL_set_session failed: %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer))); return CURLE_SSL_CONNECT_ERROR; } - /* Informational message */ - infof(data, "SSL reusing session ID"); } Curl_ssl_sessionid_unlock(data); } - backend->bio_method = bio_cf_method_create(); - if(!backend->bio_method) + return CURLE_OK; +} + +static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + struct alpn_proto_buf proto; + BIO *bio; + CURLcode result; + + DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); + DEBUGASSERT(octx); + memset(&proto, 0, sizeof(proto)); +#ifdef HAS_ALPN + if(connssl->alpn) { + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result) { + failf(data, "Error determining ALPN"); + return CURLE_SSL_CONNECT_ERROR; + } + } +#endif + + result = Curl_ossl_ctx_init(octx, cf, data, &connssl->peer, TRNSPRT_TCP, + proto.data, proto.len, NULL, NULL, + ossl_new_session_cb, cf); + if(result) + return result; + + octx->bio_method = ossl_bio_cf_method_create(); + if(!octx->bio_method) return CURLE_OUT_OF_MEMORY; - bio = BIO_new(backend->bio_method); + bio = BIO_new(octx->bio_method); if(!bio) return CURLE_OUT_OF_MEMORY; @@ -3801,83 +4044,154 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, /* with OpenSSL v1.1.1 we get an alternative to SSL_set_bio() that works * without backward compat quirks. Every call takes one reference, so we * up it and pass. SSL* then owns it and will free. - * We check on the function in configure, since libressl and friends + * We check on the function in configure, since LibreSSL and friends * each have their own versions to add support for this. */ BIO_up_ref(bio); - SSL_set0_rbio(backend->handle, bio); - SSL_set0_wbio(backend->handle, bio); + SSL_set0_rbio(octx->ssl, bio); + SSL_set0_wbio(octx->ssl, bio); #else - SSL_set_bio(backend->handle, bio, bio); + SSL_set_bio(octx->ssl, bio, bio); #endif - connssl->connecting_state = ssl_connect_2; +#ifdef HAS_ALPN + if(connssl->alpn) { + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } +#endif + connssl->connecting_state = ssl_connect_2; return CURLE_OK; } +#ifdef USE_ECH +/* If we have retry configs, then trace those out */ +static void ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL* ssl, + int reason) +{ + CURLcode result = CURLE_OK; + size_t rcl = 0; + int rv = 1; +# ifndef OPENSSL_IS_BORINGSSL + char *inner = NULL; + unsigned char *rcs = NULL; + char *outer = NULL; +# else + const char *inner = NULL; + const uint8_t *rcs = NULL; + const char *outer = NULL; + size_t out_name_len = 0; + int servername_type = 0; +# endif + + /* nothing to trace if not doing ECH */ + if(!ECH_ENABLED(data)) + return; +# ifndef OPENSSL_IS_BORINGSSL + rv = SSL_ech_get_retry_config(ssl, &rcs, &rcl); +# else + SSL_get0_ech_retry_configs(ssl, &rcs, &rcl); + rv = (int)rcl; +# endif + + if(rv && rcs) { +# define HEXSTR_MAX 800 + char *b64str = NULL; + size_t blen = 0; + + result = Curl_base64_encode((const char *)rcs, rcl, + &b64str, &blen); + if(!result && b64str) + infof(data, "ECH: retry_configs %s", b64str); + free(b64str); +# ifndef OPENSSL_IS_BORINGSSL + rv = SSL_ech_get_status(ssl, &inner, &outer); + infof(data, "ECH: retry_configs for %s from %s, %d %d", + inner ? inner : "NULL", outer ? outer : "NULL", reason, rv); +#else + rv = SSL_ech_accepted(ssl); + servername_type = SSL_get_servername_type(ssl); + inner = SSL_get_servername(ssl, servername_type); + SSL_get0_ech_name_override(ssl, &outer, &out_name_len); + /* TODO: get the inner from boring */ + infof(data, "ECH: retry_configs for %s from %s, %d %d", + inner ? inner : "NULL", outer ? outer : "NULL", reason, rv); +#endif + } + else + infof(data, "ECH: no retry_configs (rv = %d)", rv); +# ifndef OPENSSL_IS_BORINGSSL + OPENSSL_free((void *)rcs); +# endif + return; +} + +#endif + static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) { int err; struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - DEBUGASSERT(ssl_connect_2 == connssl->connecting_state - || ssl_connect_2_reading == connssl->connecting_state - || ssl_connect_2_writing == connssl->connecting_state); - DEBUGASSERT(backend); + DEBUGASSERT(ssl_connect_2 == connssl->connecting_state); + DEBUGASSERT(octx); + connssl->io_need = CURL_SSL_IO_NEED_NONE; ERR_clear_error(); - err = SSL_connect(backend->handle); + err = SSL_connect(octx->ssl); - if(!backend->x509_store_setup) { + if(!octx->x509_store_setup) { /* After having send off the ClientHello, we prepare the x509 * store to verify the coming certificate from the server */ - CURLcode result = Curl_ssl_setup_x509_store(cf, data, backend->ctx); + CURLcode result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); if(result) return result; - backend->x509_store_setup = TRUE; + octx->x509_store_setup = TRUE; } #ifndef HAVE_KEYLOG_CALLBACK - if(Curl_tls_keylog_enabled()) { - /* If key logging is enabled, wait for the handshake to complete and then - * proceed with logging secrets (for TLS 1.2 or older). - */ - ossl_log_tls12_secret(backend->handle, &backend->keylog_done); - } + /* If key logging is enabled, wait for the handshake to complete and then + * proceed with logging secrets (for TLS 1.2 or older). + */ + if(Curl_tls_keylog_enabled() && !octx->keylog_done) + ossl_log_tls12_secret(octx->ssl, &octx->keylog_done); #endif /* 1 is fine 0 is "not successful but was shut down controlled" <0 is "handshake was not successful, because a fatal error occurred" */ if(1 != err) { - int detail = SSL_get_error(backend->handle, err); + int detail = SSL_get_error(octx->ssl, err); + CURL_TRC_CF(data, cf, "SSL_connect() -> err=%d, detail=%d", err, detail); if(SSL_ERROR_WANT_READ == detail) { - connssl->connecting_state = ssl_connect_2_reading; + CURL_TRC_CF(data, cf, "SSL_connect() -> want recv"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; return CURLE_OK; } if(SSL_ERROR_WANT_WRITE == detail) { - connssl->connecting_state = ssl_connect_2_writing; + CURL_TRC_CF(data, cf, "SSL_connect() -> want send"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; return CURLE_OK; } #ifdef SSL_ERROR_WANT_ASYNC if(SSL_ERROR_WANT_ASYNC == detail) { + CURL_TRC_CF(data, cf, "SSL_connect() -> want async"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; connssl->connecting_state = ssl_connect_2; return CURLE_OK; } #endif #ifdef SSL_ERROR_WANT_RETRY_VERIFY if(SSL_ERROR_WANT_RETRY_VERIFY == detail) { + CURL_TRC_CF(data, cf, "SSL_connect() -> want retry_verify"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; connssl->connecting_state = ssl_connect_2; return CURLE_OK; } #endif - if(backend->io_result == CURLE_AGAIN) { - return CURLE_OK; - } else { /* untreated error */ sslerr_t errdetail; @@ -3887,7 +4201,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, int lib; int reason; - /* the connection failed, we're not waiting for anything else. */ + /* the connection failed, we are not waiting for anything else. */ connssl->connecting_state = ssl_connect_2; /* Get the earliest error code from the thread's error queue and remove @@ -3903,17 +4217,17 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, (reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED))) { result = CURLE_PEER_FAILED_VERIFICATION; - lerr = SSL_get_verify_result(backend->handle); + lerr = SSL_get_verify_result(octx->ssl); if(lerr != X509_V_OK) { ssl_config->certverifyresult = lerr; msnprintf(error_buffer, sizeof(error_buffer), "SSL certificate problem: %s", X509_verify_cert_error_string(lerr)); } - else - /* strcpy() is fine here as long as the string fits within - error_buffer */ - strcpy(error_buffer, "SSL certificate verification failed"); + else { + failf(data, "%s", "SSL certificate verification failed"); + return result; + } } #if defined(SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED) /* SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED is only available on @@ -3923,17 +4237,35 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, /* If client certificate is required, communicate the error to client */ result = CURLE_SSL_CLIENTCERT; - ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)); + failf(data, "TLS cert problem: %s", + ossl_strerror(errdetail, error_buffer, sizeof(error_buffer))); + } +#endif +#ifdef USE_ECH + else if((lib == ERR_LIB_SSL) && +# ifndef OPENSSL_IS_BORINGSSL + (reason == SSL_R_ECH_REQUIRED)) { +# else + (reason == SSL_R_ECH_REJECTED)) { +# endif + + /* trace retry_configs if we got some */ + ossl_trace_ech_retry_configs(data, octx->ssl, reason); + + result = CURLE_ECH_REQUIRED; + failf(data, "ECH required: %s", + ossl_strerror(errdetail, error_buffer, sizeof(error_buffer))); } #endif else { result = CURLE_SSL_CONNECT_ERROR; - ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)); + failf(data, "TLS connect error: %s", + ossl_strerror(errdetail, error_buffer, sizeof(error_buffer))); } /* detail is already set to the SSL error above */ - /* If we e.g. use SSLv2 request-method and the server doesn't like us + /* If we e.g. use SSLv2 request-method and the server does not like us * (RST connection, etc.), OpenSSL gives no explanation whatsoever and * the SO_ERROR is also lost. */ @@ -3945,24 +4277,98 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, Curl_strerror(sockerr, extramsg, sizeof(extramsg)); failf(data, OSSL_PACKAGE " SSL_connect: %s in connection to %s:%d ", extramsg[0] ? extramsg : SSL_ERROR_to_str(detail), - connssl->hostname, connssl->port); + connssl->peer.hostname, connssl->peer.port); return result; } - /* Could be a CERT problem */ - failf(data, "%s", error_buffer); - return result; } } else { - /* we connected fine, we're not waiting for anything else. */ + int psigtype_nid = NID_undef; + const char *negotiated_group_name = NULL; + + /* we connected fine, we are not waiting for anything else. */ connssl->connecting_state = ssl_connect_3; +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) + SSL_get_peer_signature_type_nid(octx->ssl, &psigtype_nid); +#if (OPENSSL_VERSION_NUMBER >= 0x30200000L) + negotiated_group_name = SSL_get0_group_name(octx->ssl); +#else + negotiated_group_name = + OBJ_nid2sn(SSL_get_negotiated_group(octx->ssl) & 0x0000FFFF); +#endif +#endif + /* Informational message */ - infof(data, "SSL connection using %s / %s", - SSL_get_version(backend->handle), - SSL_get_cipher(backend->handle)); + infof(data, "SSL connection using %s / %s / %s / %s", + SSL_get_version(octx->ssl), + SSL_get_cipher(octx->ssl), + negotiated_group_name ? negotiated_group_name : "[blank]", + OBJ_nid2sn(psigtype_nid)); + +#ifdef USE_ECH +# ifndef OPENSSL_IS_BORINGSSL + if(ECH_ENABLED(data)) { + char *inner = NULL, *outer = NULL; + const char *status = NULL; + int rv; + + rv = SSL_ech_get_status(octx->ssl, &inner, &outer); + switch(rv) { + case SSL_ECH_STATUS_SUCCESS: + status = "succeeded"; + break; + case SSL_ECH_STATUS_GREASE_ECH: + status = "sent GREASE, got retry-configs"; + break; + case SSL_ECH_STATUS_GREASE: + status = "sent GREASE"; + break; + case SSL_ECH_STATUS_NOT_TRIED: + status = "not attempted"; + break; + case SSL_ECH_STATUS_NOT_CONFIGURED: + status = "not configured"; + break; + case SSL_ECH_STATUS_BACKEND: + status = "backend (unexpected)"; + break; + case SSL_ECH_STATUS_FAILED: + status = "failed"; + break; + case SSL_ECH_STATUS_BAD_CALL: + status = "bad call (unexpected)"; + break; + case SSL_ECH_STATUS_BAD_NAME: + status = "bad name (unexpected)"; + break; + default: + status = "unexpected status"; + infof(data, "ECH: unexpected status %d",rv); + } + infof(data, "ECH: result: status is %s, inner is %s, outer is %s", + (status ? status : "NULL"), + (inner ? inner : "NULL"), + (outer ? outer : "NULL")); + OPENSSL_free(inner); + OPENSSL_free(outer); + if(rv == SSL_ECH_STATUS_GREASE_ECH) { + /* trace retry_configs if we got some */ + ossl_trace_ech_retry_configs(data, octx->ssl, 0); + } + if(rv != SSL_ECH_STATUS_SUCCESS + && data->set.tls_ech & CURLECH_HARD) { + infof(data, "ECH: ech-hard failed"); + return CURLE_SSL_CONNECT_ERROR; + } + } + else { + infof(data, "ECH: result: status is not attempted"); + } +# endif /* BORING */ +#endif /* USE_ECH */ #ifdef HAS_ALPN /* Sets data and len to negotiated protocol, len is 0 if no protocol was @@ -3971,9 +4377,9 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, if(connssl->alpn) { const unsigned char *neg_protocol; unsigned int len; - SSL_get0_alpn_selected(backend->handle, &neg_protocol, &len); + SSL_get0_alpn_selected(octx->ssl, &neg_protocol, &len); - return Curl_alpn_set_negotiated(cf, data, neg_protocol, len); + return Curl_alpn_set_negotiated(cf, data, connssl, neg_protocol, len); } #endif @@ -3995,7 +4401,7 @@ static CURLcode ossl_pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, /* Result is returned to caller */ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - /* if a path wasn't specified, don't pin */ + /* if a path was not specified, do not pin */ if(!pinnedpubkey) return CURLE_OK; @@ -4016,12 +4422,12 @@ static CURLcode ossl_pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, if(!buff1) break; /* failed */ - /* https://www.openssl.org/docs/crypto/d2i_X509.html */ + /* https://docs.openssl.org/master/man3/d2i_X509/ */ len2 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &temp); /* * These checks are verifying we got back the same values as when we - * sized the buffer. It's pretty weak since they should always be the + * sized the buffer. it is pretty weak since they should always be the * same. But it gives us something to test. */ if((len1 != len2) || !temp || ((temp - buff1) != len1)) @@ -4039,35 +4445,98 @@ static CURLcode ossl_pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, return result; } -/* - * Get the server cert, verify it and show it, etc., only call failf() if the - * 'strict' argument is TRUE as otherwise all this is for informational - * purposes only! - * - * We check certificates to authenticate the server; otherwise we risk - * man-in-the-middle attack. - */ -static CURLcode servercert(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool strict) +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ + !(defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER < 0x3060000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && \ + !defined(OPENSSL_IS_AWSLC) && \ + !defined(CURL_DISABLE_VERBOSE_STRINGS) +static void infof_certstack(struct Curl_easy *data, const SSL *ssl) +{ + STACK_OF(X509) *certstack; + long verify_result; + int num_cert_levels; + int cert_level; + + verify_result = SSL_get_verify_result(ssl); + if(verify_result != X509_V_OK) + certstack = SSL_get_peer_cert_chain(ssl); + else + certstack = SSL_get0_verified_chain(ssl); + num_cert_levels = sk_X509_num(certstack); + + for(cert_level = 0; cert_level < num_cert_levels; cert_level++) { + char cert_algorithm[80] = ""; + char group_name_final[80] = ""; + const X509_ALGOR *palg_cert = NULL; + const ASN1_OBJECT *paobj_cert = NULL; + X509 *current_cert; + EVP_PKEY *current_pkey; + int key_bits; + int key_sec_bits; + int get_group_name; + const char *type_name; + + current_cert = sk_X509_value(certstack, cert_level); + + X509_get0_signature(NULL, &palg_cert, current_cert); + X509_ALGOR_get0(&paobj_cert, NULL, NULL, palg_cert); + OBJ_obj2txt(cert_algorithm, sizeof(cert_algorithm), paobj_cert, 0); + + current_pkey = X509_get0_pubkey(current_cert); + key_bits = EVP_PKEY_bits(current_pkey); +#if (OPENSSL_VERSION_NUMBER < 0x30000000L) +#define EVP_PKEY_get_security_bits EVP_PKEY_security_bits +#endif + key_sec_bits = EVP_PKEY_get_security_bits(current_pkey); +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) + { + char group_name[80] = ""; + get_group_name = EVP_PKEY_get_group_name(current_pkey, group_name, + sizeof(group_name), NULL); + msnprintf(group_name_final, sizeof(group_name_final), "/%s", group_name); + } + type_name = EVP_PKEY_get0_type_name(current_pkey); +#else + get_group_name = 0; + type_name = NULL; +#endif + + infof(data, + " Certificate level %d: " + "Public key type %s%s (%d/%d Bits/secBits), signed using %s", + cert_level, type_name ? type_name : "?", + get_group_name == 0 ? "" : group_name_final, + key_bits, key_sec_bits, cert_algorithm); + } +} +#else +#define infof_certstack(data, ssl) +#endif + +#define MAX_CERT_NAME_LENGTH 2048 + +CURLcode Curl_oss_check_peer_cert(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ossl_ctx *octx, + struct ssl_peer *peer) { struct connectdata *conn = cf->conn; - struct ssl_connect_data *connssl = cf->ctx; struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); CURLcode result = CURLE_OK; - int rc; long lerr; X509 *issuer; BIO *fp = NULL; char error_buffer[256]=""; - char buffer[2048]; const char *ptr; BIO *mem = BIO_new(BIO_s_mem()); - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + bool strict = (conn_config->verifypeer || conn_config->verifyhost); + struct dynbuf dname; - DEBUGASSERT(backend); + DEBUGASSERT(octx); + + Curl_dyn_init(&dname, MAX_CERT_NAME_LENGTH); if(!mem) { failf(data, @@ -4080,34 +4549,34 @@ static CURLcode servercert(struct Curl_cfilter *cf, if(data->set.ssl.certinfo) /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, backend->handle); + (void)ossl_certchain(data, octx->ssl); - backend->server_cert = SSL_get1_peer_certificate(backend->handle); - if(!backend->server_cert) { + octx->server_cert = SSL_get1_peer_certificate(octx->ssl); + if(!octx->server_cert) { BIO_free(mem); if(!strict) return CURLE_OK; - failf(data, "SSL: couldn't get peer certificate"); + failf(data, "SSL: could not get peer certificate"); return CURLE_PEER_FAILED_VERIFICATION; } infof(data, "%s certificate:", - Curl_ssl_cf_is_proxy(cf)? "Proxy" : "Server"); + Curl_ssl_cf_is_proxy(cf) ? "Proxy" : "Server"); - rc = x509_name_oneline(X509_get_subject_name(backend->server_cert), - buffer, sizeof(buffer)); - infof(data, " subject: %s", rc?"[NONE]":buffer); + result = x509_name_oneline(X509_get_subject_name(octx->server_cert), + &dname); + infof(data, " subject: %s", result ? "[NONE]" : Curl_dyn_ptr(&dname)); #ifndef CURL_DISABLE_VERBOSE_STRINGS { long len; - ASN1_TIME_print(mem, X509_get0_notBefore(backend->server_cert)); + ASN1_TIME_print(mem, X509_get0_notBefore(octx->server_cert)); len = BIO_get_mem_data(mem, (char **) &ptr); infof(data, " start date: %.*s", (int)len, ptr); (void)BIO_reset(mem); - ASN1_TIME_print(mem, X509_get0_notAfter(backend->server_cert)); + ASN1_TIME_print(mem, X509_get0_notAfter(octx->server_cert)); len = BIO_get_mem_data(mem, (char **) &ptr); infof(data, " expire date: %.*s", (int)len, ptr); (void)BIO_reset(mem); @@ -4117,24 +4586,25 @@ static CURLcode servercert(struct Curl_cfilter *cf, BIO_free(mem); if(conn_config->verifyhost) { - result = ossl_verifyhost(data, conn, backend->server_cert, - connssl->hostname, connssl->dispname); + result = ossl_verifyhost(data, conn, peer, octx->server_cert); if(result) { - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; + Curl_dyn_free(&dname); return result; } } - rc = x509_name_oneline(X509_get_issuer_name(backend->server_cert), - buffer, sizeof(buffer)); - if(rc) { + result = x509_name_oneline(X509_get_issuer_name(octx->server_cert), + &dname); + if(result) { if(strict) - failf(data, "SSL: couldn't get X509-issuer name"); + failf(data, "SSL: could not get X509-issuer name"); result = CURLE_PEER_FAILED_VERIFICATION; } else { - infof(data, " issuer: %s", buffer); + infof(data, " issuer: %s", Curl_dyn_ptr(&dname)); + Curl_dyn_free(&dname); /* We could do all sorts of certificate verification stuff here before deallocating the certificate. */ @@ -4150,8 +4620,8 @@ static CURLcode servercert(struct Curl_cfilter *cf, " error %s", ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer)) ); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_OUT_OF_MEMORY; } } @@ -4163,8 +4633,8 @@ static CURLcode servercert(struct Curl_cfilter *cf, " error %s", ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer)) ); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_OUT_OF_MEMORY; } @@ -4173,8 +4643,8 @@ static CURLcode servercert(struct Curl_cfilter *cf, failf(data, "SSL: Unable to open issuer cert (%s)", conn_config->issuercert); BIO_free(fp); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_SSL_ISSUER_ERROR; } } @@ -4186,19 +4656,19 @@ static CURLcode servercert(struct Curl_cfilter *cf, conn_config->issuercert); BIO_free(fp); X509_free(issuer); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_SSL_ISSUER_ERROR; } - if(X509_check_issued(issuer, backend->server_cert) != X509_V_OK) { + if(X509_check_issued(issuer, octx->server_cert) != X509_V_OK) { if(strict) failf(data, "SSL: Certificate issuer check failed (%s)", conn_config->issuercert); BIO_free(fp); X509_free(issuer); - X509_free(backend->server_cert); - backend->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_SSL_ISSUER_ERROR; } @@ -4208,7 +4678,7 @@ static CURLcode servercert(struct Curl_cfilter *cf, X509_free(issuer); } - lerr = SSL_get_verify_result(backend->handle); + lerr = SSL_get_verify_result(octx->ssl); ssl_config->certverifyresult = lerr; if(lerr != X509_V_OK) { if(conn_config->verifypeer) { @@ -4227,35 +4697,55 @@ static CURLcode servercert(struct Curl_cfilter *cf, else infof(data, " SSL certificate verify ok."); } + infof_certstack(data, octx->ssl); #if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ !defined(OPENSSL_NO_OCSP) - if(conn_config->verifystatus) { - result = verifystatus(cf, data); + if(conn_config->verifystatus && !octx->reused_session) { + /* do not do this after Session ID reuse */ + result = verifystatus(cf, data, octx); if(result) { - X509_free(backend->server_cert); - backend->server_cert = NULL; + /* when verifystatus failed, remove the session id from the cache again + if present */ + if(!Curl_ssl_cf_is_proxy(cf)) { + void *old_ssl_sessionid = NULL; + bool incache; + Curl_ssl_sessionid_lock(data); + incache = !(Curl_ssl_getsessionid(cf, data, peer, + &old_ssl_sessionid, NULL, NULL)); + if(incache) { + infof(data, "Remove session ID again from cache"); + Curl_ssl_delsessionid(data, old_ssl_sessionid); + } + Curl_ssl_sessionid_unlock(data); + } + + X509_free(octx->server_cert); + octx->server_cert = NULL; return result; } } #endif if(!strict) - /* when not strict, we don't bother about the verify cert problems */ + /* when not strict, we do not bother about the verify cert problems */ result = CURLE_OK; - ptr = Curl_ssl_cf_is_proxy(cf)? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: +#ifndef CURL_DISABLE_PROXY + ptr = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif if(!result && ptr) { - result = ossl_pkp_pin_peer_pubkey(data, backend->server_cert, ptr); + result = ossl_pkp_pin_peer_pubkey(data, octx->server_cert, ptr); if(result) failf(data, "SSL: public key does not match pinned public key"); } - X509_free(backend->server_cert); - backend->server_cert = NULL; - connssl->connecting_state = ssl_connect_done; + X509_free(octx->server_cert); + octx->server_cert = NULL; return result; } @@ -4265,20 +4755,18 @@ static CURLcode ossl_connect_step3(struct Curl_cfilter *cf, { CURLcode result = CURLE_OK; struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); /* * We check certificates to authenticate the server; otherwise we risk - * man-in-the-middle attack; NEVERTHELESS, if we're told explicitly not to + * man-in-the-middle attack; NEVERTHELESS, if we are told explicitly not to * verify the peer, ignore faults and failures from the server cert * operations. */ - result = servercert(cf, data, conn_config->verifypeer || - conn_config->verifyhost); - + result = Curl_oss_check_peer_cert(cf, data, octx, &connssl->peer); if(!result) connssl->connecting_state = ssl_connect_done; @@ -4295,6 +4783,7 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf, curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); int what; + connssl->io_need = CURL_SSL_IO_NEED_NONE; /* check if the connection has already been established */ if(ssl_connection_complete == connssl->state) { *done = TRUE; @@ -4302,7 +4791,7 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf, } if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we're allowed */ + /* Find out how much more time we are allowed */ const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); if(timeout_ms < 0) { @@ -4316,9 +4805,7 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf, goto out; } - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { + while(ssl_connect_2 == connssl->connecting_state) { /* check allowed time left */ const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); @@ -4330,15 +4817,12 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf, goto out; } - /* if ssl is expecting something, check if it's available. */ - if(!nonblocking && - (connssl->connecting_state == ssl_connect_2_reading || - connssl->connecting_state == ssl_connect_2_writing)) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + /* if ssl is expecting something, check if it is available. */ + if(!nonblocking && connssl->io_need) { + curl_socket_t writefd = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? + sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = (connssl->io_need & CURL_SSL_IO_NEED_RECV) ? + sockfd : CURL_SOCKET_BAD; what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, timeout_ms); @@ -4364,10 +4848,7 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf, * or epoll() will always have a valid fdset to wait on. */ result = ossl_connect_step2(cf, data); - if(result || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) + if(result || (nonblocking && (ssl_connect_2 == connssl->connecting_state))) goto out; } /* repeat step2 until all transactions are done. */ @@ -4418,12 +4899,11 @@ static bool ossl_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; (void)data; - DEBUGASSERT(connssl && backend); - if(backend->handle && SSL_pending(backend->handle)) + DEBUGASSERT(connssl && octx); + if(octx->ssl && SSL_pending(octx->ssl)) return TRUE; return FALSE; } @@ -4442,26 +4922,27 @@ static ssize_t ossl_send(struct Curl_cfilter *cf, int memlen; int rc; struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; (void)data; - DEBUGASSERT(backend); + DEBUGASSERT(octx); ERR_clear_error(); + connssl->io_need = CURL_SSL_IO_NEED_NONE; memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; - rc = SSL_write(backend->handle, mem, memlen); + rc = SSL_write(octx->ssl, mem, memlen); if(rc <= 0) { - err = SSL_get_error(backend->handle, rc); + err = SSL_get_error(octx->ssl, rc); switch(err) { case SSL_ERROR_WANT_READ: + connssl->io_need = CURL_SSL_IO_NEED_RECV; + *curlcode = CURLE_AGAIN; + rc = -1; + goto out; case SSL_ERROR_WANT_WRITE: - /* The operation did not complete; the same TLS/SSL I/O function - should be called again later. This is basically an EWOULDBLOCK - equivalent. */ *curlcode = CURLE_AGAIN; rc = -1; goto out; @@ -4469,7 +4950,7 @@ static ssize_t ossl_send(struct Curl_cfilter *cf, { int sockerr = SOCKERRNO; - if(backend->io_result == CURLE_AGAIN) { + if(octx->io_result == CURLE_AGAIN) { *curlcode = CURLE_AGAIN; rc = -1; goto out; @@ -4479,10 +4960,10 @@ static ssize_t ossl_send(struct Curl_cfilter *cf, ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)); else if(sockerr) Curl_strerror(sockerr, error_buffer, sizeof(error_buffer)); - else { - strncpy(error_buffer, SSL_ERROR_to_str(err), sizeof(error_buffer)); - error_buffer[sizeof(error_buffer) - 1] = '\0'; - } + else + msnprintf(error_buffer, sizeof(error_buffer), "%s", + SSL_ERROR_to_str(err)); + failf(data, OSSL_PACKAGE " SSL_write: %s, errno %d", error_buffer, sockerr); *curlcode = CURLE_SEND_ERROR; @@ -4492,22 +4973,9 @@ static ssize_t ossl_send(struct Curl_cfilter *cf, case SSL_ERROR_SSL: { /* A failure in the SSL library occurred, usually a protocol error. The OpenSSL error queue contains more information on the error. */ - struct Curl_cfilter *cf_ssl_next = Curl_ssl_cf_get_ssl(cf->next); - struct ssl_connect_data *connssl_next = cf_ssl_next? - cf_ssl_next->ctx : NULL; sslerror = ERR_get_error(); - if(ERR_GET_LIB(sslerror) == ERR_LIB_SSL && - ERR_GET_REASON(sslerror) == SSL_R_BIO_NOT_SET && - connssl->state == ssl_connection_complete && - (connssl_next && connssl_next->state == ssl_connection_complete) - ) { - char ver[120]; - (void)ossl_version(ver, sizeof(ver)); - failf(data, "Error: %s does not support double SSL tunneling.", ver); - } - else - failf(data, "SSL_write() error: %s", - ossl_strerror(sslerror, error_buffer, sizeof(error_buffer))); + failf(data, "SSL_write() error: %s", + ossl_strerror(sslerror, error_buffer, sizeof(error_buffer))); *curlcode = CURLE_SEND_ERROR; rc = -1; goto out; @@ -4539,20 +5007,20 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf, int buffsize; struct connectdata *conn = cf->conn; struct ssl_connect_data *connssl = cf->ctx; - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; (void)data; - DEBUGASSERT(backend); + DEBUGASSERT(octx); ERR_clear_error(); + connssl->io_need = CURL_SSL_IO_NEED_NONE; buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; - nread = (ssize_t)SSL_read(backend->handle, buf, buffsize); + nread = (ssize_t)SSL_read(octx->ssl, buf, buffsize); if(nread <= 0) { /* failed SSL_read */ - int err = SSL_get_error(backend->handle, (int)nread); + int err = SSL_get_error(octx->ssl, (int)nread); switch(err) { case SSL_ERROR_NONE: /* this is not an error */ @@ -4565,16 +5033,19 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf, connclose(conn, "TLS close_notify"); break; case SSL_ERROR_WANT_READ: + *curlcode = CURLE_AGAIN; + nread = -1; + goto out; case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke SSL_read() */ + connssl->io_need = CURL_SSL_IO_NEED_SEND; *curlcode = CURLE_AGAIN; nread = -1; goto out; default: /* openssl/ssl.h for SSL_ERROR_SYSCALL says "look at error stack/return value/errno" */ - /* https://www.openssl.org/docs/crypto/ERR_get_error.html */ - if(backend->io_result == CURLE_AGAIN) { + /* https://docs.openssl.org/master/man3/ERR_get_error/ */ + if(octx->io_result == CURLE_AGAIN) { *curlcode = CURLE_AGAIN; nread = -1; goto out; @@ -4588,10 +5059,9 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf, ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)); else if(sockerr && err == SSL_ERROR_SYSCALL) Curl_strerror(sockerr, error_buffer, sizeof(error_buffer)); - else { - strncpy(error_buffer, SSL_ERROR_to_str(err), sizeof(error_buffer)); - error_buffer[sizeof(error_buffer) - 1] = '\0'; - } + else + msnprintf(error_buffer, sizeof(error_buffer), "%s", + SSL_ERROR_to_str(err)); failf(data, OSSL_PACKAGE " SSL_read: %s, errno %d", error_buffer, sockerr); *curlcode = CURLE_RECV_ERROR; @@ -4601,7 +5071,7 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf, /* For debug builds be a little stricter and error on any SSL_ERROR_SYSCALL. For example a server may have closed the connection abruptly without a close_notify alert. For compatibility with older - peers we don't do this by default. #4624 + peers we do not do this by default. #4624 We can use this to gauge how many users may be affected, and if it goes ok eventually transition to allow in dev and release with @@ -4630,12 +5100,96 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf, return nread; } +static CURLcode ossl_get_channel_binding(struct Curl_easy *data, int sockindex, + struct dynbuf *binding) +{ + /* required for X509_get_signature_nid support */ +#if OPENSSL_VERSION_NUMBER > 0x10100000L + X509 *cert; + int algo_nid; + const EVP_MD *algo_type; + const char *algo_name; + unsigned int length; + unsigned char buf[EVP_MAX_MD_SIZE]; + + const char prefix[] = "tls-server-end-point:"; + struct connectdata *conn = data->conn; + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + struct ossl_ctx *octx = NULL; + + do { + const struct Curl_cftype *cft = cf->cft; + struct ssl_connect_data *connssl = cf->ctx; + + if(cft->name && !strcmp(cft->name, "SSL")) { + octx = (struct ossl_ctx *)connssl->backend; + break; + } + + if(cf->next) + cf = cf->next; + + } while(cf->next); + + if(!octx) { + failf(data, "Failed to find the SSL filter"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + cert = SSL_get1_peer_certificate(octx->ssl); + if(!cert) { + /* No server certificate, don't do channel binding */ + return CURLE_OK; + } + + if(!OBJ_find_sigid_algs(X509_get_signature_nid(cert), &algo_nid, NULL)) { + failf(data, + "Unable to find digest NID for certificate signature algorithm"); + return CURLE_SSL_INVALIDCERTSTATUS; + } + + /* https://datatracker.ietf.org/doc/html/rfc5929#section-4.1 */ + if(algo_nid == NID_md5 || algo_nid == NID_sha1) { + algo_type = EVP_sha256(); + } + else { + algo_type = EVP_get_digestbynid(algo_nid); + if(!algo_type) { + algo_name = OBJ_nid2sn(algo_nid); + failf(data, "Could not find digest algorithm %s (NID %d)", + algo_name ? algo_name : "(null)", algo_nid); + return CURLE_SSL_INVALIDCERTSTATUS; + } + } + + if(!X509_digest(cert, algo_type, buf, &length)) { + failf(data, "X509_digest() failed"); + return CURLE_SSL_INVALIDCERTSTATUS; + } + + /* Append "tls-server-end-point:" */ + if(Curl_dyn_addn(binding, prefix, sizeof(prefix) - 1) != CURLE_OK) + return CURLE_OUT_OF_MEMORY; + /* Append digest */ + if(Curl_dyn_addn(binding, buf, length)) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +#else + /* No X509_get_signature_nid support */ + (void)data; /* unused */ + (void)sockindex; /* unused */ + (void)binding; /* unused */ + return CURLE_OK; +#endif +} + static size_t ossl_version(char *buffer, size_t size) { #ifdef LIBRESSL_VERSION_NUMBER #ifdef HAVE_OPENSSL_VERSION char *p; - int count; + size_t count; const char *ver = OpenSSL_version(OPENSSL_VERSION); const char expected[] = OSSL_PACKAGE " "; /* ie "LibreSSL " */ if(strncasecompare(ver, expected, sizeof(expected) - 1)) { @@ -4650,9 +5204,9 @@ static size_t ossl_version(char *buffer, size_t size) #else return msnprintf(buffer, size, "%s/%lx.%lx.%lx", OSSL_PACKAGE, - (LIBRESSL_VERSION_NUMBER>>28)&0xf, - (LIBRESSL_VERSION_NUMBER>>20)&0xff, - (LIBRESSL_VERSION_NUMBER>>12)&0xff); + (LIBRESSL_VERSION_NUMBER >> 28) & 0xf, + (LIBRESSL_VERSION_NUMBER >> 20) & 0xff, + (LIBRESSL_VERSION_NUMBER >> 12) & 0xff); #endif #elif defined(OPENSSL_IS_BORINGSSL) #ifdef CURL_BORINGSSL_VERSION @@ -4703,9 +5257,9 @@ static size_t ossl_version(char *buffer, size_t size) #endif , OSSL_PACKAGE, - (ssleay_value>>28)&0xf, - (ssleay_value>>20)&0xff, - (ssleay_value>>12)&0xff, + (ssleay_value >> 28) & 0xf, + (ssleay_value >> 20) & 0xff, + (ssleay_value >> 12) & 0xff, sub); #endif /* OPENSSL_IS_BORINGSSL */ } @@ -4717,14 +5271,14 @@ static CURLcode ossl_random(struct Curl_easy *data, int rc; if(data) { if(ossl_seed(data)) /* Initiate the seed if not already done */ - return CURLE_FAILED_INIT; /* couldn't seed for some reason */ + return CURLE_FAILED_INIT; /* could not seed for some reason */ } else { if(!rand_enough()) return CURLE_FAILED_INIT; } /* RAND_bytes() returns 1 on success, 0 otherwise. */ - rc = RAND_bytes(entropy, curlx_uztosi(length)); + rc = RAND_bytes(entropy, (ossl_valsize_t)curlx_uztosi(length)); return (rc == 1 ? CURLE_OK : CURLE_FAILED_INIT); } @@ -4766,25 +5320,10 @@ static void *ossl_get_internals(struct ssl_connect_data *connssl, CURLINFO info) { /* Legacy: CURLINFO_TLS_SESSION must return an SSL_CTX pointer. */ - struct ossl_ssl_backend_data *backend = - (struct ossl_ssl_backend_data *)connssl->backend; - DEBUGASSERT(backend); + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + DEBUGASSERT(octx); return info == CURLINFO_TLS_SESSION ? - (void *)backend->ctx : (void *)backend->handle; -} - -static void ossl_free_multi_ssl_backend_data( - struct multi_ssl_backend_data *mbackend) -{ -#if defined(HAVE_SSL_X509_STORE_SHARE) - if(mbackend->store) { - X509_STORE_free(mbackend->store); - } - free(mbackend->CAfile); - free(mbackend); -#else /* HAVE_SSL_X509_STORE_SHARE */ - (void)mbackend; -#endif /* HAVE_SSL_X509_STORE_SHARE */ + (void *)octx->ssl_ctx : (void *)octx->ssl; } const struct Curl_ssl Curl_ssl_openssl = { @@ -4798,9 +5337,14 @@ const struct Curl_ssl Curl_ssl_openssl = { #ifdef HAVE_SSL_CTX_SET_CIPHERSUITES SSLSUPP_TLS13_CIPHERSUITES | #endif - SSLSUPP_HTTPS_PROXY, +#ifdef USE_ECH + SSLSUPP_ECH | +#endif + SSLSUPP_CA_CACHE | + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST, - sizeof(struct ossl_ssl_backend_data), + sizeof(struct ossl_ctx), ossl_init, /* init */ ossl_cleanup, /* cleanup */ @@ -4812,11 +5356,10 @@ const struct Curl_ssl Curl_ssl_openssl = { ossl_cert_status_request, /* cert_status_request */ ossl_connect, /* connect */ ossl_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks,/* getsock */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ ossl_get_internals, /* get_internals */ ossl_close, /* close_one */ ossl_close_all, /* close_all */ - ossl_session_free, /* session_free */ ossl_set_engine, /* set_engine */ ossl_set_engine_default, /* set_engine_default */ ossl_engines_list, /* engines_list */ @@ -4828,9 +5371,9 @@ const struct Curl_ssl Curl_ssl_openssl = { #endif NULL, /* use of data in this connection */ NULL, /* remote of data from this connection */ - ossl_free_multi_ssl_backend_data, /* free_multi_ssl_backend_data */ ossl_recv, /* recv decrypted data */ ossl_send, /* send data to encrypt */ + ossl_get_channel_binding /* get_channel_binding */ }; #endif /* USE_OPENSSL */ diff --git a/deps/curl/lib/vtls/openssl.h b/deps/curl/lib/vtls/openssl.h index 950faab8892..7aba947d182 100644 --- a/deps/curl/lib/vtls/openssl.h +++ b/deps/curl/lib/vtls/openssl.h @@ -31,30 +31,50 @@ * This header should only be needed to get included by vtls.c, openssl.c * and ngtcp2.c */ +#include #include #include "urldata.h" -/* - * In an effort to avoid using 'X509 *' here, we instead use the struct - * x509_st version of the type so that we can forward-declare it here without - * having to include . Including that header causes name - * conflicts when libcurl is built with both Schannel and OpenSSL support. - */ -struct x509_st; -CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, - struct x509_st *server_cert); -extern const struct Curl_ssl Curl_ssl_openssl; +/* Struct to hold a Curl OpenSSL instance */ +struct ossl_ctx { + /* these ones requires specific SSL-types */ + SSL_CTX* ssl_ctx; + SSL* ssl; + X509* server_cert; + BIO_METHOD *bio_method; + CURLcode io_result; /* result of last BIO cfilter operation */ +#ifndef HAVE_KEYLOG_CALLBACK + /* Set to true once a valid keylog entry has been created to avoid dupes. + This is a bool and not a bitfield because it is passed by address. */ + bool keylog_done; +#endif + BIT(x509_store_setup); /* x509 store has been set up */ + BIT(reused_session); /* session-ID was reused for this */ +}; + +typedef CURLcode Curl_ossl_ctx_setup_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data); -struct ssl_ctx_st; -CURLcode Curl_ossl_set_client_cert(struct Curl_easy *data, - struct ssl_ctx_st *ctx, char *cert_file, - const struct curl_blob *cert_blob, - const char *cert_type, char *key_file, - const struct curl_blob *key_blob, - const char *key_type, char *key_passwd); +typedef int Curl_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid); -CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl); +CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + int transport, /* TCP or QUIC */ + const unsigned char *alpn, size_t alpn_len, + Curl_ossl_ctx_setup_cb *cb_setup, + void *cb_user_data, + Curl_ossl_new_session_cb *cb_new_session, + void *ssl_user_data); + +#if (OPENSSL_VERSION_NUMBER < 0x30000000L) +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#endif + +extern const struct Curl_ssl Curl_ssl_openssl; /** * Setup the OpenSSL X509_STORE in `ssl_ctx` for the cfilter `cf` and @@ -65,5 +85,27 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, SSL_CTX *ssl_ctx); +CURLcode Curl_ossl_ctx_configure(struct Curl_cfilter *cf, + struct Curl_easy *data, + SSL_CTX *ssl_ctx); + +/* + * Add a new session to the cache. Takes ownership of the session. + */ +CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct ssl_peer *peer, + SSL_SESSION *ssl_sessionid); + +/* + * Get the server cert, verify it and show it, etc., only call failf() if + * ssl config verifypeer or -host is set. Otherwise all this is for + * informational purposes only! + */ +CURLcode Curl_oss_check_peer_cert(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ossl_ctx *octx, + struct ssl_peer *peer); + #endif /* USE_OPENSSL */ #endif /* HEADER_CURL_SSLUSE_H */ diff --git a/deps/curl/lib/vtls/rustls.c b/deps/curl/lib/vtls/rustls.c index a3e9d964c9d..5d143486d65 100644 --- a/deps/curl/lib/vtls/rustls.c +++ b/deps/curl/lib/vtls/rustls.c @@ -7,6 +7,7 @@ * * Copyright (C) Jacob Hoffman-Andrews, * + * Copyright (C) kpcyrd, * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -36,15 +37,21 @@ #include "sendf.h" #include "vtls.h" #include "vtls_int.h" +#include "rustls.h" #include "select.h" #include "strerror.h" #include "multiif.h" +#include "connect.h" /* for the connect timeout */ +#include "cipher_suite.h" +#include "rand.h" struct rustls_ssl_backend_data { const struct rustls_client_config *config; struct rustls_connection *conn; - bool data_pending; + size_t plain_out_buffered; + BIT(data_in_pending); + BIT(sent_shutdown); }; /* For a given rustls_result error code, return the best-matching CURLcode. */ @@ -59,7 +66,7 @@ static CURLcode map_error(rustls_result r) case RUSTLS_RESULT_NULL_PARAMETER: return CURLE_BAD_FUNCTION_ARGUMENT; default: - return CURLE_READ_ERROR; + return CURLE_RECV_ERROR; } } @@ -72,15 +79,7 @@ cr_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) (void)data; DEBUGASSERT(ctx && ctx->backend); backend = (struct rustls_ssl_backend_data *)ctx->backend; - return backend->data_pending; -} - -static CURLcode -cr_connect(struct Curl_cfilter *cf UNUSED_PARAM, - struct Curl_easy *data UNUSED_PARAM) -{ - infof(data, "rustls_connect: unimplemented"); - return CURLE_SSL_CONNECT_ERROR; + return backend->data_in_pending; } struct io_ctx { @@ -92,6 +91,7 @@ static int read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n) { struct io_ctx *io_ctx = userdata; + struct ssl_connect_data *const connssl = io_ctx->cf->ctx; CURLcode result; int ret = 0; ssize_t nread = Curl_conn_cf_recv(io_ctx->cf->next, io_ctx->data, @@ -103,7 +103,11 @@ read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n) else ret = EINVAL; } - *out_n = (int)nread; + else if(nread == 0) + connssl->peer_closed = TRUE; + *out_n = (uintptr_t)nread; + CURL_TRC_CF(io_ctx->data, io_ctx->cf, "cf->next recv(len=%zu) -> %zd, %d", + len, nread, result); return ret; } @@ -114,7 +118,8 @@ write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n) CURLcode result; int ret = 0; ssize_t nwritten = Curl_conn_cf_send(io_ctx->cf->next, io_ctx->data, - (const char *)buf, len, &result); + (const char *)buf, len, FALSE, + &result); if(nwritten < 0) { nwritten = 0; if(CURLE_AGAIN == result) @@ -122,11 +127,9 @@ write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n) else ret = EINVAL; } - *out_n = (int)nwritten; - /* - CURL_TRC_CFX(io_ctx->data, io_ctx->cf, "cf->next send(len=%zu) -> %zd, %d", - len, nwritten, result)); - */ + *out_n = (uintptr_t)nwritten; + CURL_TRC_CF(io_ctx->data, io_ctx->cf, "cf->next send(len=%zu) -> %zd, %d", + len, nwritten, result); return ret; } @@ -153,7 +156,7 @@ static ssize_t tls_recv_more(struct Curl_cfilter *cf, char buffer[STRERROR_LEN]; failf(data, "reading from socket: %s", Curl_strerror(io_error, buffer, sizeof(buffer))); - *err = CURLE_READ_ERROR; + *err = CURLE_RECV_ERROR; return -1; } @@ -163,27 +166,27 @@ static ssize_t tls_recv_more(struct Curl_cfilter *cf, size_t errorlen; rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); failf(data, "rustls_connection_process_new_packets: %.*s", - errorlen, errorbuf); + (int)errorlen, errorbuf); *err = map_error(rresult); return -1; } - backend->data_pending = TRUE; + backend->data_in_pending = TRUE; *err = CURLE_OK; return (ssize_t)tls_bytes_read; } /* * On each run: - * - Read a chunk of bytes from the socket into rustls' TLS input buffer. - * - Tell rustls to process any new packets. - * - Read out as many plaintext bytes from rustls as possible, until hitting + * - Read a chunk of bytes from the socket into Rustls' TLS input buffer. + * - Tell Rustls to process any new packets. + * - Read out as many plaintext bytes from Rustls as possible, until hitting * error, EOF, or EAGAIN/EWOULDBLOCK, or plainbuf/plainlen is filled up. * - * It's okay to call this function with plainbuf == NULL and plainlen == 0. - * In that case, it will copy bytes from the socket into rustls' TLS input - * buffer, and process packets, but won't consume bytes from rustls' plaintext - * output buffer. + * it is okay to call this function with plainbuf == NULL and plainlen == 0. In + * that case, it will copy bytes from the socket into Rustls' TLS input + * buffer, and process packets, but will not consume bytes from Rustls' + * plaintext output buffer. */ static ssize_t cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -203,7 +206,7 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, rconn = backend->conn; while(plain_bytes_copied < plainlen) { - if(!backend->data_pending) { + if(!backend->data_in_pending) { if(tls_recv_more(cf, data, err) < 0) { if(*err != CURLE_AGAIN) { nread = -1; @@ -214,26 +217,26 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } rresult = rustls_connection_read(rconn, - (uint8_t *)plainbuf + plain_bytes_copied, - plainlen - plain_bytes_copied, - &n); + (uint8_t *)plainbuf + plain_bytes_copied, + plainlen - plain_bytes_copied, + &n); if(rresult == RUSTLS_RESULT_PLAINTEXT_EMPTY) { - backend->data_pending = FALSE; + backend->data_in_pending = FALSE; } else if(rresult == RUSTLS_RESULT_UNEXPECTED_EOF) { failf(data, "rustls: peer closed TCP connection " - "without first closing TLS connection"); - *err = CURLE_READ_ERROR; + "without first closing TLS connection"); + *err = CURLE_RECV_ERROR; nread = -1; goto out; } else if(rresult != RUSTLS_RESULT_OK) { - /* n always equals 0 in this case, don't need to check it */ + /* n always equals 0 in this case, do not need to check it */ char errorbuf[255]; size_t errorlen; rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); - failf(data, "rustls_connection_read: %.*s", errorlen, errorbuf); - *err = CURLE_READ_ERROR; + failf(data, "rustls_connection_read: %.*s", (int)errorlen, errorbuf); + *err = CURLE_RECV_ERROR; nread = -1; goto out; } @@ -268,15 +271,51 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, return nread; } +static CURLcode cr_flush_out(struct Curl_cfilter *cf, struct Curl_easy *data, + struct rustls_connection *rconn) +{ + struct io_ctx io_ctx; + rustls_io_result io_error; + size_t tlswritten = 0; + size_t tlswritten_total = 0; + CURLcode result = CURLE_OK; + + io_ctx.cf = cf; + io_ctx.data = data; + + while(rustls_connection_wants_write(rconn)) { + io_error = rustls_connection_write_tls(rconn, write_cb, &io_ctx, + &tlswritten); + if(io_error == EAGAIN || io_error == EWOULDBLOCK) { + CURL_TRC_CF(data, cf, "cf_send: EAGAIN after %zu bytes", + tlswritten_total); + return CURLE_AGAIN; + } + else if(io_error) { + char buffer[STRERROR_LEN]; + failf(data, "writing to socket: %s", + Curl_strerror(io_error, buffer, sizeof(buffer))); + return CURLE_SEND_ERROR; + } + if(tlswritten == 0) { + failf(data, "EOF in swrite"); + return CURLE_SEND_ERROR; + } + CURL_TRC_CF(data, cf, "cf_send: wrote %zu TLS bytes", tlswritten); + tlswritten_total += tlswritten; + } + return result; +} + /* * On each call: - * - Copy `plainlen` bytes into rustls' plaintext input buffer (if > 0). - * - Fully drain rustls' plaintext output buffer into the socket until + * - Copy `plainlen` bytes into Rustls' plaintext input buffer (if > 0). + * - Fully drain Rustls' plaintext output buffer into the socket until * we get either an error or EAGAIN/EWOULDBLOCK. * - * It's okay to call this function with plainbuf == NULL and plainlen == 0. - * In that case, it won't read anything into rustls' plaintext input buffer. - * It will only drain rustls' plaintext output buffer into the socket. + * it is okay to call this function with plainbuf == NULL and plainlen == 0. + * In that case, it will not read anything into Rustls' plaintext input buffer. + * It will only drain Rustls' plaintext output buffer into the socket. */ static ssize_t cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -286,29 +325,46 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, struct rustls_ssl_backend_data *const backend = (struct rustls_ssl_backend_data *)connssl->backend; struct rustls_connection *rconn = NULL; - struct io_ctx io_ctx; size_t plainwritten = 0; - size_t tlswritten = 0; - size_t tlswritten_total = 0; rustls_result rresult; - rustls_io_result io_error; char errorbuf[256]; size_t errorlen; + const unsigned char *buf = plainbuf; + size_t blen = plainlen; + ssize_t nwritten = 0; DEBUGASSERT(backend); rconn = backend->conn; + DEBUGASSERT(rconn); + + CURL_TRC_CF(data, cf, "cf_send(len=%zu)", plainlen); + + /* If a previous send blocked, we already added its plain bytes + * to rustsls and must not do that again. Flush the TLS bytes and, + * if successful, deduct the previous plain bytes from the current + * send. */ + if(backend->plain_out_buffered) { + *err = cr_flush_out(cf, data, rconn); + CURL_TRC_CF(data, cf, "cf_send: flushing %zu previously added bytes -> %d", + backend->plain_out_buffered, *err); + if(*err) + return -1; + if(blen > backend->plain_out_buffered) { + blen -= backend->plain_out_buffered; + buf += backend->plain_out_buffered; + } + else + blen = 0; + nwritten += (ssize_t)backend->plain_out_buffered; + backend->plain_out_buffered = 0; + } - CURL_TRC_CF(data, cf, "cf_send: %ld plain bytes", plainlen); - - io_ctx.cf = cf; - io_ctx.data = data; - - if(plainlen > 0) { - rresult = rustls_connection_write(rconn, plainbuf, plainlen, - &plainwritten); + if(blen > 0) { + CURL_TRC_CF(data, cf, "cf_send: adding %zu plain bytes to Rustls", blen); + rresult = rustls_connection_write(rconn, buf, blen, &plainwritten); if(rresult != RUSTLS_RESULT_OK) { rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); - failf(data, "rustls_connection_write: %.*s", errorlen, errorbuf); + failf(data, "rustls_connection_write: %.*s", (int)errorlen, errorbuf); *err = CURLE_WRITE_ERROR; return -1; } @@ -319,57 +375,151 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, } } - while(rustls_connection_wants_write(rconn)) { - io_error = rustls_connection_write_tls(rconn, write_cb, &io_ctx, - &tlswritten); - if(io_error == EAGAIN || io_error == EWOULDBLOCK) { - CURL_TRC_CF(data, cf, "cf_send: EAGAIN after %zu bytes", - tlswritten_total); - *err = CURLE_AGAIN; - return -1; - } - else if(io_error) { - char buffer[STRERROR_LEN]; - failf(data, "writing to socket: %s", - Curl_strerror(io_error, buffer, sizeof(buffer))); - *err = CURLE_WRITE_ERROR; - return -1; - } - if(tlswritten == 0) { - failf(data, "EOF in swrite"); - *err = CURLE_WRITE_ERROR; - return -1; + *err = cr_flush_out(cf, data, rconn); + if(*err) { + if(CURLE_AGAIN == *err) { + /* The TLS bytes may have been partially written, but we fail the + * complete send() and remember how much we already added to Rustls. */ + CURL_TRC_CF(data, cf, "cf_send: EAGAIN, remember we added %zu plain" + " bytes already to Rustls", blen); + backend->plain_out_buffered = plainwritten; + if(nwritten) { + *err = CURLE_OK; + return (ssize_t)nwritten; + } } - CURL_TRC_CF(data, cf, "cf_send: wrote %zu TLS bytes", tlswritten); - tlswritten_total += tlswritten; + return -1; } + else + nwritten += (ssize_t)plainwritten; - return plainwritten; + CURL_TRC_CF(data, cf, "cf_send(len=%zu) -> %d, %zd", + plainlen, *err, nwritten); + return nwritten; } -/* A server certificate verify callback for rustls that always returns +/* A server certificate verify callback for Rustls that always returns RUSTLS_RESULT_OK, or in other words disable certificate verification. */ -static enum rustls_result +static uint32_t cr_verify_none(void *userdata UNUSED_PARAM, const rustls_verify_server_cert_params *params UNUSED_PARAM) { return RUSTLS_RESULT_OK; } -static bool -cr_hostname_is_ip(const char *hostname) +static int +read_file_into(const char *filename, + struct dynbuf *out) { - struct in_addr in; -#ifdef ENABLE_IPV6 - struct in6_addr in6; - if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) { - return true; + FILE *f = fopen(filename, FOPEN_READTEXT); + if(!f) { + return 0; + } + + while(!feof(f)) { + uint8_t buf[256]; + size_t rr = fread(buf, 1, sizeof(buf), f); + if(rr == 0 || + CURLE_OK != Curl_dyn_addn(out, buf, rr)) { + fclose(f); + return 0; + } + } + + return fclose(f) == 0; +} + +static void +cr_get_selected_ciphers(struct Curl_easy *data, + const char *ciphers12, + const char *ciphers13, + const struct rustls_supported_ciphersuite **selected, + size_t *selected_size) +{ + size_t supported_len = *selected_size; + size_t default_len = rustls_default_crypto_provider_ciphersuites_len(); + const struct rustls_supported_ciphersuite *entry; + const char *ciphers = ciphers12; + size_t count = 0, default13_count = 0, i, j; + const char *ptr, *end; + + DEBUGASSERT(default_len <= supported_len); + + if(!ciphers13) { + /* Add default TLSv1.3 ciphers to selection */ + for(j = 0; j < default_len; j++) { + entry = rustls_default_crypto_provider_ciphersuites_get(j); + if(rustls_supported_ciphersuite_protocol_version(entry) != + RUSTLS_TLS_VERSION_TLSV1_3) + continue; + + selected[count++] = entry; + } + + default13_count = count; + + if(!ciphers) + ciphers = ""; + } + else + ciphers = ciphers13; + +add_ciphers: + for(ptr = ciphers; ptr[0] != '\0' && count < supported_len; ptr = end) { + uint16_t id = Curl_cipher_suite_walk_str(&ptr, &end); + + /* Check if cipher is supported */ + if(id) { + for(i = 0; i < supported_len; i++) { + entry = rustls_default_crypto_provider_ciphersuites_get(i); + if(rustls_supported_ciphersuite_get_suite(entry) == id) + break; + } + if(i == supported_len) + id = 0; + } + if(!id) { + if(ptr[0] != '\0') + infof(data, "rustls: unknown cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } + + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != entry; i++); + if(i < count) { + if(i >= default13_count) + infof(data, "rustls: duplicate cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } + + selected[count++] = entry; + } + + if(ciphers == ciphers13 && ciphers12) { + ciphers = ciphers12; + goto add_ciphers; } -#endif /* ENABLE_IPV6 */ - if(Curl_inet_pton(AF_INET, hostname, &in) > 0) { - return true; + + if(!ciphers12) { + /* Add default TLSv1.2 ciphers to selection */ + for(j = 0; j < default_len; j++) { + entry = rustls_default_crypto_provider_ciphersuites_get(j); + if(rustls_supported_ciphersuite_protocol_version(entry) == + RUSTLS_TLS_VERSION_TLSV1_3) + continue; + + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != entry; i++); + if(i < count) + continue; + + selected[count++] = entry; + } } - return false; + + *selected_size = count; } static CURLcode @@ -378,23 +528,126 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data, { struct ssl_connect_data *connssl = cf->ctx; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct rustls_crypto_provider_builder *custom_provider_builder = NULL; + const struct rustls_crypto_provider *custom_provider = NULL; struct rustls_connection *rconn = NULL; struct rustls_client_config_builder *config_builder = NULL; - struct rustls_root_cert_store *roots = NULL; + const struct rustls_root_cert_store *roots = NULL; + struct rustls_root_cert_store_builder *roots_builder = NULL; + struct rustls_web_pki_server_cert_verifier_builder *verifier_builder = NULL; + struct rustls_server_cert_verifier *server_cert_verifier = NULL; const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; const char * const ssl_cafile = /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ (ca_info_blob ? NULL : conn_config->CAfile); const bool verifypeer = conn_config->verifypeer; - const char *hostname = connssl->hostname; char errorbuf[256]; size_t errorlen; - int result; + rustls_result result; DEBUGASSERT(backend); rconn = backend->conn; - config_builder = rustls_client_config_builder_new(); + { + uint16_t tls_versions[2] = { + RUSTLS_TLS_VERSION_TLSV1_2, + RUSTLS_TLS_VERSION_TLSV1_3, + }; + size_t tls_versions_len = 2; + const struct rustls_supported_ciphersuite **cipher_suites; + size_t cipher_suites_len = + rustls_default_crypto_provider_ciphersuites_len(); + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: + case CURL_SSLVERSION_TLSv1_2: + break; + case CURL_SSLVERSION_TLSv1_3: + tls_versions[0] = RUSTLS_TLS_VERSION_TLSV1_3; + tls_versions_len = 1; + break; + default: + failf(data, "rustls: unsupported minimum TLS version value"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + switch(conn_config->version_max) { + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_TLSv1_3: + break; + case CURL_SSLVERSION_MAX_TLSv1_2: + if(tls_versions[0] == RUSTLS_TLS_VERSION_TLSV1_2) { + tls_versions_len = 1; + break; + } + FALLTHROUGH(); + case CURL_SSLVERSION_MAX_TLSv1_1: + case CURL_SSLVERSION_MAX_TLSv1_0: + default: + failf(data, "rustls: unsupported maximum TLS version value"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + cipher_suites = malloc(sizeof(cipher_suites) * (cipher_suites_len)); + if(!cipher_suites) + return CURLE_OUT_OF_MEMORY; + + cr_get_selected_ciphers(data, + conn_config->cipher_list, + conn_config->cipher_list13, + cipher_suites, &cipher_suites_len); + if(cipher_suites_len == 0) { + failf(data, "rustls: no supported cipher in list"); + free(cipher_suites); + return CURLE_SSL_CIPHER; + } + + result = rustls_crypto_provider_builder_new_from_default( + &custom_provider_builder); + if(result != RUSTLS_RESULT_OK) { + failf(data, + "rustls: failed to create crypto provider builder from default"); + return CURLE_SSL_CIPHER; + } + + result = + rustls_crypto_provider_builder_set_cipher_suites( + custom_provider_builder, + cipher_suites, + cipher_suites_len); + if(result != RUSTLS_RESULT_OK) { + failf(data, + "rustls: failed to set ciphersuites for crypto provider builder"); + rustls_crypto_provider_builder_free(custom_provider_builder); + return CURLE_SSL_CIPHER; + } + + result = rustls_crypto_provider_builder_build( + custom_provider_builder, &custom_provider); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to build custom crypto provider"); + rustls_crypto_provider_builder_free(custom_provider_builder); + return CURLE_SSL_CIPHER; + } + + result = rustls_client_config_builder_new_custom(custom_provider, + tls_versions, + tls_versions_len, + &config_builder); + free(cipher_suites); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to create client config"); + return CURLE_SSL_CIPHER; + } + } + + rustls_crypto_provider_builder_free(custom_provider_builder); + rustls_crypto_provider_free(custom_provider); + if(connssl->alpn) { struct alpn_proto_buf proto; rustls_slice_bytes alpn[ALPN_ENTRIES_MAX]; @@ -412,64 +665,101 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data, if(!verifypeer) { rustls_client_config_builder_dangerous_set_certificate_verifier( config_builder, cr_verify_none); - /* rustls doesn't support IP addresses (as of 0.19.0), and will reject - * connections created with an IP address, even when certificate - * verification is turned off. Set a placeholder hostname and disable - * SNI. */ - if(cr_hostname_is_ip(hostname)) { - rustls_client_config_builder_set_enable_sni(config_builder, false); - hostname = "example.invalid"; - } } - else if(ca_info_blob) { - roots = rustls_root_cert_store_new(); + else if(ca_info_blob || ssl_cafile) { + roots_builder = rustls_root_cert_store_builder_new(); + + if(ca_info_blob) { + /* Enable strict parsing only if verification is not disabled. */ + result = rustls_root_cert_store_builder_add_pem(roots_builder, + ca_info_blob->data, + ca_info_blob->len, + verifypeer); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to parse trusted certificates from blob"); + rustls_root_cert_store_builder_free(roots_builder); + rustls_client_config_builder_free(config_builder); + return CURLE_SSL_CACERT_BADFILE; + } + } + else if(ssl_cafile) { + /* Enable strict parsing only if verification is not disabled. */ + result = rustls_root_cert_store_builder_load_roots_from_file( + roots_builder, ssl_cafile, verifypeer); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to load trusted certificates"); + rustls_root_cert_store_builder_free(roots_builder); + rustls_client_config_builder_free(config_builder); + return CURLE_SSL_CACERT_BADFILE; + } + } - /* Enable strict parsing only if verification isn't disabled. */ - result = rustls_root_cert_store_add_pem(roots, ca_info_blob->data, - ca_info_blob->len, verifypeer); + result = rustls_root_cert_store_builder_build(roots_builder, &roots); + rustls_root_cert_store_builder_free(roots_builder); if(result != RUSTLS_RESULT_OK) { - failf(data, "rustls: failed to parse trusted certificates from blob"); - rustls_root_cert_store_free(roots); - rustls_client_config_free( - rustls_client_config_builder_build(config_builder)); + failf(data, "rustls: failed to build trusted root certificate store"); + rustls_client_config_builder_free(config_builder); return CURLE_SSL_CACERT_BADFILE; } - result = rustls_client_config_builder_use_roots(config_builder, roots); + verifier_builder = rustls_web_pki_server_cert_verifier_builder_new(roots); rustls_root_cert_store_free(roots); - if(result != RUSTLS_RESULT_OK) { - failf(data, "rustls: failed to load trusted certificates"); - rustls_client_config_free( - rustls_client_config_builder_build(config_builder)); - return CURLE_SSL_CACERT_BADFILE; + + if(conn_config->CRLfile) { + struct dynbuf crl_contents; + Curl_dyn_init(&crl_contents, SIZE_MAX); + if(!read_file_into(conn_config->CRLfile, &crl_contents)) { + failf(data, "rustls: failed to read revocation list file"); + Curl_dyn_free(&crl_contents); + rustls_web_pki_server_cert_verifier_builder_free(verifier_builder); + return CURLE_SSL_CRL_BADFILE; + } + + result = rustls_web_pki_server_cert_verifier_builder_add_crl( + verifier_builder, + Curl_dyn_uptr(&crl_contents), + Curl_dyn_len(&crl_contents)); + Curl_dyn_free(&crl_contents); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to parse revocation list"); + rustls_web_pki_server_cert_verifier_builder_free(verifier_builder); + return CURLE_SSL_CRL_BADFILE; + } } - } - else if(ssl_cafile) { - result = rustls_client_config_builder_load_roots_from_file( - config_builder, ssl_cafile); + + result = rustls_web_pki_server_cert_verifier_builder_build( + verifier_builder, &server_cert_verifier); + rustls_web_pki_server_cert_verifier_builder_free(verifier_builder); if(result != RUSTLS_RESULT_OK) { - failf(data, "rustls: failed to load trusted certificates"); - rustls_client_config_free( - rustls_client_config_builder_build(config_builder)); + failf(data, "rustls: failed to build certificate verifier"); + rustls_server_cert_verifier_free(server_cert_verifier); + rustls_client_config_builder_free(config_builder); return CURLE_SSL_CACERT_BADFILE; } + + rustls_client_config_builder_set_server_verifier(config_builder, + server_cert_verifier); + rustls_server_cert_verifier_free(server_cert_verifier); } - backend->config = rustls_client_config_builder_build(config_builder); - DEBUGASSERT(rconn == NULL); - { - char *snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost) { - failf(data, "rustls: failed to get SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - result = rustls_client_connection_new(backend->config, snihost, &rconn); + result = rustls_client_config_builder_build( + config_builder, + &backend->config); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to build client config"); + rustls_client_config_free(backend->config); + return CURLE_SSL_CONNECT_ERROR; } + + DEBUGASSERT(rconn == NULL); + result = rustls_client_connection_new(backend->config, + connssl->peer.hostname, &rconn); if(result != RUSTLS_RESULT_OK) { rustls_error(result, errorbuf, sizeof(errorbuf), &errorlen); - failf(data, "rustls_client_connection_new: %.*s", errorlen, errorbuf); + failf(data, "rustls_client_connection_new: %.*s", (int)errorlen, errorbuf); return CURLE_COULDNT_CONNECT; } + DEBUGASSERT(rconn); rustls_connection_set_userdata(rconn, backend); backend->conn = rconn; return CURLE_OK; @@ -479,16 +769,28 @@ static void cr_set_negotiated_alpn(struct Curl_cfilter *cf, struct Curl_easy *data, const struct rustls_connection *rconn) { + struct ssl_connect_data *const connssl = cf->ctx; const uint8_t *protocol = NULL; size_t len = 0; rustls_connection_get_alpn_protocol(rconn, &protocol, &len); - Curl_alpn_set_negotiated(cf, data, protocol, len); + Curl_alpn_set_negotiated(cf, data, connssl, protocol, len); } +/* Given an established network connection, do a TLS handshake. + * + * If `blocking` is true, this function will block until the handshake is + * complete. Otherwise it will return as soon as I/O would block. + * + * For the non-blocking I/O case, this function will set `*done` to true + * once the handshake is complete. This function never reads the value of + * `*done*`. + */ static CURLcode -cr_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, bool *done) +cr_connect_common(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, + bool *done) { struct ssl_connect_data *const connssl = cf->ctx; curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); @@ -502,12 +804,17 @@ cr_connect_nonblocking(struct Curl_cfilter *cf, bool wants_write; curl_socket_t writefd; curl_socket_t readfd; + timediff_t timeout_ms; + timediff_t socket_check_timeout; DEBUGASSERT(backend); - if(ssl_connection_none == connssl->state) { + CURL_TRC_CF(data, cf, "cr_connect_common, state=%d", connssl->state); + *done = FALSE; + if(!backend->conn) { result = cr_init_backend(cf, data, (struct rustls_ssl_backend_data *)connssl->backend); + CURL_TRC_CF(data, cf, "cr_connect_common, init backend -> %d", result); if(result != CURLE_OK) { return result; } @@ -519,46 +826,91 @@ cr_connect_nonblocking(struct Curl_cfilter *cf, /* Read/write data until the handshake is done or the socket would block. */ for(;;) { /* - * Connection has been established according to rustls. Set send/recv + * Connection has been established according to Rustls. Set send/recv * handlers, and update the state machine. */ + connssl->io_need = CURL_SSL_IO_NEED_NONE; if(!rustls_connection_is_handshaking(rconn)) { - infof(data, "Done handshaking"); - /* Done with the handshake. Set up callbacks to send/receive data. */ - connssl->state = ssl_connection_complete; - + /* Rustls claims it is no longer handshaking *before* it has + * send its FINISHED message off. We attempt to let it write + * one more time. Oh my. + */ cr_set_negotiated_alpn(cf, data, rconn); - + cr_send(cf, data, NULL, 0, &tmperr); + if(tmperr == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_SEND; + return CURLE_OK; + } + else if(tmperr != CURLE_OK) { + return tmperr; + } + /* REALLY Done with the handshake. */ + { + uint16_t proto = rustls_connection_get_protocol_version(rconn); + uint16_t cipher = rustls_connection_get_negotiated_ciphersuite(rconn); + char buf[64] = ""; + const char *ver = "TLS version unknown"; + if(proto == RUSTLS_TLS_VERSION_TLSV1_3) + ver = "TLSv1.3"; + if(proto == RUSTLS_TLS_VERSION_TLSV1_2) + ver = "TLSv1.2"; + Curl_cipher_suite_get_str(cipher, buf, sizeof(buf), TRUE); + infof(data, "rustls: handshake complete, %s, cipher: %s", + ver, buf); + } + connssl->state = ssl_connection_complete; *done = TRUE; return CURLE_OK; } + connssl->connecting_state = ssl_connect_2; wants_read = rustls_connection_wants_read(rconn); - wants_write = rustls_connection_wants_write(rconn); + wants_write = rustls_connection_wants_write(rconn) || + backend->plain_out_buffered; DEBUGASSERT(wants_read || wants_write); - writefd = wants_write?sockfd:CURL_SOCKET_BAD; - readfd = wants_read?sockfd:CURL_SOCKET_BAD; + writefd = wants_write ? sockfd : CURL_SOCKET_BAD; + readfd = wants_read ? sockfd : CURL_SOCKET_BAD; + + /* check allowed time left */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, 0); + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "rustls: operation timed out before socket check"); + return CURLE_OPERATION_TIMEDOUT; + } + + socket_check_timeout = blocking ? timeout_ms : 0; + + what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, + socket_check_timeout); if(what < 0) { /* fatal error */ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); return CURLE_SSL_CONNECT_ERROR; } + if(blocking && 0 == what) { + failf(data, "rustls: connection timeout after %" FMT_TIMEDIFF_T " ms", + socket_check_timeout); + return CURLE_OPERATION_TIMEDOUT; + } if(0 == what) { - infof(data, "Curl_socket_check: %s would block", - wants_read&&wants_write ? "writing and reading" : + CURL_TRC_CF(data, cf, "Curl_socket_check: %s would block", + wants_read && wants_write ? "writing and reading" : wants_write ? "writing" : "reading"); - *done = FALSE; + if(wants_write) + connssl->io_need |= CURL_SSL_IO_NEED_SEND; + if(wants_read) + connssl->io_need |= CURL_SSL_IO_NEED_RECV; return CURLE_OK; } /* socket is readable or writable */ if(wants_write) { - infof(data, "rustls_connection wants us to write_tls."); + CURL_TRC_CF(data, cf, "rustls_connection wants us to write_tls."); cr_send(cf, data, NULL, 0, &tmperr); if(tmperr == CURLE_AGAIN) { - infof(data, "writing would block"); + CURL_TRC_CF(data, cf, "writing would block"); /* fall through */ } else if(tmperr != CURLE_OK) { @@ -567,14 +919,13 @@ cr_connect_nonblocking(struct Curl_cfilter *cf, } if(wants_read) { - infof(data, "rustls_connection wants us to read_tls."); - + CURL_TRC_CF(data, cf, "rustls_connection wants us to read_tls."); if(tls_recv_more(cf, data, &tmperr) < 0) { if(tmperr == CURLE_AGAIN) { - infof(data, "reading would block"); + CURL_TRC_CF(data, cf, "reading would block"); /* fall through */ } - else if(tmperr == CURLE_READ_ERROR) { + else if(tmperr == CURLE_RECV_ERROR) { return CURLE_SSL_CONNECT_ERROR; } else { @@ -585,36 +936,22 @@ cr_connect_nonblocking(struct Curl_cfilter *cf, } /* We should never fall through the loop. We should return either because - the handshake is done or because we can't read/write without blocking. */ - DEBUGASSERT(false); + the handshake is done or because we cannot read/write without blocking. */ + DEBUGASSERT(FALSE); } -/* returns a bitmap of flags for this connection's first socket indicating - whether we want to read or write */ -static int -cr_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) +static CURLcode +cr_connect_nonblocking(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) { - struct ssl_connect_data *const connssl = cf->ctx; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - struct rustls_ssl_backend_data *const backend = - (struct rustls_ssl_backend_data *)connssl->backend; - struct rustls_connection *rconn = NULL; - - (void)data; - DEBUGASSERT(backend); - rconn = backend->conn; - - if(rustls_connection_wants_write(rconn)) { - socks[0] = sockfd; - return GETSOCK_WRITESOCK(0); - } - if(rustls_connection_wants_read(rconn)) { - socks[0] = sockfd; - return GETSOCK_READSOCK(0); - } + return cr_connect_common(cf, data, false, done); +} - return GETSOCK_BLANK; +static CURLcode +cr_connect_blocking(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + bool done; /* unused */ + return cr_connect_common(cf, data, true, &done); } static void * @@ -627,24 +964,85 @@ cr_get_internals(struct ssl_connect_data *connssl, return &backend->conn; } -static void -cr_close(struct Curl_cfilter *cf, struct Curl_easy *data) +static CURLcode +cr_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { struct ssl_connect_data *connssl = cf->ctx; struct rustls_ssl_backend_data *backend = (struct rustls_ssl_backend_data *)connssl->backend; - CURLcode tmperr = CURLE_OK; - ssize_t n = 0; + CURLcode result = CURLE_OK; + ssize_t nwritten, nread; + char buf[1024]; + size_t i; DEBUGASSERT(backend); + if(!backend->conn || cf->shutdown) { + *done = TRUE; + goto out; + } - if(backend->conn) { - rustls_connection_send_close_notify(backend->conn); - n = cr_send(cf, data, NULL, 0, &tmperr); - if(n < 0) { - failf(data, "rustls: error sending close_notify: %d", tmperr); + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + + if(!backend->sent_shutdown) { + /* do this only once */ + backend->sent_shutdown = TRUE; + if(send_shutdown) { + rustls_connection_send_close_notify(backend->conn); + } + } + + nwritten = cr_send(cf, data, NULL, 0, &result); + if(nwritten < 0) { + if(result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_SEND; + result = CURLE_OK; + goto out; } + DEBUGASSERT(result); + CURL_TRC_CF(data, cf, "shutdown send failed: %d", result); + goto out; + } + + for(i = 0; i < 10; ++i) { + nread = cr_recv(cf, data, buf, (int)sizeof(buf), &result); + if(nread <= 0) + break; + } + if(nread > 0) { + /* still data coming in? */ + } + else if(nread == 0) { + /* We got the close notify alert and are done. */ + *done = TRUE; + } + else if(result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_RECV; + result = CURLE_OK; + } + else { + DEBUGASSERT(result); + CURL_TRC_CF(data, cf, "shutdown, error: %d", result); + } + +out: + cf->shutdown = (result || *done); + return result; +} + +static void +cr_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct rustls_ssl_backend_data *backend = + (struct rustls_ssl_backend_data *)connssl->backend; + + (void)data; + DEBUGASSERT(backend); + if(backend->conn) { rustls_connection_free(backend->conn); backend->conn = NULL; } @@ -660,28 +1058,38 @@ static size_t cr_version(char *buffer, size_t size) return msnprintf(buffer, size, "%.*s", (int)ver.len, ver.data); } +static CURLcode +cr_random(struct Curl_easy *data, unsigned char *entropy, size_t length) +{ + rustls_result rresult = 0; + (void)data; + rresult = + rustls_default_crypto_provider_random(entropy, length); + return map_error(rresult); +} + const struct Curl_ssl Curl_ssl_rustls = { { CURLSSLBACKEND_RUSTLS, "rustls" }, SSLSUPP_CAINFO_BLOB | /* supports */ - SSLSUPP_TLS13_CIPHERSUITES | - SSLSUPP_HTTPS_PROXY, + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST | + SSLSUPP_TLS13_CIPHERSUITES, sizeof(struct rustls_ssl_backend_data), Curl_none_init, /* init */ Curl_none_cleanup, /* cleanup */ cr_version, /* version */ Curl_none_check_cxn, /* check_cxn */ - Curl_none_shutdown, /* shutdown */ + cr_shutdown, /* shutdown */ cr_data_pending, /* data_pending */ - Curl_none_random, /* random */ + cr_random, /* random */ Curl_none_cert_status_request, /* cert_status_request */ - cr_connect, /* connect */ + cr_connect_blocking, /* connect */ cr_connect_nonblocking, /* connect_nonblocking */ - cr_get_select_socks, /* get_select_socks */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ cr_get_internals, /* get_internals */ cr_close, /* close_one */ Curl_none_close_all, /* close_all */ - Curl_none_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ @@ -689,9 +1097,9 @@ const struct Curl_ssl Curl_ssl_rustls = { NULL, /* sha256sum */ NULL, /* associate_connection */ NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ cr_recv, /* recv decrypted data */ cr_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; #endif /* USE_RUSTLS */ diff --git a/deps/curl/lib/vtls/schannel.c b/deps/curl/lib/vtls/schannel.c index f6a5d441a9e..2085f709467 100644 --- a/deps/curl/lib/vtls/schannel.c +++ b/deps/curl/lib/vtls/schannel.c @@ -34,7 +34,7 @@ #ifdef USE_SCHANNEL #ifndef USE_WINDOWS_SSPI -# error "Can't compile SCHANNEL support without SSPI." +# error "cannot compile SCHANNEL support without SSPI." #endif #include "schannel.h" @@ -59,6 +59,17 @@ #include "curl_memory.h" #include "memdebug.h" +/* Some verbose debug messages are wrapped by SCH_DEV() instead of DEBUGF() + * and only shown if CURL_SCHANNEL_DEV_DEBUG was defined at build time. These + * messages are extra verbose and intended for curl developers debugging + * Schannel recv decryption. + */ +#ifdef CURL_SCHANNEL_DEV_DEBUG +#define SCH_DEV(x) x +#else +#define SCH_DEV(x) do { } while(0) +#endif + /* ALPN requires version 8.1 of the Windows SDK, which was shipped with Visual Studio 2013, aka _MSC_VER 1800: @@ -68,22 +79,6 @@ # define HAS_ALPN 1 #endif -#ifndef UNISP_NAME_A -#define UNISP_NAME_A "Microsoft Unified Security Protocol Provider" -#endif - -#ifndef UNISP_NAME_W -#define UNISP_NAME_W L"Microsoft Unified Security Protocol Provider" -#endif - -#ifndef UNISP_NAME -#ifdef UNICODE -#define UNISP_NAME UNISP_NAME_W -#else -#define UNISP_NAME UNISP_NAME_A -#endif -#endif - #ifndef BCRYPT_CHACHA20_POLY1305_ALGORITHM #define BCRYPT_CHACHA20_POLY1305_ALGORITHM L"CHACHA20_POLY1305" #endif @@ -108,13 +103,6 @@ #define BCRYPT_SHA384_ALGORITHM L"SHA384" #endif -/* Workaround broken compilers like MinGW. - Return the number of elements in a statically sized array. -*/ -#ifndef ARRAYSIZE -#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) -#endif - #ifdef HAS_CLIENT_CERT_PATH #ifdef UNICODE #define CURL_CERT_STORE_PROV_SYSTEM CERT_STORE_PROV_SYSTEM_W @@ -123,18 +111,6 @@ #endif #endif -#ifndef SP_PROT_SSL2_CLIENT -#define SP_PROT_SSL2_CLIENT 0x00000008 -#endif - -#ifndef SP_PROT_SSL3_CLIENT -#define SP_PROT_SSL3_CLIENT 0x00000008 -#endif - -#ifndef SP_PROT_TLS1_CLIENT -#define SP_PROT_TLS1_CLIENT 0x00000080 -#endif - #ifndef SP_PROT_TLS1_0_CLIENT #define SP_PROT_TLS1_0_CLIENT SP_PROT_TLS1_CLIENT #endif @@ -175,12 +151,6 @@ # define CALG_SHA_256 0x0000800c #endif -/* Work around typo in classic MinGW's w32api up to version 5.0, - see https://osdn.net/projects/mingw/ticket/38391 */ -#if !defined(ALG_CLASS_DHASH) && defined(ALG_CLASS_HASH) -#define ALG_CLASS_DHASH ALG_CLASS_HASH -#endif - #ifndef PKCS12_NO_PERSIST_KEY #define PKCS12_NO_PERSIST_KEY 0x00008000 #endif @@ -212,7 +182,7 @@ schannel_set_ssl_version_min_max(DWORD *enabled_protocols, { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); long ssl_version = conn_config->version; - long ssl_version_max = conn_config->version_max; + long ssl_version_max = (long)conn_config->version_max; long i = ssl_version; switch(ssl_version_max) { @@ -405,7 +375,7 @@ set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers, if(!alg) alg = get_alg_id_by_name(startCur); if(alg) - algIds[algCount++] = alg; + algIds[algCount++] = (ALG_ID)alg; else if(!strncmp(startCur, "USE_STRONG_CRYPTO", sizeof("USE_STRONG_CRYPTO") - 1) || !strncmp(startCur, "SCH_USE_STRONG_CRYPTO", @@ -418,7 +388,7 @@ set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers, startCur++; } schannel_cred->palgSupportedAlgs = algIds; - schannel_cred->cSupportedAlgs = algCount; + schannel_cred->cSupportedAlgs = (DWORD)algCount; return CURLE_OK; } @@ -480,6 +450,12 @@ get_cert_location(TCHAR *path, DWORD *store_name, TCHAR **store_path, return CURLE_OK; } #endif + +static bool algo(const char *check, char *namep, size_t nlen) +{ + return (strlen(check) == nlen) && !strncmp(check, namep, nlen); +} + static CURLcode schannel_acquire_credential_handle(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -548,7 +524,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, } if(!ssl_config->auto_client_cert) { - flags &= ~SCH_CRED_USE_DEFAULT_CREDS; + flags &= ~(DWORD)SCH_CRED_USE_DEFAULT_CREDS; flags |= SCH_CRED_NO_DEFAULT_CREDS; infof(data, "schannel: disabled automatic use of client certificate"); } @@ -701,7 +677,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, cert_showfilename_error); else failf(data, "schannel: Failed to import cert file %s, " - "last error is 0x%x", + "last error is 0x%lx", cert_showfilename_error, errorcode); return CURLE_SSL_CERTPROBLEM; } @@ -712,7 +688,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, if(!client_certs[0]) { failf(data, "schannel: Failed to get certificate from file %s" - ", last error is 0x%x", + ", last error is 0x%lx", cert_showfilename_error, GetLastError()); CertCloseStore(cert_store, 0); return CURLE_SSL_CERTPROBLEM; @@ -725,10 +701,15 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, CERT_STORE_OPEN_EXISTING_FLAG | cert_store_name, cert_store_path); if(!cert_store) { - failf(data, "schannel: Failed to open cert store %x %s, " - "last error is 0x%x", - cert_store_name, cert_store_path, GetLastError()); + char *path_utf8 = + curlx_convert_tchar_to_UTF8(cert_store_path); + failf(data, "schannel: Failed to open cert store %lx %s, " + "last error is 0x%lx", + cert_store_name, + (path_utf8 ? path_utf8 : "(unknown)"), + GetLastError()); free(cert_store_path); + curlx_unicodefree(path_utf8); curlx_unicodefree(cert_path); return CURLE_SSL_CERTPROBLEM; } @@ -769,7 +750,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, } #endif - /* allocate memory for the re-usable credential handle */ + /* allocate memory for the reusable credential handle */ backend->cred = (struct Curl_schannel_cred *) calloc(1, sizeof(struct Curl_schannel_cred)); if(!backend->cred) { @@ -831,9 +812,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, char *startCur = ciphers13; int algCount = 0; - char tmp[LONGEST_ALG_ID] = { 0 }; char *nameEnd; - size_t n; disable_aes_gcm_sha384 = TRUE; disable_aes_gcm_sha256 = TRUE; @@ -842,40 +821,34 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, disable_aes_ccm_sha256 = TRUE; while(startCur && (0 != *startCur) && (algCount < remaining_ciphers)) { + size_t n; + char *namep; nameEnd = strchr(startCur, ':'); n = nameEnd ? (size_t)(nameEnd - startCur) : strlen(startCur); + namep = startCur; - /* reject too-long cipher names */ - if(n > (LONGEST_ALG_ID - 1)) { - failf(data, "schannel: Cipher name too long, not checked"); - return CURLE_SSL_CIPHER; - } - - strncpy(tmp, startCur, n); - tmp[n] = 0; - - if(disable_aes_gcm_sha384 - && !strcmp("TLS_AES_256_GCM_SHA384", tmp)) { + if(disable_aes_gcm_sha384 && + algo("TLS_AES_256_GCM_SHA384", namep, n)) { disable_aes_gcm_sha384 = FALSE; } else if(disable_aes_gcm_sha256 - && !strcmp("TLS_AES_128_GCM_SHA256", tmp)) { + && algo("TLS_AES_128_GCM_SHA256", namep, n)) { disable_aes_gcm_sha256 = FALSE; } else if(disable_chacha_poly - && !strcmp("TLS_CHACHA20_POLY1305_SHA256", tmp)) { + && algo("TLS_CHACHA20_POLY1305_SHA256", namep, n)) { disable_chacha_poly = FALSE; } else if(disable_aes_ccm_8_sha256 - && !strcmp("TLS_AES_128_CCM_8_SHA256", tmp)) { + && algo("TLS_AES_128_CCM_8_SHA256", namep, n)) { disable_aes_ccm_8_sha256 = FALSE; } else if(disable_aes_ccm_sha256 - && !strcmp("TLS_AES_128_CCM_SHA256", tmp)) { + && algo("TLS_AES_128_CCM_SHA256", namep, n)) { disable_aes_ccm_sha256 = FALSE; } else { - failf(data, "schannel: Unknown TLS 1.3 cipher: %s", tmp); + failf(data, "schannel: Unknown TLS 1.3 cipher: %.*s", (int)n, namep); return CURLE_SSL_CIPHER; } @@ -988,7 +961,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, tls_parameters.pDisabledCrypto = crypto_settings; /* The number of blocked suites */ - tls_parameters.cDisabledCrypto = crypto_settings_idx; + tls_parameters.cDisabledCrypto = (DWORD)crypto_settings_idx; credentials.pTlsParameters = &tls_parameters; credentials.cTlsParameters = 1; @@ -1006,7 +979,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, #endif sspi_status = - s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, + Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, &credentials, NULL, NULL, &backend->cred->cred_handle, @@ -1014,7 +987,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, } else { /* Pre-Windows 10 1809 or the user set a legacy algorithm list. Although MS - doesn't document it, currently Schannel will not negotiate TLS 1.3 when + does not document it, currently Schannel will not negotiate TLS 1.3 when SCHANNEL_CRED is used. */ ALG_ID algIds[NUM_CIPHERS]; char *ciphers = conn_config->cipher_list; @@ -1053,7 +1026,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, #endif sspi_status = - s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, + Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, &schannel_cred, NULL, NULL, &backend->cred->cred_handle, @@ -1104,17 +1077,12 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) #endif SECURITY_STATUS sspi_status = SEC_E_OK; struct Curl_schannel_cred *old_cred = NULL; - struct in_addr addr; -#ifdef ENABLE_IPV6 - struct in6_addr addr6; -#endif CURLcode result; - const char *hostname = connssl->hostname; DEBUGASSERT(backend); DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 1/3)", - hostname, connssl->port)); + connssl->peer.hostname, connssl->peer.port)); if(curlx_verify_windows_version(5, 1, 0, PLATFORM_WINNT, VERSION_LESS_THAN_EQUAL)) { @@ -1126,30 +1094,30 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) #ifdef HAS_ALPN /* ALPN is only supported on Windows 8.1 / Server 2012 R2 and above. - Also it doesn't seem to be supported for Wine, see curl bug #983. */ + Also it does not seem to be supported for WINE, see curl bug #983. */ backend->use_alpn = connssl->alpn && !GetProcAddress(GetModuleHandle(TEXT("ntdll")), "wine_get_version") && curlx_verify_windows_version(6, 3, 0, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL); #else - backend->use_alpn = false; + backend->use_alpn = FALSE; #endif #ifdef _WIN32_WCE #ifdef HAS_MANUAL_VERIFY_API - /* certificate validation on CE doesn't seem to work right; we'll + /* certificate validation on CE does not seem to work right; we will * do it following a more manual process. */ - backend->use_manual_cred_validation = true; + backend->use_manual_cred_validation = TRUE; #else -#error "compiler too old to support requisite manual cert verify for Win CE" +#error "compiler too old to support Windows CE requisite manual cert verify" #endif #else #ifdef HAS_MANUAL_VERIFY_API if(conn_config->CAfile || conn_config->ca_info_blob) { if(curlx_verify_windows_version(6, 1, 0, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL)) { - backend->use_manual_cred_validation = true; + backend->use_manual_cred_validation = TRUE; } else { failf(data, "schannel: this version of Windows is too old to support " @@ -1158,7 +1126,7 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } } else - backend->use_manual_cred_validation = false; + backend->use_manual_cred_validation = FALSE; #else if(conn_config->CAfile || conn_config->ca_info_blob) { failf(data, "schannel: CA cert support not built in"); @@ -1169,10 +1137,11 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) backend->cred = NULL; - /* check for an existing re-usable credential handle */ - if(ssl_config->primary.sessionid) { + /* check for an existing reusable credential handle */ + if(ssl_config->primary.cache_session) { Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, (void **)&old_cred, NULL)) { + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, + (void **)&old_cred, NULL, NULL)) { backend->cred = old_cred; DEBUGF(infof(data, "schannel: reusing existing credential handle")); @@ -1195,22 +1164,14 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* A hostname associated with the credential is needed by InitializeSecurityContext for SNI and other reasons. */ - snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } + snihost = connssl->peer.sni ? connssl->peer.sni : connssl->peer.hostname; backend->cred->sni_hostname = curlx_convert_UTF8_to_tchar(snihost); if(!backend->cred->sni_hostname) return CURLE_OUT_OF_MEMORY; } /* Warn if SNI is disabled due to use of an IP address */ - if(Curl_inet_pton(AF_INET, hostname, &addr) -#ifdef ENABLE_IPV6 - || Curl_inet_pton(AF_INET6, hostname, &addr6) -#endif - ) { + if(connssl->peer.type != CURL_SSL_PEER_DNS) { infof(data, "schannel: using IP address, SNI is not supported by OS."); } @@ -1249,9 +1210,8 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) cur += proto.len; *list_len = curlx_uitous(cur - list_start_index); - *extension_len = *list_len + - (unsigned short)sizeof(unsigned int) + - (unsigned short)sizeof(unsigned short); + *extension_len = (unsigned int)(*list_len + + sizeof(unsigned int) + sizeof(unsigned short)); InitSecBuffer(&inbuf, SECBUFFER_APPLICATION_PROTOCOLS, alpn_buffer, cur); InitSecBufferDesc(&inbuf_desc, &inbuf, 1); @@ -1292,11 +1252,11 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* Schannel InitializeSecurityContext: https://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx - At the moment we don't pass inbuf unless we're using ALPN since we only - use it for that, and Wine (for which we currently disable ALPN) is giving + At the moment we do not pass inbuf unless we are using ALPN since we only + use it for that, and WINE (for which we currently disable ALPN) is giving us problems with inbuf regardless. https://github.com/curl/curl/issues/983 */ - sspi_status = s_pSecFn->InitializeSecurityContext( + sspi_status = Curl_pSecFn->InitializeSecurityContext( &backend->cred->cred_handle, NULL, backend->cred->sni_hostname, backend->req_flags, 0, 0, (backend->use_alpn ? &inbuf_desc : NULL), @@ -1338,9 +1298,9 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* send initial handshake data which is now stored in output buffer */ written = Curl_conn_cf_send(cf->next, data, - outbuf.pvBuffer, outbuf.cbBuffer, + outbuf.pvBuffer, outbuf.cbBuffer, FALSE, &result); - s_pSecFn->FreeContextBuffer(outbuf.pvBuffer); + Curl_pSecFn->FreeContextBuffer(outbuf.pvBuffer); if((result != CURLE_OK) || (outbuf.cbBuffer != (size_t) written)) { failf(data, "schannel: failed to send initial handshake data: " "sent %zd of %lu bytes", written, outbuf.cbBuffer); @@ -1351,10 +1311,10 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) "sent %zd bytes", written)); backend->recv_unrecoverable_err = CURLE_OK; - backend->recv_sspi_close_notify = false; - backend->recv_connection_closed = false; - backend->recv_renegotiating = false; - backend->encdata_is_incomplete = false; + backend->recv_sspi_close_notify = FALSE; + backend->recv_connection_closed = FALSE; + backend->recv_renegotiating = FALSE; + backend->encdata_is_incomplete = FALSE; /* continue to second handshake step */ connssl->connecting_state = ssl_connect_2; @@ -1383,11 +1343,12 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGASSERT(backend); - doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE; + doread = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? FALSE : TRUE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 2/3)", - connssl->hostname, connssl->port)); + connssl->peer.hostname, connssl->peer.port)); if(!backend->cred || !backend->ctxt) return CURLE_SSL_CONNECT_ERROR; @@ -1405,7 +1366,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* buffer to store previously received and encrypted data */ if(!backend->encdata_buffer) { - backend->encdata_is_incomplete = false; + backend->encdata_is_incomplete = FALSE; backend->encdata_offset = 0; backend->encdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE; backend->encdata_buffer = malloc(backend->encdata_length); @@ -1444,8 +1405,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) backend->encdata_offset, &result); if(result == CURLE_AGAIN) { - if(connssl->connecting_state != ssl_connect_2_writing) - connssl->connecting_state = ssl_connect_2_reading; + connssl->io_need = CURL_SSL_IO_NEED_RECV; DEBUGF(infof(data, "schannel: failed to receive handshake, " "need more data")); return CURLE_OK; @@ -1458,13 +1418,13 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* increase encrypted data buffer offset */ backend->encdata_offset += nread; - backend->encdata_is_incomplete = false; - DEBUGF(infof(data, "schannel: encrypted data got %zd", nread)); + backend->encdata_is_incomplete = FALSE; + SCH_DEV(infof(data, "schannel: encrypted data got %zd", nread)); } - DEBUGF(infof(data, - "schannel: encrypted data buffer: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); + SCH_DEV(infof(data, + "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); /* setup input buffers */ InitSecBuffer(&inbuf[0], SECBUFFER_TOKEN, malloc(backend->encdata_offset), @@ -1487,7 +1447,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) memcpy(inbuf[0].pvBuffer, backend->encdata_buffer, backend->encdata_offset); - sspi_status = s_pSecFn->InitializeSecurityContext( + sspi_status = Curl_pSecFn->InitializeSecurityContext( &backend->cred->cred_handle, &backend->ctxt->ctxt_handle, backend->cred->sni_hostname, backend->req_flags, 0, 0, &inbuf_desc, 0, NULL, @@ -1498,8 +1458,8 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* check if the handshake was incomplete */ if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) { - backend->encdata_is_incomplete = true; - connssl->connecting_state = ssl_connect_2_reading; + backend->encdata_is_incomplete = TRUE; + connssl->io_need = CURL_SSL_IO_NEED_RECV; DEBUGF(infof(data, "schannel: received incomplete message, need more data")); return CURLE_OK; @@ -1511,7 +1471,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) if(sspi_status == SEC_I_INCOMPLETE_CREDENTIALS && !(backend->req_flags & ISC_REQ_USE_SUPPLIED_CREDS)) { backend->req_flags |= ISC_REQ_USE_SUPPLIED_CREDS; - connssl->connecting_state = ssl_connect_2_writing; + connssl->io_need = CURL_SSL_IO_NEED_SEND; DEBUGF(infof(data, "schannel: a client certificate has been requested")); return CURLE_OK; @@ -1528,7 +1488,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* send handshake token to server */ written = Curl_conn_cf_send(cf->next, data, outbuf[i].pvBuffer, outbuf[i].cbBuffer, - &result); + FALSE, &result); if((result != CURLE_OK) || (outbuf[i].cbBuffer != (size_t) written)) { failf(data, "schannel: failed to send next handshake data: " @@ -1539,7 +1499,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* free obsolete buffer */ if(outbuf[i].pvBuffer) { - s_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer); + Curl_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer); } } } @@ -1578,11 +1538,11 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* check if there was additional remaining encrypted data */ if(inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) { - DEBUGF(infof(data, "schannel: encrypted data length: %lu", - inbuf[1].cbBuffer)); + SCH_DEV(infof(data, "schannel: encrypted data length: %lu", + inbuf[1].cbBuffer)); /* There are two cases where we could be getting extra data here: - 1) If we're renegotiating a connection and the handshake is already + 1) If we are renegotiating a connection and the handshake is already complete (from the server perspective), it can encrypted app data (not handshake data) in an extra buffer at this point. 2) (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a @@ -1611,7 +1571,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* check if the handshake needs to be continued */ if(sspi_status == SEC_I_CONTINUE_NEEDED) { - connssl->connecting_state = ssl_connect_2_reading; + connssl->io_need = CURL_SSL_IO_NEED_RECV; return CURLE_OK; } @@ -1621,9 +1581,13 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGF(infof(data, "schannel: SSL/TLS handshake complete")); } - pubkey_ptr = Curl_ssl_cf_is_proxy(cf)? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: +#ifndef CURL_DISABLE_PROXY + pubkey_ptr = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + pubkey_ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif if(pubkey_ptr) { result = schannel_pkp_pin_peer_pubkey(cf, data, pubkey_ptr); if(result) { @@ -1640,7 +1604,7 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) #endif /* Verify the hostname manually when certificate verification is disabled, - because in that case Schannel won't verify it. */ + because in that case Schannel will not verify it. */ if(!conn_config->verifypeer && conn_config->verifyhost) return Curl_verify_host(cf, data); @@ -1664,9 +1628,9 @@ traverse_cert_store(const CERT_CONTEXT *context, Read_crt_func func, void *arg) { const CERT_CONTEXT *current_context = NULL; - bool should_continue = true; - bool first = true; - bool reverse_order = false; + bool should_continue = TRUE; + bool first = TRUE; + bool reverse_order = FALSE; while(should_continue && (current_context = CertEnumCertificatesInStore( context->hCertStore, @@ -1677,9 +1641,9 @@ traverse_cert_store(const CERT_CONTEXT *context, Read_crt_func func, by comparing SECPKG_ATTR_REMOTE_CERT_CONTEXT's pbCertContext with the first certificate's pbCertContext. */ if(first && context->pbCertEncoded != current_context->pbCertEncoded) - reverse_order = true; + reverse_order = TRUE; should_continue = func(current_context, reverse_order, arg); - first = false; + first = FALSE; } if(current_context) @@ -1693,7 +1657,7 @@ cert_counter_callback(const CERT_CONTEXT *ccert_context, bool reverse_order, (void)reverse_order; /* unused */ if(valid_cert_encoding(ccert_context)) (*(int *)certs_count)++; - return true; + return TRUE; } struct Adder_args @@ -1722,6 +1686,28 @@ add_cert_to_certinfo(const CERT_CONTEXT *ccert_context, bool reverse_order, return args->result == CURLE_OK; } +static void schannel_session_free(void *sessionid, size_t idsize) +{ + /* this is expected to be called under sessionid lock */ + struct Curl_schannel_cred *cred = sessionid; + + (void)idsize; + if(cred) { + cred->refcount--; + if(cred->refcount == 0) { + Curl_pSecFn->FreeCredentialsHandle(&cred->cred_handle); + curlx_unicodefree(cred->sni_hostname); +#ifdef HAS_CLIENT_CERT_PATH + if(cred->client_cert_store) { + CertCloseStore(cred->client_cert_store, 0); + cred->client_cert_store = NULL; + } +#endif + Curl_safefree(cred); + } + } +} + static CURLcode schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -1741,7 +1727,7 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 3/3)", - connssl->hostname, connssl->port)); + connssl->peer.hostname, connssl->peer.port)); if(!backend->cred) return CURLE_SSL_CONNECT_ERROR; @@ -1764,7 +1750,7 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) #ifdef HAS_ALPN if(backend->use_alpn) { sspi_status = - s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, + Curl_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, SECPKG_ATTR_APPLICATION_PROTOCOL, &alpn_result); @@ -1777,7 +1763,7 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) SecApplicationProtocolNegotiationStatus_Success) { unsigned char prev_alpn = cf->conn->alpn; - Curl_alpn_set_negotiated(cf, data, alpn_result.ProtocolId, + Curl_alpn_set_negotiated(cf, data, connssl, alpn_result.ProtocolId, alpn_result.ProtocolIdSize); if(backend->recv_renegotiating) { if(prev_alpn != cf->conn->alpn && @@ -1791,51 +1777,29 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) } else { if(!backend->recv_renegotiating) - Curl_alpn_set_negotiated(cf, data, NULL, 0); + Curl_alpn_set_negotiated(cf, data, connssl, NULL, 0); } } #endif /* save the current session data for possible reuse */ - if(ssl_config->primary.sessionid) { - bool incache; - bool added = FALSE; - struct Curl_schannel_cred *old_cred = NULL; - + if(ssl_config->primary.cache_session) { Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(cf, data, (void **)&old_cred, NULL)); - if(incache) { - if(old_cred != backend->cred) { - DEBUGF(infof(data, - "schannel: old credential handle is stale, removing")); - /* we're not taking old_cred ownership here, no refcount++ is needed */ - Curl_ssl_delsessionid(data, (void *)old_cred); - incache = FALSE; - } - } - if(!incache) { - result = Curl_ssl_addsessionid(cf, data, backend->cred, - sizeof(struct Curl_schannel_cred), - &added); - if(result) { - Curl_ssl_sessionid_unlock(data); - failf(data, "schannel: failed to store credential handle"); - return result; - } - else if(added) { - /* this cred session is now also referenced by sessionid cache */ - backend->cred->refcount++; - DEBUGF(infof(data, - "schannel: stored credential handle in session cache")); - } - } + /* Up ref count since call takes ownership */ + backend->cred->refcount++; + result = Curl_ssl_set_sessionid(cf, data, &connssl->peer, NULL, + backend->cred, + sizeof(struct Curl_schannel_cred), + schannel_session_free); Curl_ssl_sessionid_unlock(data); + if(result) + return result; } if(data->set.ssl.certinfo) { int certs_count = 0; sspi_status = - s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, + Curl_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &ccert_context); @@ -1883,7 +1847,7 @@ schannel_connect_common(struct Curl_cfilter *cf, } if(ssl_connect_1 == connssl->connecting_state) { - /* check out how much more time we're allowed */ + /* check out how much more time we are allowed */ timeout_ms = Curl_timeleft(data, NULL, TRUE); if(timeout_ms < 0) { @@ -1897,11 +1861,9 @@ schannel_connect_common(struct Curl_cfilter *cf, return result; } - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { + while(ssl_connect_2 == connssl->connecting_state) { - /* check out how much more time we're allowed */ + /* check out how much more time we are allowed */ timeout_ms = Curl_timeleft(data, NULL, TRUE); if(timeout_ms < 0) { @@ -1910,14 +1872,13 @@ schannel_connect_common(struct Curl_cfilter *cf, return CURLE_OPERATION_TIMEDOUT; } - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { + /* if ssl is expecting something, check if it is available. */ + if(connssl->io_need) { - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state ? sockfd : CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state ? sockfd : CURL_SOCKET_BAD; + curl_socket_t writefd = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? + sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = (connssl->io_need & CURL_SSL_IO_NEED_RECV) ? + sockfd : CURL_SOCKET_BAD; what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, nonblocking ? 0 : timeout_ms); @@ -1948,10 +1909,7 @@ schannel_connect_common(struct Curl_cfilter *cf, * have a valid fdset to wait on. */ result = schannel_connect_step2(cf, data); - if(result || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) + if(result || (nonblocking && (ssl_connect_2 == connssl->connecting_state))) return result; } /* repeat step2 until all transactions are done. */ @@ -2009,7 +1967,7 @@ schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, /* check if the maximum stream sizes were queried */ if(backend->stream_sizes.cbMaximumMessage == 0) { - sspi_status = s_pSecFn->QueryContextAttributes( + sspi_status = Curl_pSecFn->QueryContextAttributes( &backend->ctxt->ctxt_handle, SECPKG_ATTR_STREAM_SIZES, &backend->stream_sizes); @@ -2048,7 +2006,7 @@ schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, memcpy(outbuf[1].pvBuffer, buf, len); /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375390.aspx */ - sspi_status = s_pSecFn->EncryptMessage(&backend->ctxt->ctxt_handle, 0, + sspi_status = Curl_pSecFn->EncryptMessage(&backend->ctxt->ctxt_handle, 0, &outbuf_desc, 0); /* check if the message was encrypted */ @@ -2059,10 +2017,10 @@ schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer; /* - It's important to send the full message which includes the header, - encrypted payload, and trailer. Until the client receives all the + it is important to send the full message which includes the header, + encrypted payload, and trailer. Until the client receives all the data a coherent message has not been delivered and the client - can't read any of it. + cannot read any of it. If we wanted to buffer the unwritten encrypted bytes, we would tell the client that all data it has requested to be sent has been @@ -2108,7 +2066,7 @@ schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, this_write = Curl_conn_cf_send(cf->next, data, ptr + written, len - written, - &result); + FALSE, &result); if(result == CURLE_AGAIN) continue; else if(result != CURLE_OK) { @@ -2159,8 +2117,14 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, DEBUGASSERT(backend); /**************************************************************************** - * Don't return or set backend->recv_unrecoverable_err unless in the cleanup. - * The pattern for return error is set *err, optional infof, goto cleanup. + * Do not return or set backend->recv_unrecoverable_err unless in the + * cleanup. The pattern for return error is set *err, optional infof, goto + * cleanup. + * + * Some verbose debug messages are wrapped by SCH_DEV() instead of DEBUGF() + * and only shown if CURL_SCHANNEL_DEV_DEBUG was defined at build time. These + * messages are extra verbose and intended for curl developers debugging + * Schannel recv decryption. * * Our priority is to always return as much decrypted data to the caller as * possible, even if an error occurs. The state of the decrypted buffer must @@ -2168,11 +2132,12 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, * handled in the cleanup. */ - DEBUGF(infof(data, "schannel: client wants to read %zu bytes", len)); + SCH_DEV(infof(data, "schannel: client wants to read %zu bytes", len)); *err = CURLE_OK; if(len && len <= backend->decdata_offset) { - infof(data, "schannel: enough decrypted data is already available"); + SCH_DEV(infof(data, + "schannel: enough decrypted data is already available")); goto cleanup; } else if(backend->recv_unrecoverable_err) { @@ -2185,8 +2150,7 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, infof(data, "schannel: server indicated shutdown in a prior call"); goto cleanup; } - - /* It's debatable what to return when !len. Regardless we can't return + /* it is debatable what to return when !len. Regardless we cannot return immediately because there may be data to decrypt (in the case we want to decrypt all encrypted cached data) so handle !len later in cleanup. */ @@ -2211,13 +2175,13 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, backend->encdata_buffer = reallocated_buffer; backend->encdata_length = reallocated_length; size = backend->encdata_length - backend->encdata_offset; - DEBUGF(infof(data, "schannel: encdata_buffer resized %zu", - backend->encdata_length)); + SCH_DEV(infof(data, "schannel: encdata_buffer resized %zu", + backend->encdata_length)); } - DEBUGF(infof(data, - "schannel: encrypted data buffer: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); + SCH_DEV(infof(data, + "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); /* read encrypted data from socket */ nread = Curl_conn_cf_recv(cf->next, data, @@ -2227,27 +2191,25 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(*err) { nread = -1; if(*err == CURLE_AGAIN) - DEBUGF(infof(data, - "schannel: recv returned CURLE_AGAIN")); + SCH_DEV(infof(data, "schannel: recv returned CURLE_AGAIN")); else if(*err == CURLE_RECV_ERROR) infof(data, "schannel: recv returned CURLE_RECV_ERROR"); else infof(data, "schannel: recv returned error %d", *err); } else if(nread == 0) { - backend->recv_connection_closed = true; + backend->recv_connection_closed = TRUE; DEBUGF(infof(data, "schannel: server closed the connection")); } else if(nread > 0) { backend->encdata_offset += (size_t)nread; - backend->encdata_is_incomplete = false; - DEBUGF(infof(data, "schannel: encrypted data got %zd", nread)); + backend->encdata_is_incomplete = FALSE; + SCH_DEV(infof(data, "schannel: encrypted data got %zd", nread)); } } - DEBUGF(infof(data, - "schannel: encrypted data buffer: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); + SCH_DEV(infof(data, "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); /* decrypt loop */ while(backend->encdata_offset > 0 && sspi_status == SEC_E_OK && @@ -2265,7 +2227,7 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375348.aspx */ - sspi_status = s_pSecFn->DecryptMessage(&backend->ctxt->ctxt_handle, + sspi_status = Curl_pSecFn->DecryptMessage(&backend->ctxt->ctxt_handle, &inbuf_desc, 0, NULL); /* check if everything went fine (server may want to renegotiate @@ -2275,8 +2237,8 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, /* check for successfully decrypted data, even before actual renegotiation or shutdown of the connection context */ if(inbuf[1].BufferType == SECBUFFER_DATA) { - DEBUGF(infof(data, "schannel: decrypted data length: %lu", - inbuf[1].cbBuffer)); + SCH_DEV(infof(data, "schannel: decrypted data length: %lu", + inbuf[1].cbBuffer)); /* increase buffer in order to fit the received amount of data */ size = inbuf[1].cbBuffer > CURL_SCHANNEL_BUFFER_FREE_SIZE ? @@ -2308,16 +2270,16 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, backend->decdata_offset += size; } - DEBUGF(infof(data, "schannel: decrypted data added: %zu", size)); - DEBUGF(infof(data, - "schannel: decrypted cached: offset %zu length %zu", - backend->decdata_offset, backend->decdata_length)); + SCH_DEV(infof(data, "schannel: decrypted data added: %zu", size)); + SCH_DEV(infof(data, + "schannel: decrypted cached: offset %zu length %zu", + backend->decdata_offset, backend->decdata_length)); } /* check for remaining encrypted data */ if(inbuf[3].BufferType == SECBUFFER_EXTRA && inbuf[3].cbBuffer > 0) { - DEBUGF(infof(data, "schannel: encrypted data length: %lu", - inbuf[3].cbBuffer)); + SCH_DEV(infof(data, "schannel: encrypted data length: %lu", + inbuf[3].cbBuffer)); /* check if the remaining data is less than the total amount * and therefore begins after the already processed data @@ -2331,9 +2293,9 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, backend->encdata_offset = inbuf[3].cbBuffer; } - DEBUGF(infof(data, - "schannel: encrypted cached: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); + SCH_DEV(infof(data, + "schannel: encrypted cached: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); } else { /* reset encrypted buffer offset, because there is no data remaining */ @@ -2344,17 +2306,18 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(sspi_status == SEC_I_RENEGOTIATE) { infof(data, "schannel: remote party requests renegotiation"); if(*err && *err != CURLE_AGAIN) { - infof(data, "schannel: can't renegotiate, an error is pending"); + infof(data, "schannel: cannot renegotiate, an error is pending"); goto cleanup; } /* begin renegotiation */ infof(data, "schannel: renegotiating SSL/TLS connection"); connssl->state = ssl_connection_negotiating; - connssl->connecting_state = ssl_connect_2_writing; - backend->recv_renegotiating = true; + connssl->connecting_state = ssl_connect_2; + connssl->io_need = CURL_SSL_IO_NEED_SEND; + backend->recv_renegotiating = TRUE; *err = schannel_connect_common(cf, data, FALSE, &done); - backend->recv_renegotiating = false; + backend->recv_renegotiating = FALSE; if(*err) { infof(data, "schannel: renegotiation failed"); goto cleanup; @@ -2368,53 +2331,56 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, else if(sspi_status == SEC_I_CONTEXT_EXPIRED) { /* In Windows 2000 SEC_I_CONTEXT_EXPIRED (close_notify) is not returned so we have to work around that in cleanup. */ - backend->recv_sspi_close_notify = true; - if(!backend->recv_connection_closed) { - backend->recv_connection_closed = true; - infof(data, "schannel: server closed the connection"); - } + backend->recv_sspi_close_notify = TRUE; + if(!backend->recv_connection_closed) + backend->recv_connection_closed = TRUE; + /* We received the close notify just fine, any error we got + * from the lower filters afterwards (e.g. the socket), is not + * an error on the TLS data stream. That one ended here. */ + if(*err == CURLE_RECV_ERROR) + *err = CURLE_OK; + infof(data, + "schannel: server close notification received (close_notify)"); goto cleanup; } } else if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) { - backend->encdata_is_incomplete = true; + backend->encdata_is_incomplete = TRUE; if(!*err) *err = CURLE_AGAIN; - infof(data, "schannel: failed to decrypt data, need more data"); + SCH_DEV(infof(data, "schannel: failed to decrypt data, need more data")); goto cleanup; } else { #ifndef CURL_DISABLE_VERBOSE_STRINGS char buffer[STRERROR_LEN]; + failf(data, "schannel: failed to read data from server: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); #endif *err = CURLE_RECV_ERROR; - infof(data, "schannel: failed to read data from server: %s", - Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); goto cleanup; } } - DEBUGF(infof(data, - "schannel: encrypted data buffer: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); + SCH_DEV(infof(data, "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); - DEBUGF(infof(data, - "schannel: decrypted data buffer: offset %zu length %zu", - backend->decdata_offset, backend->decdata_length)); + SCH_DEV(infof(data, "schannel: decrypted data buffer: offset %zu length %zu", + backend->decdata_offset, backend->decdata_length)); cleanup: /* Warning- there is no guarantee the encdata state is valid at this point */ - DEBUGF(infof(data, "schannel: schannel_recv cleanup")); + SCH_DEV(infof(data, "schannel: schannel_recv cleanup")); /* Error if the connection has closed without a close_notify. - The behavior here is a matter of debate. We don't want to be vulnerable - to a truncation attack however there's some browser precedent for + The behavior here is a matter of debate. We do not want to be vulnerable + to a truncation attack however there is some browser precedent for ignoring the close_notify for compatibility reasons. Additionally, Windows 2000 (v5.0) is a special case since it seems it - doesn't return close_notify. In that case if the connection was closed we - assume it was graceful (close_notify) since there doesn't seem to be a + does not return close_notify. In that case if the connection was closed we + assume it was graceful (close_notify) since there does not seem to be a way to tell. */ if(len && !backend->decdata_offset && backend->recv_connection_closed && @@ -2423,10 +2389,10 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, VERSION_EQUAL); if(isWin2k && sspi_status == SEC_E_OK) - backend->recv_sspi_close_notify = true; + backend->recv_sspi_close_notify = TRUE; else { *err = CURLE_RECV_ERROR; - infof(data, "schannel: server closed abruptly (missing close_notify)"); + failf(data, "schannel: server closed abruptly (missing close_notify)"); } } @@ -2440,10 +2406,10 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, memmove(backend->decdata_buffer, backend->decdata_buffer + size, backend->decdata_offset - size); backend->decdata_offset -= size; - DEBUGF(infof(data, "schannel: decrypted data returned %zu", size)); - DEBUGF(infof(data, - "schannel: decrypted data buffer: offset %zu length %zu", - backend->decdata_offset, backend->decdata_length)); + SCH_DEV(infof(data, "schannel: decrypted data returned %zu", size)); + SCH_DEV(infof(data, + "schannel: decrypted data buffer: offset %zu length %zu", + backend->decdata_offset, backend->decdata_length)); *err = CURLE_OK; return (ssize_t)size; } @@ -2451,7 +2417,7 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(!*err && !backend->recv_connection_closed) *err = CURLE_AGAIN; - /* It's debatable what to return when !len. We could return whatever error + /* it is debatable what to return when !len. We could return whatever error we got from decryption but instead we override here so the return is consistent. */ @@ -2495,37 +2461,20 @@ static bool schannel_data_pending(struct Curl_cfilter *cf, if(backend->ctxt) /* SSL/TLS is in use */ return (backend->decdata_offset > 0 || - (backend->encdata_offset > 0 && !backend->encdata_is_incomplete)); + (backend->encdata_offset > 0 && !backend->encdata_is_incomplete) || + backend->recv_connection_closed || + backend->recv_sspi_close_notify || + backend->recv_unrecoverable_err); else return FALSE; } -static void schannel_session_free(void *ptr) -{ - /* this is expected to be called under sessionid lock */ - struct Curl_schannel_cred *cred = ptr; - - if(cred) { - cred->refcount--; - if(cred->refcount == 0) { - s_pSecFn->FreeCredentialsHandle(&cred->cred_handle); - curlx_unicodefree(cred->sni_hostname); -#ifdef HAS_CLIENT_CERT_PATH - if(cred->client_cert_store) { - CertCloseStore(cred->client_cert_store, 0); - cred->client_cert_store = NULL; - } -#endif - Curl_safefree(cred); - } - } -} - /* shut down the SSL connection and clean up related memory. this function can be called multiple times on the same connection including if the SSL connection failed (eg connection made but failed handshake). */ -static int schannel_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data) +static CURLcode schannel_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { /* See https://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx * Shutting Down an Schannel Connection @@ -2533,41 +2482,57 @@ static int schannel_shutdown(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct schannel_ssl_backend_data *backend = (struct schannel_ssl_backend_data *)connssl->backend; + CURLcode result = CURLE_OK; + + if(cf->shutdown) { + *done = TRUE; + return CURLE_OK; + } DEBUGASSERT(data); DEBUGASSERT(backend); + /* Not supported in schannel */ + (void)send_shutdown; + + *done = FALSE; if(backend->ctxt) { infof(data, "schannel: shutting down SSL/TLS connection with %s port %d", - connssl->hostname, connssl->port); + connssl->peer.hostname, connssl->peer.port); } - if(backend->cred && backend->ctxt) { + if(!backend->ctxt || cf->shutdown) { + *done = TRUE; + goto out; + } + + if(backend->cred && backend->ctxt && !backend->sent_shutdown) { SecBufferDesc BuffDesc; SecBuffer Buffer; SECURITY_STATUS sspi_status; SecBuffer outbuf; SecBufferDesc outbuf_desc; - CURLcode result; DWORD dwshut = SCHANNEL_SHUTDOWN; InitSecBuffer(&Buffer, SECBUFFER_TOKEN, &dwshut, sizeof(dwshut)); InitSecBufferDesc(&BuffDesc, &Buffer, 1); - sspi_status = s_pSecFn->ApplyControlToken(&backend->ctxt->ctxt_handle, + sspi_status = Curl_pSecFn->ApplyControlToken(&backend->ctxt->ctxt_handle, &BuffDesc); if(sspi_status != SEC_E_OK) { char buffer[STRERROR_LEN]; failf(data, "schannel: ApplyControlToken failure: %s", Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + result = CURLE_SEND_ERROR; + goto out; } /* setup output buffer */ InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&outbuf_desc, &outbuf, 1); - sspi_status = s_pSecFn->InitializeSecurityContext( + sspi_status = Curl_pSecFn->InitializeSecurityContext( &backend->cred->cred_handle, &backend->ctxt->ctxt_handle, backend->cred->sni_hostname, @@ -2585,26 +2550,88 @@ static int schannel_shutdown(struct Curl_cfilter *cf, /* send close message which is in output buffer */ ssize_t written = Curl_conn_cf_send(cf->next, data, outbuf.pvBuffer, outbuf.cbBuffer, - &result); - s_pSecFn->FreeContextBuffer(outbuf.pvBuffer); - if((result != CURLE_OK) || (outbuf.cbBuffer != (size_t) written)) { - infof(data, "schannel: failed to send close msg: %s" - " (bytes written: %zd)", curl_easy_strerror(result), written); + FALSE, &result); + Curl_pSecFn->FreeContextBuffer(outbuf.pvBuffer); + if(!result) { + if(written < (ssize_t)outbuf.cbBuffer) { + /* TODO: handle partial sends */ + failf(data, "schannel: failed to send close msg: %s" + " (bytes written: %zd)", curl_easy_strerror(result), written); + result = CURLE_SEND_ERROR; + goto out; + } + backend->sent_shutdown = TRUE; + *done = TRUE; + } + else if(result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_SEND; + result = CURLE_OK; + goto out; + } + else { + if(!backend->recv_connection_closed) { + failf(data, "schannel: error sending close msg: %d", result); + result = CURLE_SEND_ERROR; + goto out; + } + /* Looks like server already closed the connection. + * An error to send our close notify is not a failure. */ + *done = TRUE; + result = CURLE_OK; } } } + /* If the connection seems open and we have not seen the close notify + * from the server yet, try to receive it. */ + if(backend->cred && backend->ctxt && + !backend->recv_sspi_close_notify && !backend->recv_connection_closed) { + char buffer[1024]; + ssize_t nread; + + nread = schannel_recv(cf, data, buffer, sizeof(buffer), &result); + if(nread > 0) { + /* still data coming in? */ + } + else if(nread == 0) { + /* We got the close notify alert and are done. */ + backend->recv_connection_closed = TRUE; + *done = TRUE; + } + else if(nread < 0 && result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_RECV; + } + else { + CURL_TRC_CF(data, cf, "SSL shutdown, error %d", result); + result = CURLE_RECV_ERROR; + } + } + +out: + cf->shutdown = (result || *done); + return result; +} + +static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + + DEBUGASSERT(data); + DEBUGASSERT(backend); + /* free SSPI Schannel API security context handle */ if(backend->ctxt) { DEBUGF(infof(data, "schannel: clear security context handle")); - s_pSecFn->DeleteSecurityContext(&backend->ctxt->ctxt_handle); + Curl_pSecFn->DeleteSecurityContext(&backend->ctxt->ctxt_handle); Curl_safefree(backend->ctxt); } /* free SSPI Schannel API credential handle */ if(backend->cred) { Curl_ssl_sessionid_lock(data); - schannel_session_free(backend->cred); + schannel_session_free(backend->cred, 0); Curl_ssl_sessionid_unlock(data); backend->cred = NULL; } @@ -2614,7 +2641,7 @@ static int schannel_shutdown(struct Curl_cfilter *cf, Curl_safefree(backend->encdata_buffer); backend->encdata_length = 0; backend->encdata_offset = 0; - backend->encdata_is_incomplete = false; + backend->encdata_is_incomplete = FALSE; } /* free internal buffer for received decrypted data */ @@ -2623,13 +2650,6 @@ static int schannel_shutdown(struct Curl_cfilter *cf, backend->decdata_length = 0; backend->decdata_offset = 0; } - - return CURLE_OK; -} - -static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - schannel_shutdown(cf, data); } static int schannel_init(void) @@ -2644,9 +2664,7 @@ static void schannel_cleanup(void) static size_t schannel_version(char *buffer, size_t size) { - size = msnprintf(buffer, size, "Schannel"); - - return size; + return msnprintf(buffer, size, "Schannel"); } static CURLcode schannel_random(struct Curl_easy *data UNUSED_PARAM, @@ -2671,7 +2689,7 @@ static CURLcode schannel_pkp_pin_peer_pubkey(struct Curl_cfilter *cf, DEBUGASSERT(backend); - /* if a path wasn't specified, don't pin */ + /* if a path was not specified, do not pin */ if(!pinnedpubkey) return CURLE_OK; @@ -2683,7 +2701,7 @@ static CURLcode schannel_pkp_pin_peer_pubkey(struct Curl_cfilter *cf, struct Curl_asn1Element *pubkey; sspi_status = - s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, + Curl_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &pCertContextServer); @@ -2733,6 +2751,13 @@ static void schannel_checksum(const unsigned char *input, DWORD provType, const unsigned int algId) { +#ifdef CURL_WINDOWS_UWP + (void)input; + (void)inputlen; + (void)provType; + (void)algId; + memset(checksum, 0, checksumlen); +#else HCRYPTPROV hProv = 0; HCRYPTHASH hHash = 0; DWORD cbHashSize = 0; @@ -2752,8 +2777,7 @@ static void schannel_checksum(const unsigned char *input, if(!CryptCreateHash(hProv, algId, 0, 0, &hHash)) break; /* failed */ - /* workaround for original MinGW, should be (const BYTE*) */ - if(!CryptHashData(hHash, (BYTE*)input, (DWORD)inputlen, 0)) + if(!CryptHashData(hHash, input, (DWORD)inputlen, 0)) break; /* failed */ /* get hash size */ @@ -2774,6 +2798,7 @@ static void schannel_checksum(const unsigned char *input, if(hProv) CryptReleaseContext(hProv, 0); +#endif } static CURLcode schannel_sha256sum(const unsigned char *input, @@ -2796,6 +2821,149 @@ static void *schannel_get_internals(struct ssl_connect_data *connssl, return &backend->ctxt->ctxt_handle; } +HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct Curl_multi *multi = data->multi; + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + struct schannel_cert_share *share; + const struct ssl_general_config *cfg = &data->set.general_ssl; + timediff_t timeout_ms; + timediff_t elapsed_ms; + struct curltime now; + unsigned char info_blob_digest[CURL_SHA256_DIGEST_LENGTH]; + + DEBUGASSERT(multi); + + if(!multi) { + return NULL; + } + + share = Curl_hash_pick(&multi->proto_hash, + (void *)MPROTO_SCHANNEL_CERT_SHARE_KEY, + sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1); + if(!share || !share->cert_store) { + return NULL; + } + + /* zero ca_cache_timeout completely disables caching */ + if(!cfg->ca_cache_timeout) { + return NULL; + } + + /* check for cache timeout by using the cached_x509_store_expired timediff + calculation pattern from openssl.c. + negative timeout means retain forever. */ + timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + if(timeout_ms >= 0) { + now = Curl_now(); + elapsed_ms = Curl_timediff(now, share->time); + if(elapsed_ms >= timeout_ms) { + return NULL; + } + } + + if(ca_info_blob) { + if(share->CAinfo_blob_size != ca_info_blob->len) { + return NULL; + } + schannel_sha256sum((const unsigned char *)ca_info_blob->data, + ca_info_blob->len, + info_blob_digest, + CURL_SHA256_DIGEST_LENGTH); + if(memcmp(share->CAinfo_blob_digest, info_blob_digest, + CURL_SHA256_DIGEST_LENGTH)) { + return NULL; + } + } + else { + if(!conn_config->CAfile || !share->CAfile || + strcmp(share->CAfile, conn_config->CAfile)) { + return NULL; + } + } + + return share->cert_store; +} + +static void schannel_cert_share_free(void *key, size_t key_len, void *p) +{ + struct schannel_cert_share *share = p; + DEBUGASSERT(key_len == (sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1)); + DEBUGASSERT(!memcmp(MPROTO_SCHANNEL_CERT_SHARE_KEY, key, key_len)); + (void)key; + (void)key_len; + if(share->cert_store) { + CertCloseStore(share->cert_store, 0); + } + free(share->CAfile); + free(share); +} + +bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, + const struct Curl_easy *data, + HCERTSTORE cert_store) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct Curl_multi *multi = data->multi; + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + struct schannel_cert_share *share; + size_t CAinfo_blob_size = 0; + char *CAfile = NULL; + + DEBUGASSERT(multi); + + if(!multi) { + return FALSE; + } + + share = Curl_hash_pick(&multi->proto_hash, + (void *)MPROTO_SCHANNEL_CERT_SHARE_KEY, + sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1); + if(!share) { + share = calloc(1, sizeof(*share)); + if(!share) { + return FALSE; + } + if(!Curl_hash_add2(&multi->proto_hash, + (void *)MPROTO_SCHANNEL_CERT_SHARE_KEY, + sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1, + share, schannel_cert_share_free)) { + free(share); + return FALSE; + } + } + + if(ca_info_blob) { + schannel_sha256sum((const unsigned char *)ca_info_blob->data, + ca_info_blob->len, + share->CAinfo_blob_digest, + CURL_SHA256_DIGEST_LENGTH); + CAinfo_blob_size = ca_info_blob->len; + } + else { + if(conn_config->CAfile) { + CAfile = strdup(conn_config->CAfile); + if(!CAfile) { + return FALSE; + } + } + } + + /* free old cache data */ + if(share->cert_store) { + CertCloseStore(share->cert_store, 0); + } + free(share->CAfile); + + share->time = Curl_now(); + share->cert_store = cert_store; + share->CAinfo_blob_size = CAinfo_blob_size; + share->CAfile = CAfile; + return TRUE; +} + const struct Curl_ssl Curl_ssl_schannel = { { CURLSSLBACKEND_SCHANNEL, "schannel" }, /* info */ @@ -2803,9 +2971,13 @@ const struct Curl_ssl Curl_ssl_schannel = { #ifdef HAS_MANUAL_VERIFY_API SSLSUPP_CAINFO_BLOB | #endif +#ifndef CURL_WINDOWS_UWP SSLSUPP_PINNEDPUBKEY | +#endif SSLSUPP_TLS13_CIPHERSUITES | - SSLSUPP_HTTPS_PROXY, + SSLSUPP_CA_CACHE | + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST, sizeof(struct schannel_ssl_backend_data), @@ -2819,11 +2991,10 @@ const struct Curl_ssl Curl_ssl_schannel = { Curl_none_cert_status_request, /* cert_status_request */ schannel_connect, /* connect */ schannel_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks, /* getsock */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ schannel_get_internals, /* get_internals */ schannel_close, /* close_one */ Curl_none_close_all, /* close_all */ - schannel_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ @@ -2831,9 +3002,9 @@ const struct Curl_ssl Curl_ssl_schannel = { schannel_sha256sum, /* sha256sum */ NULL, /* associate_connection */ NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ schannel_recv, /* recv decrypted data */ schannel_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; #endif /* USE_SCHANNEL */ diff --git a/deps/curl/lib/vtls/schannel.h b/deps/curl/lib/vtls/schannel.h index be235673028..b26334bcf87 100644 --- a/deps/curl/lib/vtls/schannel.h +++ b/deps/curl/lib/vtls/schannel.h @@ -68,7 +68,7 @@ * BoringSSL's : So just undefine those defines here * (and only here). */ -#if defined(HAVE_BORINGSSL) || defined(OPENSSL_IS_BORINGSSL) +#if defined(OPENSSL_IS_BORINGSSL) # undef X509_NAME # undef X509_CERT_PAIR # undef X509_EXTENSIONS diff --git a/deps/curl/lib/vtls/schannel_int.h b/deps/curl/lib/vtls/schannel_int.h index edb20bcd237..81476bc6d81 100644 --- a/deps/curl/lib/vtls/schannel_int.h +++ b/deps/curl/lib/vtls/schannel_int.h @@ -28,15 +28,12 @@ #ifdef USE_SCHANNEL -#ifdef __MINGW32__ -#ifdef __MINGW64_VERSION_MAJOR -#define HAS_MANUAL_VERIFY_API -#endif -#else -#ifdef CERT_CHAIN_REVOCATION_CHECK_CHAIN +#include "vtls.h" + +#if (defined(__MINGW32__) || defined(CERT_CHAIN_REVOCATION_CHECK_CHAIN)) \ + && !defined(CURL_WINDOWS_UWP) #define HAS_MANUAL_VERIFY_API #endif -#endif #if defined(CryptStringToBinary) && defined(CRYPT_STRING_HEX) \ && !defined(DISABLE_SCHANNEL_CLIENT_CERT) @@ -59,40 +56,15 @@ #define CERT_ALT_NAME_IP_ADDRESS 8 #endif - -#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) -/* Original mingw is missing CERT structs or they're disabled. - Refer to w32api-5.0.2-mingw32-dev\include\wincrypt.h. */ - -/* !checksrc! disable TYPEDEFSTRUCT 4 */ -typedef struct _CERT_OTHER_NAME { - LPSTR pszObjId; - CRYPT_OBJID_BLOB Value; -} CERT_OTHER_NAME, *PCERT_OTHER_NAME; - -typedef struct _CERT_ALT_NAME_ENTRY { - DWORD dwAltNameChoice; - union { - PCERT_OTHER_NAME pOtherName; - LPWSTR pwszRfc822Name; - LPWSTR pwszDNSName; - CERT_NAME_BLOB DirectoryName; - LPWSTR pwszURL; - CRYPT_DATA_BLOB IPAddress; - LPSTR pszRegisteredID; - }; -} CERT_ALT_NAME_ENTRY, *PCERT_ALT_NAME_ENTRY; - -typedef struct _CERT_ALT_NAME_INFO { - DWORD cAltEntry; - PCERT_ALT_NAME_ENTRY rgAltEntry; -} CERT_ALT_NAME_INFO, *PCERT_ALT_NAME_INFO; - -typedef struct _CRYPT_DECODE_PARA { - DWORD cbSize; - PFN_CRYPT_ALLOC pfnAlloc; - PFN_CRYPT_FREE pfnFree; -} CRYPT_DECODE_PARA, *PCRYPT_DECODE_PARA; +#if defined(_MSC_VER) && (_MSC_VER <= 1600) +/* Workaround for warning: + 'type cast' : conversion from 'int' to 'LPCSTR' of greater size */ +#undef CERT_STORE_PROV_MEMORY +#undef CERT_STORE_PROV_SYSTEM_A +#undef CERT_STORE_PROV_SYSTEM_W +#define CERT_STORE_PROV_MEMORY ((LPCSTR)(size_t)2) +#define CERT_STORE_PROV_SYSTEM_A ((LPCSTR)(size_t)9) +#define CERT_STORE_PROV_SYSTEM_W ((LPCSTR)(size_t)10) #endif #ifndef SCH_CREDENTIALS_VERSION @@ -175,7 +147,7 @@ struct schannel_ssl_backend_data { size_t encdata_offset, decdata_offset; unsigned char *encdata_buffer, *decdata_buffer; /* encdata_is_incomplete: if encdata contains only a partial record that - can't be decrypted without another recv() (that is, status is + cannot be decrypted without another recv() (that is, status is SEC_E_INCOMPLETE_MESSAGE) then set this true. after an recv() adds more bytes into encdata then set this back to false. */ bool encdata_is_incomplete; @@ -188,7 +160,39 @@ struct schannel_ssl_backend_data { #ifdef HAS_MANUAL_VERIFY_API bool use_manual_cred_validation; /* true if manual cred validation is used */ #endif + BIT(sent_shutdown); }; +/* key to use at `multi->proto_hash` */ +#define MPROTO_SCHANNEL_CERT_SHARE_KEY "tls:schannel:cert:share" + +struct schannel_cert_share { + unsigned char CAinfo_blob_digest[CURL_SHA256_DIGEST_LENGTH]; + size_t CAinfo_blob_size; /* CA info blob size */ + char *CAfile; /* CAfile path used to generate + certificate store */ + HCERTSTORE cert_store; /* cached certificate store or + NULL if none */ + struct curltime time; /* when the cached store was created */ +}; + +/* +* size of the structure: 20 bytes. +*/ +struct num_ip_data { + DWORD size; /* 04 bytes */ + union { + struct in_addr ia; /* 04 bytes */ + struct in6_addr ia6; /* 16 bytes */ + } bData; +}; + +HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, + const struct Curl_easy *data); + +bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, + const struct Curl_easy *data, + HCERTSTORE cert_store); + #endif /* USE_SCHANNEL */ #endif /* HEADER_CURL_SCHANNEL_INT_H */ diff --git a/deps/curl/lib/vtls/schannel_verify.c b/deps/curl/lib/vtls/schannel_verify.c index a5d5c98bb7f..4b52b8e8a49 100644 --- a/deps/curl/lib/vtls/schannel_verify.c +++ b/deps/curl/lib/vtls/schannel_verify.c @@ -33,12 +33,13 @@ #ifdef USE_SCHANNEL #ifndef USE_WINDOWS_SSPI -# error "Can't compile SCHANNEL support without SSPI." +# error "cannot compile SCHANNEL support without SSPI." #endif #include "schannel.h" #include "schannel_int.h" +#include "inet_pton.h" #include "vtls.h" #include "vtls_int.h" #include "sendf.h" @@ -54,7 +55,6 @@ #define BACKEND ((struct schannel_ssl_backend_data *)connssl->backend) - #ifdef HAS_MANUAL_VERIFY_API #define MAX_CAFILE_SIZE 1048576 /* 1 MiB */ @@ -82,8 +82,8 @@ static int is_cr_or_lf(char c) } /* Search the substring needle,needlelen into string haystack,haystacklen - * Strings don't need to be terminated by a '\0'. - * Similar of OSX/Linux memmem (not available on Visual Studio). + * Strings do not need to be terminated by a '\0'. + * Similar of macOS/Linux memmem (not available on Visual Studio). * Return position of beginning of first occurrence or NULL if not found */ static const char *c_memmem(const void *haystack, size_t haystacklen, @@ -116,7 +116,7 @@ static CURLcode add_certs_data_to_store(HCERTSTORE trust_store, const char *current_ca_file_ptr = ca_buffer; const char *ca_buffer_limit = ca_buffer + ca_buffer_size; - while(more_certs && (current_ca_file_ptrpCertInfo; - if(!cert_info) { - failf(data, "schannel: Null certificate info."); - return actual_length; - } - - extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2, - cert_info->cExtension, - cert_info->rgExtension); - if(!extension) { - failf(data, "schannel: CertFindExtension() returned no extension."); - return actual_length; - } - - decode_para.cbSize = sizeof(CRYPT_DECODE_PARA); - - ret_val = - CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - szOID_SUBJECT_ALT_NAME2, - extension->Value.pbData, - extension->Value.cbData, - CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, - &decode_para, - &alt_name_info, - &alt_name_info_size); - if(!ret_val) { - failf(data, - "schannel: CryptDecodeObjectEx() returned no alternate name " - "information."); - return actual_length; - } - current_pos = host_names; /* Iterate over the alternate names and populate host_names. */ @@ -441,14 +410,14 @@ static DWORD cert_get_name_string(struct Curl_easy *data, } /* Sanity check to prevent buffer overrun. */ if((actual_length + current_length) > length) { - failf(data, "schannel: Not enough memory to list all host names."); + failf(data, "schannel: Not enough memory to list all hostnames."); break; } dns_w = entry->pwszDNSName; - /* pwszDNSName is in ia5 string format and hence doesn't contain any - * non-ascii characters. */ + /* pwszDNSName is in ia5 string format and hence does not contain any + * non-ASCII characters. */ while(*dns_w != '\0') { - *current_pos++ = (char)(*dns_w++); + *current_pos++ = (TCHAR)(*dns_w++); } *current_pos++ = '\0'; actual_length += (DWORD)current_length; @@ -457,9 +426,92 @@ static DWORD cert_get_name_string(struct Curl_easy *data, /* Last string has double null-terminator. */ *current_pos = '\0'; } +#endif return actual_length; } +/* +* Returns TRUE if the hostname is a numeric IPv4/IPv6 Address, +* and populates the buffer with IPv4/IPv6 info. +*/ + +static bool get_num_host_info(struct num_ip_data *ip_blob, + LPCSTR hostname) +{ + struct in_addr ia; + struct in6_addr ia6; + bool result = FALSE; + + int res = Curl_inet_pton(AF_INET, hostname, &ia); + if(res) { + ip_blob->size = sizeof(struct in_addr); + memcpy(&ip_blob->bData.ia, &ia, sizeof(struct in_addr)); + result = TRUE; + } + else { + res = Curl_inet_pton(AF_INET6, hostname, &ia6); + if(res) { + ip_blob->size = sizeof(struct in6_addr); + memcpy(&ip_blob->bData.ia6, &ia6, sizeof(struct in6_addr)); + result = TRUE; + } + } + return result; +} + +static bool get_alt_name_info(struct Curl_easy *data, + PCCERT_CONTEXT ctx, + PCERT_ALT_NAME_INFO *alt_name_info, + LPDWORD alt_name_info_size) +{ + bool result = FALSE; +#if defined(CURL_WINDOWS_UWP) + (void)data; + (void)ctx; + (void)alt_name_info; + (void)alt_name_info_size; +#else + PCERT_INFO cert_info = NULL; + PCERT_EXTENSION extension = NULL; + CRYPT_DECODE_PARA decode_para = { sizeof(CRYPT_DECODE_PARA), NULL, NULL }; + + if(!ctx) { + failf(data, "schannel: Null certificate context."); + return result; + } + + cert_info = ctx->pCertInfo; + if(!cert_info) { + failf(data, "schannel: Null certificate info."); + return result; + } + + extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2, + cert_info->cExtension, + cert_info->rgExtension); + if(!extension) { + failf(data, "schannel: CertFindExtension() returned no extension."); + return result; + } + + if(!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + szOID_SUBJECT_ALT_NAME2, + extension->Value.pbData, + extension->Value.cbData, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, + &decode_para, + alt_name_info, + alt_name_info_size)) { + failf(data, + "schannel: CryptDecodeObjectEx() returned no alternate name " + "information."); + return result; + } + result = TRUE; +#endif + return result; +} + /* Verify the server's hostname */ CURLcode Curl_verify_host(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -470,13 +522,19 @@ CURLcode Curl_verify_host(struct Curl_cfilter *cf, CERT_CONTEXT *pCertContextServer = NULL; TCHAR *cert_hostname_buff = NULL; size_t cert_hostname_buff_index = 0; - const char *conn_hostname = connssl->hostname; + const char *conn_hostname = connssl->peer.hostname; size_t hostlen = strlen(conn_hostname); DWORD len = 0; DWORD actual_len = 0; + PCERT_ALT_NAME_INFO alt_name_info = NULL; + DWORD alt_name_info_size = 0; + struct num_ip_data ip_blob = { 0 }; + bool Win8_compat; + struct num_ip_data *p = &ip_blob; + DWORD i; sspi_status = - s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, + Curl_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &pCertContextServer); @@ -484,97 +542,123 @@ CURLcode Curl_verify_host(struct Curl_cfilter *cf, char buffer[STRERROR_LEN]; failf(data, "schannel: Failed to read remote certificate context: %s", Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); - result = CURLE_PEER_FAILED_VERIFICATION; goto cleanup; } - /* Determine the size of the string needed for the cert hostname */ - len = cert_get_name_string(data, pCertContextServer, NULL, 0); - if(len == 0) { - failf(data, - "schannel: CertGetNameString() returned no " - "certificate name information"); - result = CURLE_PEER_FAILED_VERIFICATION; - goto cleanup; + Win8_compat = curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL); + if(get_num_host_info(p, conn_hostname) || !Win8_compat) { + if(!get_alt_name_info(data, pCertContextServer, + &alt_name_info, &alt_name_info_size)) { + goto cleanup; + } } - /* CertGetNameString guarantees that the returned name will not contain - * embedded null bytes. This appears to be undocumented behavior. - */ - cert_hostname_buff = (LPTSTR)malloc(len * sizeof(TCHAR)); - if(!cert_hostname_buff) { - result = CURLE_OUT_OF_MEMORY; - goto cleanup; + if(p->size) { + for(i = 0; i < alt_name_info->cAltEntry; ++i) { + PCERT_ALT_NAME_ENTRY entry = &alt_name_info->rgAltEntry[i]; + if(entry->dwAltNameChoice == CERT_ALT_NAME_IP_ADDRESS) { + if(entry->IPAddress.cbData == p->size) { + if(!memcmp(entry->IPAddress.pbData, &p->bData, + entry->IPAddress.cbData)) { + result = CURLE_OK; + infof(data, + "schannel: connection hostname (%s) matched cert's IP address!", + conn_hostname); + break; + } + } + } + } } - actual_len = cert_get_name_string( - data, pCertContextServer, (LPTSTR)cert_hostname_buff, len); - /* Sanity check */ - if(actual_len != len) { - failf(data, - "schannel: CertGetNameString() returned certificate " - "name information of unexpected size"); - result = CURLE_PEER_FAILED_VERIFICATION; - goto cleanup; - } + else { + /* Determine the size of the string needed for the cert hostname */ + len = cert_get_name_string(data, pCertContextServer, + NULL, 0, alt_name_info, Win8_compat); + if(len == 0) { + failf(data, + "schannel: CertGetNameString() returned no " + "certificate name information"); + goto cleanup; + } - /* cert_hostname_buff contains all DNS names, where each name is - * null-terminated and the last DNS name is double null-terminated. Due to - * this encoding, use the length of the buffer to iterate over all names. - */ - result = CURLE_PEER_FAILED_VERIFICATION; - while(cert_hostname_buff_index < len && - cert_hostname_buff[cert_hostname_buff_index] != TEXT('\0') && - result == CURLE_PEER_FAILED_VERIFICATION) { + /* CertGetNameString guarantees that the returned name will not contain + * embedded null bytes. This appears to be undocumented behavior. + */ + cert_hostname_buff = (LPTSTR)malloc(len * sizeof(TCHAR)); + if(!cert_hostname_buff) { + result = CURLE_OUT_OF_MEMORY; + goto cleanup; + } + actual_len = cert_get_name_string(data, pCertContextServer, + (LPTSTR)cert_hostname_buff, len, alt_name_info, Win8_compat); - char *cert_hostname; + /* Sanity check */ + if(actual_len != len) { + failf(data, + "schannel: CertGetNameString() returned certificate " + "name information of unexpected size"); + goto cleanup; + } - /* Comparing the cert name and the connection hostname encoded as UTF-8 - * is acceptable since both values are assumed to use ASCII - * (or some equivalent) encoding + /* cert_hostname_buff contains all DNS names, where each name is + * null-terminated and the last DNS name is double null-terminated. Due to + * this encoding, use the length of the buffer to iterate over all names. */ - cert_hostname = curlx_convert_tchar_to_UTF8( + while(cert_hostname_buff_index < len && + cert_hostname_buff[cert_hostname_buff_index] != TEXT('\0') && + result == CURLE_PEER_FAILED_VERIFICATION) { + + char *cert_hostname; + + /* Comparing the cert name and the connection hostname encoded as UTF-8 + * is acceptable since both values are assumed to use ASCII + * (or some equivalent) encoding + */ + cert_hostname = curlx_convert_tchar_to_UTF8( &cert_hostname_buff[cert_hostname_buff_index]); - if(!cert_hostname) { - result = CURLE_OUT_OF_MEMORY; - } - else { - if(Curl_cert_hostcheck(cert_hostname, strlen(cert_hostname), - conn_hostname, hostlen)) { - infof(data, - "schannel: connection hostname (%s) validated " - "against certificate name (%s)", - conn_hostname, cert_hostname); - result = CURLE_OK; + if(!cert_hostname) { + result = CURLE_OUT_OF_MEMORY; } else { - size_t cert_hostname_len; + if(Curl_cert_hostcheck(cert_hostname, strlen(cert_hostname), + conn_hostname, hostlen)) { + infof(data, + "schannel: connection hostname (%s) validated " + "against certificate name (%s)", + conn_hostname, cert_hostname); + result = CURLE_OK; + } + else { + size_t cert_hostname_len; - infof(data, - "schannel: connection hostname (%s) did not match " - "against certificate name (%s)", - conn_hostname, cert_hostname); + infof(data, + "schannel: connection hostname (%s) did not match " + "against certificate name (%s)", + conn_hostname, cert_hostname); - cert_hostname_len = - _tcslen(&cert_hostname_buff[cert_hostname_buff_index]); + cert_hostname_len = + _tcslen(&cert_hostname_buff[cert_hostname_buff_index]); - /* Move on to next cert name */ - cert_hostname_buff_index += cert_hostname_len + 1; + /* Move on to next cert name */ + cert_hostname_buff_index += cert_hostname_len + 1; - result = CURLE_PEER_FAILED_VERIFICATION; + result = CURLE_PEER_FAILED_VERIFICATION; + } + curlx_unicodefree(cert_hostname); } - curlx_unicodefree(cert_hostname); } - } - if(result == CURLE_PEER_FAILED_VERIFICATION) { - failf(data, - "schannel: CertGetNameString() failed to match " - "connection hostname (%s) against server certificate names", - conn_hostname); + if(result == CURLE_PEER_FAILED_VERIFICATION) { + failf(data, + "schannel: CertGetNameString() failed to match " + "connection hostname (%s) against server certificate names", + conn_hostname); + } + else if(result != CURLE_OK) + failf(data, "schannel: server certificate name verification failed"); } - else if(result != CURLE_OK) - failf(data, "schannel: server certificate name verification failed"); cleanup: Curl_safefree(cert_hostname_buff); @@ -585,7 +669,6 @@ CURLcode Curl_verify_host(struct Curl_cfilter *cf, return result; } - #ifdef HAS_MANUAL_VERIFY_API /* Verify the server's certificate and hostname */ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, @@ -600,11 +683,12 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, const CERT_CHAIN_CONTEXT *pChainContext = NULL; HCERTCHAINENGINE cert_chain_engine = NULL; HCERTSTORE trust_store = NULL; + HCERTSTORE own_trust_store = NULL; DEBUGASSERT(BACKEND); sspi_status = - s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, + Curl_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &pCertContextServer); @@ -630,31 +714,46 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, result = CURLE_SSL_CACERT_BADFILE; } else { - /* Open the certificate store */ - trust_store = CertOpenStore(CERT_STORE_PROV_MEMORY, - 0, - (HCRYPTPROV)NULL, - CERT_STORE_CREATE_NEW_FLAG, - NULL); - if(!trust_store) { - char buffer[STRERROR_LEN]; - failf(data, "schannel: failed to create certificate store: %s", - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); - result = CURLE_SSL_CACERT_BADFILE; + /* try cache */ + trust_store = Curl_schannel_get_cached_cert_store(cf, data); + + if(trust_store) { + infof(data, "schannel: reusing certificate store from cache"); } else { - const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; - if(ca_info_blob) { - result = add_certs_data_to_store(trust_store, - (const char *)ca_info_blob->data, - ca_info_blob->len, - "(memory blob)", - data); + /* Open the certificate store */ + trust_store = CertOpenStore(CERT_STORE_PROV_MEMORY, + 0, + (HCRYPTPROV)NULL, + CERT_STORE_CREATE_NEW_FLAG, + NULL); + if(!trust_store) { + char buffer[STRERROR_LEN]; + failf(data, "schannel: failed to create certificate store: %s", + Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + result = CURLE_SSL_CACERT_BADFILE; } else { - result = add_certs_file_to_store(trust_store, - conn_config->CAfile, - data); + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + own_trust_store = trust_store; + + if(ca_info_blob) { + result = add_certs_data_to_store(trust_store, + (const char *)ca_info_blob->data, + ca_info_blob->len, + "(memory blob)", + data); + } + else { + result = add_certs_file_to_store(trust_store, + conn_config->CAfile, + data); + } + if(result == CURLE_OK) { + if(Curl_schannel_set_cached_cert_store(cf, data, trust_store)) { + own_trust_store = NULL; + } + } } } } @@ -737,7 +836,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, failf(data, "schannel: CertGetCertificateChain trust error" " CERT_TRUST_REVOCATION_STATUS_UNKNOWN"); else - failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x", + failf(data, "schannel: CertGetCertificateChain error mask: 0x%08lx", dwTrustErrorMask); result = CURLE_PEER_FAILED_VERIFICATION; } @@ -754,8 +853,8 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, CertFreeCertificateChainEngine(cert_chain_engine); } - if(trust_store) { - CertCloseStore(trust_store, 0); + if(own_trust_store) { + CertCloseStore(own_trust_store, 0); } if(pChainContext) diff --git a/deps/curl/lib/vtls/sectransp.c b/deps/curl/lib/vtls/sectransp.c index e6a114ad5f8..022c467f014 100644 --- a/deps/curl/lib/vtls/sectransp.c +++ b/deps/curl/lib/vtls/sectransp.c @@ -24,12 +24,14 @@ ***************************************************************************/ /* - * Source file for all iOS and macOS SecureTransport-specific code for the + * Source file for all iOS and macOS Secure Transport-specific code for the * TLS/SSL layer. No code but vtls.c should ever call or use these functions. */ #include "curl_setup.h" +#ifdef USE_SECTRANSP + #include "urldata.h" /* for the Curl_easy definition */ #include "curl_base64.h" #include "strtok.h" @@ -37,17 +39,16 @@ #include "strcase.h" #include "x509asn1.h" #include "strerror.h" - -#ifdef USE_SECTRANSP +#include "cipher_suite.h" #ifdef __clang__ #pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-pointer-compare" +#pragma clang diagnostic ignored "-Wunreachable-code" #endif /* __clang__ */ #ifdef __GNUC__ +#pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Waddress" -#pragma GCC diagnostic ignored "-Wundef" #endif #include @@ -70,7 +71,7 @@ #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) #if MAC_OS_X_VERSION_MAX_ALLOWED < 1050 -#error "The Secure Transport back-end requires Leopard or later." +#error "The Secure Transport backend requires Leopard or later." #endif /* MAC_OS_X_VERSION_MAX_ALLOWED < 1050 */ #define CURL_BUILD_IOS 0 @@ -120,7 +121,7 @@ #define CURL_SUPPORT_MAC_10_9 0 #else -#error "The Secure Transport back-end requires iOS or macOS." +#error "The Secure Transport backend requires iOS or macOS." #endif /* (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) */ #if CURL_BUILD_MAC @@ -142,7 +143,8 @@ #include "memdebug.h" -/* From MacTypes.h (which we can't include because it isn't present in iOS: */ +/* From MacTypes.h (which we cannot include because it is not present in + iOS: */ #define ioErr -36 #define paramErr -50 @@ -150,636 +152,60 @@ struct st_ssl_backend_data { SSLContextRef ssl_ctx; bool ssl_direction; /* true if writing, false if reading */ size_t ssl_write_buffered_length; + BIT(sent_shutdown); }; -struct st_cipher { - const char *name; /* Cipher suite IANA name. It starts with "TLS_" prefix */ - const char *alias_name; /* Alias name is the same as OpenSSL cipher name */ - SSLCipherSuite num; /* Cipher suite code/number defined in IANA registry */ - bool weak; /* Flag to mark cipher as weak based on previous implementation - of Secure Transport back-end by CURL */ -}; - -/* Macro to initialize st_cipher data structure: stringify id to name, cipher - number/id, 'weak' suite flag - */ -#define CIPHER_DEF(num, alias, weak) \ - { #num, alias, num, weak } - -/* - Macro to initialize st_cipher data structure with name, code (IANA cipher - number/id value), and 'weak' suite flag. The first 28 cipher suite numbers - have the same IANA code for both SSL and TLS standards: numbers 0x0000 to - 0x001B. They have different names though. The first 4 letters of the cipher - suite name are the protocol name: "SSL_" or "TLS_", rest of the IANA name is - the same for both SSL and TLS cipher suite name. - The second part of the problem is that macOS/iOS SDKs don't define all TLS - codes but only 12 of them. The SDK defines all SSL codes though, i.e. SSL_NUM - constant is always defined for those 28 ciphers while TLS_NUM is defined only - for 12 of the first 28 ciphers. Those 12 TLS cipher codes match to - corresponding SSL enum value and represent the same cipher suite. Therefore - we'll use the SSL enum value for those cipher suites because it is defined - for all 28 of them. - We make internal data consistent and based on TLS names, i.e. all st_cipher - item names start with the "TLS_" prefix. - Summarizing all the above, those 28 first ciphers are presented in our table - with both TLS and SSL names. Their cipher numbers are assigned based on the - SDK enum value for the SSL cipher, which matches to IANA TLS number. - */ -#define CIPHER_DEF_SSLTLS(num_wo_prefix, alias, weak) \ - { "TLS_" #num_wo_prefix, alias, SSL_##num_wo_prefix, weak } - -/* - Cipher suites were marked as weak based on the following: - RC4 encryption - rfc7465, the document contains a list of deprecated ciphers. - Marked in the code below as weak. - RC2 encryption - many mentions, was found vulnerable to a relatively easy - attack https://link.springer.com/chapter/10.1007%2F3-540-69710-1_14 - Marked in the code below as weak. - DES and IDEA encryption - rfc5469, has a list of deprecated ciphers. - Marked in the code below as weak. - Anonymous Diffie-Hellman authentication and anonymous elliptic curve - Diffie-Hellman - vulnerable to a man-in-the-middle attack. Deprecated by - RFC 4346 aka TLS 1.1 (section A.5, page 60) - Null bulk encryption suites - not encrypted communication - Export ciphers, i.e. ciphers with restrictions to be used outside the US for - software exported to some countries, they were excluded from TLS 1.1 - version. More precisely, they were noted as ciphers which MUST NOT be - negotiated in RFC 4346 aka TLS 1.1 (section A.5, pages 60 and 61). - All of those filters were considered weak because they contain a weak - algorithm like DES, RC2 or RC4, and already considered weak by other - criteria. - 3DES - NIST deprecated it and is going to retire it by 2023 - https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA - OpenSSL https://www.openssl.org/blog/blog/2016/08/24/sweet32/ also - deprecated those ciphers. Some other libraries also consider it - vulnerable or at least not strong enough. - - CBC ciphers are vulnerable with SSL3.0 and TLS1.0: - https://www.cisco.com/c/en/us/support/docs/security/email-security-appliance - /118518-technote-esa-00.html - We don't take care of this issue because it is resolved by later TLS - versions and for us, it requires more complicated checks, we need to - check a protocol version also. Vulnerability doesn't look very critical - and we do not filter out those cipher suites. +/* Create the list of default ciphers to use by making an intersection of the + * ciphers supported by Secure Transport and the list below, using the order + * of the former. + * This list is based on TLS recommendations by Mozilla, balancing between + * security and wide compatibility: "Most ciphers that are not clearly broken + * and dangerous to use are supported" */ - -#define CIPHER_WEAK_NOT_ENCRYPTED TRUE -#define CIPHER_WEAK_RC_ENCRYPTION TRUE -#define CIPHER_WEAK_DES_ENCRYPTION TRUE -#define CIPHER_WEAK_IDEA_ENCRYPTION TRUE -#define CIPHER_WEAK_ANON_AUTH TRUE -#define CIPHER_WEAK_3DES_ENCRYPTION TRUE -#define CIPHER_STRONG_ENOUGH FALSE - -/* Please do not change the order of the first ciphers available for SSL. - Do not insert and do not delete any of them. Code below - depends on their order and continuity. - If you add a new cipher, please maintain order by number, i.e. - insert in between existing items to appropriate place based on - cipher suite IANA number -*/ -static const struct st_cipher ciphertable[] = { - /* SSL version 3.0 and initial TLS 1.0 cipher suites. - Defined since SDK 10.2.8 */ - CIPHER_DEF_SSLTLS(NULL_WITH_NULL_NULL, /* 0x0000 */ - NULL, - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF_SSLTLS(RSA_WITH_NULL_MD5, /* 0x0001 */ - "NULL-MD5", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF_SSLTLS(RSA_WITH_NULL_SHA, /* 0x0002 */ - "NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_RC4_40_MD5, /* 0x0003 */ - "EXP-RC4-MD5", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_RC4_128_MD5, /* 0x0004 */ - "RC4-MD5", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_RC4_128_SHA, /* 0x0005 */ - "RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_RC2_CBC_40_MD5, /* 0x0006 */ - "EXP-RC2-CBC-MD5", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_IDEA_CBC_SHA, /* 0x0007 */ - "IDEA-CBC-SHA", - CIPHER_WEAK_IDEA_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x0008 */ - "EXP-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_DES_CBC_SHA, /* 0x0009 */ - "DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ - "DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_DSS_EXPORT_WITH_DES40_CBC_SHA, /* 0x000B */ - "EXP-DH-DSS-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_DSS_WITH_DES_CBC_SHA, /* 0x000C */ - "DH-DSS-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_DSS_WITH_3DES_EDE_CBC_SHA, /* 0x000D */ - "DH-DSS-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x000E */ - "EXP-DH-RSA-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_RSA_WITH_DES_CBC_SHA, /* 0x000F */ - "DH-RSA-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x0010 */ - "DH-RSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, /* 0x0011 */ - "EXP-EDH-DSS-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_DSS_WITH_DES_CBC_SHA, /* 0x0012 */ - "EDH-DSS-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_DSS_WITH_3DES_EDE_CBC_SHA, /* 0x0013 */ - "DHE-DSS-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x0014 */ - "EXP-EDH-RSA-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_RSA_WITH_DES_CBC_SHA, /* 0x0015 */ - "EDH-RSA-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x0016 */ - "DHE-RSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_anon_EXPORT_WITH_RC4_40_MD5, /* 0x0017 */ - "EXP-ADH-RC4-MD5", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF_SSLTLS(DH_anon_WITH_RC4_128_MD5, /* 0x0018 */ - "ADH-RC4-MD5", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF_SSLTLS(DH_anon_EXPORT_WITH_DES40_CBC_SHA, /* 0x0019 */ - "EXP-ADH-DES-CBC-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF_SSLTLS(DH_anon_WITH_DES_CBC_SHA, /* 0x001A */ - "ADH-DES-CBC-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF_SSLTLS(DH_anon_WITH_3DES_EDE_CBC_SHA, /* 0x001B */ - "ADH-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(SSL_FORTEZZA_DMS_WITH_NULL_SHA, /* 0x001C */ - NULL, - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA, /* 0x001D */ - NULL, - CIPHER_STRONG_ENOUGH), - -#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - /* RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */ - CIPHER_DEF(TLS_PSK_WITH_NULL_SHA, /* 0x002C */ - "PSK-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA, /* 0x002D */ - "DHE-PSK-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA, /* 0x002E */ - "RSA-PSK-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), -#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ - - /* TLS addenda using AES, per RFC 3268. Defined since SDK 10.4u */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ - "AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_CBC_SHA, /* 0x0030 */ - "DH-DSS-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_CBC_SHA, /* 0x0031 */ - "DH-RSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_CBC_SHA, /* 0x0032 */ - "DHE-DSS-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_CBC_SHA, /* 0x0033 */ - "DHE-RSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_128_CBC_SHA, /* 0x0034 */ - "ADH-AES128-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ - "AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_CBC_SHA, /* 0x0036 */ - "DH-DSS-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_CBC_SHA, /* 0x0037 */ - "DH-RSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_CBC_SHA, /* 0x0038 */ - "DHE-DSS-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_CBC_SHA, /* 0x0039 */ - "DHE-RSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_256_CBC_SHA, /* 0x003A */ - "ADH-AES256-SHA", - CIPHER_WEAK_ANON_AUTH), - -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - /* TLS 1.2 addenda, RFC 5246 */ - /* Server provided RSA certificate for key exchange. */ - CIPHER_DEF(TLS_RSA_WITH_NULL_SHA256, /* 0x003B */ - "NULL-SHA256", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ - "AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ - "AES256-SHA256", - CIPHER_STRONG_ENOUGH), - /* Server-authenticated (and optionally client-authenticated) - Diffie-Hellman. */ - CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_CBC_SHA256, /* 0x003E */ - "DH-DSS-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_CBC_SHA256, /* 0x003F */ - "DH-RSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, /* 0x0040 */ - "DHE-DSS-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - - /* TLS 1.2 addenda, RFC 5246 */ - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, /* 0x0067 */ - "DHE-RSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_CBC_SHA256, /* 0x0068 */ - "DH-DSS-AES256-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_CBC_SHA256, /* 0x0069 */ - "DH-RSA-AES256-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, /* 0x006A */ - "DHE-DSS-AES256-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, /* 0x006B */ - "DHE-RSA-AES256-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_128_CBC_SHA256, /* 0x006C */ - "ADH-AES128-SHA256", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_256_CBC_SHA256, /* 0x006D */ - "ADH-AES256-SHA256", - CIPHER_WEAK_ANON_AUTH), -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - -#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - /* Addendum from RFC 4279, TLS PSK */ - CIPHER_DEF(TLS_PSK_WITH_RC4_128_SHA, /* 0x008A */ - "PSK-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x008B */ - "PSK-3DES-EDE-CBC-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_PSK_WITH_AES_128_CBC_SHA, /* 0x008C */ - "PSK-AES128-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_AES_256_CBC_SHA, /* 0x008D */ - "PSK-AES256-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_RC4_128_SHA, /* 0x008E */ - "DHE-PSK-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x008F */ - "DHE-PSK-3DES-EDE-CBC-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_CBC_SHA, /* 0x0090 */ - "DHE-PSK-AES128-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_CBC_SHA, /* 0x0091 */ - "DHE-PSK-AES256-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_RC4_128_SHA, /* 0x0092 */ - "RSA-PSK-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x0093 */ - "RSA-PSK-3DES-EDE-CBC-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_CBC_SHA, /* 0x0094 */ - "RSA-PSK-AES128-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_CBC_SHA, /* 0x0095 */ - "RSA-PSK-AES256-CBC-SHA", - CIPHER_STRONG_ENOUGH), -#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ - -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - /* Addenda from rfc 5288 AES Galois Counter Mode (GCM) Cipher Suites - for TLS. */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ - "AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ - "AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, /* 0x009E */ - "DHE-RSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, /* 0x009F */ - "DHE-RSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_GCM_SHA256, /* 0x00A0 */ - "DH-RSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_GCM_SHA384, /* 0x00A1 */ - "DH-RSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, /* 0x00A2 */ - "DHE-DSS-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, /* 0x00A3 */ - "DHE-DSS-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_GCM_SHA256, /* 0x00A4 */ - "DH-DSS-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_GCM_SHA384, /* 0x00A5 */ - "DH-DSS-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_128_GCM_SHA256, /* 0x00A6 */ - "ADH-AES128-GCM-SHA256", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_256_GCM_SHA384, /* 0x00A7 */ - "ADH-AES256-GCM-SHA384", - CIPHER_WEAK_ANON_AUTH), -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - -#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - /* RFC 5487 - PSK with SHA-256/384 and AES GCM */ - CIPHER_DEF(TLS_PSK_WITH_AES_128_GCM_SHA256, /* 0x00A8 */ - "PSK-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_AES_256_GCM_SHA384, /* 0x00A9 */ - "PSK-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_GCM_SHA256, /* 0x00AA */ - "DHE-PSK-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, /* 0x00AB */ - "DHE-PSK-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_GCM_SHA256, /* 0x00AC */ - "RSA-PSK-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_GCM_SHA384, /* 0x00AD */ - "RSA-PSK-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_AES_128_CBC_SHA256, /* 0x00AE */ - "PSK-AES128-CBC-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_AES_256_CBC_SHA384, /* 0x00AF */ - "PSK-AES256-CBC-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_NULL_SHA256, /* 0x00B0 */ - "PSK-NULL-SHA256", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_PSK_WITH_NULL_SHA384, /* 0x00B1 */ - "PSK-NULL-SHA384", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, /* 0x00B2 */ - "DHE-PSK-AES128-CBC-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_CBC_SHA384, /* 0x00B3 */ - "DHE-PSK-AES256-CBC-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA256, /* 0x00B4 */ - "DHE-PSK-NULL-SHA256", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA384, /* 0x00B5 */ - "DHE-PSK-NULL-SHA384", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, /* 0x00B6 */ - "RSA-PSK-AES128-CBC-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, /* 0x00B7 */ - "RSA-PSK-AES256-CBC-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA256, /* 0x00B8 */ - "RSA-PSK-NULL-SHA256", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA384, /* 0x00B9 */ - "RSA-PSK-NULL-SHA384", - CIPHER_WEAK_NOT_ENCRYPTED), -#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ - - /* RFC 5746 - Secure Renegotiation. This is not a real suite, - it is a response to initiate negotiation again */ - CIPHER_DEF(TLS_EMPTY_RENEGOTIATION_INFO_SCSV, /* 0x00FF */ - NULL, - CIPHER_STRONG_ENOUGH), - -#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 - /* TLS 1.3 standard cipher suites for ChaCha20+Poly1305. - Note: TLS 1.3 ciphersuites do not specify the key exchange - algorithm -- they only specify the symmetric ciphers. - Cipher alias name matches to OpenSSL cipher name, and for - TLS 1.3 ciphers */ - CIPHER_DEF(TLS_AES_128_GCM_SHA256, /* 0x1301 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_AES_256_GCM_SHA384, /* 0x1302 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_CHACHA20_POLY1305_SHA256, /* 0x1303 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_AES_128_CCM_SHA256, /* 0x1304 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_AES_128_CCM_8_SHA256, /* 0x1305 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), -#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ +static const uint16_t default_ciphers[] = { + TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ + TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ + TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ #if CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS - /* ECDSA addenda, RFC 4492 */ - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_NULL_SHA, /* 0xC001 */ - "ECDH-ECDSA-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_RC4_128_SHA, /* 0xC002 */ - "ECDH-ECDSA-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */ - "ECDH-ECDSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */ - "ECDH-ECDSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */ - "ECDH-ECDSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_NULL_SHA, /* 0xC006 */ - "ECDHE-ECDSA-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, /* 0xC007 */ - "ECDHE-ECDSA-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */ - "ECDHE-ECDSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ - "ECDHE-ECDSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ - "ECDHE-ECDSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_NULL_SHA, /* 0xC00B */ - "ECDH-RSA-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_ECDH_RSA_WITH_RC4_128_SHA, /* 0xC00C */ - "ECDH-RSA-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */ - "ECDH-RSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */ - "ECDH-RSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */ - "ECDH-RSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_NULL_SHA, /* 0xC010 */ - "ECDHE-RSA-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_RC4_128_SHA, /* 0xC011 */ - "ECDHE-RSA-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */ - "ECDHE-RSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ - "ECDHE-RSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ - "ECDHE-RSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_anon_WITH_NULL_SHA, /* 0xC015 */ - "AECDH-NULL-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_ECDH_anon_WITH_RC4_128_SHA, /* 0xC016 */ - "AECDH-RC4-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA, /* 0xC017 */ - "AECDH-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_anon_WITH_AES_128_CBC_SHA, /* 0xC018 */ - "AECDH-AES128-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_ECDH_anon_WITH_AES_256_CBC_SHA, /* 0xC019 */ - "AECDH-AES256-SHA", - CIPHER_WEAK_ANON_AUTH), + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ #endif /* CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS */ #if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - /* Addenda from rfc 5289 Elliptic Curve Cipher Suites with - HMAC SHA-256/384. */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ - "ECDHE-ECDSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ - "ECDHE-ECDSA-AES256-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */ - "ECDH-ECDSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */ - "ECDH-ECDSA-AES256-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ - "ECDHE-RSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ - "ECDHE-RSA-AES256-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */ - "ECDH-RSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */ - "ECDH-RSA-AES256-SHA384", - CIPHER_STRONG_ENOUGH), - /* Addenda from rfc 5289 Elliptic Curve Cipher Suites with - SHA-256/384 and AES Galois Counter Mode (GCM) */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ - "ECDHE-ECDSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ - "ECDHE-ECDSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */ - "ECDH-ECDSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */ - "ECDH-ECDSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ - "ECDHE-RSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ - "ECDHE-RSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */ - "ECDH-RSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */ - "ECDH-RSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), + TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ + TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, /* 0x0067 */ + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, /* 0x006B */ + TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ + TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, /* 0x009E */ + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, /* 0x009F */ + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ #endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ -#if CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 - /* ECDHE_PSK Cipher Suites for Transport Layer Security (TLS), RFC 5489 */ - CIPHER_DEF(TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, /* 0xC035 */ - "ECDHE-PSK-AES128-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA, /* 0xC036 */ - "ECDHE-PSK-AES256-CBC-SHA", - CIPHER_STRONG_ENOUGH), -#endif /* CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 */ - #if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 - /* Addenda from rfc 7905 ChaCha20-Poly1305 Cipher Suites for - Transport Layer Security (TLS). */ - CIPHER_DEF(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ - "ECDHE-RSA-CHACHA20-POLY1305", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ - "ECDHE-ECDSA-CHACHA20-POLY1305", - CIPHER_STRONG_ENOUGH), + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ + + /* TLSv1.3 is not supported by Secure Transport, but there is also other + * code referencing TLSv1.3, like: kTLSProtocol13 ? */ + TLS_AES_128_GCM_SHA256, /* 0x1301 */ + TLS_AES_256_GCM_SHA384, /* 0x1302 */ + TLS_CHACHA20_POLY1305_SHA256, /* 0x1303 */ #endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ - -#if CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 - /* ChaCha20-Poly1305 Cipher Suites for Transport Layer Security (TLS), - RFC 7905 */ - CIPHER_DEF(TLS_PSK_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCAB */ - "PSK-CHACHA20-POLY1305", - CIPHER_STRONG_ENOUGH), -#endif /* CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 */ - - /* Tags for SSL 2 cipher kinds which are not specified for SSL 3. - Defined since SDK 10.2.8 */ - CIPHER_DEF(SSL_RSA_WITH_RC2_CBC_MD5, /* 0xFF80 */ - NULL, - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(SSL_RSA_WITH_IDEA_CBC_MD5, /* 0xFF81 */ - NULL, - CIPHER_WEAK_IDEA_ENCRYPTION), - CIPHER_DEF(SSL_RSA_WITH_DES_CBC_MD5, /* 0xFF82 */ - NULL, - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF(SSL_RSA_WITH_3DES_EDE_CBC_MD5, /* 0xFF83 */ - NULL, - CIPHER_WEAK_3DES_ENCRYPTION), }; -#define NUM_OF_CIPHERS sizeof(ciphertable)/sizeof(ciphertable[0]) +#define DEFAULT_CIPHERS_LEN sizeof(default_ciphers)/sizeof(default_ciphers[0]) /* pinned public key support tests */ @@ -790,7 +216,7 @@ static const struct st_cipher ciphertable[] = { #define SECTRANSP_PINNEDPUBKEY_V1 1 #endif -/* version 2 supports MacOSX 10.7+ */ +/* version 2 supports macOS 10.7+ */ #if (!TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070) #define SECTRANSP_PINNEDPUBKEY_V2 1 #endif @@ -814,7 +240,7 @@ static const unsigned char rsa2048SpkiHeader[] = { 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00}; #ifdef SECTRANSP_PINNEDPUBKEY_V1 -/* the *new* version doesn't return DER encoded ecdsa certs like the old... */ +/* the *new* version does not return DER encoded ecdsa certs like the old... */ static const unsigned char ecDsaSecp256r1SpkiHeader[] = { 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, @@ -830,9 +256,9 @@ static const unsigned char ecDsaSecp384r1SpkiHeader[] = { #endif /* SECTRANSP_PINNEDPUBKEY_V1 */ #endif /* SECTRANSP_PINNEDPUBKEY */ -static OSStatus bio_cf_in_read(SSLConnectionRef connection, - void *buf, - size_t *dataLength) /* IN/OUT */ +static OSStatus sectransp_bio_cf_in_read(SSLConnectionRef connection, + void *buf, + size_t *dataLength) /* IN/OUT */ { struct Curl_cfilter *cf = (struct Curl_cfilter *)connection; struct ssl_connect_data *connssl = cf->ctx; @@ -852,7 +278,7 @@ static OSStatus bio_cf_in_read(SSLConnectionRef connection, case CURLE_OK: case CURLE_AGAIN: rtn = errSSLWouldBlock; - backend->ssl_direction = false; + backend->ssl_direction = FALSE; break; default: rtn = ioErr; @@ -870,9 +296,9 @@ static OSStatus bio_cf_in_read(SSLConnectionRef connection, return rtn; } -static OSStatus bio_cf_out_write(SSLConnectionRef connection, - const void *buf, - size_t *dataLength) /* IN/OUT */ +static OSStatus sectransp_bio_cf_out_write(SSLConnectionRef connection, + const void *buf, + size_t *dataLength) /* IN/OUT */ { struct Curl_cfilter *cf = (struct Curl_cfilter *)connection; struct ssl_connect_data *connssl = cf->ctx; @@ -884,13 +310,14 @@ static OSStatus bio_cf_out_write(SSLConnectionRef connection, OSStatus rtn = noErr; DEBUGASSERT(data); - nwritten = Curl_conn_cf_send(cf->next, data, buf, *dataLength, &result); + nwritten = Curl_conn_cf_send(cf->next, data, buf, *dataLength, FALSE, + &result); CURL_TRC_CF(data, cf, "bio_send(len=%zu) -> %zd, result=%d", *dataLength, nwritten, result); if(nwritten <= 0) { if(result == CURLE_AGAIN) { rtn = errSSLWouldBlock; - backend->ssl_direction = true; + backend->ssl_direction = TRUE; } else { rtn = ioErr; @@ -904,27 +331,6 @@ static OSStatus bio_cf_out_write(SSLConnectionRef connection, return rtn; } -#ifndef CURL_DISABLE_VERBOSE_STRINGS -CF_INLINE const char *TLSCipherNameForNumber(SSLCipherSuite cipher) -{ - /* The first ciphers in the ciphertable are continuous. Here we do small - optimization and instead of loop directly get SSL name by cipher number. - */ - size_t i; - if(cipher <= SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA) { - return ciphertable[cipher].name; - } - /* Iterate through the rest of the ciphers */ - for(i = SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA + 1; i < NUM_OF_CIPHERS; - ++i) { - if(ciphertable[i].num == cipher) { - return ciphertable[i].name; - } - } - return ciphertable[SSL_NULL_WITH_NULL_NULL].name; -} -#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ - #if CURL_BUILD_MAC CF_INLINE void GetDarwinVersionNumber(int *major, int *minor) { @@ -957,27 +363,27 @@ CF_INLINE void GetDarwinVersionNumber(int *major, int *minor) #endif /* CURL_BUILD_MAC */ /* Apple provides a myriad of ways of getting information about a certificate - into a string. Some aren't available under iOS or newer cats. So here's - a unified function for getting a string describing the certificate that - ought to work in all cats starting with Leopard. */ + into a string. Some are not available under iOS or newer cats. Here's a + unified function for getting a string describing the certificate that ought + to work in all cats starting with Leopard. */ CF_INLINE CFStringRef getsubject(SecCertificateRef cert) { CFStringRef server_cert_summary = CFSTR("(null)"); #if CURL_BUILD_IOS - /* iOS: There's only one way to do this. */ + /* iOS: There is only one way to do this. */ server_cert_summary = SecCertificateCopySubjectSummary(cert); #else #if CURL_BUILD_MAC_10_7 /* Lion & later: Get the long description if we can. */ - if(SecCertificateCopyLongDescription) + if(&SecCertificateCopyLongDescription) server_cert_summary = SecCertificateCopyLongDescription(NULL, cert, NULL); else #endif /* CURL_BUILD_MAC_10_7 */ #if CURL_BUILD_MAC_10_6 /* Snow Leopard: Get the certificate summary. */ - if(SecCertificateCopySubjectSummary) + if(&SecCertificateCopySubjectSummary) server_cert_summary = SecCertificateCopySubjectSummary(cert); else #endif /* CURL_BUILD_MAC_10_6 */ @@ -1013,9 +419,9 @@ static CURLcode CopyCertSubject(struct Curl_easy *data, } else { size_t cbuf_size = ((size_t)CFStringGetLength(c) * 4) + 1; - cbuf = calloc(cbuf_size, 1); + cbuf = calloc(1, cbuf_size); if(cbuf) { - if(!CFStringGetCString(c, cbuf, cbuf_size, + if(!CFStringGetCString(c, cbuf, (CFIndex)cbuf_size, kCFStringEncodingUTF8)) { failf(data, "SSL: invalid CA certificate subject"); result = CURLE_PEER_FAILED_VERIFICATION; @@ -1025,7 +431,7 @@ static CURLcode CopyCertSubject(struct Curl_easy *data, *certp = cbuf; } else { - failf(data, "SSL: couldn't allocate %zu bytes of memory", cbuf_size); + failf(data, "SSL: could not allocate %zu bytes of memory", cbuf_size); result = CURLE_OUT_OF_MEMORY; } } @@ -1037,7 +443,7 @@ static CURLcode CopyCertSubject(struct Curl_easy *data, #if CURL_SUPPORT_MAC_10_6 /* The SecKeychainSearch API was deprecated in Lion, and using it will raise - deprecation warnings, so let's not compile this unless it's necessary: */ + deprecation warnings, so let's not compile this unless it is necessary: */ static OSStatus CopyIdentityWithLabelOldSchool(char *label, SecIdentityRef *out_c_a_k) { @@ -1090,7 +496,7 @@ static OSStatus CopyIdentityWithLabel(char *label, /* SecItemCopyMatching() was introduced in iOS and Snow Leopard. kSecClassIdentity was introduced in Lion. If both exist, let's use them to find the certificate. */ - if(SecItemCopyMatching && kSecClassIdentity) { + if(&SecItemCopyMatching && kSecClassIdentity) { CFTypeRef keys[5]; CFTypeRef values[5]; CFDictionaryRef query_dict; @@ -1106,9 +512,9 @@ static OSStatus CopyIdentityWithLabel(char *label, * label matching below worked correctly */ keys[2] = kSecMatchLimit; /* identity searches need a SecPolicyRef in order to work */ - values[3] = SecPolicyCreateSSL(false, NULL); + values[3] = SecPolicyCreateSSL(FALSE, NULL); keys[3] = kSecMatchPolicy; - /* match the name of the certificate (doesn't work in macOS 10.12.1) */ + /* match the name of the certificate (does not work in macOS 10.12.1) */ values[4] = label_cf; keys[4] = kSecAttrLabel; query_dict = CFDictionaryCreate(NULL, (const void **)keys, @@ -1120,13 +526,13 @@ static OSStatus CopyIdentityWithLabel(char *label, /* Do we have a match? */ status = SecItemCopyMatching(query_dict, (CFTypeRef *) &keys_list); - /* Because kSecAttrLabel matching doesn't work with kSecClassIdentity, + /* Because kSecAttrLabel matching does not work with kSecClassIdentity, * we need to find the correct identity ourselves */ if(status == noErr) { keys_list_count = CFArrayGetCount(keys_list); *out_cert_and_key = NULL; status = 1; - for(i = 0; idata, blob->len); + (const unsigned char *)blob->data, + (CFIndex)blob->len); status = (pkcs_data != NULL) ? errSecSuccess : errSecAllocate; resource_imported = (pkcs_data != NULL); } @@ -1202,7 +609,7 @@ static OSStatus CopyIdentityFromPKCS12File(const char *cPath, pkcs_url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)cPath, - strlen(cPath), false); + (CFIndex)strlen(cPath), FALSE); resource_imported = CFURLCreateDataAndPropertiesFromResource(NULL, pkcs_url, &pkcs_data, @@ -1231,7 +638,7 @@ static OSStatus CopyIdentityFromPKCS12File(const char *cPath, /* On macOS SecPKCS12Import will always add the client certificate to * the Keychain. * - * As this doesn't match iOS, and apps may not want to see their client + * As this does not match iOS, and apps may not want to see their client * certificate saved in the user's keychain, we use SecItemImport * with a NULL keychain to avoid importing it. * @@ -1304,336 +711,320 @@ CF_INLINE bool is_file(const char *filename) struct_stat st; if(!filename) - return false; + return FALSE; if(stat(filename, &st) == 0) return S_ISREG(st.st_mode); - return false; + return FALSE; } -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS -static CURLcode sectransp_version_from_curl(SSLProtocol *darwinver, - long ssl_version) +static CURLcode +sectransp_set_ssl_version_min_max(struct Curl_easy *data, + struct st_ssl_backend_data *backend, + struct ssl_primary_config *conn_config) { - switch(ssl_version) { +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS + OSStatus err; + SSLProtocol ver_min; + SSLProtocol ver_max; + +#if CURL_SUPPORT_MAC_10_7 + if(!&SSLSetProtocolVersionMax) + goto legacy; +#endif + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: case CURL_SSLVERSION_TLSv1_0: - *darwinver = kTLSProtocol1; - return CURLE_OK; + ver_min = kTLSProtocol1; + break; case CURL_SSLVERSION_TLSv1_1: - *darwinver = kTLSProtocol11; - return CURLE_OK; + ver_min = kTLSProtocol11; + break; case CURL_SSLVERSION_TLSv1_2: - *darwinver = kTLSProtocol12; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_3: - /* TLS 1.3 support first appeared in iOS 11 and macOS 10.13 */ -#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 - if(__builtin_available(macOS 10.13, iOS 11.0, *)) { - *darwinver = kTLSProtocol13; - return CURLE_OK; - } -#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && - HAVE_BUILTIN_AVAILABLE == 1 */ + ver_min = kTLSProtocol12; break; + case CURL_SSLVERSION_TLSv1_3: + default: + failf(data, "SSL: unsupported minimum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; } - return CURLE_SSL_CONNECT_ERROR; -} -#endif - -static CURLcode set_ssl_version_min_max(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct st_ssl_backend_data *backend = - (struct st_ssl_backend_data *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - long ssl_version = conn_config->version; - long ssl_version_max = conn_config->version_max; - long max_supported_version_by_os; - DEBUGASSERT(backend); + switch(conn_config->version_max) { + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_TLSv1_3: + case CURL_SSLVERSION_MAX_TLSv1_2: + ver_max = kTLSProtocol12; + break; + case CURL_SSLVERSION_MAX_TLSv1_1: + ver_max = kTLSProtocol11; + break; + case CURL_SSLVERSION_MAX_TLSv1_0: + ver_max = kTLSProtocol1; + break; + default: + failf(data, "SSL: unsupported maximum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } - /* macOS 10.5-10.7 supported TLS 1.0 only. - macOS 10.8 and later, and iOS 5 and later, added TLS 1.1 and 1.2. - macOS 10.13 and later, and iOS 11 and later, added TLS 1.3. */ -#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 - if(__builtin_available(macOS 10.13, iOS 11.0, *)) { - max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_3; + err = SSLSetProtocolVersionMin(backend->ssl_ctx, ver_min); + if(err != noErr) { + failf(data, "SSL: failed to set minimum TLS version"); + return CURLE_SSL_CONNECT_ERROR; } - else { - max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_2; + err = SSLSetProtocolVersionMax(backend->ssl_ctx, ver_max); + if(err != noErr) { + failf(data, "SSL: failed to set maximum TLS version"); + return CURLE_SSL_CONNECT_ERROR; } -#else - max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_2; -#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && - HAVE_BUILTIN_AVAILABLE == 1 */ - switch(ssl_version) { + return CURLE_OK; +#endif +#if CURL_SUPPORT_MAC_10_7 + goto legacy; +legacy: + switch(conn_config->version) { case CURL_SSLVERSION_DEFAULT: case CURL_SSLVERSION_TLSv1: - ssl_version = CURL_SSLVERSION_TLSv1_0; + case CURL_SSLVERSION_TLSv1_0: break; + default: + failf(data, "SSL: unsupported minimum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; } - switch(ssl_version_max) { - case CURL_SSLVERSION_MAX_NONE: - case CURL_SSLVERSION_MAX_DEFAULT: - ssl_version_max = max_supported_version_by_os; - break; - } + /* only TLS 1.0 is supported, disable SSL 3.0 and SSL 2.0 */ + SSLSetProtocolVersionEnabled(backend->ssl_ctx, kSSLProtocolAll, FALSE); + SSLSetProtocolVersionEnabled(backend->ssl_ctx, kTLSProtocol1, TRUE); -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - if(SSLSetProtocolVersionMax) { - SSLProtocol darwin_ver_min = kTLSProtocol1; - SSLProtocol darwin_ver_max = kTLSProtocol1; - CURLcode result = sectransp_version_from_curl(&darwin_ver_min, - ssl_version); - if(result) { - failf(data, "unsupported min version passed via CURLOPT_SSLVERSION"); - return result; - } - result = sectransp_version_from_curl(&darwin_ver_max, - ssl_version_max >> 16); - if(result) { - failf(data, "unsupported max version passed via CURLOPT_SSLVERSION"); - return result; - } - - (void)SSLSetProtocolVersionMin(backend->ssl_ctx, darwin_ver_min); - (void)SSLSetProtocolVersionMax(backend->ssl_ctx, darwin_ver_max); - return result; - } - else { -#if CURL_SUPPORT_MAC_10_8 - long i = ssl_version; - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kSSLProtocolAll, - false); - for(; i <= (ssl_version_max >> 16); i++) { - switch(i) { - case CURL_SSLVERSION_TLSv1_0: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol1, - true); - break; - case CURL_SSLVERSION_TLSv1_1: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol11, - true); - break; - case CURL_SSLVERSION_TLSv1_2: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol12, - true); - break; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "Your version of the OS does not support TLSv1.3"); - return CURLE_SSL_CONNECT_ERROR; - } - } - return CURLE_OK; -#endif /* CURL_SUPPORT_MAC_10_8 */ - } -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - failf(data, "Secure Transport: cannot set SSL protocol"); - return CURLE_SSL_CONNECT_ERROR; + return CURLE_OK; +#endif } -static bool is_cipher_suite_strong(SSLCipherSuite suite_num) +static int sectransp_cipher_suite_get_str(uint16_t id, char *buf, + size_t buf_size, bool prefer_rfc) { - size_t i; - for(i = 0; i < NUM_OF_CIPHERS; ++i) { - if(ciphertable[i].num == suite_num) { - return !ciphertable[i].weak; - } - } - /* If the cipher is not in our list, assume it is a new one - and therefore strong. Previous implementation was the same, - if cipher suite is not in the list, it was considered strong enough */ - return true; + /* are these fortezza suites even supported ? */ + if(id == SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA) + msnprintf(buf, buf_size, "%s", "SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA"); + else if(id == SSL_FORTEZZA_DMS_WITH_NULL_SHA) + msnprintf(buf, buf_size, "%s", "SSL_FORTEZZA_DMS_WITH_NULL_SHA"); + /* can TLS_EMPTY_RENEGOTIATION_INFO_SCSV even be set ? */ + else if(id == TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + msnprintf(buf, buf_size, "%s", "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"); + /* do we still need to support these SSL2-only ciphers ? */ + else if(id == SSL_RSA_WITH_RC2_CBC_MD5) + msnprintf(buf, buf_size, "%s", "SSL_RSA_WITH_RC2_CBC_MD5"); + else if(id == SSL_RSA_WITH_IDEA_CBC_MD5) + msnprintf(buf, buf_size, "%s", "SSL_RSA_WITH_IDEA_CBC_MD5"); + else if(id == SSL_RSA_WITH_DES_CBC_MD5) + msnprintf(buf, buf_size, "%s", "SSL_RSA_WITH_DES_CBC_MD5"); + else if(id == SSL_RSA_WITH_3DES_EDE_CBC_MD5) + msnprintf(buf, buf_size, "%s", "SSL_RSA_WITH_3DES_EDE_CBC_MD5"); + else + return Curl_cipher_suite_get_str(id, buf, buf_size, prefer_rfc); + return 0; } -static bool is_separator(char c) +static uint16_t sectransp_cipher_suite_walk_str(const char **str, + const char **end) { - /* Return whether character is a cipher list separator. */ - switch(c) { - case ' ': - case '\t': - case ':': - case ',': - case ';': - return true; - } - return false; + uint16_t id = Curl_cipher_suite_walk_str(str, end); + size_t len = *end - *str; + + if(!id) { + /* are these fortezza suites even supported ? */ + if(strncasecompare("SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA", *str, len)) + id = SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA; + else if(strncasecompare("SSL_FORTEZZA_DMS_WITH_NULL_SHA", *str, len)) + id = SSL_FORTEZZA_DMS_WITH_NULL_SHA; + /* can TLS_EMPTY_RENEGOTIATION_INFO_SCSV even be set ? */ + else if(strncasecompare("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", *str, len)) + id = TLS_EMPTY_RENEGOTIATION_INFO_SCSV; + /* do we still need to support these SSL2-only ciphers ? */ + else if(strncasecompare("SSL_RSA_WITH_RC2_CBC_MD5", *str, len)) + id = SSL_RSA_WITH_RC2_CBC_MD5; + else if(strncasecompare("SSL_RSA_WITH_IDEA_CBC_MD5", *str, len)) + id = SSL_RSA_WITH_IDEA_CBC_MD5; + else if(strncasecompare("SSL_RSA_WITH_DES_CBC_MD5", *str, len)) + id = SSL_RSA_WITH_DES_CBC_MD5; + else if(strncasecompare("SSL_RSA_WITH_3DES_EDE_CBC_MD5", *str, len)) + id = SSL_RSA_WITH_3DES_EDE_CBC_MD5; + } + return id; } -static CURLcode sectransp_set_default_ciphers(struct Curl_easy *data, - SSLContextRef ssl_ctx) +/* allocated memory must be freed */ +static SSLCipherSuite * sectransp_get_supported_ciphers(SSLContextRef ssl_ctx, + size_t *len) { - size_t all_ciphers_count = 0UL, allowed_ciphers_count = 0UL, i; - SSLCipherSuite *all_ciphers = NULL, *allowed_ciphers = NULL; + SSLCipherSuite *ciphers = NULL; OSStatus err = noErr; + *len = 0; -#if CURL_BUILD_MAC - int darwinver_maj = 0, darwinver_min = 0; + err = SSLGetNumberSupportedCiphers(ssl_ctx, len); + if(err != noErr) + goto failed; - GetDarwinVersionNumber(&darwinver_maj, &darwinver_min); -#endif /* CURL_BUILD_MAC */ + ciphers = malloc(*len * sizeof(SSLCipherSuite)); + if(!ciphers) + goto failed; + + err = SSLGetSupportedCiphers(ssl_ctx, ciphers, len); + if(err != noErr) + goto failed; - /* Disable cipher suites that ST supports but are not safe. These ciphers - are unlikely to be used in any case since ST gives other ciphers a much - higher priority, but it's probably better that we not connect at all than - to give the user a false sense of security if the server only supports - insecure ciphers. (Note: We don't care about SSLv2-only ciphers.) */ - err = SSLGetNumberSupportedCiphers(ssl_ctx, &all_ciphers_count); - if(err != noErr) { - failf(data, "SSL: SSLGetNumberSupportedCiphers() failed: OSStatus %d", - err); - return CURLE_SSL_CIPHER; - } - all_ciphers = malloc(all_ciphers_count*sizeof(SSLCipherSuite)); - if(!all_ciphers) { - failf(data, "SSL: Failed to allocate memory for all ciphers"); - return CURLE_OUT_OF_MEMORY; - } - allowed_ciphers = malloc(all_ciphers_count*sizeof(SSLCipherSuite)); - if(!allowed_ciphers) { - Curl_safefree(all_ciphers); - failf(data, "SSL: Failed to allocate memory for allowed ciphers"); - return CURLE_OUT_OF_MEMORY; - } - err = SSLGetSupportedCiphers(ssl_ctx, all_ciphers, - &all_ciphers_count); - if(err != noErr) { - Curl_safefree(all_ciphers); - Curl_safefree(allowed_ciphers); - return CURLE_SSL_CIPHER; - } - for(i = 0UL ; i < all_ciphers_count ; i++) { #if CURL_BUILD_MAC - /* There's a known bug in early versions of Mountain Lion where ST's ECC - ciphers (cipher suite 0xC001 through 0xC032) simply do not work. - Work around the problem here by disabling those ciphers if we are - running in an affected version of OS X. */ - if(darwinver_maj == 12 && darwinver_min <= 3 && - all_ciphers[i] >= 0xC001 && all_ciphers[i] <= 0xC032) { - continue; + { + int maj = 0, min = 0; + GetDarwinVersionNumber(&maj, &min); + /* There is a known bug in early versions of Mountain Lion where ST's ECC + ciphers (cipher suite 0xC001 through 0xC032) simply do not work. + Work around the problem here by disabling those ciphers if we are + running in an affected version of macOS. */ + if(maj == 12 && min <= 3) { + size_t i = 0, j = 0; + for(; i < *len; i++) { + if(ciphers[i] >= 0xC001 && ciphers[i] <= 0xC032) + continue; + ciphers[j++] = ciphers[i]; + } + *len = j; } -#endif /* CURL_BUILD_MAC */ - if(is_cipher_suite_strong(all_ciphers[i])) { - allowed_ciphers[allowed_ciphers_count++] = all_ciphers[i]; + } +#endif + + return ciphers; +failed: + *len = 0; + Curl_safefree(ciphers); + return NULL; +} + +static CURLcode sectransp_set_default_ciphers(struct Curl_easy *data, + SSLContextRef ssl_ctx) +{ + CURLcode ret = CURLE_SSL_CIPHER; + size_t count = 0, i, j; + OSStatus err; + size_t supported_len; + SSLCipherSuite *ciphers = NULL; + + ciphers = sectransp_get_supported_ciphers(ssl_ctx, &supported_len); + if(!ciphers) { + failf(data, "SSL: Failed to get supported ciphers"); + goto failed; + } + + /* Intersect the ciphers supported by Secure Transport with the default + * ciphers, using the order of the former. */ + for(i = 0; i < supported_len; i++) { + for(j = 0; j < DEFAULT_CIPHERS_LEN; j++) { + if(default_ciphers[j] == ciphers[i]) { + ciphers[count++] = ciphers[i]; + break; + } } } - err = SSLSetEnabledCiphers(ssl_ctx, allowed_ciphers, - allowed_ciphers_count); - Curl_safefree(all_ciphers); - Curl_safefree(allowed_ciphers); + + if(count == 0) { + failf(data, "SSL: no supported default ciphers"); + goto failed; + } + + err = SSLSetEnabledCiphers(ssl_ctx, ciphers, count); if(err != noErr) { failf(data, "SSL: SSLSetEnabledCiphers() failed: OSStatus %d", err); - return CURLE_SSL_CIPHER; + goto failed; } - return CURLE_OK; + + ret = CURLE_OK; +failed: + Curl_safefree(ciphers); + return ret; } static CURLcode sectransp_set_selected_ciphers(struct Curl_easy *data, SSLContextRef ssl_ctx, const char *ciphers) { - size_t ciphers_count = 0; - const char *cipher_start = ciphers; - OSStatus err = noErr; - SSLCipherSuite selected_ciphers[NUM_OF_CIPHERS]; + CURLcode ret = CURLE_SSL_CIPHER; + size_t count = 0, i; + const char *ptr, *end; + OSStatus err; + size_t supported_len; + SSLCipherSuite *supported = NULL; + SSLCipherSuite *selected = NULL; - if(!ciphers) - return CURLE_OK; + supported = sectransp_get_supported_ciphers(ssl_ctx, &supported_len); + if(!supported) { + failf(data, "SSL: Failed to get supported ciphers"); + goto failed; + } - while(is_separator(*ciphers)) /* Skip initial separators. */ - ciphers++; - if(!*ciphers) - return CURLE_OK; + selected = malloc(supported_len * sizeof(SSLCipherSuite)); + if(!selected) { + failf(data, "SSL: Failed to allocate memory"); + goto failed; + } - cipher_start = ciphers; - while(*cipher_start && ciphers_count < NUM_OF_CIPHERS) { - bool cipher_found = FALSE; - size_t cipher_len = 0; - const char *cipher_end = NULL; - bool tls_name = FALSE; - size_t i; - - /* Skip separators */ - while(is_separator(*cipher_start)) - cipher_start++; - if(*cipher_start == '\0') { - break; - } - /* Find last position of a cipher in the ciphers string */ - cipher_end = cipher_start; - while(*cipher_end != '\0' && !is_separator(*cipher_end)) { - ++cipher_end; - } + for(ptr = ciphers; ptr[0] != '\0' && count < supported_len; ptr = end) { + uint16_t id = sectransp_cipher_suite_walk_str(&ptr, &end); - /* IANA cipher names start with the TLS_ or SSL_ prefix. - If the 4th symbol of the cipher is '_' we look for a cipher in the - table by its (TLS) name. - Otherwise, we try to match cipher by an alias. */ - if(cipher_start[3] == '_') { - tls_name = TRUE; - } - /* Iterate through the cipher table and look for the cipher, starting - the cipher number 0x01 because the 0x00 is not the real cipher */ - cipher_len = cipher_end - cipher_start; - for(i = 1; i < NUM_OF_CIPHERS; ++i) { - const char *table_cipher_name = NULL; - if(tls_name) { - table_cipher_name = ciphertable[i].name; - } - else if(ciphertable[i].alias_name) { - table_cipher_name = ciphertable[i].alias_name; - } - else { - continue; - } - /* Compare a part of the string between separators with a cipher name - in the table and make sure we matched the whole cipher name */ - if(strncmp(cipher_start, table_cipher_name, cipher_len) == 0 - && table_cipher_name[cipher_len] == '\0') { - selected_ciphers[ciphers_count] = ciphertable[i].num; - ++ciphers_count; - cipher_found = TRUE; - break; - } + /* Check if cipher is supported */ + if(id) { + for(i = 0; i < supported_len && supported[i] != id; i++); + if(i == supported_len) + id = 0; } - if(!cipher_found) { - /* It would be more human-readable if we print the wrong cipher name - but we don't want to allocate any additional memory and copy the name - into it, then add it into logs. - Also, we do not modify an original cipher list string. We just point - to positions where cipher starts and ends in the cipher list string. - The message is a bit cryptic and longer than necessary but can be - understood by humans. */ - failf(data, "SSL: cipher string \"%s\" contains unsupported cipher name" - " starting position %zd and ending position %zd", - ciphers, - cipher_start - ciphers, - cipher_end - ciphers); - return CURLE_SSL_CIPHER; - } - if(*cipher_end) { - cipher_start = cipher_end + 1; + if(!id) { + if(ptr[0] != '\0') + infof(data, "SSL: unknown cipher in list: \"%.*s\"", (int) (end - ptr), + ptr); + continue; } - else { - break; + + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != id; i++); + if(i < count) { + infof(data, "SSL: duplicate cipher in list: \"%.*s\"", (int) (end - ptr), + ptr); + continue; } + + selected[count++] = id; + } + + if(count == 0) { + failf(data, "SSL: no supported cipher in list"); + goto failed; } - /* All cipher suites in the list are found. Report to logs as-is */ - infof(data, "SSL: Setting cipher suites list \"%s\"", ciphers); - err = SSLSetEnabledCiphers(ssl_ctx, selected_ciphers, ciphers_count); + err = SSLSetEnabledCiphers(ssl_ctx, selected, count); if(err != noErr) { failf(data, "SSL: SSLSetEnabledCiphers() failed: OSStatus %d", err); - return CURLE_SSL_CIPHER; + goto failed; } - return CURLE_OK; + + ret = CURLE_OK; +failed: + Curl_safefree(supported); + Curl_safefree(selected); + return ret; +} + +static void sectransp_session_free(void *sessionid, size_t idsize) +{ + /* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a + cached session ID inside the Security framework. There is a private + function that does this, but I do not want to have to explain to you why I + got your application rejected from the App Store due to the use of a + private API, so the best we can do is free up our own char array that we + created way back in sectransp_connect_step1... */ + (void)idsize; + Curl_safefree(sessionid); } static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, @@ -1651,13 +1042,9 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, const bool verifypeer = conn_config->verifypeer; char * const ssl_cert = ssl_config->primary.clientcert; const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif /* ENABLE_IPV6 */ char *ciphers; OSStatus err = noErr; + CURLcode result; #if CURL_BUILD_MAC int darwinver_maj = 0, darwinver_min = 0; @@ -1668,23 +1055,23 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, #endif /* CURL_BUILD_MAC */ #if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - if(SSLCreateContext) { /* use the newer API if available */ + if(&SSLCreateContext) { /* use the newer API if available */ if(backend->ssl_ctx) CFRelease(backend->ssl_ctx); backend->ssl_ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType); if(!backend->ssl_ctx) { - failf(data, "SSL: couldn't create a context"); + failf(data, "SSL: could not create a context"); return CURLE_OUT_OF_MEMORY; } } else { - /* The old ST API does not exist under iOS, so don't compile it: */ + /* The old ST API does not exist under iOS, so do not compile it: */ #if CURL_SUPPORT_MAC_10_8 if(backend->ssl_ctx) (void)SSLDisposeContext(backend->ssl_ctx); - err = SSLNewContext(false, &(backend->ssl_ctx)); + err = SSLNewContext(FALSE, &(backend->ssl_ctx)); if(err != noErr) { - failf(data, "SSL: couldn't create a context: OSStatus %d", err); + failf(data, "SSL: could not create a context: OSStatus %d", err); return CURLE_OUT_OF_MEMORY; } #endif /* CURL_SUPPORT_MAC_10_8 */ @@ -1692,125 +1079,20 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, #else if(backend->ssl_ctx) (void)SSLDisposeContext(backend->ssl_ctx); - err = SSLNewContext(false, &(backend->ssl_ctx)); + err = SSLNewContext(FALSE, &(backend->ssl_ctx)); if(err != noErr) { - failf(data, "SSL: couldn't create a context: OSStatus %d", err); + failf(data, "SSL: could not create a context: OSStatus %d", err); return CURLE_OUT_OF_MEMORY; } #endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ backend->ssl_write_buffered_length = 0UL; /* reset buffered write length */ - /* check to see if we've been told to use an explicit SSL/TLS version */ -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - if(SSLSetProtocolVersionMax) { - switch(conn_config->version) { - case CURL_SSLVERSION_TLSv1: - (void)SSLSetProtocolVersionMin(backend->ssl_ctx, kTLSProtocol1); -#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 - if(__builtin_available(macOS 10.13, iOS 11.0, *)) { - (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol13); - } - else { - (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol12); - } -#else - (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol12); -#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && - HAVE_BUILTIN_AVAILABLE == 1 */ - break; - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - { - CURLcode result = set_ssl_version_min_max(cf, data); - if(result != CURLE_OK) - return result; - break; - } - case CURL_SSLVERSION_SSLv3: - case CURL_SSLVERSION_SSLv2: - failf(data, "SSL versions not supported"); - return CURLE_NOT_BUILT_IN; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - } - else { -#if CURL_SUPPORT_MAC_10_8 - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kSSLProtocolAll, - false); - switch(conn_config->version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol1, - true); - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol11, - true); - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol12, - true); - break; - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - { - CURLcode result = set_ssl_version_min_max(cf, data); - if(result != CURLE_OK) - return result; - break; - } - case CURL_SSLVERSION_SSLv3: - case CURL_SSLVERSION_SSLv2: - failf(data, "SSL versions not supported"); - return CURLE_NOT_BUILT_IN; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } -#endif /* CURL_SUPPORT_MAC_10_8 */ - } -#else - if(conn_config->version_max != CURL_SSLVERSION_MAX_NONE) { - failf(data, "Your version of the OS does not support to set maximum" - " SSL/TLS version"); - return CURLE_SSL_CONNECT_ERROR; - } - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, kSSLProtocolAll, false); - switch(conn_config->version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - case CURL_SSLVERSION_TLSv1_0: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol1, - true); - break; - case CURL_SSLVERSION_TLSv1_1: - failf(data, "Your version of the OS does not support TLSv1.1"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_TLSv1_2: - failf(data, "Your version of the OS does not support TLSv1.2"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "Your version of the OS does not support TLSv1.3"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_SSLv2: - case CURL_SSLVERSION_SSLv3: - failf(data, "SSL versions not supported"); - return CURLE_NOT_BUILT_IN; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ + result = sectransp_set_ssl_version_min_max(data, backend, conn_config); + if(result != CURLE_OK) + return result; -#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 +#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && \ + defined(HAVE_BUILTIN_AVAILABLE) if(connssl->alpn) { if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) { struct alpn_proto_buf proto; @@ -1879,7 +1161,7 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, err = SecIdentityCopyCertificate(cert_and_key, &cert); if(err == noErr) { char *certp; - CURLcode result = CopyCertSubject(data, cert, &certp); + result = CopyCertSubject(data, cert, &certp); if(!result) { infof(data, "Client certificate: %s", certp); free(certp); @@ -1922,11 +1204,11 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, cert_showfilename_error); break; case errSecItemNotFound: - failf(data, "SSL: Can't find the certificate \"%s\" and its private " + failf(data, "SSL: cannot find the certificate \"%s\" and its private " "key in the Keychain.", cert_showfilename_error); break; default: - failf(data, "SSL: Can't load the certificate \"%s\" and its private " + failf(data, "SSL: cannot load the certificate \"%s\" and its private " "key: OSStatus %d", cert_showfilename_error, err); break; } @@ -1941,12 +1223,11 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, #if CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS /* Snow Leopard introduced the SSLSetSessionOption() function, but due to a library bug with the way the kSSLSessionOptionBreakOnServerAuth flag - works, it doesn't work as expected under Snow Leopard, Lion or + works, it does not work as expected under Snow Leopard, Lion or Mountain Lion. So we need to call SSLSetEnableCertVerify() on those older cats in order to disable certificate validation if the user turned that off. - (SecureTransport will always validate the certificate chain by - default.) + (Secure Transport always validates the certificate chain by default.) Note: Darwin 11.x.x is Lion (10.7) Darwin 12.x.x is Mountain Lion (10.8) @@ -1955,9 +1236,9 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, Darwin 15.x.x is El Capitan (10.11) */ #if CURL_BUILD_MAC - if(SSLSetSessionOption && darwinver_maj >= 13) { + if(&SSLSetSessionOption && darwinver_maj >= 13) { #else - if(SSLSetSessionOption) { + if(&SSLSetSessionOption) { #endif /* CURL_BUILD_MAC */ bool break_on_auth = !conn_config->verifypeer || ssl_cafile || ssl_cablob; @@ -1972,7 +1253,7 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, else { #if CURL_SUPPORT_MAC_10_8 err = SSLSetEnableCertVerify(backend->ssl_ctx, - conn_config->verifypeer?true:false); + conn_config->verifypeer ? true : FALSE); if(err != noErr) { failf(data, "SSL: SSLSetEnableCertVerify() failed: OSStatus %d", err); return CURLE_SSL_CONNECT_ERROR; @@ -1981,7 +1262,7 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, } #else err = SSLSetEnableCertVerify(backend->ssl_ctx, - conn_config->verifypeer?true:false); + conn_config->verifypeer ? true : FALSE); if(err != noErr) { failf(data, "SSL: SSLSetEnableCertVerify() failed: OSStatus %d", err); return CURLE_SSL_CONNECT_ERROR; @@ -1993,7 +1274,7 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, bool is_cert_file = (!is_cert_data) && is_file(ssl_cafile); if(!(is_cert_file || is_cert_data)) { - failf(data, "SSL: can't load CA certificate file %s", + failf(data, "SSL: cannot load CA certificate file %s", ssl_cafile ? ssl_cafile : "(blob memory)"); return CURLE_SSL_CACERT_BADFILE; } @@ -2003,13 +1284,9 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, * Both hostname check and SNI require SSLSetPeerDomainName(). * Also: the verifyhost setting influences SNI usage */ if(conn_config->verifyhost) { - size_t snilen; - char *snihost = Curl_ssl_snihost(data, connssl->hostname, &snilen); - if(!snihost) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - err = SSLSetPeerDomainName(backend->ssl_ctx, snihost, snilen); + char *server = connssl->peer.sni ? + connssl->peer.sni : connssl->peer.hostname; + err = SSLSetPeerDomainName(backend->ssl_ctx, server, strlen(server)); if(err != noErr) { failf(data, "SSL: SSLSetPeerDomainName() failed: OSStatus %d", @@ -2017,11 +1294,7 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } - if((Curl_inet_pton(AF_INET, connssl->hostname, &addr)) - #ifdef ENABLE_IPV6 - || (Curl_inet_pton(AF_INET6, connssl->hostname, &addr)) - #endif - ) { + if(connssl->peer.type != CURL_SSL_PEER_DNS) { infof(data, "WARNING: using IP address, SNI is being disabled by " "the OS."); } @@ -2032,21 +1305,21 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, ciphers = conn_config->cipher_list; if(ciphers) { - err = sectransp_set_selected_ciphers(data, backend->ssl_ctx, ciphers); + result = sectransp_set_selected_ciphers(data, backend->ssl_ctx, ciphers); } else { - err = sectransp_set_default_ciphers(data, backend->ssl_ctx); + result = sectransp_set_default_ciphers(data, backend->ssl_ctx); } - if(err != noErr) { + if(result != CURLE_OK) { failf(data, "SSL: Unable to set ciphers for SSL/TLS handshake. " - "Error code: %d", err); + "Error code: %d", (int)result); return CURLE_SSL_CIPHER; } #if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 /* We want to enable 1/n-1 when using a CBC cipher unless the user - specifically doesn't want us doing that: */ - if(SSLSetSessionOption) { + specifically does not want us doing that: */ + if(&SSLSetSessionOption) { SSLSetSessionOption(backend->ssl_ctx, kSSLSessionOptionSendOneByteRecord, !ssl_config->enable_beast); SSLSetSessionOption(backend->ssl_ctx, kSSLSessionOptionFalseStart, @@ -2054,14 +1327,15 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, } #endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ - /* Check if there's a cached ID we can/should use here! */ - if(ssl_config->primary.sessionid) { + /* Check if there is a cached ID we can/should use here! */ + if(ssl_config->primary.cache_session) { char *ssl_sessionid; size_t ssl_sessionid_len; Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, (void **)&ssl_sessionid, - &ssl_sessionid_len)) { + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, + (void **)&ssl_sessionid, &ssl_sessionid_len, + NULL)) { /* we got a session id, use it! */ err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len); Curl_ssl_sessionid_unlock(data); @@ -2072,15 +1346,14 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, /* Informational message */ infof(data, "SSL reusing session ID"); } - /* If there isn't one, then let's make one up! This has to be done prior + /* If there is not one, then let's make one up! This has to be done prior to starting the handshake. */ else { - CURLcode result; ssl_sessionid = aprintf("%s:%d:%d:%s:%d", ssl_cafile ? ssl_cafile : "(blob memory)", - verifypeer, conn_config->verifyhost, connssl->hostname, - connssl->port); + verifypeer, conn_config->verifyhost, connssl->peer.hostname, + connssl->peer.port); ssl_sessionid_len = strlen(ssl_sessionid); err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len); @@ -2090,17 +1363,18 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } - result = Curl_ssl_addsessionid(cf, data, ssl_sessionid, - ssl_sessionid_len, NULL); + result = Curl_ssl_set_sessionid(cf, data, &connssl->peer, NULL, + ssl_sessionid, ssl_sessionid_len, + sectransp_session_free); Curl_ssl_sessionid_unlock(data); - if(result) { - failf(data, "failed to store ssl session"); + if(result) return result; - } } } - err = SSLSetIOFuncs(backend->ssl_ctx, bio_cf_in_read, bio_cf_out_write); + err = SSLSetIOFuncs(backend->ssl_ctx, + sectransp_bio_cf_in_read, + sectransp_bio_cf_out_write); if(err != noErr) { failf(data, "SSL: SSLSetIOFuncs() failed: OSStatus %d", err); return CURLE_SSL_CONNECT_ERROR; @@ -2121,7 +1395,7 @@ static long pem_to_der(const char *in, unsigned char **out, size_t *outlen) char *sep_start, *sep_end, *cert_start, *cert_end; size_t i, j, err; size_t len; - unsigned char *b64; + char *b64; /* Jump through the separators at the beginning of the certificate. */ sep_start = strstr(in, "-----"); @@ -2202,16 +1476,16 @@ static int read_cert(const char *file, unsigned char **out, size_t *outlen) return 0; } -static int append_cert_to_array(struct Curl_easy *data, - const unsigned char *buf, size_t buflen, - CFMutableArrayRef array) +static CURLcode append_cert_to_array(struct Curl_easy *data, + const unsigned char *buf, size_t buflen, + CFMutableArrayRef array) { char *certp; CURLcode result; SecCertificateRef cacert; CFDataRef certdata; - certdata = CFDataCreate(kCFAllocatorDefault, buf, buflen); + certdata = CFDataCreate(kCFAllocatorDefault, buf, (CFIndex)buflen); if(!certdata) { failf(data, "SSL: failed to allocate array for CA certificate"); return CURLE_OUT_OF_MEMORY; @@ -2248,7 +1522,8 @@ static CURLcode verify_cert_buf(struct Curl_cfilter *cf, const unsigned char *certbuf, size_t buflen, SSLContextRef ctx) { - int n = 0, rc; + int n = 0; + CURLcode rc; long res; unsigned char *der; size_t derlen, offset = 0; @@ -2330,7 +1605,7 @@ static CURLcode verify_cert_buf(struct Curl_cfilter *cf, failf(data, "SecTrustSetAnchorCertificates() returned error %d", ret); goto out; } - ret = SecTrustSetAnchorCertificatesOnly(trust, true); + ret = SecTrustSetAnchorCertificatesOnly(trust, TRUE); if(ret != noErr) { failf(data, "SecTrustSetAnchorCertificatesOnly() returned error %d", ret); goto out; @@ -2378,19 +1653,15 @@ static CURLcode verify_cert(struct Curl_cfilter *cf, const struct curl_blob *ca_info_blob, SSLContextRef ctx) { - int result; + CURLcode result; unsigned char *certbuf; size_t buflen; + bool free_certbuf = FALSE; if(ca_info_blob) { CURL_TRC_CF(data, cf, "verify_peer, CA from config blob"); - certbuf = (unsigned char *)malloc(ca_info_blob->len + 1); - if(!certbuf) { - return CURLE_OUT_OF_MEMORY; - } + certbuf = ca_info_blob->data; buflen = ca_info_blob->len; - memcpy(certbuf, ca_info_blob->data, ca_info_blob->len); - certbuf[ca_info_blob->len]='\0'; } else if(cafile) { CURL_TRC_CF(data, cf, "verify_peer, CA from file '%s'", cafile); @@ -2398,12 +1669,14 @@ static CURLcode verify_cert(struct Curl_cfilter *cf, failf(data, "SSL: failed to read or invalid CA certificate"); return CURLE_SSL_CACERT_BADFILE; } + free_certbuf = TRUE; } else return CURLE_SSL_CACERT_BADFILE; result = verify_cert_buf(cf, data, certbuf, buflen, ctx); - free(certbuf); + if(free_certbuf) + free(certbuf); return result; } @@ -2421,7 +1694,7 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, /* Result is returned to caller */ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - /* if a path wasn't specified, don't pin */ + /* if a path was not specified, do not pin */ if(!pinnedpubkey) return CURLE_OK; @@ -2453,17 +1726,17 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, #elif SECTRANSP_PINNEDPUBKEY_V2 { - OSStatus success; - success = SecItemExport(keyRef, kSecFormatOpenSSL, 0, NULL, - &publicKeyBits); - CFRelease(keyRef); - if(success != errSecSuccess || !publicKeyBits) - break; + OSStatus success; + success = SecItemExport(keyRef, kSecFormatOpenSSL, 0, NULL, + &publicKeyBits); + CFRelease(keyRef); + if(success != errSecSuccess || !publicKeyBits) + break; } #endif /* SECTRANSP_PINNEDPUBKEY_V2 */ - pubkeylen = CFDataGetLength(publicKeyBits); + pubkeylen = (size_t)CFDataGetLength(publicKeyBits); pubkey = (unsigned char *)CFDataGetBytePtr(publicKeyBits); switch(pubkeylen) { @@ -2532,24 +1805,23 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, SSLCipherSuite cipher; SSLProtocol protocol = 0; - DEBUGASSERT(ssl_connect_2 == connssl->connecting_state - || ssl_connect_2_reading == connssl->connecting_state - || ssl_connect_2_writing == connssl->connecting_state); + DEBUGASSERT(ssl_connect_2 == connssl->connecting_state); DEBUGASSERT(backend); CURL_TRC_CF(data, cf, "connect_step2"); /* Here goes nothing: */ check_handshake: + connssl->io_need = CURL_SSL_IO_NEED_NONE; err = SSLHandshake(backend->ssl_ctx); if(err != noErr) { switch(err) { - case errSSLWouldBlock: /* they're not done with us yet */ - connssl->connecting_state = backend->ssl_direction ? - ssl_connect_2_writing : ssl_connect_2_reading; + case errSSLWouldBlock: /* they are not done with us yet */ + connssl->io_need = backend->ssl_direction ? + CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV; return CURLE_OK; - /* The below is errSSLServerAuthCompleted; it's not defined in + /* The below is errSSLServerAuthCompleted; it is not defined in Leopard's headers */ case -9841: if((conn_config->CAfile || conn_config->ca_info_blob) && @@ -2659,11 +1931,11 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, "authority"); break; - /* This error is raised if the server's cert didn't match the server's - host name: */ + /* This error is raised if the server's cert did not match the server's + hostname: */ case errSSLHostNameMismatch: failf(data, "SSL certificate peer verification failed, the " - "certificate did not match \"%s\"\n", connssl->dispname); + "certificate did not match \"%s\"\n", connssl->peer.dispname); return CURLE_PEER_FAILED_VERIFICATION; /* Problem with SSL / TLS negotiation */ @@ -2755,13 +2027,14 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, default: /* May also return codes listed in Security Framework Result Codes */ failf(data, "Unknown SSL protocol error in connection to %s:%d", - connssl->hostname, err); + connssl->peer.hostname, err); break; } return CURLE_SSL_CONNECT_ERROR; } else { - /* we have been connected fine, we're not waiting for anything else. */ + char cipher_str[64]; + /* we have been connected fine, we are not waiting for anything else. */ connssl->connecting_state = ssl_connect_3; #ifdef SECTRANSP_PINNEDPUBKEY @@ -2779,33 +2052,30 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, /* Informational message */ (void)SSLGetNegotiatedCipher(backend->ssl_ctx, &cipher); (void)SSLGetNegotiatedProtocolVersion(backend->ssl_ctx, &protocol); + + sectransp_cipher_suite_get_str((uint16_t) cipher, cipher_str, + sizeof(cipher_str), TRUE); switch(protocol) { case kSSLProtocol2: - infof(data, "SSL 2.0 connection using %s", - TLSCipherNameForNumber(cipher)); + infof(data, "SSL 2.0 connection using %s", cipher_str); break; case kSSLProtocol3: - infof(data, "SSL 3.0 connection using %s", - TLSCipherNameForNumber(cipher)); + infof(data, "SSL 3.0 connection using %s", cipher_str); break; case kTLSProtocol1: - infof(data, "TLS 1.0 connection using %s", - TLSCipherNameForNumber(cipher)); + infof(data, "TLS 1.0 connection using %s", cipher_str); break; #if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS case kTLSProtocol11: - infof(data, "TLS 1.1 connection using %s", - TLSCipherNameForNumber(cipher)); + infof(data, "TLS 1.1 connection using %s", cipher_str); break; case kTLSProtocol12: - infof(data, "TLS 1.2 connection using %s", - TLSCipherNameForNumber(cipher)); + infof(data, "TLS 1.2 connection using %s", cipher_str); break; #endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ #if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 case kTLSProtocol13: - infof(data, "TLS 1.3 connection using %s", - TLSCipherNameForNumber(cipher)); + infof(data, "TLS 1.3 connection using %s", cipher_str); break; #endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ default: @@ -2813,7 +2083,8 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, break; } -#if(CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 +#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && \ + defined(HAVE_BUILTIN_AVAILABLE) if(connssl->alpn) { if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) { CFArrayRef alpnArr = NULL; @@ -2837,11 +2108,8 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, else infof(data, VTLS_INFOF_NO_ALPN); - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); - /* chosenProtocol is a reference to the string within alpnArr - and doesn't need to be freed separately */ + and does not need to be freed separately */ if(alpnArr) CFRelease(alpnArr); } @@ -2901,7 +2169,7 @@ static CURLcode collect_server_cert(struct Curl_cfilter *cf, #ifndef CURL_DISABLE_VERBOSE_STRINGS const bool show_verbose_server_cert = data->set.verbose; #else - const bool show_verbose_server_cert = false; + const bool show_verbose_server_cert = FALSE; #endif struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); CURLcode result = ssl_config->certinfo ? @@ -2943,10 +2211,10 @@ static CURLcode collect_server_cert(struct Curl_cfilter *cf, /* SSLCopyPeerCertificates() is deprecated as of Mountain Lion. The function SecTrustGetCertificateAtIndex() is officially present in Lion, but it is unfortunately also present in Snow Leopard as - private API and doesn't work as expected. So we have to look for + private API and does not work as expected. So we have to look for a different symbol to make sure this code is only executed under Lion or later. */ - if(SecTrustCopyPublicKey) { + if(&SecTrustCopyPublicKey) { #pragma unused(server_certs) err = SSLCopyPeerTrust(backend->ssl_ctx, &trust); /* For some reason, SSLCopyPeerTrust() can return noErr and yet return @@ -3032,7 +2300,7 @@ sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, } if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we're allowed */ + /* Find out how much more time we are allowed */ const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); if(timeout_ms < 0) { @@ -3046,9 +2314,7 @@ sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, return result; } - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { + while(ssl_connect_2 == connssl->connecting_state) { /* check allowed time left */ const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); @@ -3059,14 +2325,13 @@ sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, return CURLE_OPERATION_TIMEDOUT; } - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading || - connssl->connecting_state == ssl_connect_2_writing) { + /* if ssl is expecting something, check if it is available. */ + if(connssl->io_need) { - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + curl_socket_t writefd = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? + sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = (connssl->io_need & CURL_SSL_IO_NEED_RECV) ? + sockfd : CURL_SOCKET_BAD; what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, nonblocking ? 0 : timeout_ms); @@ -3096,10 +2361,7 @@ sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, * or epoll() will always have a valid fdset to wait on. */ result = sectransp_connect_step2(cf, data); - if(result || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) + if(result || (nonblocking && (ssl_connect_2 == connssl->connecting_state))) return result; } /* repeat step2 until all transactions are done. */ @@ -3148,105 +2410,116 @@ static CURLcode sectransp_connect(struct Curl_cfilter *cf, return CURLE_OK; } -static void sectransp_close(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct st_ssl_backend_data *backend = - (struct st_ssl_backend_data *)connssl->backend; - - (void) data; - - DEBUGASSERT(backend); - - if(backend->ssl_ctx) { - CURL_TRC_CF(data, cf, "close"); - (void)SSLClose(backend->ssl_ctx); -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - if(SSLCreateContext) - CFRelease(backend->ssl_ctx); -#if CURL_SUPPORT_MAC_10_8 - else - (void)SSLDisposeContext(backend->ssl_ctx); -#endif /* CURL_SUPPORT_MAC_10_8 */ -#else - (void)SSLDisposeContext(backend->ssl_ctx); -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - backend->ssl_ctx = NULL; - } -} +static ssize_t sectransp_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, + size_t buffersize, + CURLcode *curlcode); -static int sectransp_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data) +static CURLcode sectransp_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { struct ssl_connect_data *connssl = cf->ctx; struct st_ssl_backend_data *backend = (struct st_ssl_backend_data *)connssl->backend; + CURLcode result = CURLE_OK; ssize_t nread; - int what; - int rc; - char buf[120]; - int loop = 10; /* avoid getting stuck */ - CURLcode result; + char buf[1024]; + size_t i; DEBUGASSERT(backend); + if(!backend->ssl_ctx || cf->shutdown) { + *done = TRUE; + goto out; + } - if(!backend->ssl_ctx) - return 0; - -#ifndef CURL_DISABLE_FTP - if(data->set.ftp_ccc != CURLFTPSSL_CCC_ACTIVE) - return 0; -#endif - - sectransp_close(cf, data); - - rc = 0; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; - what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), - SSL_SHUTDOWN_TIMEOUT); + if(send_shutdown && !backend->sent_shutdown) { + OSStatus err; - CURL_TRC_CF(data, cf, "shutdown"); - while(loop--) { - if(what < 0) { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - rc = -1; - break; + CURL_TRC_CF(data, cf, "shutdown, send close notify"); + err = SSLClose(backend->ssl_ctx); + switch(err) { + case noErr: + backend->sent_shutdown = TRUE; + break; + case errSSLWouldBlock: + connssl->io_need = CURL_SSL_IO_NEED_SEND; + result = CURLE_OK; + goto out; + default: + CURL_TRC_CF(data, cf, "shutdown, error: %d", (int)err); + result = CURLE_SEND_ERROR; + goto out; } + } - if(!what) { /* timeout */ - failf(data, "SSL shutdown timeout"); - break; + for(i = 0; i < 10; ++i) { + if(!backend->sent_shutdown) { + nread = sectransp_recv(cf, data, buf, (int)sizeof(buf), &result); } - - /* Something to read, let's do it and hope that it is the close - notify alert from the server. No way to SSL_Read now, so use read(). */ - - nread = Curl_conn_cf_recv(cf->next, data, buf, sizeof(buf), &result); - - if(nread < 0) { - failf(data, "read: %s", curl_easy_strerror(result)); - rc = -1; + else { + /* We would like to read the close notify from the server using + * Secure Transport, however SSLRead() no longer works after we + * sent the notify from our side. So, we just read from the + * underlying filter and hope it will end. */ + nread = Curl_conn_cf_recv(cf->next, data, buf, sizeof(buf), &result); } - + CURL_TRC_CF(data, cf, "shutdown read -> %zd, %d", nread, result); if(nread <= 0) break; + } - what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0); + if(nread > 0) { + /* still data coming in? */ + connssl->io_need = CURL_SSL_IO_NEED_RECV; + } + else if(nread == 0) { + /* We got the close notify alert and are done. */ + CURL_TRC_CF(data, cf, "shutdown done"); + *done = TRUE; + } + else if(result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_RECV; + result = CURLE_OK; + } + else { + DEBUGASSERT(result); + CURL_TRC_CF(data, cf, "shutdown, error: %d", result); } - return rc; +out: + cf->shutdown = (result || *done); + return result; } -static void sectransp_session_free(void *ptr) +static void sectransp_close(struct Curl_cfilter *cf, struct Curl_easy *data) { - /* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a - cached session ID inside the Security framework. There is a private - function that does this, but I don't want to have to explain to you why I - got your application rejected from the App Store due to the use of a - private API, so the best we can do is free up our own char array that we - created way back in sectransp_connect_step1... */ - Curl_safefree(ptr); + struct ssl_connect_data *connssl = cf->ctx; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + + (void) data; + + DEBUGASSERT(backend); + + if(backend->ssl_ctx) { + CURL_TRC_CF(data, cf, "close"); +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS + if(&SSLCreateContext) + CFRelease(backend->ssl_ctx); +#if CURL_SUPPORT_MAC_10_8 + else + (void)SSLDisposeContext(backend->ssl_ctx); +#endif /* CURL_SUPPORT_MAC_10_8 */ +#else + (void)SSLDisposeContext(backend->ssl_ctx); +#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ + backend->ssl_ctx = NULL; + } } static size_t sectransp_version(char *buffer, size_t size) @@ -3271,16 +2544,16 @@ static bool sectransp_data_pending(struct Curl_cfilter *cf, err = SSLGetBufferedReadSize(backend->ssl_ctx, &buffer); if(err == noErr) return buffer > 0UL; - return false; + return FALSE; } else - return false; + return FALSE; } static CURLcode sectransp_random(struct Curl_easy *data UNUSED_PARAM, unsigned char *entropy, size_t length) { - /* arc4random_buf() isn't available on cats older than Lion, so let's + /* arc4random_buf() is not available on cats older than Lion, so let's do this manually for the benefit of the older cats. */ size_t i; u_int32_t random_number = 0; @@ -3311,7 +2584,7 @@ static CURLcode sectransp_sha256sum(const unsigned char *tmp, /* input */ static bool sectransp_false_start(void) { #if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - if(SSLSetSessionOption) + if(&SSLSetSessionOption) return TRUE; #endif return FALSE; @@ -3338,7 +2611,7 @@ static ssize_t sectransp_send(struct Curl_cfilter *cf, Now, one could interpret that as "written to the socket," but actually, it returns the amount of data that was written to a buffer internal to - the SSLContextRef instead. So it's possible for SSLWrite() to return + the SSLContextRef instead. So it is possible for SSLWrite() to return errSSLWouldBlock and a number of bytes "written" because those bytes were encrypted and written to a buffer, not to the socket. @@ -3351,7 +2624,7 @@ static ssize_t sectransp_send(struct Curl_cfilter *cf, err = SSLWrite(backend->ssl_ctx, NULL, 0UL, &processed); switch(err) { case noErr: - /* processed is always going to be 0 because we didn't write to + /* processed is always going to be 0 because we did not write to the buffer, so return how much was written to the socket */ processed = backend->ssl_write_buffered_length; backend->ssl_write_buffered_length = 0UL; @@ -3366,7 +2639,7 @@ static ssize_t sectransp_send(struct Curl_cfilter *cf, } } else { - /* We've got new data to write: */ + /* We have got new data to write: */ err = SSLWrite(backend->ssl_ctx, mem, len, &processed); if(err != noErr) { switch(err) { @@ -3413,7 +2686,6 @@ static ssize_t sectransp_recv(struct Curl_cfilter *cf, } *curlcode = CURLE_AGAIN; return -1L; - break; /* errSSLClosedGraceful - server gracefully shut down the SSL session errSSLClosedNoNotify - server hung up on us instead of sending a @@ -3423,9 +2695,8 @@ static ssize_t sectransp_recv(struct Curl_cfilter *cf, case errSSLClosedNoNotify: *curlcode = CURLE_OK; return 0; - break; - /* The below is errSSLPeerAuthCompleted; it's not defined in + /* The below is errSSLPeerAuthCompleted; it is not defined in Leopard's headers */ case -9841: if((conn_config->CAfile || conn_config->ca_info_blob) && @@ -3443,7 +2714,6 @@ static ssize_t sectransp_recv(struct Curl_cfilter *cf, failf(data, "SSLRead() return error %d", err); *curlcode = CURLE_RECV_ERROR; return -1L; - break; } } return (ssize_t)processed; @@ -3467,7 +2737,8 @@ const struct Curl_ssl Curl_ssl_sectransp = { #ifdef SECTRANSP_PINNEDPUBKEY SSLSUPP_PINNEDPUBKEY | #endif /* SECTRANSP_PINNEDPUBKEY */ - SSLSUPP_HTTPS_PROXY, + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST, sizeof(struct st_ssl_backend_data), @@ -3481,11 +2752,10 @@ const struct Curl_ssl Curl_ssl_sectransp = { Curl_none_cert_status_request, /* cert_status_request */ sectransp_connect, /* connect */ sectransp_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks, /* getsock */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ sectransp_get_internals, /* get_internals */ sectransp_close, /* close_one */ Curl_none_close_all, /* close_all */ - sectransp_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ @@ -3493,11 +2763,15 @@ const struct Curl_ssl Curl_ssl_sectransp = { sectransp_sha256sum, /* sha256sum */ NULL, /* associate_connection */ NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ sectransp_recv, /* recv decrypted data */ sectransp_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + #ifdef __clang__ #pragma clang diagnostic pop #endif diff --git a/deps/curl/lib/vtls/vtls.c b/deps/curl/lib/vtls/vtls.c index 38a20e8bea2..61b407ab22e 100644 --- a/deps/curl/lib/vtls/vtls.c +++ b/deps/curl/lib/vtls/vtls.c @@ -55,6 +55,16 @@ #include "vtls.h" /* generic SSL protos etc */ #include "vtls_int.h" + +#include "openssl.h" /* OpenSSL versions */ +#include "gtls.h" /* GnuTLS versions */ +#include "wolfssl.h" /* wolfSSL versions */ +#include "schannel.h" /* Schannel SSPI version */ +#include "sectransp.h" /* Secure Transport (Darwin) version */ +#include "mbedtls.h" /* mbedTLS versions */ +#include "bearssl.h" /* BearSSL versions */ +#include "rustls.h" /* Rustls versions */ + #include "slist.h" #include "sendf.h" #include "strcase.h" @@ -67,7 +77,11 @@ #include "warnless.h" #include "curl_base64.h" #include "curl_printf.h" +#include "inet_pton.h" +#include "connect.h" +#include "select.h" #include "strdup.h" +#include "rand.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -102,7 +116,7 @@ static CURLcode blobdup(struct curl_blob **dest, DEBUGASSERT(dest); DEBUGASSERT(!*dest); if(src) { - /* only if there's data to dupe! */ + /* only if there is data to dupe! */ struct curl_blob *d; d = malloc(sizeof(struct curl_blob) + src->len); if(!d) @@ -131,13 +145,13 @@ static bool blobcmp(struct curl_blob *first, struct curl_blob *second) } #ifdef USE_SSL -static const struct alpn_spec ALPN_SPEC_H10 = { - { ALPN_HTTP_1_0 }, 1 -}; static const struct alpn_spec ALPN_SPEC_H11 = { { ALPN_HTTP_1_1 }, 1 }; #ifdef USE_HTTP2 +static const struct alpn_spec ALPN_SPEC_H2 = { + { ALPN_H2 }, 1 +}; static const struct alpn_spec ALPN_SPEC_H2_H11 = { { ALPN_H2, ALPN_HTTP_1_1 }, 2 }; @@ -147,58 +161,92 @@ static const struct alpn_spec *alpn_get_spec(int httpwant, bool use_alpn) { if(!use_alpn) return NULL; - if(httpwant == CURL_HTTP_VERSION_1_0) - return &ALPN_SPEC_H10; #ifdef USE_HTTP2 + if(httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) + return &ALPN_SPEC_H2; if(httpwant >= CURL_HTTP_VERSION_2) return &ALPN_SPEC_H2_H11; +#else + (void)httpwant; #endif + /* Use the ALPN protocol "http/1.1" for HTTP/1.x. + Avoid "http/1.0" because some servers do not support it. */ return &ALPN_SPEC_H11; } #endif /* USE_SSL */ -bool -Curl_ssl_config_matches(struct ssl_primary_config *data, - struct ssl_primary_config *needle) -{ - if((data->version == needle->version) && - (data->version_max == needle->version_max) && - (data->ssl_options == needle->ssl_options) && - (data->verifypeer == needle->verifypeer) && - (data->verifyhost == needle->verifyhost) && - (data->verifystatus == needle->verifystatus) && - blobcmp(data->cert_blob, needle->cert_blob) && - blobcmp(data->ca_info_blob, needle->ca_info_blob) && - blobcmp(data->issuercert_blob, needle->issuercert_blob) && - Curl_safecmp(data->CApath, needle->CApath) && - Curl_safecmp(data->CAfile, needle->CAfile) && - Curl_safecmp(data->issuercert, needle->issuercert) && - Curl_safecmp(data->clientcert, needle->clientcert) && +void Curl_ssl_easy_config_init(struct Curl_easy *data) +{ + /* + * libcurl 7.10 introduced SSL verification *by default*! This needs to be + * switched off unless wanted. + */ + data->set.ssl.primary.verifypeer = TRUE; + data->set.ssl.primary.verifyhost = TRUE; + data->set.ssl.primary.cache_session = TRUE; /* caching by default */ +#ifndef CURL_DISABLE_PROXY + data->set.proxy_ssl = data->set.ssl; +#endif +} + +static bool +match_ssl_primary_config(struct Curl_easy *data, + struct ssl_primary_config *c1, + struct ssl_primary_config *c2) +{ + (void)data; + if((c1->version == c2->version) && + (c1->version_max == c2->version_max) && + (c1->ssl_options == c2->ssl_options) && + (c1->verifypeer == c2->verifypeer) && + (c1->verifyhost == c2->verifyhost) && + (c1->verifystatus == c2->verifystatus) && + blobcmp(c1->cert_blob, c2->cert_blob) && + blobcmp(c1->ca_info_blob, c2->ca_info_blob) && + blobcmp(c1->issuercert_blob, c2->issuercert_blob) && + Curl_safecmp(c1->CApath, c2->CApath) && + Curl_safecmp(c1->CAfile, c2->CAfile) && + Curl_safecmp(c1->issuercert, c2->issuercert) && + Curl_safecmp(c1->clientcert, c2->clientcert) && #ifdef USE_TLS_SRP - !Curl_timestrcmp(data->username, needle->username) && - !Curl_timestrcmp(data->password, needle->password) && + !Curl_timestrcmp(c1->username, c2->username) && + !Curl_timestrcmp(c1->password, c2->password) && #endif - strcasecompare(data->cipher_list, needle->cipher_list) && - strcasecompare(data->cipher_list13, needle->cipher_list13) && - strcasecompare(data->curves, needle->curves) && - strcasecompare(data->CRLfile, needle->CRLfile) && - strcasecompare(data->pinned_key, needle->pinned_key)) + strcasecompare(c1->cipher_list, c2->cipher_list) && + strcasecompare(c1->cipher_list13, c2->cipher_list13) && + strcasecompare(c1->curves, c2->curves) && + strcasecompare(c1->CRLfile, c2->CRLfile) && + strcasecompare(c1->pinned_key, c2->pinned_key)) return TRUE; return FALSE; } -bool -Curl_clone_primary_ssl_config(struct ssl_primary_config *source, - struct ssl_primary_config *dest) +bool Curl_ssl_conn_config_match(struct Curl_easy *data, + struct connectdata *candidate, + bool proxy) +{ +#ifndef CURL_DISABLE_PROXY + if(proxy) + return match_ssl_primary_config(data, &data->set.proxy_ssl.primary, + &candidate->proxy_ssl_config); +#else + (void)proxy; +#endif + return match_ssl_primary_config(data, &data->set.ssl.primary, + &candidate->ssl_config); +} + +static bool clone_ssl_primary_config(struct ssl_primary_config *source, + struct ssl_primary_config *dest) { dest->version = source->version; dest->version_max = source->version_max; dest->verifypeer = source->verifypeer; dest->verifyhost = source->verifyhost; dest->verifystatus = source->verifystatus; - dest->sessionid = source->sessionid; + dest->cache_session = source->cache_session; dest->ssl_options = source->ssl_options; CLONE_BLOB(cert_blob); @@ -221,7 +269,7 @@ Curl_clone_primary_ssl_config(struct ssl_primary_config *source, return TRUE; } -void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc) +static void free_primary_ssl_config(struct ssl_primary_config *sslc) { Curl_safefree(sslc->CApath); Curl_safefree(sslc->CAfile); @@ -241,6 +289,111 @@ void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc) #endif } +CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data) +{ + data->set.ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH]; + data->set.ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE]; + data->set.ssl.primary.CRLfile = data->set.str[STRING_SSL_CRLFILE]; + data->set.ssl.primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT]; + data->set.ssl.primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; + data->set.ssl.primary.cipher_list = + data->set.str[STRING_SSL_CIPHER_LIST]; + data->set.ssl.primary.cipher_list13 = + data->set.str[STRING_SSL_CIPHER13_LIST]; + data->set.ssl.primary.pinned_key = + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + data->set.ssl.primary.cert_blob = data->set.blobs[BLOB_CERT]; + data->set.ssl.primary.ca_info_blob = data->set.blobs[BLOB_CAINFO]; + data->set.ssl.primary.curves = data->set.str[STRING_SSL_EC_CURVES]; +#ifdef USE_TLS_SRP + data->set.ssl.primary.username = data->set.str[STRING_TLSAUTH_USERNAME]; + data->set.ssl.primary.password = data->set.str[STRING_TLSAUTH_PASSWORD]; +#endif + data->set.ssl.cert_type = data->set.str[STRING_CERT_TYPE]; + data->set.ssl.key = data->set.str[STRING_KEY]; + data->set.ssl.key_type = data->set.str[STRING_KEY_TYPE]; + data->set.ssl.key_passwd = data->set.str[STRING_KEY_PASSWD]; + data->set.ssl.primary.clientcert = data->set.str[STRING_CERT]; + data->set.ssl.key_blob = data->set.blobs[BLOB_KEY]; + +#ifndef CURL_DISABLE_PROXY + data->set.proxy_ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY]; + data->set.proxy_ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE_PROXY]; + data->set.proxy_ssl.primary.cipher_list = + data->set.str[STRING_SSL_CIPHER_LIST_PROXY]; + data->set.proxy_ssl.primary.cipher_list13 = + data->set.str[STRING_SSL_CIPHER13_LIST_PROXY]; + data->set.proxy_ssl.primary.pinned_key = + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]; + data->set.proxy_ssl.primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY]; + data->set.proxy_ssl.primary.ca_info_blob = + data->set.blobs[BLOB_CAINFO_PROXY]; + data->set.proxy_ssl.primary.issuercert = + data->set.str[STRING_SSL_ISSUERCERT_PROXY]; + data->set.proxy_ssl.primary.issuercert_blob = + data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; + data->set.proxy_ssl.primary.CRLfile = + data->set.str[STRING_SSL_CRLFILE_PROXY]; + data->set.proxy_ssl.cert_type = data->set.str[STRING_CERT_TYPE_PROXY]; + data->set.proxy_ssl.key = data->set.str[STRING_KEY_PROXY]; + data->set.proxy_ssl.key_type = data->set.str[STRING_KEY_TYPE_PROXY]; + data->set.proxy_ssl.key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY]; + data->set.proxy_ssl.primary.clientcert = data->set.str[STRING_CERT_PROXY]; + data->set.proxy_ssl.key_blob = data->set.blobs[BLOB_KEY_PROXY]; +#ifdef USE_TLS_SRP + data->set.proxy_ssl.primary.username = + data->set.str[STRING_TLSAUTH_USERNAME_PROXY]; + data->set.proxy_ssl.primary.password = + data->set.str[STRING_TLSAUTH_PASSWORD_PROXY]; +#endif +#endif /* CURL_DISABLE_PROXY */ + + return CURLE_OK; +} + +CURLcode Curl_ssl_conn_config_init(struct Curl_easy *data, + struct connectdata *conn) +{ + /* Clone "primary" SSL configurations from the esay handle to + * the connection. They are used for connection cache matching and + * probably outlive the easy handle */ + if(!clone_ssl_primary_config(&data->set.ssl.primary, &conn->ssl_config)) + return CURLE_OUT_OF_MEMORY; +#ifndef CURL_DISABLE_PROXY + if(!clone_ssl_primary_config(&data->set.proxy_ssl.primary, + &conn->proxy_ssl_config)) + return CURLE_OUT_OF_MEMORY; +#endif + return CURLE_OK; +} + +void Curl_ssl_conn_config_cleanup(struct connectdata *conn) +{ + free_primary_ssl_config(&conn->ssl_config); +#ifndef CURL_DISABLE_PROXY + free_primary_ssl_config(&conn->proxy_ssl_config); +#endif +} + +void Curl_ssl_conn_config_update(struct Curl_easy *data, bool for_proxy) +{ + /* May be called on an easy that has no connection yet */ + if(data->conn) { + struct ssl_primary_config *src, *dest; +#ifndef CURL_DISABLE_PROXY + src = for_proxy ? &data->set.proxy_ssl.primary : &data->set.ssl.primary; + dest = for_proxy ? &data->conn->proxy_ssl_config : &data->conn->ssl_config; +#else + (void)for_proxy; + src = &data->set.ssl.primary; + dest = &data->conn->ssl_config; +#endif + dest->verifyhost = src->verifyhost; + dest->verifypeer = src->verifypeer; + dest->verifystatus = src->verifystatus; + } +} + #ifdef USE_SSL static int multissl_setup(const struct Curl_ssl *backend); #endif @@ -276,23 +429,6 @@ int Curl_ssl_init(void) return Curl_ssl->init(); } -#if defined(CURL_WITH_MULTI_SSL) -static const struct Curl_ssl Curl_ssl_multi; -#endif - -/* Global cleanup */ -void Curl_ssl_cleanup(void) -{ - if(init_ssl) { - /* only cleanup if we did a previous init */ - Curl_ssl->cleanup(); -#if defined(CURL_WITH_MULTI_SSL) - Curl_ssl = &Curl_ssl_multi; -#endif - init_ssl = FALSE; - } -} - static bool ssl_prefs_check(struct Curl_easy *data) { /* check for CURLOPT_SSLVERSION invalid parameter value */ @@ -318,7 +454,7 @@ static bool ssl_prefs_check(struct Curl_easy *data) } static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data, - const struct alpn_spec *alpn) + const struct alpn_spec *alpn) { struct ssl_connect_data *ctx; @@ -328,6 +464,7 @@ static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data, return NULL; ctx->alpn = alpn; + Curl_bufq_init2(&ctx->earlydata, CURL_SSL_EARLY_MAX, 1, BUFQ_OPT_NO_SPARES); ctx->backend = calloc(1, Curl_ssl->sizeof_ssl_backend_data); if(!ctx->backend) { free(ctx); @@ -339,6 +476,8 @@ static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data, static void cf_ctx_free(struct ssl_connect_data *ctx) { if(ctx) { + Curl_safefree(ctx->alpn_negotiated); + Curl_bufq_free(&ctx->earlydata); free(ctx->backend); free(ctx); } @@ -394,15 +533,16 @@ void Curl_ssl_sessionid_unlock(struct Curl_easy *data) } /* - * Check if there's a session ID for the given connection in the cache, and if - * there's one suitable, it is provided. Returns TRUE when no entry matched. + * Check if there is a session ID for the given connection in the cache, and if + * there is one suitable, it is provided. Returns TRUE when no entry matched. */ bool Curl_ssl_getsessionid(struct Curl_cfilter *cf, struct Curl_easy *data, + const struct ssl_peer *peer, void **ssl_sessionid, - size_t *idsize) /* set 0 if unknown */ + size_t *idsize, /* set 0 if unknown */ + char **palpn) { - struct ssl_connect_data *connssl = cf->ctx; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); struct Curl_ssl_session *check; @@ -411,12 +551,14 @@ bool Curl_ssl_getsessionid(struct Curl_cfilter *cf, bool no_match = TRUE; *ssl_sessionid = NULL; + if(palpn) + *palpn = NULL; if(!ssl_config) return TRUE; - DEBUGASSERT(ssl_config->primary.sessionid); + DEBUGASSERT(ssl_config->primary.cache_session); - if(!ssl_config->primary.sessionid || !data->state.session) + if(!ssl_config->primary.cache_session || !data->state.session) /* session ID reuse is disabled or the session cache has not been setup */ return TRUE; @@ -432,31 +574,33 @@ bool Curl_ssl_getsessionid(struct Curl_cfilter *cf, if(!check->sessionid) /* not session ID means blank entry */ continue; - if(strcasecompare(connssl->hostname, check->name) && + if(strcasecompare(peer->hostname, check->name) && ((!cf->conn->bits.conn_to_host && !check->conn_to_host) || (cf->conn->bits.conn_to_host && check->conn_to_host && strcasecompare(cf->conn->conn_to_host.name, check->conn_to_host))) && ((!cf->conn->bits.conn_to_port && check->conn_to_port == -1) || (cf->conn->bits.conn_to_port && check->conn_to_port != -1 && cf->conn->conn_to_port == check->conn_to_port)) && - (connssl->port == check->remote_port) && + (peer->port == check->remote_port) && + (peer->transport == check->transport) && strcasecompare(cf->conn->handler->scheme, check->scheme) && - Curl_ssl_config_matches(conn_config, &check->ssl_config)) { + match_ssl_primary_config(data, conn_config, &check->ssl_config)) { /* yes, we have a session ID! */ (*general_age)++; /* increase general age */ check->age = *general_age; /* set this as used in this age */ *ssl_sessionid = check->sessionid; if(idsize) *idsize = check->idsize; + if(palpn) + *palpn = check->alpn; no_match = FALSE; break; } } - DEBUGF(infof(data, "%s Session ID in cache for %s %s://%s:%d", - no_match? "Didn't find": "Found", - Curl_ssl_cf_is_proxy(cf) ? "proxy" : "host", - cf->conn->handler->scheme, connssl->hostname, connssl->port)); + CURL_TRC_CF(data, cf, "%s cached session ID for %s://%s:%d", + no_match ? "No" : "Found", + cf->conn->handler->scheme, peer->hostname, peer->port); return no_match; } @@ -469,15 +613,17 @@ void Curl_ssl_kill_session(struct Curl_ssl_session *session) /* defensive check */ /* free the ID the SSL-layer specific way */ - Curl_ssl->session_free(session->sessionid); + session->sessionid_free(session->sessionid, session->idsize); session->sessionid = NULL; + session->sessionid_free = NULL; session->age = 0; /* fresh */ - Curl_free_primary_ssl_config(&session->ssl_config); + free_primary_ssl_config(&session->ssl_config); Curl_safefree(session->name); Curl_safefree(session->conn_to_host); + Curl_safefree(session->alpn); } } @@ -498,60 +644,72 @@ void Curl_ssl_delsessionid(struct Curl_easy *data, void *ssl_sessionid) } } -/* - * Store session id in the session cache. The ID passed on to this function - * must already have been extracted and allocated the proper way for the SSL - * layer. Curl_XXXX_session_free() will be called to free/kill the session ID - * later on. - */ -CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf, - struct Curl_easy *data, - void *ssl_sessionid, - size_t idsize, - bool *added) +CURLcode Curl_ssl_set_sessionid(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct ssl_peer *peer, + const char *alpn, + void *ssl_sessionid, + size_t idsize, + Curl_ssl_sessionid_dtor *sessionid_free_cb) { - struct ssl_connect_data *connssl = cf->ctx; struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); size_t i; struct Curl_ssl_session *store; long oldest_age; - char *clone_host; - char *clone_conn_to_host; + char *clone_host = NULL; + char *clone_conn_to_host = NULL; + char *clone_alpn = NULL; int conn_to_port; long *general_age; + void *old_sessionid; + size_t old_size; + CURLcode result = CURLE_OUT_OF_MEMORY; - if(added) - *added = FALSE; + DEBUGASSERT(ssl_sessionid); + DEBUGASSERT(sessionid_free_cb); - if(!data->state.session) + if(!data->state.session) { + sessionid_free_cb(ssl_sessionid, idsize); return CURLE_OK; + } + + if(!Curl_ssl_getsessionid(cf, data, peer, &old_sessionid, &old_size, NULL)) { + if((old_size == idsize) && + ((old_sessionid == ssl_sessionid) || + (idsize && !memcmp(old_sessionid, ssl_sessionid, idsize)))) { + /* the very same */ + sessionid_free_cb(ssl_sessionid, idsize); + return CURLE_OK; + } + Curl_ssl_delsessionid(data, old_sessionid); + } store = &data->state.session[0]; oldest_age = data->state.session[0].age; /* zero if unused */ + DEBUGASSERT(ssl_config->primary.cache_session); (void)ssl_config; - DEBUGASSERT(ssl_config->primary.sessionid); - clone_host = strdup(connssl->hostname); + clone_host = strdup(peer->hostname); if(!clone_host) - return CURLE_OUT_OF_MEMORY; /* bail out */ + goto out; if(cf->conn->bits.conn_to_host) { clone_conn_to_host = strdup(cf->conn->conn_to_host.name); - if(!clone_conn_to_host) { - free(clone_host); - return CURLE_OUT_OF_MEMORY; /* bail out */ - } + if(!clone_conn_to_host) + goto out; } - else - clone_conn_to_host = NULL; + + clone_alpn = alpn ? strdup(alpn) : NULL; + if(alpn && !clone_alpn) + goto out; if(cf->conn->bits.conn_to_port) conn_to_port = cf->conn->conn_to_port; else conn_to_port = -1; - /* Now we should add the session ID and the host name to the cache, (remove + /* Now we should add the session ID and the hostname to the cache, (remove the oldest if necessary) */ /* If using shared SSL session, lock! */ @@ -577,40 +735,55 @@ CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf, store = &data->state.session[i]; /* use this slot */ /* now init the session struct wisely */ + if(!clone_ssl_primary_config(conn_config, &store->ssl_config)) { + free_primary_ssl_config(&store->ssl_config); + store->sessionid = NULL; /* let caller free sessionid */ + goto out; + } store->sessionid = ssl_sessionid; store->idsize = idsize; + store->sessionid_free = sessionid_free_cb; store->age = *general_age; /* set current age */ - /* free it if there's one already present */ + /* free it if there is one already present */ free(store->name); free(store->conn_to_host); - store->name = clone_host; /* clone host name */ - store->conn_to_host = clone_conn_to_host; /* clone connect to host name */ + store->name = clone_host; /* clone hostname */ + clone_host = NULL; + store->conn_to_host = clone_conn_to_host; /* clone connect to hostname */ + clone_conn_to_host = NULL; store->conn_to_port = conn_to_port; /* connect to port number */ + store->alpn = clone_alpn; + clone_alpn = NULL; /* port number */ - store->remote_port = connssl->port; + store->remote_port = peer->port; store->scheme = cf->conn->handler->scheme; + store->transport = peer->transport; - if(!Curl_clone_primary_ssl_config(conn_config, &store->ssl_config)) { - Curl_free_primary_ssl_config(&store->ssl_config); - store->sessionid = NULL; /* let caller free sessionid */ - free(clone_host); - free(clone_conn_to_host); - return CURLE_OUT_OF_MEMORY; - } + result = CURLE_OK; - if(added) - *added = TRUE; - - DEBUGF(infof(data, "Added Session ID to cache for %s://%s:%d [%s]", - store->scheme, store->name, store->remote_port, - Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server")); +out: + free(clone_host); + free(clone_conn_to_host); + free(clone_alpn); + if(result) { + failf(data, "Failed to add Session ID to cache for %s://%s:%d [%s]", + store->scheme, store->name, store->remote_port, + Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server"); + sessionid_free_cb(ssl_sessionid, idsize); + return result; + } + CURL_TRC_CF(data, cf, "Added Session ID to cache for %s://%s:%d [%s]", + store->scheme, store->name, store->remote_port, + Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server"); return CURLE_OK; } -void Curl_free_multi_ssl_backend_data(struct multi_ssl_backend_data *mbackend) +CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex, + struct dynbuf *binding) { - if(Curl_ssl->free_multi_ssl_backend_data && mbackend) - Curl_ssl->free_multi_ssl_backend_data(mbackend); + if(Curl_ssl->get_channel_binding) + return Curl_ssl->get_channel_binding(data, sockindex, binding); + return CURLE_OK; } void Curl_ssl_close_all(struct Curl_easy *data) @@ -629,22 +802,26 @@ void Curl_ssl_close_all(struct Curl_easy *data) Curl_ssl->close_all(data); } -int Curl_ssl_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) +void Curl_ssl_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, + struct easy_pollset *ps) { struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data); - if(sock == CURL_SOCKET_BAD) - return GETSOCK_BLANK; - - if(connssl->connecting_state == ssl_connect_2_writing) { - /* we are only interested in writing */ - socks[0] = sock; - return GETSOCK_WRITESOCK(0); + if(connssl->io_need) { + curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data); + if(sock != CURL_SOCKET_BAD) { + if(connssl->io_need & CURL_SSL_IO_NEED_SEND) { + Curl_pollset_set_out_only(data, ps, sock); + CURL_TRC_CF(data, cf, "adjust_pollset, POLLOUT fd=%" FMT_SOCKET_T, + sock); + } + else { + Curl_pollset_set_in_only(data, ps, sock); + CURL_TRC_CF(data, cf, "adjust_pollset, POLLIN fd=%" FMT_SOCKET_T, + sock); + } + } } - socks[0] = sock; - return GETSOCK_READSOCK(0); } /* Selects an SSL crypto engine @@ -708,7 +885,7 @@ void Curl_ssl_free_certinfo(struct Curl_easy *data) if(ci->num_of_certs) { /* free all individual lists used */ int i; - for(i = 0; inum_of_certs; i++) { + for(i = 0; i < ci->num_of_certs; i++) { curl_slist_free_all(ci->certinfo[i]); ci->certinfo[i] = NULL; } @@ -748,28 +925,23 @@ CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data, size_t valuelen) { struct curl_certinfo *ci = &data->info.certs; - char *output; struct curl_slist *nl; CURLcode result = CURLE_OK; - size_t labellen = strlen(label); - size_t outlen = labellen + 1 + valuelen + 1; /* label:value\0 */ + struct dynbuf build; - output = malloc(outlen); - if(!output) - return CURLE_OUT_OF_MEMORY; + DEBUGASSERT(certnum < ci->num_of_certs); - /* sprintf the label and colon */ - msnprintf(output, outlen, "%s:", label); + Curl_dyn_init(&build, CURL_X509_STR_MAX); - /* memcpy the value (it might not be null-terminated) */ - memcpy(&output[labellen + 1], value, valuelen); - - /* null-terminate the output */ - output[labellen + 1 + valuelen] = 0; + if(Curl_dyn_add(&build, label) || + Curl_dyn_addn(&build, ":", 1) || + Curl_dyn_addn(&build, value, valuelen)) + return CURLE_OUT_OF_MEMORY; - nl = Curl_slist_append_nodup(ci->certinfo[certnum], output); + nl = Curl_slist_append_nodup(ci->certinfo[certnum], + Curl_dyn_ptr(&build)); if(!nl) { - free(output); + Curl_dyn_free(&build); curl_slist_free_all(ci->certinfo[certnum]); result = CURLE_OUT_OF_MEMORY; } @@ -778,37 +950,16 @@ CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data, return result; } +/* get 32 bits of random */ CURLcode Curl_ssl_random(struct Curl_easy *data, unsigned char *entropy, size_t length) { - return Curl_ssl->random(data, entropy, length); -} - -/* - * Curl_ssl_snihost() converts the input host name to a suitable SNI name put - * in data->state.buffer. Returns a pointer to the name (or NULL if a problem) - * and stores the new length in 'olen'. - * - * SNI fields must not have any trailing dot and while RFC 6066 section 3 says - * the SNI field is case insensitive, browsers always send the data lowercase - * and subsequently there are numerous servers out there that don't work - * unless the name is lowercased. - */ - -char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen) -{ - size_t len = strlen(host); - if(len && (host[len-1] == '.')) - len--; - if(len >= data->set.buffer_size) - return NULL; - - Curl_strntolower(data->state.buffer, host, len); - data->state.buffer[len] = 0; - if(olen) - *olen = len; - return data->state.buffer; + DEBUGASSERT(length == sizeof(int)); + if(Curl_ssl->random) + return Curl_ssl->random(data, entropy, length); + else + return CURLE_NOT_BUILT_IN; } /* @@ -818,14 +969,17 @@ char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen) static CURLcode pubkey_pem_to_der(const char *pem, unsigned char **der, size_t *der_len) { - char *stripped_pem, *begin_pos, *end_pos; - size_t pem_count, stripped_pem_count = 0, pem_len; + char *begin_pos, *end_pos; + size_t pem_count, pem_len; CURLcode result; + struct dynbuf pbuf; /* if no pem, exit. */ if(!pem) return CURLE_BAD_CONTENT_ENCODING; + Curl_dyn_init(&pbuf, MAX_PINNED_PUBKEY_SIZE); + begin_pos = strstr(pem, "-----BEGIN PUBLIC KEY-----"); if(!begin_pos) return CURLE_BAD_CONTENT_ENCODING; @@ -845,26 +999,23 @@ static CURLcode pubkey_pem_to_der(const char *pem, pem_len = end_pos - pem; - stripped_pem = malloc(pem_len - pem_count + 1); - if(!stripped_pem) - return CURLE_OUT_OF_MEMORY; - /* * Here we loop through the pem array one character at a time between the * correct indices, and place each character that is not '\n' or '\r' * into the stripped_pem array, which should represent the raw base64 string */ while(pem_count < pem_len) { - if('\n' != pem[pem_count] && '\r' != pem[pem_count]) - stripped_pem[stripped_pem_count++] = pem[pem_count]; + if('\n' != pem[pem_count] && '\r' != pem[pem_count]) { + result = Curl_dyn_addn(&pbuf, &pem[pem_count], 1); + if(result) + return result; + } ++pem_count; } - /* Place the null terminator in the correct place */ - stripped_pem[stripped_pem_count] = '\0'; - result = Curl_base64_decode(stripped_pem, der, der_len); + result = Curl_base64_decode(Curl_dyn_ptr(&pbuf), der, der_len); - Curl_safefree(stripped_pem); + Curl_dyn_free(&pbuf); return result; } @@ -877,23 +1028,21 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, const char *pinnedpubkey, const unsigned char *pubkey, size_t pubkeylen) { - FILE *fp; - unsigned char *buf = NULL, *pem_ptr = NULL; CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; #ifdef CURL_DISABLE_VERBOSE_STRINGS (void)data; #endif - /* if a path wasn't specified, don't pin */ + /* if a path was not specified, do not pin */ if(!pinnedpubkey) return CURLE_OK; if(!pubkey || !pubkeylen) return result; /* only do this if pinnedpubkey starts with "sha256//", length 8 */ - if(strncmp(pinnedpubkey, "sha256//", 8) == 0) { + if(!strncmp(pinnedpubkey, "sha256//", 8)) { CURLcode encode; - size_t encodedlen = 0, pinkeylen; + size_t encodedlen = 0; char *encoded = NULL, *pinkeycopy, *begin_pos, *end_pos; unsigned char *sha256sumdigest; @@ -921,20 +1070,18 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, infof(data, " public key hash: sha256//%s", encoded); /* it starts with sha256//, copy so we can modify it */ - pinkeylen = strlen(pinnedpubkey) + 1; - pinkeycopy = malloc(pinkeylen); + pinkeycopy = strdup(pinnedpubkey); if(!pinkeycopy) { Curl_safefree(encoded); return CURLE_OUT_OF_MEMORY; } - memcpy(pinkeycopy, pinnedpubkey, pinkeylen); /* point begin_pos to the copy, and start extracting keys */ begin_pos = pinkeycopy; do { end_pos = strstr(begin_pos, ";sha256//"); /* * if there is an end_pos, null terminate, - * otherwise it'll go to the end of the original string + * otherwise it will go to the end of the original string */ if(end_pos) end_pos[0] = '\0'; @@ -957,75 +1104,78 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, } while(end_pos && begin_pos); Curl_safefree(encoded); Curl_safefree(pinkeycopy); - return result; } - - fp = fopen(pinnedpubkey, "rb"); - if(!fp) - return result; - - do { + else { long filesize; size_t size, pem_len; CURLcode pem_read; + struct dynbuf buf; + char unsigned *pem_ptr = NULL; + size_t left; + FILE *fp = fopen(pinnedpubkey, "rb"); + if(!fp) + return result; + + Curl_dyn_init(&buf, MAX_PINNED_PUBKEY_SIZE); /* Determine the file's size */ if(fseek(fp, 0, SEEK_END)) - break; + goto end; filesize = ftell(fp); if(fseek(fp, 0, SEEK_SET)) - break; + goto end; if(filesize < 0 || filesize > MAX_PINNED_PUBKEY_SIZE) - break; + goto end; /* * if the size of our certificate is bigger than the file - * size then it can't match + * size then it cannot match */ size = curlx_sotouz((curl_off_t) filesize); if(pubkeylen > size) - break; + goto end; /* - * Allocate buffer for the pinned key - * With 1 additional byte for null terminator in case of PEM key + * Read the file into the dynbuf */ - buf = malloc(size + 1); - if(!buf) - break; - - /* Returns number of elements read, which should be 1 */ - if((int) fread(buf, size, 1, fp) != 1) - break; - - /* If the sizes are the same, it can't be base64 encoded, must be der */ + left = size; + do { + char buffer[1024]; + size_t want = left > sizeof(buffer) ? sizeof(buffer) : left; + if(want != fread(buffer, 1, want, fp)) + goto end; + if(Curl_dyn_addn(&buf, buffer, want)) + goto end; + left -= want; + } while(left); + + /* If the sizes are the same, it cannot be base64 encoded, must be der */ if(pubkeylen == size) { - if(!memcmp(pubkey, buf, pubkeylen)) + if(!memcmp(pubkey, Curl_dyn_ptr(&buf), pubkeylen)) result = CURLE_OK; - break; + goto end; } /* - * Otherwise we will assume it's PEM and try to decode it + * Otherwise we will assume it is PEM and try to decode it * after placing null terminator */ - buf[size] = '\0'; - pem_read = pubkey_pem_to_der((const char *)buf, &pem_ptr, &pem_len); - /* if it wasn't read successfully, exit */ + pem_read = pubkey_pem_to_der(Curl_dyn_ptr(&buf), &pem_ptr, &pem_len); + /* if it was not read successfully, exit */ if(pem_read) - break; + goto end; /* - * if the size of our certificate doesn't match the size of - * the decoded file, they can't be the same, otherwise compare + * if the size of our certificate does not match the size of + * the decoded file, they cannot be the same, otherwise compare */ if(pubkeylen == pem_len && !memcmp(pubkey, pem_ptr, pubkeylen)) result = CURLE_OK; - } while(0); - - Curl_safefree(buf); - Curl_safefree(pem_ptr); - fclose(fp); +end: + Curl_dyn_free(&buf); + Curl_safefree(pem_ptr); + fclose(fp); + } return result; } @@ -1059,12 +1209,18 @@ int Curl_none_init(void) void Curl_none_cleanup(void) { } -int Curl_none_shutdown(struct Curl_cfilter *cf UNUSED_PARAM, - struct Curl_easy *data UNUSED_PARAM) +CURLcode Curl_none_shutdown(struct Curl_cfilter *cf UNUSED_PARAM, + struct Curl_easy *data UNUSED_PARAM, + bool send_shutdown UNUSED_PARAM, + bool *done) { (void)data; (void)cf; - return 0; + (void)send_shutdown; + /* Every SSL backend should have a shutdown implementation. Until we + * have implemented that, we put this fake in place. */ + *done = TRUE; + return CURLE_OK; } int Curl_none_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -1074,16 +1230,6 @@ int Curl_none_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data) return -1; } -CURLcode Curl_none_random(struct Curl_easy *data UNUSED_PARAM, - unsigned char *entropy UNUSED_PARAM, - size_t length UNUSED_PARAM) -{ - (void)data; - (void)entropy; - (void)length; - return CURLE_NOT_BUILT_IN; -} - void Curl_none_close_all(struct Curl_easy *data UNUSED_PARAM) { (void)data; @@ -1156,13 +1302,13 @@ static CURLcode multissl_connect_nonblocking(struct Curl_cfilter *cf, return Curl_ssl->connect_nonblocking(cf, data, done); } -static int multissl_get_select_socks(struct Curl_cfilter *cf, +static void multissl_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) + struct easy_pollset *ps) { if(multissl_setup(NULL)) - return 0; - return Curl_ssl->get_select_socks(cf, data, socks); + return; + Curl_ssl->adjust_pollset(cf, data, ps); } static void *multissl_get_internals(struct ssl_connect_data *connssl, @@ -1210,15 +1356,14 @@ static const struct Curl_ssl Curl_ssl_multi = { Curl_none_check_cxn, /* check_cxn */ Curl_none_shutdown, /* shutdown */ Curl_none_data_pending, /* data_pending */ - Curl_none_random, /* random */ + NULL, /* random */ Curl_none_cert_status_request, /* cert_status_request */ multissl_connect, /* connect */ multissl_connect_nonblocking, /* connect_nonblocking */ - multissl_get_select_socks, /* getsock */ + multissl_adjust_pollset, /* adjust_pollset */ multissl_get_internals, /* get_internals */ multissl_close, /* close_one */ Curl_none_close_all, /* close_all */ - Curl_none_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ @@ -1226,9 +1371,9 @@ static const struct Curl_ssl Curl_ssl_multi = { NULL, /* sha256sum */ NULL, /* associate_connection */ NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ multissl_recv_plain, /* recv decrypted data */ multissl_send_plain, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; const struct Curl_ssl *Curl_ssl = @@ -1236,8 +1381,6 @@ const struct Curl_ssl *Curl_ssl = &Curl_ssl_multi; #elif defined(USE_WOLFSSL) &Curl_ssl_wolfssl; -#elif defined(USE_SECTRANSP) - &Curl_ssl_sectransp; #elif defined(USE_GNUTLS) &Curl_ssl_gnutls; #elif defined(USE_MBEDTLS) @@ -1246,6 +1389,8 @@ const struct Curl_ssl *Curl_ssl = &Curl_ssl_rustls; #elif defined(USE_OPENSSL) &Curl_ssl_openssl; +#elif defined(USE_SECTRANSP) + &Curl_ssl_sectransp; #elif defined(USE_SCHANNEL) &Curl_ssl_schannel; #elif defined(USE_BEARSSL) @@ -1258,9 +1403,6 @@ static const struct Curl_ssl *available_backends[] = { #if defined(USE_WOLFSSL) &Curl_ssl_wolfssl, #endif -#if defined(USE_SECTRANSP) - &Curl_ssl_sectransp, -#endif #if defined(USE_GNUTLS) &Curl_ssl_gnutls, #endif @@ -1270,6 +1412,9 @@ static const struct Curl_ssl *available_backends[] = { #if defined(USE_OPENSSL) &Curl_ssl_openssl, #endif +#if defined(USE_SECTRANSP) + &Curl_ssl_sectransp, +#endif #if defined(USE_SCHANNEL) &Curl_ssl_schannel, #endif @@ -1282,6 +1427,19 @@ static const struct Curl_ssl *available_backends[] = { NULL }; +/* Global cleanup */ +void Curl_ssl_cleanup(void) +{ + if(init_ssl) { + /* only cleanup if we did a previous init */ + Curl_ssl->cleanup(); +#if defined(CURL_WITH_MULTI_SSL) + Curl_ssl = &Curl_ssl_multi; +#endif + init_ssl = FALSE; + } +} + static size_t multissl_version(char *buffer, size_t size) { static const struct Curl_ssl *selected; @@ -1313,17 +1471,13 @@ static size_t multissl_version(char *buffer, size_t size) backends_len = p - backends; } - if(!size) - return 0; - - if(size <= backends_len) { - strncpy(buffer, backends, size - 1); - buffer[size - 1] = '\0'; - return size - 1; + if(size) { + if(backends_len < size) + strcpy(buffer, backends); + else + *buffer = 0; /* did not fit */ } - - strcpy(buffer, backends); - return backends_len; + return 0; } static int multissl_setup(const struct Curl_ssl *backend) @@ -1409,12 +1563,14 @@ CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, #ifdef USE_SSL -static void free_hostname(struct ssl_connect_data *connssl) +void Curl_ssl_peer_cleanup(struct ssl_peer *peer) { - if(connssl->dispname != connssl->hostname) - free(connssl->dispname); - free(connssl->hostname); - connssl->hostname = connssl->dispname = NULL; + if(peer->dispname != peer->hostname) + free(peer->dispname); + free(peer->sni); + free(peer->hostname); + peer->hostname = peer->sni = peer->dispname = NULL; + peer->type = CURL_SSL_PEER_DNS; } static void cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -1423,57 +1579,98 @@ static void cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) if(connssl) { Curl_ssl->close(cf, data); connssl->state = ssl_connection_none; - free_hostname(connssl); + Curl_ssl_peer_cleanup(&connssl->peer); } cf->connected = FALSE; } -static CURLcode reinit_hostname(struct Curl_cfilter *cf) +static ssl_peer_type get_peer_type(const char *hostname) { - struct ssl_connect_data *connssl = cf->ctx; - const char *ehostname, *edispname; - int eport; + if(hostname && hostname[0]) { +#ifdef USE_IPV6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + if(Curl_inet_pton(AF_INET, hostname, &addr)) + return CURL_SSL_PEER_IPV4; +#ifdef USE_IPV6 + else if(Curl_inet_pton(AF_INET6, hostname, &addr)) { + return CURL_SSL_PEER_IPV6; + } +#endif + } + return CURL_SSL_PEER_DNS; +} - /* We need the hostname for SNI negotiation. Once handshaked, this - * remains the SNI hostname for the TLS connection. But when the - * connection is reused, the settings in cf->conn might change. - * So we keep a copy of the hostname we use for SNI. +CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, struct Curl_cfilter *cf, + int transport) +{ + const char *ehostname, *edispname; + CURLcode result = CURLE_OUT_OF_MEMORY; + + /* We expect a clean struct, e.g. called only ONCE */ + DEBUGASSERT(peer); + DEBUGASSERT(!peer->hostname); + DEBUGASSERT(!peer->dispname); + DEBUGASSERT(!peer->sni); + /* We need the hostname for SNI negotiation. Once handshaked, this remains + * the SNI hostname for the TLS connection. When the connection is reused, + * the settings in cf->conn might change. We keep a copy of the hostname we + * use for SNI. */ + peer->transport = transport; #ifndef CURL_DISABLE_PROXY if(Curl_ssl_cf_is_proxy(cf)) { ehostname = cf->conn->http_proxy.host.name; edispname = cf->conn->http_proxy.host.dispname; - eport = cf->conn->http_proxy.port; + peer->port = cf->conn->http_proxy.port; } else #endif { ehostname = cf->conn->host.name; edispname = cf->conn->host.dispname; - eport = cf->conn->remote_port; + peer->port = cf->conn->remote_port; } - /* change if ehostname changed */ - if(ehostname && (!connssl->hostname - || strcmp(ehostname, connssl->hostname))) { - free_hostname(connssl); - connssl->hostname = strdup(ehostname); - if(!connssl->hostname) { - free_hostname(connssl); - return CURLE_OUT_OF_MEMORY; - } - if(!edispname || !strcmp(ehostname, edispname)) - connssl->dispname = connssl->hostname; - else { - connssl->dispname = strdup(edispname); - if(!connssl->dispname) { - free_hostname(connssl); - return CURLE_OUT_OF_MEMORY; - } + /* hostname MUST exist and not be empty */ + if(!ehostname || !ehostname[0]) { + result = CURLE_FAILED_INIT; + goto out; + } + + peer->hostname = strdup(ehostname); + if(!peer->hostname) + goto out; + if(!edispname || !strcmp(ehostname, edispname)) + peer->dispname = peer->hostname; + else { + peer->dispname = strdup(edispname); + if(!peer->dispname) + goto out; + } + peer->type = get_peer_type(peer->hostname); + if(peer->type == CURL_SSL_PEER_DNS) { + /* not an IP address, normalize according to RCC 6066 ch. 3, + * max len of SNI is 2^16-1, no trailing dot */ + size_t len = strlen(peer->hostname); + if(len && (peer->hostname[len-1] == '.')) + len--; + if(len < USHRT_MAX) { + peer->sni = calloc(1, len + 1); + if(!peer->sni) + goto out; + Curl_strntolower(peer->sni, peer->hostname, len); + peer->sni[len] = 0; } } - connssl->port = eport; - return CURLE_OK; + result = CURLE_OK; + +out: + if(result) + Curl_ssl_peer_cleanup(peer); + return result; } static void ssl_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -1494,7 +1691,8 @@ static void ssl_cf_close(struct Curl_cfilter *cf, CF_DATA_SAVE(save, cf, data); cf_close(cf, data); - cf->next->cft->do_close(cf->next, data); + if(cf->next) + cf->next->cft->do_close(cf->next, data); CF_DATA_RESTORE(cf, save); } @@ -1511,22 +1709,29 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, return CURLE_OK; } + if(!cf->next) { + *done = FALSE; + return CURLE_FAILED_INIT; + } + + if(!cf->next->connected) { + result = cf->next->cft->do_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; + } + CF_DATA_SAVE(save, cf, data); CURL_TRC_CF(data, cf, "cf_connect()"); - (void)connssl; DEBUGASSERT(data->conn); DEBUGASSERT(data->conn == cf->conn); DEBUGASSERT(connssl); - DEBUGASSERT(cf->conn->host.name); - - result = cf->next->cft->do_connect(cf->next, data, blocking, done); - if(result || !*done) - goto out; *done = FALSE; - result = reinit_hostname(cf); - if(result) - goto out; + if(!connssl->peer.hostname) { + result = Curl_ssl_peer_init(&connssl->peer, cf, TRNSPRT_TCP); + if(result) + goto out; + } if(blocking) { result = ssl_connect(cf, data); @@ -1539,7 +1744,9 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, if(!result && *done) { cf->connected = TRUE; connssl->handshake_done = Curl_now(); - DEBUGASSERT(connssl->state == ssl_connection_complete); + /* Connection can be deferred when sending early data */ + DEBUGASSERT(connssl->state == ssl_connection_complete || + connssl->state == ssl_connection_deferred); } out: CURL_TRC_CF(data, cf, "cf_connect() -> %d, done=%d", result, *done); @@ -1564,15 +1771,19 @@ static bool ssl_cf_data_pending(struct Curl_cfilter *cf, static ssize_t ssl_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *buf, size_t len, - CURLcode *err) + bool eos, CURLcode *err) { struct cf_call_data save; - ssize_t nwritten; + ssize_t nwritten = 0; - CF_DATA_SAVE(save, cf, data); + (void)eos; + /* OpenSSL and maybe other TLS libs do not like 0-length writes. Skip. */ *err = CURLE_OK; - nwritten = Curl_ssl->send_plain(cf, data, buf, len, err); - CF_DATA_RESTORE(cf, save); + if(len > 0) { + CF_DATA_SAVE(save, cf, data); + nwritten = Curl_ssl->send_plain(cf, data, buf, len, err); + CF_DATA_RESTORE(cf, save); + } return nwritten; } @@ -1593,27 +1804,40 @@ static ssize_t ssl_cf_recv(struct Curl_cfilter *cf, /* eof */ *err = CURLE_OK; } - CURL_TRC_CF(data, cf, "cf_recv(len=%zu) -> %zd, %d", len, nread, *err); + CURL_TRC_CF(data, cf, "cf_recv(len=%zu) -> %zd, %d", len, + nread, *err); CF_DATA_RESTORE(cf, save); return nread; } -static int ssl_cf_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) +static CURLcode ssl_cf_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { - struct cf_call_data save; - int fds = GETSOCK_BLANK; + CURLcode result = CURLE_OK; + + *done = TRUE; + if(!cf->shutdown) { + struct cf_call_data save; - if(!cf->next->connected) { - fds = cf->next->cft->get_select_socks(cf->next, data, socks); - } - else if(!cf->connected) { CF_DATA_SAVE(save, cf, data); - fds = Curl_ssl->get_select_socks(cf, data, socks); + result = Curl_ssl->shut_down(cf, data, TRUE, done); + CURL_TRC_CF(data, cf, "cf_shutdown -> %d, done=%d", result, *done); CF_DATA_RESTORE(cf, save); + cf->shutdown = (result || *done); } - return fds; + return result; +} + +static void ssl_cf_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + Curl_ssl->adjust_pollset(cf, data, ps); + CF_DATA_RESTORE(cf, save); } static CURLcode ssl_cf_cntrl(struct Curl_cfilter *cf, @@ -1661,7 +1885,7 @@ static CURLcode ssl_cf_query(struct Curl_cfilter *cf, default: break; } - return cf->next? + return cf->next ? cf->next->cft->query(cf->next, data, query, pres1, pres2) : CURLE_UNKNOWN_OPTION; } @@ -1691,7 +1915,7 @@ static bool cf_ssl_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data, return FALSE; } /* ssl backend does not know */ - return cf->next? + return cf->next ? cf->next->cft->is_alive(cf->next, data, input_pending) : FALSE; /* pessimistic in absence of data */ } @@ -1703,8 +1927,9 @@ struct Curl_cftype Curl_cft_ssl = { ssl_cf_destroy, ssl_cf_connect, ssl_cf_close, + ssl_cf_shutdown, Curl_cf_def_get_host, - ssl_cf_get_select_socks, + ssl_cf_adjust_pollset, ssl_cf_data_pending, ssl_cf_send, ssl_cf_recv, @@ -1714,15 +1939,18 @@ struct Curl_cftype Curl_cft_ssl = { ssl_cf_query, }; +#ifndef CURL_DISABLE_PROXY + struct Curl_cftype Curl_cft_ssl_proxy = { "SSL-PROXY", - CF_TYPE_SSL, + CF_TYPE_SSL|CF_TYPE_PROXY, CURL_LOG_LVL_NONE, ssl_cf_destroy, ssl_cf_connect, ssl_cf_close, + ssl_cf_shutdown, Curl_cf_def_get_host, - ssl_cf_get_select_socks, + ssl_cf_adjust_pollset, ssl_cf_data_pending, ssl_cf_send, ssl_cf_recv, @@ -1732,6 +1960,8 @@ struct Curl_cftype Curl_cft_ssl_proxy = { Curl_cf_def_query, }; +#endif /* !CURL_DISABLE_PROXY */ + static CURLcode cf_ssl_create(struct Curl_cfilter **pcf, struct Curl_easy *data, struct connectdata *conn) @@ -1754,7 +1984,7 @@ static CURLcode cf_ssl_create(struct Curl_cfilter **pcf, out: if(result) cf_ctx_free(ctx); - *pcf = result? NULL : cf; + *pcf = result ? NULL : cf; return result; } @@ -1812,7 +2042,7 @@ static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf, out: if(result) cf_ctx_free(ctx); - *pcf = result? NULL : cf; + *pcf = result ? NULL : cf; return result; } @@ -1830,12 +2060,26 @@ CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, #endif /* !CURL_DISABLE_PROXY */ -bool Curl_ssl_supports(struct Curl_easy *data, int option) +bool Curl_ssl_supports(struct Curl_easy *data, unsigned int ssl_option) { (void)data; - return (Curl_ssl->supports & option)? TRUE : FALSE; + return (Curl_ssl->supports & ssl_option); } +static struct Curl_cfilter *get_ssl_filter(struct Curl_cfilter *cf) +{ + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_ssl) + return cf; +#ifndef CURL_DISABLE_PROXY + if(cf->cft == &Curl_cft_ssl_proxy) + return cf; +#endif + } + return NULL; +} + + void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex, CURLINFO info, int n) { @@ -1843,8 +2087,8 @@ void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex, (void)n; if(data->conn) { struct Curl_cfilter *cf; - /* get first filter in chain, if any is present */ - cf = Curl_ssl_cf_get_ssl(data->conn->cfilter[sockindex]); + /* get first SSL filter in chain, if any is present */ + cf = get_ssl_filter(data->conn->cfilter[sockindex]); if(cf) { struct cf_call_data save; CF_DATA_SAVE(save, cf, data); @@ -1855,45 +2099,86 @@ void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex, return result; } +static CURLcode vtls_shutdown_blocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct cf_call_data save; + CURLcode result = CURLE_OK; + timediff_t timeout_ms; + int what, loop = 10; + + if(cf->shutdown) { + *done = TRUE; + return CURLE_OK; + } + CF_DATA_SAVE(save, cf, data); + + *done = FALSE; + while(!result && !*done && loop--) { + timeout_ms = Curl_shutdown_timeleft(cf->conn, cf->sockindex, NULL); + + if(timeout_ms < 0) { + /* no need to continue if time is already up */ + failf(data, "SSL shutdown timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + result = Curl_ssl->shut_down(cf, data, send_shutdown, done); + if(result ||*done) + goto out; + + if(connssl->io_need) { + what = Curl_conn_cf_poll(cf, data, timeout_ms); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + result = CURLE_RECV_ERROR; + goto out; + } + else if(0 == what) { + /* timeout */ + failf(data, "SSL shutdown timeout"); + result = CURLE_OPERATION_TIMEDOUT; + goto out; + } + /* socket is readable or writable */ + } + } +out: + CF_DATA_RESTORE(cf, save); + cf->shutdown = (result || *done); + return result; +} + CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, - int sockindex) + int sockindex, bool send_shutdown) { struct Curl_cfilter *cf, *head; CURLcode result = CURLE_OK; - (void)data; - head = data->conn? data->conn->cfilter[sockindex] : NULL; + head = data->conn ? data->conn->cfilter[sockindex] : NULL; for(cf = head; cf; cf = cf->next) { if(cf->cft == &Curl_cft_ssl) { - if(Curl_ssl->shut_down(cf, data)) + bool done; + CURL_TRC_CF(data, cf, "shutdown and remove SSL, start"); + Curl_shutdown_start(data, sockindex, NULL); + result = vtls_shutdown_blocking(cf, data, send_shutdown, &done); + Curl_shutdown_clear(data, sockindex); + if(!result && !done) /* blocking failed? */ result = CURLE_SSL_SHUTDOWN_FAILED; Curl_conn_cf_discard_sub(head, cf, data, FALSE); + CURL_TRC_CF(data, cf, "shutdown and remove SSL, done -> %d", result); break; } } return result; } -static struct Curl_cfilter *get_ssl_cf_engaged(struct connectdata *conn, - int sockindex) -{ - struct Curl_cfilter *cf, *lowest_ssl_cf = NULL; - - for(cf = conn->cfilter[sockindex]; cf; cf = cf->next) { - if(cf->cft == &Curl_cft_ssl || cf->cft == &Curl_cft_ssl_proxy) { - lowest_ssl_cf = cf; - if(cf->connected || (cf->next && cf->next->connected)) { - /* connected or about to start */ - return cf; - } - } - } - return lowest_ssl_cf; -} - bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf) { - return (cf->cft == &Curl_cft_ssl_proxy); + return (cf->cft->flags & CF_TYPE_SSL) && (cf->cft->flags & CF_TYPE_PROXY); } struct ssl_config_data * @@ -1903,41 +2188,21 @@ Curl_ssl_cf_get_config(struct Curl_cfilter *cf, struct Curl_easy *data) (void)cf; return &data->set.ssl; #else - return Curl_ssl_cf_is_proxy(cf)? &data->set.proxy_ssl : &data->set.ssl; + return Curl_ssl_cf_is_proxy(cf) ? &data->set.proxy_ssl : &data->set.ssl; #endif } -struct ssl_config_data * -Curl_ssl_get_config(struct Curl_easy *data, int sockindex) -{ - struct Curl_cfilter *cf; - - (void)data; - DEBUGASSERT(data->conn); - cf = get_ssl_cf_engaged(data->conn, sockindex); - return cf? Curl_ssl_cf_get_config(cf, data) : &data->set.ssl; -} - struct ssl_primary_config * Curl_ssl_cf_get_primary_config(struct Curl_cfilter *cf) { #ifdef CURL_DISABLE_PROXY return &cf->conn->ssl_config; #else - return Curl_ssl_cf_is_proxy(cf)? + return Curl_ssl_cf_is_proxy(cf) ? &cf->conn->proxy_ssl_config : &cf->conn->ssl_config; #endif } -struct Curl_cfilter *Curl_ssl_cf_get_ssl(struct Curl_cfilter *cf) -{ - for(; cf; cf = cf->next) { - if(cf->cft == &Curl_cft_ssl || cf->cft == &Curl_cft_ssl_proxy) - return cf; - } - return NULL; -} - CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf, const struct alpn_spec *spec) { @@ -1984,42 +2249,88 @@ CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf, return CURLE_OK; } +bool Curl_alpn_contains_proto(const struct alpn_spec *spec, + const char *proto) +{ + size_t i, plen = proto ? strlen(proto) : 0; + for(i = 0; spec && plen && i < spec->count; ++i) { + size_t slen = strlen(spec->entries[i]); + if((slen == plen) && !memcmp(proto, spec->entries[i], plen)) + return TRUE; + } + return FALSE; +} + CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf, struct Curl_easy *data, + struct ssl_connect_data *connssl, const unsigned char *proto, size_t proto_len) { - int can_multi = 0; + CURLcode result = CURLE_OK; unsigned char *palpn = #ifndef CURL_DISABLE_PROXY - (cf->conn->bits.tunnel_proxy && Curl_ssl_cf_is_proxy(cf))? + (cf->conn->bits.tunnel_proxy && Curl_ssl_cf_is_proxy(cf)) ? &cf->conn->proxy_alpn : &cf->conn->alpn #else &cf->conn->alpn #endif ; + if(connssl->alpn_negotiated) { + /* When we ask for a specific ALPN protocol, we need the confirmation + * of it by the server, as we have installed protocol handler and + * connection filter chain for exactly this protocol. */ + if(!proto_len) { + failf(data, "ALPN: asked for '%s' from previous session, " + "but server did not confirm it. Refusing to continue.", + connssl->alpn_negotiated); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + else if((strlen(connssl->alpn_negotiated) != proto_len) || + memcmp(connssl->alpn_negotiated, proto, proto_len)) { + failf(data, "ALPN: asked for '%s' from previous session, but server " + "selected '%.*s'. Refusing to continue.", + connssl->alpn_negotiated, (int)proto_len, proto); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + /* ALPN is exactly what we asked for, done. */ + infof(data, "ALPN: server confirmed to use '%s'", + connssl->alpn_negotiated); + goto out; + } + + if(proto && proto_len) { + if(memchr(proto, '\0', proto_len)) { + failf(data, "ALPN: server selected protocol contains NUL. " + "Refusing to continue."); + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + connssl->alpn_negotiated = malloc(proto_len + 1); + if(!connssl->alpn_negotiated) + return CURLE_OUT_OF_MEMORY; + memcpy(connssl->alpn_negotiated, proto, proto_len); + connssl->alpn_negotiated[proto_len] = 0; + } + if(proto && proto_len) { if(proto_len == ALPN_HTTP_1_1_LENGTH && !memcmp(ALPN_HTTP_1_1, proto, ALPN_HTTP_1_1_LENGTH)) { *palpn = CURL_HTTP_VERSION_1_1; } - else if(proto_len == ALPN_HTTP_1_0_LENGTH && - !memcmp(ALPN_HTTP_1_0, proto, ALPN_HTTP_1_0_LENGTH)) { - *palpn = CURL_HTTP_VERSION_1_0; - } #ifdef USE_HTTP2 else if(proto_len == ALPN_H2_LENGTH && !memcmp(ALPN_H2, proto, ALPN_H2_LENGTH)) { *palpn = CURL_HTTP_VERSION_2; - can_multi = 1; } #endif #ifdef USE_HTTP3 else if(proto_len == ALPN_H3_LENGTH && !memcmp(ALPN_H3, proto, ALPN_H3_LENGTH)) { *palpn = CURL_HTTP_VERSION_3; - can_multi = 1; } #endif else { @@ -2030,18 +2341,22 @@ CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf, /* return CURLE_NOT_BUILT_IN; */ goto out; } - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, (int)proto_len, proto); + + if(connssl->state == ssl_connection_deferred) + infof(data, VTLS_INFOF_ALPN_DEFERRED, (int)proto_len, proto); + else + infof(data, VTLS_INFOF_ALPN_ACCEPTED, (int)proto_len, proto); } else { *palpn = CURL_HTTP_VERSION_NONE; - infof(data, VTLS_INFOF_NO_ALPN); + if(connssl->state == ssl_connection_deferred) + infof(data, VTLS_INFOF_NO_ALPN_DEFERRED); + else + infof(data, VTLS_INFOF_NO_ALPN); } out: - if(!Curl_ssl_cf_is_proxy(cf)) - Curl_multiuse_state(data, can_multi? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); - return CURLE_OK; + return result; } #endif /* USE_SSL */ diff --git a/deps/curl/lib/vtls/vtls.h b/deps/curl/lib/vtls/vtls.h index 8ad1cf6def9..7a223f6fead 100644 --- a/deps/curl/lib/vtls/vtls.h +++ b/deps/curl/lib/vtls/vtls.h @@ -37,21 +37,26 @@ struct Curl_ssl_session; #define SSLSUPP_HTTPS_PROXY (1<<4) /* supports access via HTTPS proxies */ #define SSLSUPP_TLS13_CIPHERSUITES (1<<5) /* supports TLS 1.3 ciphersuites */ #define SSLSUPP_CAINFO_BLOB (1<<6) +#define SSLSUPP_ECH (1<<7) +#define SSLSUPP_CA_CACHE (1<<8) +#define SSLSUPP_CIPHER_LIST (1<<9) /* supports TLS 1.0-1.2 ciphersuites */ #define ALPN_ACCEPTED "ALPN: server accepted " -#define VTLS_INFOF_NO_ALPN \ +#define VTLS_INFOF_NO_ALPN \ "ALPN: server did not agree on a protocol. Uses default." -#define VTLS_INFOF_ALPN_OFFER_1STR \ +#define VTLS_INFOF_ALPN_OFFER_1STR \ "ALPN: curl offers %s" -#define VTLS_INFOF_ALPN_ACCEPTED_1STR \ - ALPN_ACCEPTED "%s" -#define VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR \ +#define VTLS_INFOF_ALPN_ACCEPTED \ ALPN_ACCEPTED "%.*s" +#define VTLS_INFOF_NO_ALPN_DEFERRED \ + "ALPN: deferred handshake for early data without specific protocol." +#define VTLS_INFOF_ALPN_DEFERRED \ + "ALPN: deferred handshake for early data using '%.*s'." + /* Curl_multi SSL backend-specific data; declared differently by each SSL backend */ -struct multi_ssl_backend_data; struct Curl_cfilter; CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, @@ -65,15 +70,55 @@ CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, #define CURL_SHA256_DIGEST_LENGTH 32 /* fixed size */ #endif -char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen); -bool Curl_ssl_config_matches(struct ssl_primary_config *data, - struct ssl_primary_config *needle); -bool Curl_clone_primary_ssl_config(struct ssl_primary_config *source, - struct ssl_primary_config *dest); -void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc); - curl_sslbackend Curl_ssl_backend(void); +/** + * Init ssl config for a new easy handle. + */ +void Curl_ssl_easy_config_init(struct Curl_easy *data); + +/** + * Init the `data->set.ssl` and `data->set.proxy_ssl` for + * connection matching use. + */ +CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data); + +/** + * Init SSL configs (main + proxy) for a new connection from the easy handle. + */ +CURLcode Curl_ssl_conn_config_init(struct Curl_easy *data, + struct connectdata *conn); + +/** + * Free allocated resources in SSL configs (main + proxy) for + * the given connection. + */ +void Curl_ssl_conn_config_cleanup(struct connectdata *conn); + +/** + * Return TRUE iff SSL configuration from `data` is functionally the + * same as the one on `candidate`. + * @param proxy match the proxy SSL config or the main one + */ +bool Curl_ssl_conn_config_match(struct Curl_easy *data, + struct connectdata *candidate, + bool proxy); + +/* Update certain connection SSL config flags after they have + * been changed on the easy handle. Will work for `verifypeer`, + * `verifyhost` and `verifystatus`. */ +void Curl_ssl_conn_config_update(struct Curl_easy *data, bool for_proxy); + +/** + * Init SSL peer information for filter. Can be called repeatedly. + */ +CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, + struct Curl_cfilter *cf, int transport); +/** + * Free all allocated data and reset peer information. + */ +void Curl_ssl_peer_cleanup(struct ssl_peer *peer); + #ifdef USE_SSL int Curl_ssl_init(void); void Curl_ssl_cleanup(void); @@ -90,6 +135,7 @@ CURLcode Curl_ssl_initsessions(struct Curl_easy *, size_t); void Curl_ssl_version(char *buffer, size_t size); /* Certificate information list handling. */ +#define CURL_X509_STR_MAX 100000 void Curl_ssl_free_certinfo(struct Curl_easy *data); CURLcode Curl_ssl_init_certinfo(struct Curl_easy *data, int num); @@ -140,7 +186,24 @@ bool Curl_ssl_cert_status_request(void); bool Curl_ssl_false_start(struct Curl_easy *data); -void Curl_free_multi_ssl_backend_data(struct multi_ssl_backend_data *mbackend); +/* The maximum size of the SSL channel binding is 85 bytes, as defined in + * RFC 5929, Section 4.1. The 'tls-server-end-point:' prefix is 21 bytes long, + * and SHA-512 is the longest supported hash algorithm, with a digest length of + * 64 bytes. + * The maximum size of the channel binding is therefore 21 + 64 = 85 bytes. + */ +#define SSL_CB_MAX_SIZE 85 + +/* Return the tls-server-end-point channel binding, including the + * 'tls-server-end-point:' prefix. + * If successful, the data is written to the dynbuf, and CURLE_OK is returned. + * The dynbuf MUST HAVE a minimum toobig size of SSL_CB_MAX_SIZE. + * If the dynbuf is too small, CURLE_OUT_OF_MEMORY is returned. + * If channel binding is not supported, binding stays empty and CURLE_OK is + * returned. + */ +CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex, + struct dynbuf *binding); #define SSL_SHUTDOWN_TIMEOUT 10000 /* ms */ @@ -152,31 +215,19 @@ CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data); CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, - int sockindex); + int sockindex, bool send_shutdown); #ifndef CURL_DISABLE_PROXY CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data); #endif /* !CURL_DISABLE_PROXY */ -/** - * Get the SSL configuration that is used on the connection. - * This returns NULL if no SSL is configured. - * Otherwise it returns the config of the first (highest) one that is - * either connected, in handshake or about to start - * (e.g. all filters below it are connected). If SSL filters are present, - * but neither can start operating, return the config of the lowest one - * that will first come into effect when connecting. - */ -struct ssl_config_data *Curl_ssl_get_config(struct Curl_easy *data, - int sockindex); - /** * True iff the underlying SSL implementation supports the option. * Option is one of the defined SSLSUPP_* values. * `data` maybe NULL for the features of the default implementation. */ -bool Curl_ssl_supports(struct Curl_easy *data, int ssl_option); +bool Curl_ssl_supports(struct Curl_easy *data, unsigned int ssl_option); /** * Get the internal ssl instance (like OpenSSL's SSL*) from the filter @@ -188,8 +239,22 @@ bool Curl_ssl_supports(struct Curl_easy *data, int ssl_option); void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex, CURLINFO info, int n); +/** + * Get the ssl_config_data in `data` that is relevant for cfilter `cf`. + */ +struct ssl_config_data *Curl_ssl_cf_get_config(struct Curl_cfilter *cf, + struct Curl_easy *data); + +/** + * Get the primary config relevant for the filter from its connection. + */ +struct ssl_primary_config * + Curl_ssl_cf_get_primary_config(struct Curl_cfilter *cf); + extern struct Curl_cftype Curl_cft_ssl; +#ifndef CURL_DISABLE_PROXY extern struct Curl_cftype Curl_cft_ssl_proxy; +#endif #else /* if not USE_SSL */ @@ -209,8 +274,9 @@ extern struct Curl_cftype Curl_cft_ssl_proxy; #define Curl_ssl_get_internals(a,b,c,d) NULL #define Curl_ssl_supports(a,b) FALSE #define Curl_ssl_cfilter_add(a,b,c) CURLE_NOT_BUILT_IN -#define Curl_ssl_get_config(a,b) NULL -#define Curl_ssl_cfilter_remove(a,b) CURLE_OK +#define Curl_ssl_cfilter_remove(a,b,c) CURLE_OK +#define Curl_ssl_cf_get_config(a,b) NULL +#define Curl_ssl_cf_get_primary_config(a) NULL #endif #endif /* HEADER_CURL_VTLS_H */ diff --git a/deps/curl/lib/vtls/vtls_int.h b/deps/curl/lib/vtls/vtls_int.h index a6e4544a87e..13bd3fbb5e7 100644 --- a/deps/curl/lib/vtls/vtls_int.h +++ b/deps/curl/lib/vtls/vtls_int.h @@ -29,11 +29,11 @@ #ifdef USE_SSL +struct ssl_connect_data; + /* see https://www.iana.org/assignments/tls-extensiontype-values/ */ #define ALPN_HTTP_1_1_LENGTH 8 #define ALPN_HTTP_1_1 "http/1.1" -#define ALPN_HTTP_1_0_LENGTH 8 -#define ALPN_HTTP_1_0 "http/1.0" #define ALPN_H2_LENGTH 2 #define ALPN_H2 "h2" #define ALPN_H3_LENGTH 2 @@ -63,21 +63,62 @@ CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf, CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf, struct Curl_easy *data, + struct ssl_connect_data *connssl, const unsigned char *proto, size_t proto_len); +bool Curl_alpn_contains_proto(const struct alpn_spec *spec, + const char *proto); + +/* enum for the nonblocking SSL connection state machine */ +typedef enum { + ssl_connect_1, + ssl_connect_2, + ssl_connect_3, + ssl_connect_done +} ssl_connect_state; + +typedef enum { + ssl_connection_none, + ssl_connection_deferred, + ssl_connection_negotiating, + ssl_connection_complete +} ssl_connection_state; + +typedef enum { + ssl_earlydata_none, + ssl_earlydata_use, + ssl_earlydata_sending, + ssl_earlydata_sent, + ssl_earlydata_accepted, + ssl_earlydata_rejected +} ssl_earlydata_state; + +#define CURL_SSL_IO_NEED_NONE (0) +#define CURL_SSL_IO_NEED_RECV (1<<0) +#define CURL_SSL_IO_NEED_SEND (1<<1) + +/* Max earlydata payload we want to send */ +#define CURL_SSL_EARLY_MAX (64*1024) + /* Information in each SSL cfilter context: cf->ctx */ struct ssl_connect_data { - ssl_connection_state state; - ssl_connect_state connecting_state; - char *hostname; /* hostname for verification */ - char *dispname; /* display version of hostname */ + struct ssl_peer peer; const struct alpn_spec *alpn; /* ALPN to use or NULL for none */ void *backend; /* vtls backend specific props */ struct cf_call_data call_data; /* data handle used in current call */ struct curltime handshake_done; /* time when handshake finished */ - int port; /* remote port at origin */ + char *alpn_negotiated; /* negotiated ALPN value or NULL */ + struct bufq earlydata; /* earlydata to be send to peer */ + size_t earlydata_max; /* max earlydata allowed by peer */ + size_t earlydata_skip; /* sending bytes to skip when earlydata + * is accepted by peer */ + ssl_connection_state state; + ssl_connect_state connecting_state; + ssl_earlydata_state earlydata_state; + int io_need; /* TLS signals special SEND/RECV needs */ BIT(use_alpn); /* if ALPN shall be used in handshake */ + BIT(peer_closed); /* peer has closed connection */ }; @@ -102,8 +143,8 @@ struct Curl_ssl { size_t (*version)(char *buffer, size_t size); int (*check_cxn)(struct Curl_cfilter *cf, struct Curl_easy *data); - int (*shut_down)(struct Curl_cfilter *cf, - struct Curl_easy *data); + CURLcode (*shut_down)(struct Curl_cfilter *cf, struct Curl_easy *data, + bool send_shutdown, bool *done); bool (*data_pending)(struct Curl_cfilter *cf, const struct Curl_easy *data); @@ -118,18 +159,13 @@ struct Curl_ssl { struct Curl_easy *data, bool *done); - /* If the SSL backend wants to read or write on this connection during a - handshake, set socks[0] to the connection's FIRSTSOCKET, and return - a bitmap indicating read or write with GETSOCK_WRITESOCK(0) or - GETSOCK_READSOCK(0). Otherwise return GETSOCK_BLANK. - Mandatory. */ - int (*get_select_socks)(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks); - + /* During handshake/shutdown, adjust the pollset to include the socket + * for POLLOUT or POLLIN as needed. Mandatory. */ + void (*adjust_pollset)(struct Curl_cfilter *cf, struct Curl_easy *data, + struct easy_pollset *ps); void *(*get_internals)(struct ssl_connect_data *connssl, CURLINFO info); void (*close)(struct Curl_cfilter *cf, struct Curl_easy *data); void (*close_all)(struct Curl_easy *data); - void (*session_free)(void *ptr); CURLcode (*set_engine)(struct Curl_easy *data, const char *engine); CURLcode (*set_engine_default)(struct Curl_easy *data); @@ -142,13 +178,14 @@ struct Curl_ssl { bool (*attach_data)(struct Curl_cfilter *cf, struct Curl_easy *data); void (*detach_data)(struct Curl_cfilter *cf, struct Curl_easy *data); - void (*free_multi_ssl_backend_data)(struct multi_ssl_backend_data *mbackend); - ssize_t (*recv_plain)(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *code); ssize_t (*send_plain)(struct Curl_cfilter *cf, struct Curl_easy *data, const void *mem, size_t len, CURLcode *code); + CURLcode (*get_channel_binding)(struct Curl_easy *data, int sockindex, + struct dynbuf *binding); + }; extern const struct Curl_ssl *Curl_ssl; @@ -156,10 +193,9 @@ extern const struct Curl_ssl *Curl_ssl; int Curl_none_init(void); void Curl_none_cleanup(void); -int Curl_none_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data); +CURLcode Curl_none_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data, + bool send_shutdown, bool *done); int Curl_none_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data); -CURLcode Curl_none_random(struct Curl_easy *data, unsigned char *entropy, - size_t length); void Curl_none_close_all(struct Curl_easy *data); void Curl_none_session_free(void *ptr); bool Curl_none_data_pending(struct Curl_cfilter *cf, @@ -169,25 +205,8 @@ CURLcode Curl_none_set_engine(struct Curl_easy *data, const char *engine); CURLcode Curl_none_set_engine_default(struct Curl_easy *data); struct curl_slist *Curl_none_engines_list(struct Curl_easy *data); bool Curl_none_false_start(void); -int Curl_ssl_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks); - -/** - * Get the ssl_config_data in `data` that is relevant for cfilter `cf`. - */ -struct ssl_config_data *Curl_ssl_cf_get_config(struct Curl_cfilter *cf, - struct Curl_easy *data); - -/** - * Get the primary config relevant for the filter from its connection. - */ -struct ssl_primary_config * - Curl_ssl_cf_get_primary_config(struct Curl_cfilter *cf); - -/** - * Get the first SSL filter in the chain starting with `cf`, or NULL. - */ -struct Curl_cfilter *Curl_ssl_cf_get_ssl(struct Curl_cfilter *cf); +void Curl_ssl_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, + struct easy_pollset *ps); /** * Get the SSL filter below the given one or NULL if there is none. @@ -199,30 +218,37 @@ bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf); * Caller must make sure that the ownership of returned sessionid object * is properly taken (e.g. its refcount is incremented * under sessionid mutex). + * @param cf the connection filter wanting to use it + * @param data the transfer involved + * @param peer the peer the filter wants to talk to + * @param sessionid on return the TLS session + * @param idsize on return the size of the TLS session data + * @param palpn on return the ALPN string used by the session, + * set to NULL when not interested */ bool Curl_ssl_getsessionid(struct Curl_cfilter *cf, struct Curl_easy *data, + const struct ssl_peer *peer, void **ssl_sessionid, - size_t *idsize); /* set 0 if unknown */ -/* add a new session ID + size_t *idsize, /* set 0 if unknown */ + char **palpn); + +/* Set a TLS session ID for `peer`. Replaces an existing session ID if + * not already the same. * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). + * Call takes ownership of `ssl_sessionid`, using `sessionid_free_cb` + * to deallocate it. Is called in all outcomes, either right away or + * later when the session cache is cleaned up. * Caller must ensure that it has properly shared ownership of this sessionid * object with cache (e.g. incrementing refcount on success) */ -CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf, - struct Curl_easy *data, - void *ssl_sessionid, - size_t idsize, - bool *added); - -#include "openssl.h" /* OpenSSL versions */ -#include "gtls.h" /* GnuTLS versions */ -#include "wolfssl.h" /* wolfSSL versions */ -#include "schannel.h" /* Schannel SSPI version */ -#include "sectransp.h" /* SecureTransport (Darwin) version */ -#include "mbedtls.h" /* mbedTLS versions */ -#include "bearssl.h" /* BearSSL versions */ -#include "rustls.h" /* rustls versions */ +CURLcode Curl_ssl_set_sessionid(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct ssl_peer *peer, + const char *alpn, + void *sessionid, + size_t sessionid_size, + Curl_ssl_sessionid_dtor *sessionid_free_cb); #endif /* USE_SSL */ diff --git a/deps/curl/lib/vtls/wolfssl.c b/deps/curl/lib/vtls/wolfssl.c index 5f157207428..0d74b3e763d 100644 --- a/deps/curl/lib/vtls/wolfssl.c +++ b/deps/curl/lib/vtls/wolfssl.c @@ -36,6 +36,10 @@ #include #include +#if LIBWOLFSSL_VERSION_HEX < 0x03004006 /* wolfSSL 3.4.6 (2015) */ +#error "wolfSSL version should be at least 3.4.6" +#endif + /* To determine what functions are available we rely on one or both of: - the user's options.h generated by wolfSSL - the symbols detected by curl's configure @@ -74,6 +78,14 @@ #include "curl_memory.h" #include "memdebug.h" +#ifdef USE_ECH +# include "curl_base64.h" +# define ECH_ENABLED(__data__) \ + (__data__->set.tls_ech && \ + !(__data__->set.tls_ech & CURLECH_DISABLE)\ + ) +#endif /* USE_ECH */ + /* KEEP_PEER_CERT is a product of the presence of build time symbol OPENSSL_EXTRA without NO_CERTS, depending on the version. KEEP_PEER_CERT is in wolfSSL's settings.h, and the latter two are build time symbols in @@ -85,23 +97,33 @@ #endif #endif -#if defined(HAVE_WOLFSSL_FULL_BIO) && HAVE_WOLFSSL_FULL_BIO +#ifdef HAVE_WOLFSSL_BIO #define USE_BIO_CHAIN -#else +#ifdef HAVE_WOLFSSL_FULL_BIO +#define USE_FULL_BIO +#else /* HAVE_WOLFSSL_FULL_BIO */ +#undef USE_FULL_BIO +#endif +/* wolfSSL 5.7.4 and older do not have these symbols, but only the + * OpenSSL ones. */ +#ifndef WOLFSSL_BIO_CTRL_GET_CLOSE +#define WOLFSSL_BIO_CTRL_GET_CLOSE BIO_CTRL_GET_CLOSE +#define WOLFSSL_BIO_CTRL_SET_CLOSE BIO_CTRL_SET_CLOSE +#define WOLFSSL_BIO_CTRL_FLUSH BIO_CTRL_FLUSH +#define WOLFSSL_BIO_CTRL_DUP BIO_CTRL_DUP +#define wolfSSL_BIO_set_retry_write BIO_set_retry_write +#define wolfSSL_BIO_set_retry_read BIO_set_retry_read +#endif /* !WOLFSSL_BIO_CTRL_GET_CLOSE */ + +#else /* HAVE_WOLFSSL_BIO */ #undef USE_BIO_CHAIN #endif -struct wolfssl_ssl_backend_data { - WOLFSSL_CTX *ctx; - WOLFSSL *handle; - CURLcode io_result; /* result of last BIO cfilter operation */ -}; - #ifdef OPENSSL_EXTRA /* * Availability note: * The TLS 1.3 secret callback (wolfSSL_set_tls13_secret_cb) was added in - * WolfSSL 4.4.0, but requires the -DHAVE_SECRET_CALLBACK build option. If that + * wolfSSL 4.4.0, but requires the -DHAVE_SECRET_CALLBACK build option. If that * option is not set, then TLS 1.3 will not be logged. * For TLS 1.2 and before, we use wolfSSL_get_keys(). * SSL_get_client_random and wolfSSL_get_keys require OPENSSL_EXTRA @@ -157,7 +179,7 @@ wolfssl_tls13_secret_callback(SSL *ssl, int id, const unsigned char *secret, #endif /* defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) */ static void -wolfssl_log_tls12_secret(SSL *ssl) +wolfssl_log_tls12_secret(WOLFSSL *ssl) { unsigned char *ms, *sr, *cr; unsigned int msLen, srLen, crLen, i, x = 0; @@ -181,7 +203,7 @@ wolfssl_log_tls12_secret(SSL *ssl) #endif if(wolfSSL_get_keys(ssl, &ms, &msLen, &sr, &srLen, &cr, &crLen) != - SSL_SUCCESS) { + WOLFSSL_SUCCESS) { return; } @@ -199,18 +221,18 @@ wolfssl_log_tls12_secret(SSL *ssl) } #endif /* OPENSSL_EXTRA */ -static int do_file_type(const char *type) +static int wolfssl_do_file_type(const char *type) { if(!type || !type[0]) - return SSL_FILETYPE_PEM; + return WOLFSSL_FILETYPE_PEM; if(strcasecompare(type, "PEM")) - return SSL_FILETYPE_PEM; + return WOLFSSL_FILETYPE_PEM; if(strcasecompare(type, "DER")) - return SSL_FILETYPE_ASN1; + return WOLFSSL_FILETYPE_ASN1; return -1; } -#ifdef HAVE_LIBOQS +#ifdef WOLFSSL_HAVE_KYBER struct group_name_map { const word16 group; const char *name; @@ -229,45 +251,53 @@ static const struct group_name_map gnm[] = { #ifdef USE_BIO_CHAIN -static int bio_cf_create(WOLFSSL_BIO *bio) +static int wolfssl_bio_cf_create(WOLFSSL_BIO *bio) { +#ifdef USE_FULL_BIO wolfSSL_BIO_set_shutdown(bio, 1); - wolfSSL_BIO_set_init(bio, 1); +#endif wolfSSL_BIO_set_data(bio, NULL); return 1; } -static int bio_cf_destroy(WOLFSSL_BIO *bio) +static int wolfssl_bio_cf_destroy(WOLFSSL_BIO *bio) { if(!bio) return 0; return 1; } -static long bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) +static long wolfssl_bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) { - struct Curl_cfilter *cf = BIO_get_data(bio); + struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio); long ret = 1; (void)cf; (void)ptr; + (void)num; switch(cmd) { - case BIO_CTRL_GET_CLOSE: + case WOLFSSL_BIO_CTRL_GET_CLOSE: +#ifdef USE_FULL_BIO ret = (long)wolfSSL_BIO_get_shutdown(bio); +#else + ret = 0; +#endif break; - case BIO_CTRL_SET_CLOSE: + case WOLFSSL_BIO_CTRL_SET_CLOSE: +#ifdef USE_FULL_BIO wolfSSL_BIO_set_shutdown(bio, (int)num); +#endif break; - case BIO_CTRL_FLUSH: + case WOLFSSL_BIO_CTRL_FLUSH: /* we do no delayed writes, but if we ever would, this * needs to trigger it. */ ret = 1; break; - case BIO_CTRL_DUP: + case WOLFSSL_BIO_CTRL_DUP: ret = 1; break; -#ifdef BIO_CTRL_EOF - case BIO_CTRL_EOF: +#ifdef WOLFSSL_BIO_CTRL_EOF + case WOLFSSL_BIO_CTRL_EOF: /* EOF has been reached on input? */ return (!cf->next || !cf->next->connected); #endif @@ -278,33 +308,51 @@ static long bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) return ret; } -static int bio_cf_out_write(WOLFSSL_BIO *bio, const char *buf, int blen) +static int wolfssl_bio_cf_out_write(WOLFSSL_BIO *bio, + const char *buf, int blen) { struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio); struct ssl_connect_data *connssl = cf->ctx; - struct wolfssl_ssl_backend_data *backend = - (struct wolfssl_ssl_backend_data *)connssl->backend; + struct wolfssl_ctx *backend = + (struct wolfssl_ctx *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); - ssize_t nwritten; + ssize_t nwritten, skiplen = 0; CURLcode result = CURLE_OK; DEBUGASSERT(data); - nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result); + if(backend->shutting_down && backend->io_send_blocked_len && + (backend->io_send_blocked_len < blen)) { + /* bug in wolfSSL: + * It adds the close notify message again every time we retry + * sending during shutdown. */ + CURL_TRC_CF(data, cf, "bio_write, shutdown restrict send of %d" + " to %d bytes", blen, backend->io_send_blocked_len); + skiplen = (ssize_t)(blen - backend->io_send_blocked_len); + blen = backend->io_send_blocked_len; + } + nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, FALSE, &result); backend->io_result = result; CURL_TRC_CF(data, cf, "bio_write(len=%d) -> %zd, %d", blen, nwritten, result); +#ifdef USE_FULL_BIO wolfSSL_BIO_clear_retry_flags(bio); - if(nwritten < 0 && CURLE_AGAIN == result) - BIO_set_retry_write(bio); +#endif + if(nwritten < 0 && CURLE_AGAIN == result) { + wolfSSL_BIO_set_retry_write(bio); + if(backend->shutting_down && !backend->io_send_blocked_len) + backend->io_send_blocked_len = blen; + } + else if(!result && skiplen) + nwritten += skiplen; return (int)nwritten; } -static int bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen) +static int wolfssl_bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen) { struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio); struct ssl_connect_data *connssl = cf->ctx; - struct wolfssl_ssl_backend_data *backend = - (struct wolfssl_ssl_backend_data *)connssl->backend; + struct wolfssl_ctx *backend = + (struct wolfssl_ctx *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result = CURLE_OK; @@ -317,36 +365,475 @@ static int bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen) nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); backend->io_result = result; CURL_TRC_CF(data, cf, "bio_read(len=%d) -> %zd, %d", blen, nread, result); +#ifdef USE_FULL_BIO wolfSSL_BIO_clear_retry_flags(bio); +#endif if(nread < 0 && CURLE_AGAIN == result) - BIO_set_retry_read(bio); + wolfSSL_BIO_set_retry_read(bio); + else if(nread == 0) + connssl->peer_closed = TRUE; return (int)nread; } -static WOLFSSL_BIO_METHOD *bio_cf_method = NULL; +static WOLFSSL_BIO_METHOD *wolfssl_bio_cf_method = NULL; -static void bio_cf_init_methods(void) +static void wolfssl_bio_cf_init_methods(void) { - bio_cf_method = wolfSSL_BIO_meth_new(BIO_TYPE_MEM, "wolfSSL CF BIO"); - wolfSSL_BIO_meth_set_write(bio_cf_method, &bio_cf_out_write); - wolfSSL_BIO_meth_set_read(bio_cf_method, &bio_cf_in_read); - wolfSSL_BIO_meth_set_ctrl(bio_cf_method, &bio_cf_ctrl); - wolfSSL_BIO_meth_set_create(bio_cf_method, &bio_cf_create); - wolfSSL_BIO_meth_set_destroy(bio_cf_method, &bio_cf_destroy); + wolfssl_bio_cf_method = wolfSSL_BIO_meth_new(WOLFSSL_BIO_MEMORY, + "wolfSSL CF BIO"); + wolfSSL_BIO_meth_set_write(wolfssl_bio_cf_method, &wolfssl_bio_cf_out_write); + wolfSSL_BIO_meth_set_read(wolfssl_bio_cf_method, &wolfssl_bio_cf_in_read); + wolfSSL_BIO_meth_set_ctrl(wolfssl_bio_cf_method, &wolfssl_bio_cf_ctrl); + wolfSSL_BIO_meth_set_create(wolfssl_bio_cf_method, &wolfssl_bio_cf_create); + wolfSSL_BIO_meth_set_destroy(wolfssl_bio_cf_method, &wolfssl_bio_cf_destroy); } -static void bio_cf_free_methods(void) +static void wolfssl_bio_cf_free_methods(void) { - wolfSSL_BIO_meth_free(bio_cf_method); + wolfSSL_BIO_meth_free(wolfssl_bio_cf_method); } #else /* USE_BIO_CHAIN */ -#define bio_cf_init_methods() Curl_nop_stmt -#define bio_cf_free_methods() Curl_nop_stmt +#define wolfssl_bio_cf_init_methods() Curl_nop_stmt +#define wolfssl_bio_cf_free_methods() Curl_nop_stmt #endif /* !USE_BIO_CHAIN */ +static void wolfssl_session_free(void *sdata, size_t slen) +{ + (void)slen; + free(sdata); +} + +CURLcode wssl_cache_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + WOLFSSL_SESSION *session) +{ + CURLcode result = CURLE_OK; + unsigned char *sdata = NULL; + unsigned int slen; + + if(!session) + goto out; + + slen = wolfSSL_i2d_SSL_SESSION(session, NULL); + if(slen <= 0) { + CURL_TRC_CF(data, cf, "fail to assess session length: %u", slen); + result = CURLE_FAILED_INIT; + goto out; + } + sdata = calloc(1, slen); + if(!sdata) { + failf(data, "unable to allocate session buffer of %u bytes", slen); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + slen = wolfSSL_i2d_SSL_SESSION(session, &sdata); + if(slen <= 0) { + CURL_TRC_CF(data, cf, "fail to serialize session: %u", slen); + result = CURLE_FAILED_INIT; + goto out; + } + + Curl_ssl_sessionid_lock(data); + result = Curl_ssl_set_sessionid(cf, data, peer, NULL, + sdata, slen, wolfssl_session_free); + Curl_ssl_sessionid_unlock(data); + if(result) + failf(data, "failed to add new ssl session to cache (%d)", result); + else { + CURL_TRC_CF(data, cf, "added new session to cache"); + sdata = NULL; + } + +out: + free(sdata); + return 0; +} + +static int wssl_vtls_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) +{ + struct Curl_cfilter *cf; + + cf = (struct Curl_cfilter*)wolfSSL_get_app_data(ssl); + DEBUGASSERT(cf != NULL); + if(cf && session) { + struct ssl_connect_data *connssl = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + DEBUGASSERT(connssl); + DEBUGASSERT(data); + if(connssl && data) { + (void)wssl_cache_session(cf, data, &connssl->peer, session); + } + } + return 0; +} + +CURLcode wssl_setup_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wolfssl_ctx *wss, + struct ssl_peer *peer) +{ + void *psdata; + const unsigned char *sdata = NULL; + size_t slen = 0; + CURLcode result = CURLE_OK; + + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, peer, &psdata, &slen, NULL)) { + WOLFSSL_SESSION *session; + sdata = psdata; + session = wolfSSL_d2i_SSL_SESSION(NULL, &sdata, (long)slen); + if(session) { + int ret = wolfSSL_set_session(wss->handle, session); + if(ret != WOLFSSL_SUCCESS) { + Curl_ssl_delsessionid(data, psdata); + infof(data, "previous session not accepted (%d), " + "removing from cache", ret); + } + else + infof(data, "SSL reusing session ID"); + wolfSSL_SESSION_free(session); + } + else { + failf(data, "could not decode previous session"); + } + } + Curl_ssl_sessionid_unlock(data); + return result; +} + +static CURLcode populate_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + WOLFSSL_X509_STORE *store, + struct wolfssl_ctx *wssl) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + const char * const ssl_cafile = + /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ + (ca_info_blob ? NULL : conn_config->CAfile); + const char * const ssl_capath = conn_config->CApath; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + bool imported_native_ca = FALSE; + +#if !defined(NO_FILESYSTEM) && defined(WOLFSSL_SYS_CA_CERTS) + /* load native CA certificates */ + if(ssl_config->native_ca_store) { + if(wolfSSL_CTX_load_system_CA_certs(wssl->ctx) != WOLFSSL_SUCCESS) { + infof(data, "error importing native CA store, continuing anyway"); + } + else { + imported_native_ca = TRUE; + infof(data, "successfully imported native CA store"); + wssl->x509_store_setup = TRUE; + } + } +#endif /* !NO_FILESYSTEM */ + + /* load certificate blob */ + if(ca_info_blob) { + if(wolfSSL_CTX_load_verify_buffer(wssl->ctx, ca_info_blob->data, + (long)ca_info_blob->len, + WOLFSSL_FILETYPE_PEM) != + WOLFSSL_SUCCESS) { + if(imported_native_ca) { + infof(data, "error importing CA certificate blob, continuing anyway"); + } + else { + failf(data, "error importing CA certificate blob"); + return CURLE_SSL_CACERT_BADFILE; + } + } + else { + infof(data, "successfully imported CA certificate blob"); + wssl->x509_store_setup = TRUE; + } + } + +#ifndef NO_FILESYSTEM + /* load trusted cacert from file if not blob */ + + CURL_TRC_CF(data, cf, "populate_x509_store, path=%s, blob=%d", + ssl_cafile ? ssl_cafile : "none", !!ca_info_blob); + if(!store) + return CURLE_OUT_OF_MEMORY; + + if((ssl_cafile || ssl_capath) && (!wssl->x509_store_setup)) { + int rc = + wolfSSL_CTX_load_verify_locations_ex(wssl->ctx, + ssl_cafile, + ssl_capath, + WOLFSSL_LOAD_FLAG_IGNORE_ERR); + if(WOLFSSL_SUCCESS != rc) { + if(conn_config->verifypeer) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate verify locations:" + " CAfile: %s CApath: %s", + ssl_cafile ? ssl_cafile : "none", + ssl_capath ? ssl_capath : "none"); + return CURLE_SSL_CACERT_BADFILE; + } + else { + /* Just continue with a warning if no strict certificate + verification is required. */ + infof(data, "error setting certificate verify locations," + " continuing anyway:"); + } + } + else { + /* Everything is fine. */ + infof(data, "successfully set certificate verify locations:"); + } + infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); + infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); + } +#endif + (void)store; + wssl->x509_store_setup = TRUE; + return CURLE_OK; +} + +/* key to use at `multi->proto_hash` */ +#define MPROTO_WSSL_X509_KEY "tls:wssl:x509:share" + +struct wssl_x509_share { + char *CAfile; /* CAfile path used to generate X509 store */ + WOLFSSL_X509_STORE *store; /* cached X509 store or NULL if none */ + struct curltime time; /* when the cached store was created */ +}; + +static void wssl_x509_share_free(void *key, size_t key_len, void *p) +{ + struct wssl_x509_share *share = p; + DEBUGASSERT(key_len == (sizeof(MPROTO_WSSL_X509_KEY)-1)); + DEBUGASSERT(!memcmp(MPROTO_WSSL_X509_KEY, key, key_len)); + (void)key; + (void)key_len; + if(share->store) { + wolfSSL_X509_STORE_free(share->store); + } + free(share->CAfile); + free(share); +} + +static bool +cached_x509_store_expired(const struct Curl_easy *data, + const struct wssl_x509_share *mb) +{ + const struct ssl_general_config *cfg = &data->set.general_ssl; + struct curltime now = Curl_now(); + timediff_t elapsed_ms = Curl_timediff(now, mb->time); + timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + + if(timeout_ms < 0) + return FALSE; + + return elapsed_ms >= timeout_ms; +} + +static bool +cached_x509_store_different(struct Curl_cfilter *cf, + const struct wssl_x509_share *mb) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!mb->CAfile || !conn_config->CAfile) + return mb->CAfile != conn_config->CAfile; + + return strcmp(mb->CAfile, conn_config->CAfile); +} + +static WOLFSSL_X509_STORE *get_cached_x509_store(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct Curl_multi *multi = data->multi; + struct wssl_x509_share *share; + WOLFSSL_X509_STORE *store = NULL; + + DEBUGASSERT(multi); + share = multi ? Curl_hash_pick(&multi->proto_hash, + (void *)MPROTO_WSSL_X509_KEY, + sizeof(MPROTO_WSSL_X509_KEY)-1) : NULL; + if(share && share->store && + !cached_x509_store_expired(data, share) && + !cached_x509_store_different(cf, share)) { + store = share->store; + } + + return store; +} + +static void set_cached_x509_store(struct Curl_cfilter *cf, + const struct Curl_easy *data, + WOLFSSL_X509_STORE *store) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct Curl_multi *multi = data->multi; + struct wssl_x509_share *share; + + DEBUGASSERT(multi); + if(!multi) + return; + share = Curl_hash_pick(&multi->proto_hash, + (void *)MPROTO_WSSL_X509_KEY, + sizeof(MPROTO_WSSL_X509_KEY)-1); + + if(!share) { + share = calloc(1, sizeof(*share)); + if(!share) + return; + if(!Curl_hash_add2(&multi->proto_hash, + (void *)MPROTO_WSSL_X509_KEY, + sizeof(MPROTO_WSSL_X509_KEY)-1, + share, wssl_x509_share_free)) { + free(share); + return; + } + } + + if(wolfSSL_X509_STORE_up_ref(store)) { + char *CAfile = NULL; + + if(conn_config->CAfile) { + CAfile = strdup(conn_config->CAfile); + if(!CAfile) { + wolfSSL_X509_STORE_free(store); + return; + } + } + + if(share->store) { + wolfSSL_X509_STORE_free(share->store); + free(share->CAfile); + } + + share->time = Curl_now(); + share->store = store; + share->CAfile = CAfile; + } +} + +CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wolfssl_ctx *wssl) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + CURLcode result = CURLE_OK; + WOLFSSL_X509_STORE *cached_store; + bool cache_criteria_met; + + /* Consider the X509 store cacheable if it comes exclusively from a CAfile, + or no source is provided and we are falling back to wolfSSL's built-in + default. */ + cache_criteria_met = (data->set.general_ssl.ca_cache_timeout != 0) && + conn_config->verifypeer && + !conn_config->CApath && + !conn_config->ca_info_blob && + !ssl_config->primary.CRLfile && + !ssl_config->native_ca_store; + + cached_store = cache_criteria_met ? get_cached_x509_store(cf, data) : NULL; + if(cached_store && wolfSSL_CTX_get_cert_store(wssl->ctx) == cached_store) { + /* The cached store is already in use, do nothing. */ + } + else if(cached_store && wolfSSL_X509_STORE_up_ref(cached_store)) { + wolfSSL_CTX_set_cert_store(wssl->ctx, cached_store); + } + else if(cache_criteria_met) { + /* wolfSSL's initial store in CTX is not shareable by default. + * Make a new one, suitable for adding to the cache. See #14278 */ + WOLFSSL_X509_STORE *store = wolfSSL_X509_STORE_new(); + if(!store) { + failf(data, "SSL: could not create a X509 store"); + return CURLE_OUT_OF_MEMORY; + } + wolfSSL_CTX_set_cert_store(wssl->ctx, store); + + result = populate_x509_store(cf, data, store, wssl); + if(!result) { + set_cached_x509_store(cf, data, store); + } + } + else { + /* We never share the CTX's store, use it. */ + WOLFSSL_X509_STORE *store = wolfSSL_CTX_get_cert_store(wssl->ctx); + result = populate_x509_store(cf, data, store, wssl); + } + + return result; +} + +#ifdef WOLFSSL_TLS13 +static CURLcode +wssl_add_default_ciphers(bool tls13, struct dynbuf *buf) +{ + int i; + char *str; + + for(i = 0; (str = wolfSSL_get_cipher_list(i)); i++) { + size_t n; + if((strncmp(str, "TLS13", 5) == 0) != tls13) + continue; + + /* if there already is data in the string, add colon separator */ + if(Curl_dyn_len(buf)) { + CURLcode result = Curl_dyn_addn(buf, ":", 1); + if(result) + return result; + } + + n = strlen(str); + if(Curl_dyn_addn(buf, str, n)) + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} +#endif + +/* 4.2.0 (2019) */ +#if LIBWOLFSSL_VERSION_HEX < 0x04002000 || !defined(OPENSSL_EXTRA) +static int +wssl_legacy_CTX_set_min_proto_version(WOLFSSL_CTX* ctx, int version) +{ + int res; + switch(version) { + default: + case TLS1_VERSION: + res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1); + if(res == WOLFSSL_SUCCESS) + return res; + FALLTHROUGH(); + case TLS1_1_VERSION: + res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1_1); + if(res == WOLFSSL_SUCCESS) + return res; + FALLTHROUGH(); + case TLS1_2_VERSION: + res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1_2); +#ifdef WOLFSSL_TLS13 + if(res == WOLFSSL_SUCCESS) + return res; + FALLTHROUGH(); + case TLS1_3_VERSION: + res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1_3); +#endif + } + return res; +} +static int +wssl_legacy_CTX_set_max_proto_version(WOLFSSL_CTX* ctx, int version) +{ + (void) ctx, (void) version; + return WOLFSSL_NOT_IMPLEMENTED; +} +#define wolfSSL_CTX_set_min_proto_version wssl_legacy_CTX_set_min_proto_version +#define wolfSSL_CTX_set_max_proto_version wssl_legacy_CTX_set_max_proto_version +#endif + /* * This function loads all the client/CA certificates and CRLs. Setup the TLS * layer and do all necessary magic. @@ -354,241 +841,249 @@ static void bio_cf_free_methods(void) static CURLcode wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) { - char *ciphers, *curves; + int res; + char *curves; struct ssl_connect_data *connssl = cf->ctx; - struct wolfssl_ssl_backend_data *backend = - (struct wolfssl_ssl_backend_data *)connssl->backend; + struct wolfssl_ctx *backend = + (struct wolfssl_ctx *)connssl->backend; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; const struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); WOLFSSL_METHOD* req_method = NULL; -#ifdef HAVE_LIBOQS - word16 oqsAlg = 0; +#ifdef WOLFSSL_HAVE_KYBER + word16 pqkem = 0; size_t idx = 0; #endif -#ifdef HAVE_SNI - bool sni = FALSE; -#define use_sni(x) sni = (x) -#else -#define use_sni(x) Curl_nop_stmt -#endif - bool imported_native_ca = false; - bool imported_ca_info_blob = false; DEBUGASSERT(backend); if(connssl->state == ssl_connection_complete) return CURLE_OK; - if(conn_config->version_max != CURL_SSLVERSION_MAX_NONE) { - failf(data, "wolfSSL does not support to set maximum SSL/TLS version"); - return CURLE_SSL_CONNECT_ERROR; +#if LIBWOLFSSL_VERSION_HEX < 0x04002000 /* 4.2.0 (2019) */ + req_method = wolfSSLv23_client_method(); +#else + req_method = wolfTLS_client_method(); +#endif + if(!req_method) { + failf(data, "wolfSSL: could not create a client method"); + return CURLE_OUT_OF_MEMORY; + } + + if(backend->ctx) + wolfSSL_CTX_free(backend->ctx); + + backend->ctx = wolfSSL_CTX_new(req_method); + if(!backend->ctx) { + failf(data, "wolfSSL: could not create a context"); + return CURLE_OUT_OF_MEMORY; } - /* check to see if we've been told to use an explicit SSL/TLS version */ switch(conn_config->version) { case CURL_SSLVERSION_DEFAULT: case CURL_SSLVERSION_TLSv1: -#if LIBWOLFSSL_VERSION_HEX >= 0x03003000 /* >= 3.3.0 */ - /* minimum protocol version is set later after the CTX object is created */ - req_method = SSLv23_client_method(); -#else - infof(data, "wolfSSL <3.3.0 cannot be configured to use TLS 1.0-1.2, " - "TLS 1.0 is used exclusively"); - req_method = TLSv1_client_method(); -#endif - use_sni(TRUE); - break; case CURL_SSLVERSION_TLSv1_0: -#if defined(WOLFSSL_ALLOW_TLSV10) && !defined(NO_OLD_TLS) - req_method = TLSv1_client_method(); - use_sni(TRUE); -#else - failf(data, "wolfSSL does not support TLS 1.0"); - return CURLE_NOT_BUILT_IN; -#endif + res = wolfSSL_CTX_set_min_proto_version(backend->ctx, TLS1_VERSION); break; case CURL_SSLVERSION_TLSv1_1: -#ifndef NO_OLD_TLS - req_method = TLSv1_1_client_method(); - use_sni(TRUE); -#else - failf(data, "wolfSSL does not support TLS 1.1"); - return CURLE_NOT_BUILT_IN; -#endif + res = wolfSSL_CTX_set_min_proto_version(backend->ctx, TLS1_1_VERSION); break; case CURL_SSLVERSION_TLSv1_2: -#ifndef WOLFSSL_NO_TLS12 - req_method = TLSv1_2_client_method(); - use_sni(TRUE); -#else - failf(data, "wolfSSL does not support TLS 1.2"); - return CURLE_NOT_BUILT_IN; -#endif + res = wolfSSL_CTX_set_min_proto_version(backend->ctx, TLS1_2_VERSION); break; - case CURL_SSLVERSION_TLSv1_3: #ifdef WOLFSSL_TLS13 - req_method = wolfTLSv1_3_client_method(); - use_sni(TRUE); + case CURL_SSLVERSION_TLSv1_3: + res = wolfSSL_CTX_set_min_proto_version(backend->ctx, TLS1_3_VERSION); break; -#else - failf(data, "wolfSSL: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; #endif default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + failf(data, "wolfSSL: unsupported minimum TLS version value"); return CURLE_SSL_CONNECT_ERROR; } - - if(!req_method) { - failf(data, "SSL: couldn't create a method"); - return CURLE_OUT_OF_MEMORY; - } - - if(backend->ctx) - wolfSSL_CTX_free(backend->ctx); - backend->ctx = wolfSSL_CTX_new(req_method); - - if(!backend->ctx) { - failf(data, "SSL: couldn't create a context"); - return CURLE_OUT_OF_MEMORY; + if(res != WOLFSSL_SUCCESS) { + failf(data, "wolfSSL: failed set the minimum TLS version"); + return CURLE_SSL_CONNECT_ERROR; } - switch(conn_config->version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: -#if LIBWOLFSSL_VERSION_HEX > 0x03004006 /* > 3.4.6 */ - /* Versions 3.3.0 to 3.4.6 we know the minimum protocol version is - * whatever minimum version of TLS was built in and at least TLS 1.0. For - * later library versions that could change (eg TLS 1.0 built in but - * defaults to TLS 1.1) so we have this short circuit evaluation to find - * the minimum supported TLS version. - */ - if((wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1) != 1) && - (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_1) != 1) && - (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_2) != 1) + switch(conn_config->version_max) { #ifdef WOLFSSL_TLS13 - && (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_3) != 1) -#endif - ) { - failf(data, "SSL: couldn't set the minimum protocol version"); - return CURLE_SSL_CONNECT_ERROR; - } + case CURL_SSLVERSION_MAX_TLSv1_3: + res = wolfSSL_CTX_set_max_proto_version(backend->ctx, TLS1_3_VERSION); + break; #endif + case CURL_SSLVERSION_MAX_TLSv1_2: + res = wolfSSL_CTX_set_max_proto_version(backend->ctx, TLS1_2_VERSION); + break; + case CURL_SSLVERSION_MAX_TLSv1_1: + res = wolfSSL_CTX_set_max_proto_version(backend->ctx, TLS1_1_VERSION); + break; + case CURL_SSLVERSION_MAX_TLSv1_0: + res = wolfSSL_CTX_set_max_proto_version(backend->ctx, TLS1_VERSION); + break; + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + res = WOLFSSL_SUCCESS; break; + default: + failf(data, "wolfSSL: unsupported maximum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } + if(res != WOLFSSL_SUCCESS) { + failf(data, "wolfSSL: failed set the maximum TLS version"); + return CURLE_SSL_CONNECT_ERROR; + } + +#ifndef WOLFSSL_TLS13 + { + char *ciphers = conn_config->cipher_list; + if(ciphers) { + if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) { + failf(data, "failed setting cipher list: %s", ciphers); + return CURLE_SSL_CIPHER; + } + infof(data, "Cipher selection: %s", ciphers); + } } +#else +#define MAX_CIPHER_LEN 4096 + if(conn_config->cipher_list || conn_config->cipher_list13) { + const char *ciphers12 = conn_config->cipher_list; + const char *ciphers13 = conn_config->cipher_list13; + struct dynbuf c; + CURLcode result; + Curl_dyn_init(&c, MAX_CIPHER_LEN); + + if(ciphers13) + result = Curl_dyn_add(&c, ciphers13); + else + result = wssl_add_default_ciphers(TRUE, &c); + + if(!result) { + if(ciphers12) { + if(Curl_dyn_len(&c)) + result = Curl_dyn_addn(&c, ":", 1); + if(!result) + result = Curl_dyn_add(&c, ciphers12); + } + else + result = wssl_add_default_ciphers(FALSE, &c); + } + if(result) + return result; - ciphers = conn_config->cipher_list; - if(ciphers) { - if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) { - failf(data, "failed setting cipher list: %s", ciphers); + if(!wolfSSL_CTX_set_cipher_list(backend->ctx, Curl_dyn_ptr(&c))) { + failf(data, "failed setting cipher list: %s", Curl_dyn_ptr(&c)); + Curl_dyn_free(&c); return CURLE_SSL_CIPHER; } - infof(data, "Cipher selection: %s", ciphers); + infof(data, "Cipher selection: %s", Curl_dyn_ptr(&c)); + Curl_dyn_free(&c); } +#endif curves = conn_config->curves; if(curves) { -#ifdef HAVE_LIBOQS +#ifdef WOLFSSL_HAVE_KYBER for(idx = 0; gnm[idx].name != NULL; idx++) { if(strncmp(curves, gnm[idx].name, strlen(gnm[idx].name)) == 0) { - oqsAlg = gnm[idx].group; + pqkem = gnm[idx].group; break; } } - if(oqsAlg == 0) + if(pqkem == 0) #endif { - if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) { + if(!wolfSSL_CTX_set1_curves_list(backend->ctx, curves)) { failf(data, "failed setting curves list: '%s'", curves); return CURLE_SSL_CIPHER; } } } + /* Load the client certificate, and private key */ #ifndef NO_FILESYSTEM - /* load native CA certificates */ - if(ssl_config->native_ca_store) { - if(wolfSSL_CTX_load_system_CA_certs(backend->ctx) != WOLFSSL_SUCCESS) { - infof(data, "error importing native CA store, continuing anyway"); - } - else { - imported_native_ca = true; - infof(data, "successfully imported native CA store"); - } - } -#endif /* !NO_FILESYSTEM */ + if(ssl_config->primary.cert_blob || ssl_config->primary.clientcert) { + const char *cert_file = ssl_config->primary.clientcert; + const char *key_file = ssl_config->key; + const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; + const struct curl_blob *key_blob = ssl_config->key_blob; + int file_type = wolfssl_do_file_type(ssl_config->cert_type); + int rc; - /* load certificate blob */ - if(ca_info_blob) { - if(wolfSSL_CTX_load_verify_buffer(backend->ctx, ca_info_blob->data, - ca_info_blob->len, - SSL_FILETYPE_PEM) != SSL_SUCCESS) { - if(imported_native_ca) { - infof(data, "error importing CA certificate blob, continuing anyway"); - } - else { - failf(data, "error importing CA certificate blob"); - return CURLE_SSL_CACERT_BADFILE; - } + switch(file_type) { + case WOLFSSL_FILETYPE_PEM: + rc = cert_blob ? + wolfSSL_CTX_use_certificate_chain_buffer(backend->ctx, + cert_blob->data, + (long)cert_blob->len) : + wolfSSL_CTX_use_certificate_chain_file(backend->ctx, cert_file); + break; + case WOLFSSL_FILETYPE_ASN1: + rc = cert_blob ? + wolfSSL_CTX_use_certificate_buffer(backend->ctx, cert_blob->data, + (long)cert_blob->len, file_type) : + wolfSSL_CTX_use_certificate_file(backend->ctx, cert_file, file_type); + break; + default: + failf(data, "unknown cert type"); + return CURLE_BAD_FUNCTION_ARGUMENT; } - else { - imported_ca_info_blob = true; - infof(data, "successfully imported CA certificate blob"); + if(rc != 1) { + failf(data, "unable to use client certificate"); + return CURLE_SSL_CONNECT_ERROR; } - } -#ifndef NO_FILESYSTEM - /* load trusted cacert */ - if(conn_config->CAfile) { - if(1 != wolfSSL_CTX_load_verify_locations(backend->ctx, - conn_config->CAfile, - conn_config->CApath)) { - if(conn_config->verifypeer && !imported_ca_info_blob && - !imported_native_ca) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:" - " CAfile: %s CApath: %s", - conn_config->CAfile? - conn_config->CAfile: "none", - conn_config->CApath? - conn_config->CApath : "none"); - return CURLE_SSL_CACERT_BADFILE; - } - else { - /* Just continue with a warning if no strict certificate - verification is required. */ - infof(data, "error setting certificate verify locations," - " continuing anyway:"); - } + if(!key_blob && !key_file) { + key_blob = cert_blob; + key_file = cert_file; } - else { - /* Everything is fine. */ - infof(data, "successfully set certificate verify locations:"); + else + file_type = wolfssl_do_file_type(ssl_config->key_type); + + rc = key_blob ? + wolfSSL_CTX_use_PrivateKey_buffer(backend->ctx, key_blob->data, + (long)key_blob->len, file_type) : + wolfSSL_CTX_use_PrivateKey_file(backend->ctx, key_file, file_type); + if(rc != 1) { + failf(data, "unable to set private key"); + return CURLE_SSL_CONNECT_ERROR; } - infof(data, " CAfile: %s", - conn_config->CAfile ? conn_config->CAfile : "none"); - infof(data, " CApath: %s", - conn_config->CApath ? conn_config->CApath : "none"); } +#else /* NO_FILESYSTEM */ + if(ssl_config->primary.cert_blob) { + const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; + const struct curl_blob *key_blob = ssl_config->key_blob; + int file_type = wolfssl_do_file_type(ssl_config->cert_type); + int rc; - /* Load the client certificate, and private key */ - if(ssl_config->primary.clientcert && ssl_config->key) { - int file_type = do_file_type(ssl_config->cert_type); - - if(wolfSSL_CTX_use_certificate_file(backend->ctx, - ssl_config->primary.clientcert, - file_type) != 1) { - failf(data, "unable to use client certificate (no key or wrong pass" - " phrase?)"); + switch(file_type) { + case WOLFSSL_FILETYPE_PEM: + rc = wolfSSL_CTX_use_certificate_chain_buffer(backend->ctx, + cert_blob->data, + (long)cert_blob->len); + break; + case WOLFSSL_FILETYPE_ASN1: + rc = wolfSSL_CTX_use_certificate_buffer(backend->ctx, cert_blob->data, + (long)cert_blob->len, file_type); + break; + default: + failf(data, "unknown cert type"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(rc != 1) { + failf(data, "unable to use client certificate"); return CURLE_SSL_CONNECT_ERROR; } - file_type = do_file_type(ssl_config->key_type); - if(wolfSSL_CTX_use_PrivateKey_file(backend->ctx, ssl_config->key, - file_type) != 1) { + if(!key_blob) + key_blob = cert_blob; + else + file_type = wolfssl_do_file_type(ssl_config->key_type); + + if(wolfSSL_CTX_use_PrivateKey_buffer(backend->ctx, key_blob->data, + (long)key_blob->len, + file_type) != 1) { failf(data, "unable to set private key"); return CURLE_SSL_CONNECT_ERROR; } @@ -600,28 +1095,16 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) * anyway. In the latter case the result of the verification is checked with * SSL_get_verify_result() below. */ wolfSSL_CTX_set_verify(backend->ctx, - conn_config->verifypeer?SSL_VERIFY_PEER: - SSL_VERIFY_NONE, NULL); + conn_config->verifypeer ? WOLFSSL_VERIFY_PEER : + WOLFSSL_VERIFY_NONE, NULL); #ifdef HAVE_SNI - if(sni) { - struct in_addr addr4; -#ifdef ENABLE_IPV6 - struct in6_addr addr6; -#endif - size_t hostname_len = strlen(connssl->hostname); - - if((hostname_len < USHRT_MAX) && - !Curl_inet_pton(AF_INET, connssl->hostname, &addr4) -#ifdef ENABLE_IPV6 - && !Curl_inet_pton(AF_INET6, connssl->hostname, &addr6) -#endif - ) { - size_t snilen; - char *snihost = Curl_ssl_snihost(data, connssl->hostname, &snilen); - if(!snihost || - wolfSSL_CTX_UseSNI(backend->ctx, WOLFSSL_SNI_HOST_NAME, snihost, - (unsigned short)snilen) != 1) { + if(connssl->peer.sni) { + size_t sni_len = strlen(connssl->peer.sni); + if((sni_len < USHRT_MAX)) { + if(wolfSSL_CTX_UseSNI(backend->ctx, WOLFSSL_SNI_HOST_NAME, + connssl->peer.sni, + (unsigned short)sni_len) != 1) { failf(data, "Failed to set SNI"); return CURLE_SSL_CONNECT_ERROR; } @@ -631,8 +1114,14 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* give application a chance to interfere with SSL set up. */ if(data->set.ssl.fsslctx) { - CURLcode result = (*data->set.ssl.fsslctx)(data, backend->ctx, - data->set.ssl.fsslctxp); + CURLcode result; + if(!backend->x509_store_setup) { + result = Curl_wssl_setup_x509_store(cf, data, backend); + if(result) + return result; + } + result = (*data->set.ssl.fsslctx)(data, backend->ctx, + data->set.ssl.fsslctxp); if(result) { failf(data, "error signaled by ssl ctx callback"); return result; @@ -640,7 +1129,7 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } #ifdef NO_FILESYSTEM else if(conn_config->verifypeer) { - failf(data, "SSL: Certificates can't be loaded because wolfSSL was built" + failf(data, "SSL: Certificates cannot be loaded because wolfSSL was built" " with \"no filesystem\". Either disable peer verification" " (insecure) or if you are building an application with libcurl you" " can load certificates via CURLOPT_SSL_CTX_FUNCTION."); @@ -653,14 +1142,14 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) wolfSSL_free(backend->handle); backend->handle = wolfSSL_new(backend->ctx); if(!backend->handle) { - failf(data, "SSL: couldn't create a handle"); + failf(data, "SSL: could not create a handle"); return CURLE_OUT_OF_MEMORY; } -#ifdef HAVE_LIBOQS - if(oqsAlg) { - if(wolfSSL_UseKeyShare(backend->handle, oqsAlg) != WOLFSSL_SUCCESS) { - failf(data, "unable to use oqs KEM"); +#ifdef WOLFSSL_HAVE_KYBER + if(pqkem) { + if(wolfSSL_UseKeyShare(backend->handle, pqkem) != WOLFSSL_SUCCESS) { + failf(data, "unable to use PQ KEM"); } } #endif @@ -672,8 +1161,9 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) result = Curl_alpn_to_proto_str(&proto, connssl->alpn); if(result || - wolfSSL_UseALPN(backend->handle, (char *)proto.data, proto.len, - WOLFSSL_ALPN_CONTINUE_ON_MISMATCH) != SSL_SUCCESS) { + wolfSSL_UseALPN(backend->handle, + (char *)proto.data, (unsigned int)proto.len, + WOLFSSL_ALPN_CONTINUE_ON_MISMATCH) != WOLFSSL_SUCCESS) { failf(data, "SSL: failed setting ALPN protocols"); return CURLE_SSL_CONNECT_ERROR; } @@ -699,28 +1189,96 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } #endif /* HAVE_SECURE_RENEGOTIATION */ - /* Check if there's a cached ID we can/should use here! */ - if(ssl_config->primary.sessionid) { - void *ssl_sessionid = NULL; + /* Check if there is a cached ID we can/should use here! */ + if(ssl_config->primary.cache_session) { + /* Set session from cache if there is one */ + (void)wssl_setup_session(cf, data, backend, &connssl->peer); + /* Register to get notified when a new session is received */ + wolfSSL_set_app_data(backend->handle, cf); + wolfSSL_CTX_sess_set_new_cb(backend->ctx, wssl_vtls_new_session_cb); + } + +#ifdef USE_ECH + if(ECH_ENABLED(data)) { + int trying_ech_now = 0; - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL)) { - /* we got a session id, use it! */ - if(!SSL_set_session(backend->handle, ssl_sessionid)) { - Curl_ssl_delsessionid(data, ssl_sessionid); - infof(data, "Can't use session ID, going on without"); + if(data->set.str[STRING_ECH_PUBLIC]) { + infof(data, "ECH: outername not (yet) supported with wolfSSL"); + return CURLE_SSL_CONNECT_ERROR; + } + if(data->set.tls_ech == CURLECH_GREASE) { + infof(data, "ECH: GREASE'd ECH not yet supported for wolfSSL"); + return CURLE_SSL_CONNECT_ERROR; + } + if(data->set.tls_ech & CURLECH_CLA_CFG + && data->set.str[STRING_ECH_CONFIG]) { + char *b64val = data->set.str[STRING_ECH_CONFIG]; + word32 b64len = 0; + + b64len = (word32) strlen(b64val); + if(b64len + && wolfSSL_SetEchConfigsBase64(backend->handle, b64val, b64len) + != WOLFSSL_SUCCESS) { + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else { + trying_ech_now = 1; + infof(data, "ECH: ECHConfig from command line"); } - else - infof(data, "SSL reusing session ID"); } - Curl_ssl_sessionid_unlock(data); + else { + struct Curl_dns_entry *dns = NULL; + + dns = Curl_fetch_addr(data, connssl->peer.hostname, connssl->peer.port); + if(!dns) { + infof(data, "ECH: requested but no DNS info available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else { + struct Curl_https_rrinfo *rinfo = NULL; + + rinfo = dns->hinfo; + if(rinfo && rinfo->echconfiglist) { + unsigned char *ecl = rinfo->echconfiglist; + size_t elen = rinfo->echconfiglist_len; + + infof(data, "ECH: ECHConfig from DoH HTTPS RR"); + if(wolfSSL_SetEchConfigs(backend->handle, ecl, (word32) elen) != + WOLFSSL_SUCCESS) { + infof(data, "ECH: wolfSSL_SetEchConfigs failed"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else { + trying_ech_now = 1; + infof(data, "ECH: imported ECHConfigList of length %ld", elen); + } + } + else { + infof(data, "ECH: requested but no ECHConfig available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + Curl_resolv_unlink(data, &dns); + } + } + + if(trying_ech_now + && SSL_set_min_proto_version(backend->handle, TLS1_3_VERSION) != 1) { + infof(data, "ECH: cannot force TLSv1.3 [ERROR]"); + return CURLE_SSL_CONNECT_ERROR; + } + } +#endif /* USE_ECH */ #ifdef USE_BIO_CHAIN { WOLFSSL_BIO *bio; - bio = BIO_new(bio_cf_method); + bio = wolfSSL_BIO_new(wolfssl_bio_cf_method); if(!bio) return CURLE_OUT_OF_MEMORY; @@ -741,17 +1299,39 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } +static char *wolfssl_strerror(unsigned long error, char *buf, + unsigned long size) +{ + DEBUGASSERT(size > 40); + *buf = '\0'; + + wolfSSL_ERR_error_string_n(error, buf, size); + + if(!*buf) { + const char *msg = error ? "Unknown error" : "No error"; + /* the string fits because the assert above assures this */ + strcpy(buf, msg); + } + + return buf; +} + + static CURLcode wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) { int ret = -1; struct ssl_connect_data *connssl = cf->ctx; - struct wolfssl_ssl_backend_data *backend = - (struct wolfssl_ssl_backend_data *)connssl->backend; + struct wolfssl_ctx *backend = + (struct wolfssl_ctx *)connssl->backend; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf)? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: +#ifndef CURL_DISABLE_PROXY + const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + const char * const pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif DEBUGASSERT(backend); @@ -759,12 +1339,22 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) /* Enable RFC2818 checks */ if(conn_config->verifyhost) { - char *snihost = Curl_ssl_snihost(data, connssl->hostname, NULL); - if(!snihost || - (wolfSSL_check_domain_name(backend->handle, snihost) == SSL_FAILURE)) + char *snihost = connssl->peer.sni ? + connssl->peer.sni : connssl->peer.hostname; + if(wolfSSL_check_domain_name(backend->handle, snihost) == WOLFSSL_FAILURE) return CURLE_SSL_CONNECT_ERROR; } + if(!backend->x509_store_setup) { + /* After having send off the ClientHello, we prepare the x509 + * store to verify the coming certificate from the server */ + CURLcode result; + result = Curl_wssl_setup_x509_store(cf, data, backend); + if(result) + return result; + } + + connssl->io_need = CURL_SSL_IO_NEED_NONE; ret = wolfSSL_connect(backend->handle); #ifdef OPENSSL_EXTRA @@ -780,7 +1370,7 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) * Note that OpenSSL SSL_want_read() is always true here. If wolfSSL ever * changes, the worst case is that no key is logged on error. */ - if(ret == SSL_SUCCESS || + if(ret == WOLFSSL_SUCCESS || (!wolfSSL_want_read(backend->handle) && !wolfSSL_want_write(backend->handle))) { wolfssl_log_tls12_secret(backend->handle); @@ -792,15 +1382,14 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) #endif /* OPENSSL_EXTRA */ if(ret != 1) { - char error_buffer[WOLFSSL_MAX_ERROR_SZ]; - int detail = wolfSSL_get_error(backend->handle, ret); + int detail = wolfSSL_get_error(backend->handle, ret); - if(SSL_ERROR_WANT_READ == detail) { - connssl->connecting_state = ssl_connect_2_reading; + if(WOLFSSL_ERROR_WANT_READ == detail) { + connssl->io_need = CURL_SSL_IO_NEED_RECV; return CURLE_OK; } - else if(SSL_ERROR_WANT_WRITE == detail) { - connssl->connecting_state = ssl_connect_2_writing; + else if(WOLFSSL_ERROR_WANT_WRITE == detail) { + connssl->io_need = CURL_SSL_IO_NEED_SEND; return CURLE_OK; } /* There is no easy way to override only the CN matching. @@ -809,7 +1398,7 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) else if(DOMAIN_NAME_MISMATCH == detail) { #if 1 failf(data, " subject alt name(s) or common name do not match \"%s\"", - connssl->dispname); + connssl->peer.dispname); return CURLE_PEER_FAILED_VERIFICATION; #else /* When the wolfssl_check_domain_name() is used and you desire to @@ -832,7 +1421,6 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) } #endif } -#if LIBWOLFSSL_VERSION_HEX >= 0x02007000 /* 2.7.0 */ else if(ASN_NO_SIGNER_E == detail) { if(conn_config->verifypeer) { failf(data, " CA signer not available for verification"); @@ -845,20 +1433,54 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) "continuing anyway"); } } + else if(ASN_AFTER_DATE_E == detail) { + failf(data, "server verification failed: certificate has expired."); + return CURLE_PEER_FAILED_VERIFICATION; + } + else if(ASN_BEFORE_DATE_E == detail) { + failf(data, "server verification failed: certificate not valid yet."); + return CURLE_PEER_FAILED_VERIFICATION; + } +#ifdef USE_ECH + else if(-1 == detail) { + /* try access a retry_config ECHConfigList for tracing */ + byte echConfigs[1000]; + word32 echConfigsLen = 1000; + int rv = 0; + + /* this currently does not produce the retry_configs */ + rv = wolfSSL_GetEchConfigs(backend->handle, echConfigs, + &echConfigsLen); + if(rv != WOLFSSL_SUCCESS) { + infof(data, "Failed to get ECHConfigs"); + } + else { + char *b64str = NULL; + size_t blen = 0; + + rv = Curl_base64_encode((const char *)echConfigs, echConfigsLen, + &b64str, &blen); + if(!rv && b64str) + infof(data, "ECH: (not yet) retry_configs %s", b64str); + free(b64str); + } + } #endif else if(backend->io_result == CURLE_AGAIN) { return CURLE_OK; } else { + char error_buffer[256]; failf(data, "SSL_connect failed with error %d: %s", detail, - wolfSSL_ERR_error_string(detail, error_buffer)); + wolfssl_strerror((unsigned long)detail, error_buffer, + sizeof(error_buffer))); return CURLE_SSL_CONNECT_ERROR; } } if(pinnedpubkey) { #ifdef KEEP_PEER_CERT - X509 *x509; + WOLFSSL_X509 *x509; const char *x509_der; int x509_der_len; struct Curl_X509certificate x509_parsed; @@ -891,6 +1513,7 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) pinnedpubkey, (const unsigned char *)pubkey->header, (size_t)(pubkey->end - pubkey->header)); + wolfSSL_FreeX509(x509); if(result) { failf(data, "SSL: public key does not match pinned public key"); return result; @@ -909,12 +1532,12 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) rc = wolfSSL_ALPN_GetProtocol(backend->handle, &protocol, &protocol_len); - if(rc == SSL_SUCCESS) { - Curl_alpn_set_negotiated(cf, data, (const unsigned char *)protocol, - protocol_len); + if(rc == WOLFSSL_SUCCESS) { + Curl_alpn_set_negotiated(cf, data, connssl, + (const unsigned char *)protocol, protocol_len); } - else if(rc == SSL_ALPN_NOT_FOUND) - Curl_alpn_set_negotiated(cf, data, NULL, 0); + else if(rc == WOLFSSL_ALPN_NOT_FOUND) + Curl_alpn_set_negotiated(cf, data, connssl, NULL, 0); else { failf(data, "ALPN, failure getting protocol, error %d", rc); return CURLE_SSL_CONNECT_ERROR; @@ -934,64 +1557,6 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_OK; } - -static CURLcode -wolfssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct ssl_connect_data *connssl = cf->ctx; - struct wolfssl_ssl_backend_data *backend = - (struct wolfssl_ssl_backend_data *)connssl->backend; - const struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); - DEBUGASSERT(backend); - - if(ssl_config->primary.sessionid) { - bool incache; - bool added = FALSE; - void *old_ssl_sessionid = NULL; - /* wolfSSL_get1_session allocates memory that has to be freed. */ - WOLFSSL_SESSION *our_ssl_sessionid = wolfSSL_get1_session(backend->handle); - - if(our_ssl_sessionid) { - Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL)); - if(incache) { - if(old_ssl_sessionid != our_ssl_sessionid) { - infof(data, "old SSL session ID is stale, removing"); - Curl_ssl_delsessionid(data, old_ssl_sessionid); - incache = FALSE; - } - } - - if(!incache) { - result = Curl_ssl_addsessionid(cf, data, our_ssl_sessionid, 0, NULL); - if(result) { - Curl_ssl_sessionid_unlock(data); - wolfSSL_SESSION_free(our_ssl_sessionid); - failf(data, "failed to store ssl session"); - return result; - } - else { - added = TRUE; - } - } - Curl_ssl_sessionid_unlock(data); - - if(!added) { - /* If the session info wasn't added to the cache, free our copy. */ - wolfSSL_SESSION_free(our_ssl_sessionid); - } - } - } - - connssl->connecting_state = ssl_connect_done; - - return result; -} - - static ssize_t wolfssl_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *mem, @@ -999,9 +1564,8 @@ static ssize_t wolfssl_send(struct Curl_cfilter *cf, CURLcode *curlcode) { struct ssl_connect_data *connssl = cf->ctx; - struct wolfssl_ssl_backend_data *backend = - (struct wolfssl_ssl_backend_data *)connssl->backend; - char error_buffer[WOLFSSL_MAX_ERROR_SZ]; + struct wolfssl_ctx *backend = + (struct wolfssl_ctx *)connssl->backend; int memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; int rc; @@ -1014,9 +1578,9 @@ static ssize_t wolfssl_send(struct Curl_cfilter *cf, int err = wolfSSL_get_error(backend->handle, rc); switch(err) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke SSL_write() */ + case WOLFSSL_ERROR_WANT_READ: + case WOLFSSL_ERROR_WANT_WRITE: + /* there is data pending, re-invoke SSL_write() */ CURL_TRC_CF(data, cf, "wolfssl_send(len=%zu) -> AGAIN", len); *curlcode = CURLE_AGAIN; return -1; @@ -1027,9 +1591,13 @@ static ssize_t wolfssl_send(struct Curl_cfilter *cf, return -1; } CURL_TRC_CF(data, cf, "wolfssl_send(len=%zu) -> %d, %d", len, rc, err); - failf(data, "SSL write: %s, errno %d", - wolfSSL_ERR_error_string(err, error_buffer), - SOCKERRNO); + { + char error_buffer[256]; + failf(data, "SSL write: %s, errno %d", + wolfssl_strerror((unsigned long)err, error_buffer, + sizeof(error_buffer)), + SOCKERRNO); + } *curlcode = CURLE_SEND_ERROR; return -1; } @@ -1038,22 +1606,122 @@ static ssize_t wolfssl_send(struct Curl_cfilter *cf, return rc; } +static CURLcode wolfssl_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct wolfssl_ctx *wctx = (struct wolfssl_ctx *)connssl->backend; + CURLcode result = CURLE_OK; + char buf[1024]; + char error_buffer[256]; + int nread = -1, err; + size_t i; + int detail; + + DEBUGASSERT(wctx); + if(!wctx->handle || cf->shutdown) { + *done = TRUE; + goto out; + } + + wctx->shutting_down = TRUE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + if(!(wolfSSL_get_shutdown(wctx->handle) & WOLFSSL_SENT_SHUTDOWN)) { + /* We have not started the shutdown from our side yet. Check + * if the server already sent us one. */ + wolfSSL_ERR_clear_error(); + nread = wolfSSL_read(wctx->handle, buf, (int)sizeof(buf)); + err = wolfSSL_get_error(wctx->handle, nread); + CURL_TRC_CF(data, cf, "wolfSSL_read, nread=%d, err=%d", nread, err); + if(!nread && err == WOLFSSL_ERROR_ZERO_RETURN) { + bool input_pending; + /* Yes, it did. */ + if(!send_shutdown) { + CURL_TRC_CF(data, cf, "SSL shutdown received, not sending"); + *done = TRUE; + goto out; + } + else if(!cf->next->cft->is_alive(cf->next, data, &input_pending)) { + /* Server closed the connection after its closy notify. It + * seems not interested to see our close notify, so do not + * send it. We are done. */ + CURL_TRC_CF(data, cf, "peer closed connection"); + connssl->peer_closed = TRUE; + *done = TRUE; + goto out; + } + } + } + + /* SSL should now have started the shutdown from our side. Since it + * was not complete, we are lacking the close notify from the server. */ + if(send_shutdown) { + wolfSSL_ERR_clear_error(); + if(wolfSSL_shutdown(wctx->handle) == 1) { + CURL_TRC_CF(data, cf, "SSL shutdown finished"); + *done = TRUE; + goto out; + } + if(WOLFSSL_ERROR_WANT_WRITE == wolfSSL_get_error(wctx->handle, nread)) { + CURL_TRC_CF(data, cf, "SSL shutdown still wants to send"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + goto out; + } + /* Having sent the close notify, we use wolfSSL_read() to get the + * missing close notify from the server. */ + } + + for(i = 0; i < 10; ++i) { + wolfSSL_ERR_clear_error(); + nread = wolfSSL_read(wctx->handle, buf, (int)sizeof(buf)); + if(nread <= 0) + break; + } + err = wolfSSL_get_error(wctx->handle, nread); + switch(err) { + case WOLFSSL_ERROR_ZERO_RETURN: /* no more data */ + CURL_TRC_CF(data, cf, "SSL shutdown received"); + *done = TRUE; + break; + case WOLFSSL_ERROR_NONE: /* just did not get anything */ + case WOLFSSL_ERROR_WANT_READ: + /* SSL has send its notify and now wants to read the reply + * from the server. We are not really interested in that. */ + CURL_TRC_CF(data, cf, "SSL shutdown sent, want receive"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + break; + case WOLFSSL_ERROR_WANT_WRITE: + CURL_TRC_CF(data, cf, "SSL shutdown send blocked"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + break; + default: + detail = wolfSSL_get_error(wctx->handle, err); + CURL_TRC_CF(data, cf, "SSL shutdown, error: '%s'(%d)", + wolfssl_strerror((unsigned long)err, error_buffer, + sizeof(error_buffer)), + detail); + result = CURLE_RECV_ERROR; + break; + } + +out: + cf->shutdown = (result || *done); + return result; +} + static void wolfssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct wolfssl_ssl_backend_data *backend = - (struct wolfssl_ssl_backend_data *)connssl->backend; + struct wolfssl_ctx *backend = + (struct wolfssl_ctx *)connssl->backend; (void) data; DEBUGASSERT(backend); if(backend->handle) { - char buf[32]; - /* Maybe the server has already sent a close notify alert. - Read it to avoid an RST on the TCP connection. */ - (void)wolfSSL_read(backend->handle, buf, (int)sizeof(buf)); - (void)wolfSSL_shutdown(backend->handle); wolfSSL_free(backend->handle); backend->handle = NULL; } @@ -1069,9 +1737,8 @@ static ssize_t wolfssl_recv(struct Curl_cfilter *cf, CURLcode *curlcode) { struct ssl_connect_data *connssl = cf->ctx; - struct wolfssl_ssl_backend_data *backend = - (struct wolfssl_ssl_backend_data *)connssl->backend; - char error_buffer[WOLFSSL_MAX_ERROR_SZ]; + struct wolfssl_ctx *backend = + (struct wolfssl_ctx *)connssl->backend; int buffsize = (blen > (size_t)INT_MAX) ? INT_MAX : (int)blen; int nread; @@ -1086,16 +1753,19 @@ static ssize_t wolfssl_recv(struct Curl_cfilter *cf, int err = wolfSSL_get_error(backend->handle, nread); switch(err) { - case SSL_ERROR_ZERO_RETURN: /* no more data */ + case WOLFSSL_ERROR_ZERO_RETURN: /* no more data */ CURL_TRC_CF(data, cf, "wolfssl_recv(len=%zu) -> CLOSED", blen); *curlcode = CURLE_OK; return 0; - case SSL_ERROR_NONE: - /* FALLTHROUGH */ - case SSL_ERROR_WANT_READ: - /* FALLTHROUGH */ - case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke wolfSSL_read() */ + case WOLFSSL_ERROR_NONE: + case WOLFSSL_ERROR_WANT_READ: + case WOLFSSL_ERROR_WANT_WRITE: + if(!backend->io_result && connssl->peer_closed) { + CURL_TRC_CF(data, cf, "wolfssl_recv(len=%zu) -> CLOSED", blen); + *curlcode = CURLE_OK; + return 0; + } + /* there is data pending, re-invoke wolfSSL_read() */ CURL_TRC_CF(data, cf, "wolfssl_recv(len=%zu) -> AGAIN", blen); *curlcode = CURLE_AGAIN; return -1; @@ -1105,8 +1775,18 @@ static ssize_t wolfssl_recv(struct Curl_cfilter *cf, *curlcode = CURLE_AGAIN; return -1; } - failf(data, "SSL read: %s, errno %d", - wolfSSL_ERR_error_string(err, error_buffer), SOCKERRNO); + else if(!backend->io_result && connssl->peer_closed) { + CURL_TRC_CF(data, cf, "wolfssl_recv(len=%zu) -> CLOSED", blen); + *curlcode = CURLE_OK; + return 0; + } + else { + char error_buffer[256]; + failf(data, "SSL read: %s, errno %d", + wolfssl_strerror((unsigned long)err, error_buffer, + sizeof(error_buffer)), + SOCKERRNO); + } *curlcode = CURLE_RECV_ERROR; return -1; } @@ -1116,12 +1796,6 @@ static ssize_t wolfssl_recv(struct Curl_cfilter *cf, } -static void wolfssl_session_free(void *ptr) -{ - wolfSSL_SESSION_free(ptr); -} - - static size_t wolfssl_version(char *buffer, size_t size) { #if LIBWOLFSSL_VERSION_HEX >= 0x03006000 @@ -1139,15 +1813,15 @@ static int wolfssl_init(void) #ifdef OPENSSL_EXTRA Curl_tls_keylog_open(); #endif - ret = (wolfSSL_Init() == SSL_SUCCESS); - bio_cf_init_methods(); + ret = (wolfSSL_Init() == WOLFSSL_SUCCESS); + wolfssl_bio_cf_init_methods(); return ret; } static void wolfssl_cleanup(void) { - bio_cf_free_methods(); + wolfssl_bio_cf_free_methods(); wolfSSL_Cleanup(); #ifdef OPENSSL_EXTRA Curl_tls_keylog_close(); @@ -1159,43 +1833,18 @@ static bool wolfssl_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct ssl_connect_data *ctx = cf->ctx; - struct wolfssl_ssl_backend_data *backend; + struct wolfssl_ctx *backend; (void)data; DEBUGASSERT(ctx && ctx->backend); - backend = (struct wolfssl_ssl_backend_data *)ctx->backend; + backend = (struct wolfssl_ctx *)ctx->backend; if(backend->handle) /* SSL is in use */ - return (0 != wolfSSL_pending(backend->handle)) ? TRUE : FALSE; + return wolfSSL_pending(backend->handle); else return FALSE; } - -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -static int wolfssl_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_connect_data *ctx = cf->ctx; - struct wolfssl_ssl_backend_data *backend; - int retval = 0; - - (void)data; - DEBUGASSERT(ctx && ctx->backend); - - backend = (struct wolfssl_ssl_backend_data *)ctx->backend; - if(backend->handle) { - wolfSSL_ERR_clear_error(); - wolfSSL_free(backend->handle); - backend->handle = NULL; - } - return retval; -} - - static CURLcode wolfssl_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -1205,7 +1854,6 @@ wolfssl_connect_common(struct Curl_cfilter *cf, CURLcode result; struct ssl_connect_data *connssl = cf->ctx; curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - int what; /* check if the connection has already been established */ if(ssl_connection_complete == connssl->state) { @@ -1214,7 +1862,7 @@ wolfssl_connect_common(struct Curl_cfilter *cf, } if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we're allowed */ + /* Find out how much more time we are allowed */ const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); if(timeout_ms < 0) { @@ -1228,9 +1876,7 @@ wolfssl_connect_common(struct Curl_cfilter *cf, return result; } - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { + while(ssl_connect_2 == connssl->connecting_state) { /* check allowed time left */ const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); @@ -1241,17 +1887,14 @@ wolfssl_connect_common(struct Curl_cfilter *cf, return CURLE_OPERATION_TIMEDOUT; } - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking?0:timeout_ms); + /* if ssl is expecting something, check if it is available. */ + if(connssl->io_need) { + curl_socket_t writefd = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? + sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = (connssl->io_need & CURL_SSL_IO_NEED_RECV) ? + sockfd : CURL_SOCKET_BAD; + int what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, + nonblocking ? 0 : timeout_ms); if(what < 0) { /* fatal error */ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); @@ -1279,17 +1922,14 @@ wolfssl_connect_common(struct Curl_cfilter *cf, * have a valid fdset to wait on. */ result = wolfssl_connect_step2(cf, data); - if(result || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) + if(result || (nonblocking && (ssl_connect_2 == connssl->connecting_state))) return result; } /* repeat step2 until all transactions are done. */ if(ssl_connect_3 == connssl->connecting_state) { - result = wolfssl_connect_step3(cf, data); - if(result) - return result; + /* In other backends, this is where we verify the certificate, but + * wolfSSL already does that as part of the handshake. */ + connssl->connecting_state = ssl_connect_done; } if(ssl_connect_done == connssl->connecting_state) { @@ -1362,15 +2002,15 @@ static CURLcode wolfssl_sha256sum(const unsigned char *tmp, /* input */ static void *wolfssl_get_internals(struct ssl_connect_data *connssl, CURLINFO info UNUSED_PARAM) { - struct wolfssl_ssl_backend_data *backend = - (struct wolfssl_ssl_backend_data *)connssl->backend; + struct wolfssl_ctx *backend = + (struct wolfssl_ctx *)connssl->backend; (void)info; DEBUGASSERT(backend); return backend->handle; } const struct Curl_ssl Curl_ssl_wolfssl = { - { CURLSSLBACKEND_WOLFSSL, "WolfSSL" }, /* info */ + { CURLSSLBACKEND_WOLFSSL, "wolfssl" }, /* info */ #ifdef KEEP_PEER_CERT SSLSUPP_PINNEDPUBKEY | @@ -1378,10 +2018,19 @@ const struct Curl_ssl Curl_ssl_wolfssl = { #ifdef USE_BIO_CHAIN SSLSUPP_HTTPS_PROXY | #endif + SSLSUPP_CA_PATH | SSLSUPP_CAINFO_BLOB | - SSLSUPP_SSL_CTX, +#ifdef USE_ECH + SSLSUPP_ECH | +#endif + SSLSUPP_SSL_CTX | +#ifdef WOLFSSL_TLS13 + SSLSUPP_TLS13_CIPHERSUITES | +#endif + SSLSUPP_CA_CACHE | + SSLSUPP_CIPHER_LIST, - sizeof(struct wolfssl_ssl_backend_data), + sizeof(struct wolfssl_ctx), wolfssl_init, /* init */ wolfssl_cleanup, /* cleanup */ @@ -1393,11 +2042,10 @@ const struct Curl_ssl Curl_ssl_wolfssl = { Curl_none_cert_status_request, /* cert_status_request */ wolfssl_connect, /* connect */ wolfssl_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_get_select_socks, /* getsock */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ wolfssl_get_internals, /* get_internals */ wolfssl_close, /* close_one */ Curl_none_close_all, /* close_all */ - wolfssl_session_free, /* session_free */ Curl_none_set_engine, /* set_engine */ Curl_none_set_engine_default, /* set_engine_default */ Curl_none_engines_list, /* engines_list */ @@ -1405,9 +2053,9 @@ const struct Curl_ssl Curl_ssl_wolfssl = { wolfssl_sha256sum, /* sha256sum */ NULL, /* associate_connection */ NULL, /* disassociate_connection */ - NULL, /* free_multi_ssl_backend_data */ wolfssl_recv, /* recv decrypted data */ wolfssl_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ }; #endif diff --git a/deps/curl/lib/vtls/wolfssl.h b/deps/curl/lib/vtls/wolfssl.h index a5ed8480996..dc2967d69c7 100644 --- a/deps/curl/lib/vtls/wolfssl.h +++ b/deps/curl/lib/vtls/wolfssl.h @@ -27,7 +27,40 @@ #ifdef USE_WOLFSSL +#include "urldata.h" + +struct WOLFSSL; +typedef struct WOLFSSL WOLFSSL; +struct WOLFSSL_CTX; +typedef struct WOLFSSL_CTX WOLFSSL_CTX; +struct WOLFSSL_SESSION; +typedef struct WOLFSSL_SESSION WOLFSSL_SESSION; + extern const struct Curl_ssl Curl_ssl_wolfssl; +struct wolfssl_ctx { + WOLFSSL_CTX *ctx; + WOLFSSL *handle; + CURLcode io_result; /* result of last BIO cfilter operation */ + int io_send_blocked_len; /* length of last BIO write that EAGAINed */ + BIT(x509_store_setup); /* x509 store has been set up */ + BIT(shutting_down); /* TLS is being shut down */ +}; + +CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wolfssl_ctx *wssl); + +CURLcode wssl_setup_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wolfssl_ctx *wss, + struct ssl_peer *peer); + +CURLcode wssl_cache_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + WOLFSSL_SESSION *session); + + #endif /* USE_WOLFSSL */ #endif /* HEADER_CURL_WOLFSSL_H */ diff --git a/deps/curl/lib/vtls/x509asn1.c b/deps/curl/lib/vtls/x509asn1.c index c3fd3a30bba..fe4a38b8f74 100644 --- a/deps/curl/lib/vtls/x509asn1.c +++ b/deps/curl/lib/vtls/x509asn1.c @@ -25,13 +25,15 @@ #include "curl_setup.h" #if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ - defined(USE_SCHANNEL) || defined(USE_SECTRANSP) + defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_MBEDTLS) #if defined(USE_WOLFSSL) || defined(USE_SCHANNEL) #define WANT_PARSEX509 /* uses Curl_parseX509() */ #endif -#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) +#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_MBEDTLS) #define WANT_EXTRACT_CERTINFO /* uses Curl_extract_certinfo() */ #define WANT_PARSEX509 /* ... uses Curl_parseX509() */ #endif @@ -97,6 +99,7 @@ #define CURL_ASN1_CHARACTER_STRING 29 #define CURL_ASN1_BMP_STRING 30 + #ifdef WANT_EXTRACT_CERTINFO /* ASN.1 OID table entry. */ struct Curl_OID { @@ -105,15 +108,16 @@ struct Curl_OID { }; /* ASN.1 OIDs. */ -static const char cnOID[] = "2.5.4.3"; /* Common name. */ -static const char sanOID[] = "2.5.29.17"; /* Subject alternative name. */ - static const struct Curl_OID OIDtable[] = { { "1.2.840.10040.4.1", "dsa" }, { "1.2.840.10040.4.3", "dsa-with-sha1" }, { "1.2.840.10045.2.1", "ecPublicKey" }, { "1.2.840.10045.3.0.1", "c2pnb163v1" }, { "1.2.840.10045.4.1", "ecdsa-with-SHA1" }, + { "1.2.840.10045.4.3.1", "ecdsa-with-SHA224" }, + { "1.2.840.10045.4.3.2", "ecdsa-with-SHA256" }, + { "1.2.840.10045.4.3.3", "ecdsa-with-SHA384" }, + { "1.2.840.10045.4.3.4", "ecdsa-with-SHA512" }, { "1.2.840.10046.2.1", "dhpublicnumber" }, { "1.2.840.113549.1.1.1", "rsaEncryption" }, { "1.2.840.113549.1.1.2", "md2WithRSAEncryption" }, @@ -127,7 +131,7 @@ static const struct Curl_OID OIDtable[] = { { "1.2.840.113549.2.2", "md2" }, { "1.2.840.113549.2.5", "md5" }, { "1.3.14.3.2.26", "sha1" }, - { cnOID, "CN" }, + { "2.5.4.3", "CN" }, { "2.5.4.4", "SN" }, { "2.5.4.5", "serialNumber" }, { "2.5.4.6", "C" }, @@ -148,13 +152,14 @@ static const struct Curl_OID OIDtable[] = { { "2.5.4.65", "pseudonym" }, { "1.2.840.113549.1.9.1", "emailAddress" }, { "2.5.4.72", "role" }, - { sanOID, "subjectAltName" }, + { "2.5.29.17", "subjectAltName" }, { "2.5.29.18", "issuerAltName" }, { "2.5.29.19", "basicConstraints" }, { "2.16.840.1.101.3.4.2.4", "sha224" }, { "2.16.840.1.101.3.4.2.1", "sha256" }, { "2.16.840.1.101.3.4.2.2", "sha384" }, { "2.16.840.1.101.3.4.2.3", "sha512" }, + { "1.2.840.113549.1.9.2", "unstructuredName" }, { (const char *) NULL, (const char *) NULL } }; @@ -255,61 +260,61 @@ static const struct Curl_OID *searchOID(const char *oid) } /* - * Convert an ASN.1 Boolean value into its string representation. Return the - * dynamically allocated string, or NULL if source is not an ASN.1 Boolean - * value. + * Convert an ASN.1 Boolean value into its string representation. + * + * Return error code. */ -static const char *bool2str(const char *beg, const char *end) +static CURLcode bool2str(struct dynbuf *store, + const char *beg, const char *end) { if(end - beg != 1) - return NULL; - return strdup(*beg? "TRUE": "FALSE"); + return CURLE_BAD_FUNCTION_ARGUMENT; + return Curl_dyn_add(store, *beg ? "TRUE": "FALSE"); } /* * Convert an ASN.1 octet string to a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. + * + * Return error code. */ -static const char *octet2str(const char *beg, const char *end) +static CURLcode octet2str(struct dynbuf *store, + const char *beg, const char *end) { - struct dynbuf buf; - CURLcode result; - - Curl_dyn_init(&buf, 3 * CURL_ASN1_MAX + 1); - result = Curl_dyn_addn(&buf, "", 0); + CURLcode result = CURLE_OK; while(!result && beg < end) - result = Curl_dyn_addf(&buf, "%02x:", (unsigned char) *beg++); + result = Curl_dyn_addf(store, "%02x:", (unsigned char) *beg++); - return Curl_dyn_ptr(&buf); + return result; } -static const char *bit2str(const char *beg, const char *end) +static CURLcode bit2str(struct dynbuf *store, + const char *beg, const char *end) { - /* Convert an ASN.1 bit string to a printable string. - Return the dynamically allocated string, or NULL if an error occurs. */ + /* Convert an ASN.1 bit string to a printable string. */ if(++beg > end) - return NULL; - return octet2str(beg, end); + return CURLE_BAD_FUNCTION_ARGUMENT; + return octet2str(store, beg, end); } /* * Convert an ASN.1 integer value into its string representation. - * Return the dynamically allocated string, or NULL if source is not an - * ASN.1 integer value. + * + * Returns error. */ -static const char *int2str(const char *beg, const char *end) +static CURLcode int2str(struct dynbuf *store, + const char *beg, const char *end) { unsigned int val = 0; size_t n = end - beg; if(!n) - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; if(n > 4) - return octet2str(beg, end); + return octet2str(store, beg, end); /* Represent integers <= 32-bit as a single value. */ if(*beg & 0x80) @@ -318,25 +323,24 @@ static const char *int2str(const char *beg, const char *end) do val = (val << 8) | *(const unsigned char *) beg++; while(beg < end); - return curl_maprintf("%s%x", val >= 10? "0x": "", val); + return Curl_dyn_addf(store, "%s%x", val >= 10 ? "0x" : "", val); } /* - * Perform a lazy conversion from an ASN.1 typed string to UTF8. Allocate the - * destination buffer dynamically. The allocation size will normally be too - * large: this is to avoid buffer overflows. - * Terminate the string with a nul byte and return the converted - * string length. + * Convert from an ASN.1 typed string to UTF8. + * + * The result is stored in a dynbuf that is inited by the user of this + * function. + * + * Returns error. */ -static ssize_t -utf8asn1str(char **to, int type, const char *from, const char *end) +static CURLcode +utf8asn1str(struct dynbuf *to, int type, const char *from, const char *end) { size_t inlength = end - from; int size = 1; - size_t outlength; - char *buf; + CURLcode result = CURLE_OK; - *to = NULL; switch(type) { case CURL_ASN1_BMP_STRING: size = 2; @@ -352,133 +356,84 @@ utf8asn1str(char **to, int type, const char *from, const char *end) case CURL_ASN1_UTF8_STRING: break; default: - return -1; /* Conversion not supported. */ + return CURLE_BAD_FUNCTION_ARGUMENT; /* Conversion not supported. */ } if(inlength % size) - return -1; /* Length inconsistent with character size. */ - if(inlength / size > (SIZE_T_MAX - 1) / 4) - return -1; /* Too big. */ - buf = malloc(4 * (inlength / size) + 1); - if(!buf) - return -1; /* Not enough memory. */ + /* Length inconsistent with character size. */ + return CURLE_BAD_FUNCTION_ARGUMENT; if(type == CURL_ASN1_UTF8_STRING) { /* Just copy. */ - outlength = inlength; - if(outlength) - memcpy(buf, from, outlength); + if(inlength) + result = Curl_dyn_addn(to, from, inlength); } else { - for(outlength = 0; from < end;) { - int charsize; - unsigned int wc; + while(!result && (from < end)) { + char buf[4]; /* decode buffer */ + size_t charsize = 1; + unsigned int wc = 0; - wc = 0; switch(size) { case 4: wc = (wc << 8) | *(const unsigned char *) from++; wc = (wc << 8) | *(const unsigned char *) from++; - /* FALLTHROUGH */ + FALLTHROUGH(); case 2: wc = (wc << 8) | *(const unsigned char *) from++; - /* FALLTHROUGH */ + FALLTHROUGH(); default: /* case 1: */ wc = (wc << 8) | *(const unsigned char *) from++; } - charsize = 1; if(wc >= 0x00000080) { if(wc >= 0x00000800) { if(wc >= 0x00010000) { if(wc >= 0x00200000) { - free(buf); - return -1; /* Invalid char. size for target encoding. */ + /* Invalid char. size for target encoding. */ + return CURLE_WEIRD_SERVER_REPLY; } - buf[outlength + 3] = (char) (0x80 | (wc & 0x3F)); + buf[3] = (char) (0x80 | (wc & 0x3F)); wc = (wc >> 6) | 0x00010000; charsize++; } - buf[outlength + 2] = (char) (0x80 | (wc & 0x3F)); + buf[2] = (char) (0x80 | (wc & 0x3F)); wc = (wc >> 6) | 0x00000800; charsize++; } - buf[outlength + 1] = (char) (0x80 | (wc & 0x3F)); + buf[1] = (char) (0x80 | (wc & 0x3F)); wc = (wc >> 6) | 0x000000C0; charsize++; } - buf[outlength] = (char) wc; - outlength += charsize; + buf[0] = (char) wc; + result = Curl_dyn_addn(to, buf, charsize); } } - buf[outlength] = '\0'; - *to = buf; - return outlength; -} - -/* - * Convert an ASN.1 String into its UTF-8 string representation. - * Return the dynamically allocated string, or NULL if an error occurs. - */ -static const char *string2str(int type, const char *beg, const char *end) -{ - char *buf; - if(utf8asn1str(&buf, type, beg, end) < 0) - return NULL; - return buf; -} - -/* - * Decimal ASCII encode unsigned integer `x' into the buflen sized buffer at - * buf. Return the total number of encoded digits, even if larger than - * `buflen'. - */ -static size_t encodeUint(char *buf, size_t buflen, unsigned int x) -{ - size_t i = 0; - unsigned int y = x / 10; - - if(y) { - i = encodeUint(buf, buflen, y); - x -= y * 10; - } - if(i < buflen) - buf[i] = (char) ('0' + x); - i++; - if(i < buflen) - buf[i] = '\0'; /* Store a terminator if possible. */ - return i; + return result; } /* * Convert an ASN.1 OID into its dotted string representation. - * Store the result in th `n'-byte buffer at `buf'. - * Return the converted string length, or 0 on errors. + * + * Return error code. */ -static size_t encodeOID(char *buf, size_t buflen, - const char *beg, const char *end) +static CURLcode encodeOID(struct dynbuf *store, + const char *beg, const char *end) { - size_t i; unsigned int x; unsigned int y; + CURLcode result = CURLE_OK; /* Process the first two numbers. */ y = *(const unsigned char *) beg++; x = y / 40; y -= x * 40; - i = encodeUint(buf, buflen, x); - if(i < buflen) - buf[i] = '.'; - i++; - if(i >= buflen) - i += encodeUint(NULL, 0, y); - else - i += encodeUint(buf + i, buflen - i, y); + + result = Curl_dyn_addf(store, "%u.%u", x, y); + if(result) + return result; /* Process the trailing numbers. */ while(beg < end) { - if(i < buflen) - buf[i] = '.'; - i++; x = 0; do { if(x & 0xFF000000) @@ -486,46 +441,44 @@ static size_t encodeOID(char *buf, size_t buflen, y = *(const unsigned char *) beg++; x = (x << 7) | (y & 0x7F); } while(y & 0x80); - if(i >= buflen) - i += encodeUint(NULL, 0, x); - else - i += encodeUint(buf + i, buflen - i, x); + result = Curl_dyn_addf(store, ".%u", x); } - if(i < buflen) - buf[i] = '\0'; - return i; + return result; } /* * Convert an ASN.1 OID into its dotted or symbolic string representation. - * Return the dynamically allocated string, or NULL if an error occurs. + * + * Return error code. */ -static const char *OID2str(const char *beg, const char *end, bool symbolic) +static CURLcode OID2str(struct dynbuf *store, + const char *beg, const char *end, bool symbolic) { - char *buf = NULL; + CURLcode result = CURLE_OK; if(beg < end) { - size_t buflen = encodeOID(NULL, 0, beg, end); - if(buflen) { - buf = malloc(buflen + 1); /* one extra for the zero byte */ - if(buf) { - encodeOID(buf, buflen, beg, end); - buf[buflen] = '\0'; - - if(symbolic) { - const struct Curl_OID *op = searchOID(buf); - if(op) { - free(buf); - buf = strdup(op->textoid); - } - } + if(symbolic) { + struct dynbuf buf; + Curl_dyn_init(&buf, CURL_X509_STR_MAX); + result = encodeOID(&buf, beg, end); + + if(!result) { + const struct Curl_OID *op = searchOID(Curl_dyn_ptr(&buf)); + if(op) + result = Curl_dyn_add(store, op->textoid); + else + result = Curl_dyn_add(store, Curl_dyn_ptr(&buf)); + Curl_dyn_free(&buf); } } + else + result = encodeOID(store, beg, end); } - return buf; + return result; } -static const char *GTime2str(const char *beg, const char *end) +static CURLcode GTime2str(struct dynbuf *store, + const char *beg, const char *end) { const char *tzp; const char *fracp; @@ -537,7 +490,7 @@ static const char *GTime2str(const char *beg, const char *end) /* Convert an ASN.1 Generalized time to a printable string. Return the dynamically allocated string, or NULL if an error occurs. */ - for(fracp = beg; fracp < end && *fracp >= '0' && *fracp <= '9'; fracp++) + for(fracp = beg; fracp < end && ISDIGIT(*fracp); fracp++) ; /* Get seconds digits. */ @@ -548,52 +501,76 @@ static const char *GTime2str(const char *beg, const char *end) break; case 2: sec1 = fracp[-2]; - /* FALLTHROUGH */ + FALLTHROUGH(); case 1: sec2 = fracp[-1]; break; default: - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; } - /* Scan for timezone, measure fractional seconds. */ + /* timezone follows optional fractional seconds. */ tzp = fracp; - fracl = 0; + fracl = 0; /* no fractional seconds detected so far */ if(fracp < end && (*fracp == '.' || *fracp == ',')) { - fracp++; - do + /* Have fractional seconds, e.g. "[.,]\d+". How many? */ + fracp++; /* should be a digit char or BAD ARGUMENT */ + tzp = fracp; + while(tzp < end && ISDIGIT(*tzp)) tzp++; - while(tzp < end && *tzp >= '0' && *tzp <= '9'); - /* Strip leading zeroes in fractional seconds. */ - for(fracl = tzp - fracp - 1; fracl && fracp[fracl - 1] == '0'; fracl--) - ; + if(tzp == fracp) /* never looped, no digit after [.,] */ + return CURLE_BAD_FUNCTION_ARGUMENT; + fracl = tzp - fracp; /* number of fractional sec digits */ + DEBUGASSERT(fracl > 0); + /* Strip trailing zeroes in fractional seconds. + * May reduce fracl to 0 if only '0's are present. */ + while(fracl && fracp[fracl - 1] == '0') + fracl--; } /* Process timezone. */ - if(tzp >= end) - ; /* Nothing to do. */ + if(tzp >= end) { + tzp = ""; + tzl = 0; + } else if(*tzp == 'Z') { - tzp = " GMT"; - end = tzp + 4; + sep = " "; + tzp = "GMT"; + tzl = 3; + } + else if((*tzp == '+') || (*tzp == '-')) { + sep = " UTC"; + tzl = end - tzp; } else { sep = " "; - tzp++; + tzl = end - tzp; } - tzl = end - tzp; - return curl_maprintf("%.4s-%.2s-%.2s %.2s:%.2s:%c%c%s%.*s%s%.*s", + return Curl_dyn_addf(store, + "%.4s-%.2s-%.2s %.2s:%.2s:%c%c%s%.*s%s%.*s", beg, beg + 4, beg + 6, beg + 8, beg + 10, sec1, sec2, - fracl? ".": "", (int)fracl, fracp, + fracl ? ".": "", (int)fracl, fracp, sep, (int)tzl, tzp); } +#ifdef UNITTESTS +/* used by unit1656.c */ +CURLcode Curl_x509_GTime2str(struct dynbuf *store, + const char *beg, const char *end) +{ + return GTime2str(store, beg, end); +} +#endif + /* - * Convert an ASN.1 UTC time to a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. + * Convert an ASN.1 UTC time to a printable string. + * + * Return error code. */ -static const char *UTime2str(const char *beg, const char *end) +static CURLcode UTime2str(struct dynbuf *store, + const char *beg, const char *end) { const char *tzp; size_t tzl; @@ -606,15 +583,16 @@ static const char *UTime2str(const char *beg, const char *end) switch(tzp - sec) { case 0: sec = "00"; + FALLTHROUGH(); case 2: break; default: - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; } /* Process timezone. */ if(tzp >= end) - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; if(*tzp == 'Z') { tzp = "GMT"; end = tzp + 3; @@ -623,7 +601,7 @@ static const char *UTime2str(const char *beg, const char *end) tzp++; tzl = end - tzp; - return curl_maprintf("%u%.2s-%.2s-%.2s %.2s:%.2s:%.2s %.*s", + return Curl_dyn_addf(store, "%u%.2s-%.2s-%.2s %.2s:%.2s:%.2s %.*s", 20 - (*beg >= '5'), beg, beg + 2, beg + 4, beg + 6, beg + 8, sec, (int)tzl, tzp); @@ -631,34 +609,45 @@ static const char *UTime2str(const char *beg, const char *end) /* * Convert an ASN.1 element to a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. + * + * Return error */ -static const char *ASN1tostr(struct Curl_asn1Element *elem, int type) +static CURLcode ASN1tostr(struct dynbuf *store, + struct Curl_asn1Element *elem, int type) { + CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; if(elem->constructed) - return NULL; /* No conversion of structured elements. */ + return result; /* No conversion of structured elements. */ if(!type) type = elem->tag; /* Type not forced: use element tag as type. */ switch(type) { case CURL_ASN1_BOOLEAN: - return bool2str(elem->beg, elem->end); + result = bool2str(store, elem->beg, elem->end); + break; case CURL_ASN1_INTEGER: case CURL_ASN1_ENUMERATED: - return int2str(elem->beg, elem->end); + result = int2str(store, elem->beg, elem->end); + break; case CURL_ASN1_BIT_STRING: - return bit2str(elem->beg, elem->end); + result = bit2str(store, elem->beg, elem->end); + break; case CURL_ASN1_OCTET_STRING: - return octet2str(elem->beg, elem->end); + result = octet2str(store, elem->beg, elem->end); + break; case CURL_ASN1_NULL: - return strdup(""); + result = Curl_dyn_addn(store, "", 1); + break; case CURL_ASN1_OBJECT_IDENTIFIER: - return OID2str(elem->beg, elem->end, TRUE); + result = OID2str(store, elem->beg, elem->end, TRUE); + break; case CURL_ASN1_UTC_TIME: - return UTime2str(elem->beg, elem->end); + result = UTime2str(store, elem->beg, elem->end); + break; case CURL_ASN1_GENERALIZED_TIME: - return GTime2str(elem->beg, elem->end); + result = GTime2str(store, elem->beg, elem->end); + break; case CURL_ASN1_UTF8_STRING: case CURL_ASN1_NUMERIC_STRING: case CURL_ASN1_PRINTABLE_STRING: @@ -667,87 +656,101 @@ static const char *ASN1tostr(struct Curl_asn1Element *elem, int type) case CURL_ASN1_VISIBLE_STRING: case CURL_ASN1_UNIVERSAL_STRING: case CURL_ASN1_BMP_STRING: - return string2str(type, elem->beg, elem->end); + result = utf8asn1str(store, type, elem->beg, elem->end); + break; } - return NULL; /* Unsupported. */ + return result; } /* - * ASCII encode distinguished name at `dn' into the `buflen'-sized buffer at - * `buf'. + * ASCII encode distinguished name at `dn' into the store dynbuf. * - * Returns the total string length, even if larger than `buflen' or -1 on - * error. + * Returns error. */ -static ssize_t encodeDN(char *buf, size_t buflen, struct Curl_asn1Element *dn) +static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) { struct Curl_asn1Element rdn; struct Curl_asn1Element atv; struct Curl_asn1Element oid; struct Curl_asn1Element value; - size_t l = 0; const char *p1; const char *p2; const char *p3; const char *str; + CURLcode result = CURLE_OK; + bool added = FALSE; + struct dynbuf temp; + Curl_dyn_init(&temp, CURL_X509_STR_MAX); for(p1 = dn->beg; p1 < dn->end;) { p1 = getASN1Element(&rdn, p1, dn->end); - if(!p1) - return -1; + if(!p1) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } for(p2 = rdn.beg; p2 < rdn.end;) { p2 = getASN1Element(&atv, p2, rdn.end); - if(!p2) - return -1; + if(!p2) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } p3 = getASN1Element(&oid, atv.beg, atv.end); - if(!p3) - return -1; - if(!getASN1Element(&value, p3, atv.end)) - return -1; - str = ASN1tostr(&oid, 0); - if(!str) - return -1; + if(!p3) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } + if(!getASN1Element(&value, p3, atv.end)) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } + Curl_dyn_reset(&temp); + result = ASN1tostr(&temp, &oid, 0); + if(result) + goto error; + + str = Curl_dyn_ptr(&temp); + + if(!str) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } /* Encode delimiter. If attribute has a short uppercase name, delimiter is ", ". */ - if(l) { - for(p3 = str; ISUPPER(*p3); p3++) - ; - for(p3 = (*p3 || p3 - str > 2)? "/": ", "; *p3; p3++) { - if(l < buflen) - buf[l] = *p3; - l++; - } + for(p3 = str; ISUPPER(*p3); p3++) + ; + if(added) { + if(p3 - str > 2) + result = Curl_dyn_addn(store, "/", 1); + else + result = Curl_dyn_addn(store, ", ", 2); + if(result) + goto error; } /* Encode attribute name. */ - for(p3 = str; *p3; p3++) { - if(l < buflen) - buf[l] = *p3; - l++; - } - free((char *) str); + result = Curl_dyn_add(store, str); + if(result) + goto error; /* Generate equal sign. */ - if(l < buflen) - buf[l] = '='; - l++; + result = Curl_dyn_addn(store, "=", 1); + if(result) + goto error; /* Generate value. */ - str = ASN1tostr(&value, 0); - if(!str) - return -1; - for(p3 = str; *p3; p3++) { - if(l < buflen) - buf[l] = *p3; - l++; - } - free((char *) str); + result = ASN1tostr(store, &value, 0); + if(result) + goto error; + Curl_dyn_reset(&temp); + added = TRUE; /* use separator for next */ } } +error: + Curl_dyn_free(&temp); - return l; + return result; } #endif /* WANT_EXTRACT_CERTINFO */ @@ -876,25 +879,9 @@ int Curl_parseX509(struct Curl_X509certificate *cert, #ifdef WANT_EXTRACT_CERTINFO -/* - * Copy at most 64-characters, terminate with a newline and returns the - * effective number of stored characters. - */ -static size_t copySubstring(char *to, const char *from) -{ - size_t i; - for(i = 0; i < 64; i++) { - to[i] = *from; - if(!*from++) - break; - } - - to[i++] = '\n'; - return i; -} - -static const char *dumpAlgo(struct Curl_asn1Element *param, - const char *beg, const char *end) +static CURLcode dumpAlgo(struct dynbuf *store, + struct Curl_asn1Element *param, + const char *beg, const char *end) { struct Curl_asn1Element oid; @@ -902,14 +889,16 @@ static const char *dumpAlgo(struct Curl_asn1Element *param, beg = getASN1Element(&oid, beg, end); if(!beg) - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; param->header = NULL; param->tag = 0; param->beg = param->end = end; - if(beg < end) - if(!getASN1Element(param, beg, end)) - return NULL; - return OID2str(oid.beg, oid.end, TRUE); + if(beg < end) { + const char *p = getASN1Element(param, beg, end); + if(!p) + return CURLE_BAD_FUNCTION_ARGUMENT; + } + return OID2str(store, oid.beg, oid.end, TRUE); } /* @@ -926,24 +915,47 @@ static CURLcode ssl_push_certinfo(struct Curl_easy *data, return Curl_ssl_push_certinfo_len(data, certnum, label, value, valuelen); } -/* return 0 on success, 1 on error */ -static int do_pubkey_field(struct Curl_easy *data, int certnum, - const char *label, struct Curl_asn1Element *elem) +/* + * This is a convenience function for push_certinfo_len that takes a + * dynbuf value. + * + * It also does the verbose output if !certnum. + */ +static CURLcode ssl_push_certinfo_dyn(struct Curl_easy *data, + int certnum, + const char *label, + struct dynbuf *ptr) { - const char *output; - CURLcode result = CURLE_OK; + size_t valuelen = Curl_dyn_len(ptr); + char *value = Curl_dyn_ptr(ptr); + + CURLcode result = Curl_ssl_push_certinfo_len(data, certnum, label, + value, valuelen); + + if(!certnum && !result) + infof(data, " %s: %s", label, value); + + return result; +} + +static CURLcode do_pubkey_field(struct Curl_easy *data, int certnum, + const char *label, + struct Curl_asn1Element *elem) +{ + CURLcode result; + struct dynbuf out; + + Curl_dyn_init(&out, CURL_X509_STR_MAX); /* Generate a certificate information record for the public key. */ - output = ASN1tostr(elem, 0); - if(output) { + result = ASN1tostr(&out, elem, 0); + if(!result) { if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, label, output); - if(!certnum && !result) - infof(data, " %s: %s", label, output); - free((char *) output); + result = ssl_push_certinfo_dyn(data, certnum, label, &out); + Curl_dyn_free(&out); } - return result ? 1 : 0; + return result; } /* return 0 on success, 1 on error */ @@ -964,14 +976,15 @@ static int do_pubkey(struct Curl_easy *data, int certnum, */ const size_t len = ((pubkey->end - pubkey->beg - 2) * 4); if(!certnum) - infof(data, " ECC Public Key (%lu bits)", len); + infof(data, " ECC Public Key (%zu bits)", len); if(data->set.ssl.certinfo) { char q[sizeof(len) * 8 / 3 + 1]; (void)msnprintf(q, sizeof(q), "%zu", len); if(ssl_push_certinfo(data, certnum, "ECC Public Key", q)) return 1; } - return do_pubkey_field(data, certnum, "ecPublicKey", pubkey); + return do_pubkey_field(data, certnum, "ecPublicKey", pubkey) == CURLE_OK + ? 0 : 1; } /* Get the public key (single element). */ @@ -998,7 +1011,7 @@ static int do_pubkey(struct Curl_easy *data, int certnum, if(len > 32) elem.beg = q; /* Strip leading zero bytes. */ if(!certnum) - infof(data, " RSA Public Key (%lu bits)", len); + infof(data, " RSA Public Key (%zu bits)", len); if(data->set.ssl.certinfo) { char r[sizeof(len) * 8 / 3 + 1]; msnprintf(r, sizeof(r), "%zu", len); @@ -1049,24 +1062,12 @@ static int do_pubkey(struct Curl_easy *data, int certnum, /* * Convert an ASN.1 distinguished name into a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. + * Return error. */ -static const char *DNtostr(struct Curl_asn1Element *dn) +static CURLcode DNtostr(struct dynbuf *store, + struct Curl_asn1Element *dn) { - char *buf = NULL; - ssize_t buflen = encodeDN(NULL, 0, dn); - - if(buflen >= 0) { - buf = malloc(buflen + 1); - if(buf) { - if(encodeDN(buf, buflen + 1, dn) == -1) { - free(buf); - return NULL; - } - buf[buflen] = '\0'; - } - } - return buf; + return encodeDN(store, dn); } CURLcode Curl_extract_certinfo(struct Curl_easy *data, @@ -1076,19 +1077,19 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, { struct Curl_X509certificate cert; struct Curl_asn1Element param; - const char *ccp; - char *cp1; - size_t cl1; - char *cp2; + char *certptr; + size_t clen; + struct dynbuf out; CURLcode result = CURLE_OK; unsigned int version; - size_t i; - size_t j; + const char *ptr; + int rc; if(!data->set.ssl.certinfo) if(certnum) return CURLE_OK; + Curl_dyn_init(&out, CURL_X509_STR_MAX); /* Prepare the certificate information for curl_easy_getinfo(). */ /* Extract the certificate ASN.1 elements. */ @@ -1096,135 +1097,126 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, return CURLE_PEER_FAILED_VERIFICATION; /* Subject. */ - ccp = DNtostr(&cert.subject); - if(!ccp) - return CURLE_OUT_OF_MEMORY; + result = DNtostr(&out, &cert.subject); + if(result) + goto done; if(data->set.ssl.certinfo) { - result = ssl_push_certinfo(data, certnum, "Subject", ccp); + result = ssl_push_certinfo_dyn(data, certnum, "Subject", &out); if(result) - return result; + goto done; } - if(!certnum) - infof(data, "%2d Subject: %s", certnum, ccp); - free((char *) ccp); + Curl_dyn_reset(&out); /* Issuer. */ - ccp = DNtostr(&cert.issuer); - if(!ccp) - return CURLE_OUT_OF_MEMORY; + result = DNtostr(&out, &cert.issuer); + if(result) + goto done; if(data->set.ssl.certinfo) { - result = ssl_push_certinfo(data, certnum, "Issuer", ccp); + result = ssl_push_certinfo_dyn(data, certnum, "Issuer", &out); + if(result) + goto done; } - if(!certnum) - infof(data, " Issuer: %s", ccp); - free((char *) ccp); - if(result) - return result; + Curl_dyn_reset(&out); /* Version (always fits in less than 32 bits). */ version = 0; - for(ccp = cert.version.beg; ccp < cert.version.end; ccp++) - version = (version << 8) | *(const unsigned char *) ccp; + for(ptr = cert.version.beg; ptr < cert.version.end; ptr++) + version = (version << 8) | *(const unsigned char *) ptr; if(data->set.ssl.certinfo) { - ccp = curl_maprintf("%x", version); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - result = ssl_push_certinfo(data, certnum, "Version", ccp); - free((char *) ccp); + result = Curl_dyn_addf(&out, "%x", version); if(result) - return result; + goto done; + result = ssl_push_certinfo_dyn(data, certnum, "Version", &out); + if(result) + goto done; + Curl_dyn_reset(&out); } - if(!certnum) - infof(data, " Version: %u (0x%x)", version + 1, version); /* Serial number. */ - ccp = ASN1tostr(&cert.serialNumber, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Serial Number", ccp); - if(!certnum) - infof(data, " Serial Number: %s", ccp); - free((char *) ccp); + result = ASN1tostr(&out, &cert.serialNumber, 0); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Serial Number", &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); /* Signature algorithm .*/ - ccp = dumpAlgo(¶m, cert.signatureAlgorithm.beg, - cert.signatureAlgorithm.end); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Signature Algorithm", ccp); - if(!certnum) - infof(data, " Signature Algorithm: %s", ccp); - free((char *) ccp); + result = dumpAlgo(&out, ¶m, cert.signatureAlgorithm.beg, + cert.signatureAlgorithm.end); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Signature Algorithm", + &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); /* Start Date. */ - ccp = ASN1tostr(&cert.notBefore, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Start Date", ccp); - if(!certnum) - infof(data, " Start Date: %s", ccp); - free((char *) ccp); + result = ASN1tostr(&out, &cert.notBefore, 0); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Start Date", &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); /* Expire Date. */ - ccp = ASN1tostr(&cert.notAfter, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Expire Date", ccp); - if(!certnum) - infof(data, " Expire Date: %s", ccp); - free((char *) ccp); + result = ASN1tostr(&out, &cert.notAfter, 0); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Expire Date", &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); /* Public Key Algorithm. */ - ccp = dumpAlgo(¶m, cert.subjectPublicKeyAlgorithm.beg, - cert.subjectPublicKeyAlgorithm.end); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Public Key Algorithm", - ccp); - if(!result) { - int ret; - if(!certnum) - infof(data, " Public Key Algorithm: %s", ccp); - ret = do_pubkey(data, certnum, ccp, ¶m, &cert.subjectPublicKey); - if(ret) - result = CURLE_OUT_OF_MEMORY; /* the most likely error */ - } - free((char *) ccp); + result = dumpAlgo(&out, ¶m, cert.subjectPublicKeyAlgorithm.beg, + cert.subjectPublicKeyAlgorithm.end); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Public Key Algorithm", + &out); + if(result) + goto done; + } + + rc = do_pubkey(data, certnum, Curl_dyn_ptr(&out), + ¶m, &cert.subjectPublicKey); + if(rc) { + result = CURLE_OUT_OF_MEMORY; /* the most likely error */ + goto done; + } + Curl_dyn_reset(&out); /* Signature. */ - ccp = ASN1tostr(&cert.signature, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Signature", ccp); - if(!certnum) - infof(data, " Signature: %s", ccp); - free((char *) ccp); + result = ASN1tostr(&out, &cert.signature, 0); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Signature", &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); /* Generate PEM certificate. */ result = Curl_base64_encode(cert.certificate.beg, cert.certificate.end - cert.certificate.beg, - &cp1, &cl1); + &certptr, &clen); if(result) - return result; - /* Compute the number of characters in final certificate string. Format is: + goto done; + + /* Generate the final output certificate string. Format is: -----BEGIN CERTIFICATE-----\n \n . @@ -1232,207 +1224,36 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, . -----END CERTIFICATE-----\n */ - i = 28 + cl1 + (cl1 + 64 - 1) / 64 + 26; - cp2 = malloc(i + 1); - if(!cp2) { - free(cp1); - return CURLE_OUT_OF_MEMORY; - } - /* Build the certificate string. */ - i = copySubstring(cp2, "-----BEGIN CERTIFICATE-----"); - for(j = 0; j < cl1; j += 64) - i += copySubstring(cp2 + i, cp1 + j); - i += copySubstring(cp2 + i, "-----END CERTIFICATE-----"); - cp2[i] = '\0'; - free(cp1); - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Cert", cp2); - if(!certnum) - infof(data, "%s", cp2); - free(cp2); - return result; -} -#endif /* WANT_EXTRACT_CERTINFO */ - -#endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP */ - -#ifdef WANT_VERIFYHOST - -static const char *checkOID(const char *beg, const char *end, - const char *oid) -{ - struct Curl_asn1Element e; - const char *ccp; - const char *p; - bool matched; - - /* Check if first ASN.1 element at `beg' is the given OID. - Return a pointer in the source after the OID if found, else NULL. */ - - ccp = getASN1Element(&e, beg, end); - if(!ccp || e.tag != CURL_ASN1_OBJECT_IDENTIFIER) - return NULL; - - p = OID2str(e.beg, e.end, FALSE); - if(!p) - return NULL; - - matched = !strcmp(p, oid); - free((char *) p); - return matched? ccp: NULL; -} - -CURLcode Curl_verifyhost(struct Curl_cfilter *cf, - struct Curl_easy *data, - const char *beg, const char *end) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct Curl_X509certificate cert; - struct Curl_asn1Element dn; - struct Curl_asn1Element elem; - struct Curl_asn1Element ext; - struct Curl_asn1Element name; - const char *p; - const char *q; - char *dnsname; - int matched = -1; - size_t addrlen = (size_t) -1; - ssize_t len; - size_t hostlen; - -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif - - /* Verify that connection server matches info in X509 certificate at - `beg'..`end'. */ - - if(!conn_config->verifyhost) - return CURLE_OK; - - if(Curl_parseX509(&cert, beg, end)) - return CURLE_PEER_FAILED_VERIFICATION; - - hostlen = strlen(connssl->hostname); - - /* Get the server IP address. */ -#ifdef ENABLE_IPV6 - if(cf->conn->bits.ipv6_ip && - Curl_inet_pton(AF_INET6, connssl->hostname, &addr)) - addrlen = sizeof(struct in6_addr); - else -#endif - if(Curl_inet_pton(AF_INET, connssl->hostname, &addr)) - addrlen = sizeof(struct in_addr); + Curl_dyn_reset(&out); - /* Process extensions. */ - for(p = cert.extensions.beg; p < cert.extensions.end && matched != 1;) { - p = getASN1Element(&ext, p, cert.extensions.end); - if(!p) - return CURLE_PEER_FAILED_VERIFICATION; - - /* Check if extension is a subjectAlternativeName. */ - ext.beg = checkOID(ext.beg, ext.end, sanOID); - if(ext.beg) { - ext.beg = getASN1Element(&elem, ext.beg, ext.end); - if(!ext.beg) - return CURLE_PEER_FAILED_VERIFICATION; - /* Skip critical if present. */ - if(elem.tag == CURL_ASN1_BOOLEAN) { - ext.beg = getASN1Element(&elem, ext.beg, ext.end); - if(!ext.beg) - return CURLE_PEER_FAILED_VERIFICATION; - } - /* Parse the octet string contents: is a single sequence. */ - if(!getASN1Element(&elem, elem.beg, elem.end)) - return CURLE_PEER_FAILED_VERIFICATION; - /* Check all GeneralNames. */ - for(q = elem.beg; matched != 1 && q < elem.end;) { - q = getASN1Element(&name, q, elem.end); - if(!q) - break; - switch(name.tag) { - case 2: /* DNS name. */ - len = utf8asn1str(&dnsname, CURL_ASN1_IA5_STRING, - name.beg, name.end); - if(len > 0 && (size_t)len == strlen(dnsname)) - matched = Curl_cert_hostcheck(dnsname, (size_t)len, - connssl->hostname, hostlen); - else - matched = 0; - free(dnsname); - break; - - case 7: /* IP address. */ - matched = (size_t)(name.end - name.beg) == addrlen && - !memcmp(&addr, name.beg, addrlen); - break; - } - } - } - } - - switch(matched) { - case 1: - /* an alternative name matched the server hostname */ - infof(data, " subjectAltName: %s matched", connssl->dispname); - return CURLE_OK; - case 0: - /* an alternative name field existed, but didn't match and then - we MUST fail */ - infof(data, " subjectAltName does not match %s", connssl->dispname); - return CURLE_PEER_FAILED_VERIFICATION; - } - - /* Process subject. */ - name.header = NULL; - name.beg = name.end = ""; - q = cert.subject.beg; - /* we have to look to the last occurrence of a commonName in the - distinguished one to get the most significant one. */ - while(q < cert.subject.end) { - q = getASN1Element(&dn, q, cert.subject.end); - if(!q) - break; - for(p = dn.beg; p < dn.end;) { - p = getASN1Element(&elem, p, dn.end); - if(!p) - return CURLE_PEER_FAILED_VERIFICATION; - /* We have a DN's AttributeTypeAndValue: check it in case it's a CN. */ - elem.beg = checkOID(elem.beg, elem.end, cnOID); - if(elem.beg) - name = elem; /* Latch CN. */ - } - } - - /* Check the CN if found. */ - if(!getASN1Element(&elem, name.beg, name.end)) - failf(data, "SSL: unable to obtain common name from peer certificate"); - else { - len = utf8asn1str(&dnsname, elem.tag, elem.beg, elem.end); - if(len < 0) { - free(dnsname); - return CURLE_OUT_OF_MEMORY; - } - if(strlen(dnsname) != (size_t) len) /* Nul byte in string ? */ - failf(data, "SSL: illegal cert name field"); - else if(Curl_cert_hostcheck((const char *) dnsname, - len, connssl->hostname, hostlen)) { - infof(data, " common name: %s (matched)", dnsname); - free(dnsname); - return CURLE_OK; + /* Build the certificate string. */ + result = Curl_dyn_add(&out, "-----BEGIN CERTIFICATE-----\n"); + if(!result) { + size_t j = 0; + + while(!result && (j < clen)) { + size_t chunksize = (clen - j) > 64 ? 64 : (clen - j); + result = Curl_dyn_addn(&out, &certptr[j], chunksize); + if(!result) + result = Curl_dyn_addn(&out, "\n", 1); + j += chunksize; } - else - failf(data, "SSL: certificate subject name '%s' does not match " - "target host name '%s'", dnsname, connssl->dispname); - free(dnsname); + if(!result) + result = Curl_dyn_add(&out, "-----END CERTIFICATE-----\n"); } + free(certptr); + if(!result) + if(data->set.ssl.certinfo) + result = ssl_push_certinfo_dyn(data, certnum, "Cert", &out); - return CURLE_PEER_FAILED_VERIFICATION; +done: + if(result) + failf(data, "Failed extracting certificate chain"); + Curl_dyn_free(&out); + return result; } -#endif /* WANT_VERIFYHOST */ +#endif /* WANT_EXTRACT_CERTINFO */ + +#endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP */ diff --git a/deps/curl/lib/vtls/x509asn1.h b/deps/curl/lib/vtls/x509asn1.h index 23a67b828a6..5b48596c759 100644 --- a/deps/curl/lib/vtls/x509asn1.h +++ b/deps/curl/lib/vtls/x509asn1.h @@ -28,7 +28,8 @@ #include "curl_setup.h" #if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ - defined(USE_SCHANNEL) || defined(USE_SECTRANSP) + defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_MBEDTLS) #include "cfilters.h" #include "urldata.h" @@ -76,5 +77,16 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, int certnum, const char *beg, const char *end); CURLcode Curl_verifyhost(struct Curl_cfilter *cf, struct Curl_easy *data, const char *beg, const char *end); + +#ifdef UNITTESTS +#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_MBEDTLS) + +/* used by unit1656.c */ +CURLcode Curl_x509_GTime2str(struct dynbuf *store, + const char *beg, const char *end); +#endif +#endif + #endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP */ #endif /* HEADER_CURL_X509ASN1_H */ diff --git a/deps/curl/lib/warnless.c b/deps/curl/lib/warnless.c index 65c5ec53540..8da3be3a8ae 100644 --- a/deps/curl/lib/warnless.c +++ b/deps/curl/lib/warnless.c @@ -37,7 +37,7 @@ #include "warnless.h" -#ifdef WIN32 +#ifdef _WIN32 #undef read #undef write #endif @@ -345,29 +345,7 @@ size_t curlx_sitouz(int sinum) #endif } -#ifdef USE_WINSOCK - -/* -** curl_socket_t to signed int -*/ - -int curlx_sktosi(curl_socket_t s) -{ - return (int)((ssize_t) s); -} - -/* -** signed int to curl_socket_t -*/ - -curl_socket_t curlx_sitosk(int i) -{ - return (curl_socket_t)((ssize_t) i); -} - -#endif /* USE_WINSOCK */ - -#if defined(WIN32) +#if defined(_WIN32) ssize_t curlx_read(int fd, void *buf, size_t count) { @@ -379,59 +357,8 @@ ssize_t curlx_write(int fd, const void *buf, size_t count) return (ssize_t)write(fd, buf, curlx_uztoui(count)); } -/* Ensure that warnless.h continues to have an effect in "unity" builds. */ -#undef HEADER_CURL_WARNLESS_H +#endif /* _WIN32 */ -#endif /* WIN32 */ - -#if defined(__INTEL_COMPILER) && defined(__unix__) - -int curlx_FD_ISSET(int fd, fd_set *fdset) -{ - #pragma warning(push) - #pragma warning(disable:1469) /* clobber ignored */ - return FD_ISSET(fd, fdset); - #pragma warning(pop) -} - -void curlx_FD_SET(int fd, fd_set *fdset) -{ - #pragma warning(push) - #pragma warning(disable:1469) /* clobber ignored */ - FD_SET(fd, fdset); - #pragma warning(pop) -} - -void curlx_FD_ZERO(fd_set *fdset) -{ - #pragma warning(push) - #pragma warning(disable:593) /* variable was set but never used */ - FD_ZERO(fdset); - #pragma warning(pop) -} - -unsigned short curlx_htons(unsigned short usnum) -{ -#if (__INTEL_COMPILER == 910) && defined(__i386__) - return (unsigned short)(((usnum << 8) & 0xFF00) | ((usnum >> 8) & 0x00FF)); -#else - #pragma warning(push) - #pragma warning(disable:810) /* conversion may lose significant bits */ - return htons(usnum); - #pragma warning(pop) -#endif -} - -unsigned short curlx_ntohs(unsigned short usnum) -{ -#if (__INTEL_COMPILER == 910) && defined(__i386__) - return (unsigned short)(((usnum << 8) & 0xFF00) | ((usnum >> 8) & 0x00FF)); -#else - #pragma warning(push) - #pragma warning(disable:810) /* conversion may lose significant bits */ - return ntohs(usnum); - #pragma warning(pop) -#endif -} - -#endif /* __INTEL_COMPILER && __unix__ */ +/* Ensure that warnless.h redefinitions continue to have an effect + in "unity" builds. */ +#undef HEADER_CURL_WARNLESS_H_REDEFS diff --git a/deps/curl/lib/warnless.h b/deps/curl/lib/warnless.h index 2a5301628f3..972c7a9c0d9 100644 --- a/deps/curl/lib/warnless.h +++ b/deps/curl/lib/warnless.h @@ -61,39 +61,24 @@ unsigned short curlx_uitous(unsigned int uinum); size_t curlx_sitouz(int sinum); -#ifdef USE_WINSOCK - -int curlx_sktosi(curl_socket_t s); +#if defined(_WIN32) -curl_socket_t curlx_sitosk(int i); +ssize_t curlx_read(int fd, void *buf, size_t count); -#endif /* USE_WINSOCK */ +ssize_t curlx_write(int fd, const void *buf, size_t count); -#if defined(WIN32) +#endif /* _WIN32 */ -ssize_t curlx_read(int fd, void *buf, size_t count); +#endif /* HEADER_CURL_WARNLESS_H */ -ssize_t curlx_write(int fd, const void *buf, size_t count); +#ifndef HEADER_CURL_WARNLESS_H_REDEFS +#define HEADER_CURL_WARNLESS_H_REDEFS +#if defined(_WIN32) #undef read #define read(fd, buf, count) curlx_read(fd, buf, count) #undef write #define write(fd, buf, count) curlx_write(fd, buf, count) +#endif -#endif /* WIN32 */ - -#if defined(__INTEL_COMPILER) && defined(__unix__) - -int curlx_FD_ISSET(int fd, fd_set *fdset); - -void curlx_FD_SET(int fd, fd_set *fdset); - -void curlx_FD_ZERO(fd_set *fdset); - -unsigned short curlx_htons(unsigned short usnum); - -unsigned short curlx_ntohs(unsigned short usnum); - -#endif /* __INTEL_COMPILER && __unix__ */ - -#endif /* HEADER_CURL_WARNLESS_H */ +#endif /* HEADER_CURL_WARNLESS_H_REDEFS */ diff --git a/deps/curl/lib/ws.c b/deps/curl/lib/ws.c index 3c1964b8606..3d739a538a1 100644 --- a/deps/curl/lib/ws.c +++ b/deps/curl/lib/ws.c @@ -24,7 +24,7 @@ #include "curl_setup.h" #include -#ifdef USE_WEBSOCKETS +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) #include "urldata.h" #include "bufq.h" @@ -37,6 +37,7 @@ #include "ws.h" #include "easyif.h" #include "transfer.h" +#include "select.h" #include "nonblock.h" /* The last 3 #include files should be in this order */ @@ -102,7 +103,7 @@ static unsigned char ws_frame_flags2op(int flags) size_t i; for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) { if(WS_FRAMES[i].flags & flags) - return WS_FRAMES[i].proto_opcode; + return (unsigned char)WS_FRAMES[i].proto_opcode; } return 0; } @@ -114,28 +115,31 @@ static void ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data, case 0: break; case 1: - infof(data, "WS-DEC: %s [%s%s]", msg, - ws_frame_name_of_op(dec->head[0]), - (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL"); + CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s]", msg, + ws_frame_name_of_op(dec->head[0]), + (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL"); break; default: if(dec->head_len < dec->head_total) { - infof(data, "WS-DEC: %s [%s%s](%d/%d)", msg, - ws_frame_name_of_op(dec->head[0]), - (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL", - dec->head_len, dec->head_total); + CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s](%d/%d)", msg, + ws_frame_name_of_op(dec->head[0]), + (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL", + dec->head_len, dec->head_total); } else { - infof(data, "WS-DEC: %s [%s%s payload=%" CURL_FORMAT_CURL_OFF_T - "/%" CURL_FORMAT_CURL_OFF_T "]", - msg, ws_frame_name_of_op(dec->head[0]), - (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL", - dec->payload_offset, dec->payload_len); + CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s payload=%" + FMT_OFF_T "/%" FMT_OFF_T "]", + msg, ws_frame_name_of_op(dec->head[0]), + (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL", + dec->payload_offset, dec->payload_len); } break; } } +static CURLcode ws_send_raw_blocking(CURL *data, struct websocket *ws, + const char *buffer, size_t buflen); + typedef ssize_t ws_write_payload(const unsigned char *buf, size_t buflen, int frame_age, int frame_flags, curl_off_t payload_offset, @@ -171,7 +175,7 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, dec->head[0] = *inbuf; Curl_bufq_skip(inraw, 1); - dec->frame_flags = ws_frame_op2flags(dec->head[0]); + dec->frame_flags = ws_frame_op2flags(dec->head[0]); if(!dec->frame_flags) { failf(data, "WS: unknown opcode: %x", dec->head[0]); ws_dec_reset(dec); @@ -225,6 +229,10 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, dec->payload_len = (dec->head[2] << 8) | dec->head[3]; break; case 10: + if(dec->head[2] > 127) { + failf(data, "WS: frame length longer than 64 signed not supported"); + return CURLE_RECV_ERROR; + } dec->payload_len = ((curl_off_t)dec->head[2] << 56) | (curl_off_t)dec->head[3] << 48 | (curl_off_t)dec->head[4] << 40 | @@ -273,12 +281,11 @@ static CURLcode ws_dec_pass_payload(struct ws_decoder *dec, Curl_bufq_skip(inraw, (size_t)nwritten); dec->payload_offset += (curl_off_t)nwritten; remain = dec->payload_len - dec->payload_offset; - /* infof(data, "WS-DEC: passed %zd bytes payload, %" - CURL_FORMAT_CURL_OFF_T " remain", - nwritten, remain); */ + CURL_TRC_WRITE(data, "websocket, passed %zd bytes payload, %" + FMT_OFF_T " remain", nwritten, remain); } - return remain? CURLE_AGAIN : CURLE_OK; + return remain ? CURLE_AGAIN : CURLE_OK; } static CURLcode ws_dec_pass(struct ws_decoder *dec, @@ -296,7 +303,7 @@ static CURLcode ws_dec_pass(struct ws_decoder *dec, case WS_DEC_INIT: ws_dec_reset(dec); dec->state = WS_DEC_HEAD; - /* FALLTHROUGH */ + FALLTHROUGH(); case WS_DEC_HEAD: result = ws_dec_read_head(dec, data, inraw); if(result) { @@ -321,7 +328,7 @@ static CURLcode ws_dec_pass(struct ws_decoder *dec, dec->state = WS_DEC_INIT; break; } - /* FALLTHROUGH */ + FALLTHROUGH(); case WS_DEC_PAYLOAD: result = ws_dec_pass_payload(dec, data, inraw, write_payload, write_ctx); ws_dec_info(dec, data, "passing"); @@ -350,15 +357,146 @@ static void update_meta(struct websocket *ws, ws->frame.bytesleft = (payload_len - payload_offset - cur_len); } +/* WebSockets decoding client writer */ +struct ws_cw_ctx { + struct Curl_cwriter super; + struct bufq buf; +}; + +static CURLcode ws_cw_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct ws_cw_ctx *ctx = writer->ctx; + (void)data; + Curl_bufq_init2(&ctx->buf, WS_CHUNK_SIZE, 1, BUFQ_OPT_SOFT_LIMIT); + return CURLE_OK; +} + +static void ws_cw_close(struct Curl_easy *data, struct Curl_cwriter *writer) +{ + struct ws_cw_ctx *ctx = writer->ctx; + (void) data; + Curl_bufq_free(&ctx->buf); +} + +struct ws_cw_dec_ctx { + struct Curl_easy *data; + struct websocket *ws; + struct Curl_cwriter *next_writer; + int cw_type; +}; + +static ssize_t ws_cw_dec_next(const unsigned char *buf, size_t buflen, + int frame_age, int frame_flags, + curl_off_t payload_offset, + curl_off_t payload_len, + void *user_data, + CURLcode *err) +{ + struct ws_cw_dec_ctx *ctx = user_data; + struct Curl_easy *data = ctx->data; + struct websocket *ws = ctx->ws; + curl_off_t remain = (payload_len - (payload_offset + buflen)); + + (void)frame_age; + if((frame_flags & CURLWS_PING) && !remain) { + /* auto-respond to PINGs, only works for single-frame payloads atm */ + size_t bytes; + infof(data, "WS: auto-respond to PING with a PONG"); + /* send back the exact same content as a PONG */ + *err = curl_ws_send(data, buf, buflen, &bytes, 0, CURLWS_PONG); + if(*err) + return -1; + } + else if(buflen || !remain) { + /* forward the decoded frame to the next client writer. */ + update_meta(ws, frame_age, frame_flags, payload_offset, + payload_len, buflen); + + *err = Curl_cwriter_write(data, ctx->next_writer, ctx->cw_type, + (const char *)buf, buflen); + if(*err) + return -1; + } + *err = CURLE_OK; + return (ssize_t)buflen; +} + +static CURLcode ws_cw_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + struct ws_cw_ctx *ctx = writer->ctx; + struct websocket *ws; + CURLcode result; + + if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + + ws = data->conn->proto.ws; + if(!ws) { + failf(data, "WS: not a websocket transfer"); + return CURLE_FAILED_INIT; + } + + if(nbytes) { + ssize_t nwritten; + nwritten = Curl_bufq_write(&ctx->buf, (const unsigned char *)buf, + nbytes, &result); + if(nwritten < 0) { + infof(data, "WS: error adding data to buffer %d", result); + return result; + } + } + + while(!Curl_bufq_is_empty(&ctx->buf)) { + struct ws_cw_dec_ctx pass_ctx; + pass_ctx.data = data; + pass_ctx.ws = ws; + pass_ctx.next_writer = writer->next; + pass_ctx.cw_type = type; + result = ws_dec_pass(&ws->dec, data, &ctx->buf, + ws_cw_dec_next, &pass_ctx); + if(result == CURLE_AGAIN) { + /* insufficient amount of data, keep it for later. + * we pretend to have written all since we have a copy */ + CURL_TRC_WRITE(data, "websocket, buffered incomplete frame head"); + return CURLE_OK; + } + else if(result) { + infof(data, "WS: decode error %d", (int)result); + return result; + } + } + + if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) { + infof(data, "WS: decode ending with %zd frame bytes remaining", + Curl_bufq_len(&ctx->buf)); + return CURLE_RECV_ERROR; + } + + return CURLE_OK; +} + +/* WebSocket payload decoding client writer. */ +static const struct Curl_cwtype ws_cw_decode = { + "ws-decode", + NULL, + ws_cw_init, + ws_cw_write, + ws_cw_close, + sizeof(struct ws_cw_ctx) +}; + + static void ws_enc_info(struct ws_encoder *enc, struct Curl_easy *data, const char *msg) { - infof(data, "WS-ENC: %s [%s%s%s payload=%" CURL_FORMAT_CURL_OFF_T - "/%" CURL_FORMAT_CURL_OFF_T "]", + infof(data, "WS-ENC: %s [%s%s%s payload=%" FMT_OFF_T "/%" FMT_OFF_T "]", msg, ws_frame_name_of_op(enc->firstbyte), (enc->firstbyte & WSBIT_OPCODE_MASK) == WSBIT_OPCODE_CONT ? " CONT" : "", - (enc->firstbyte & WSBIT_FIN)? "" : " NON-FIN", + (enc->firstbyte & WSBIT_FIN) ? "" : " NON-FIN", enc->payload_len - enc->payload_remain, enc->payload_len); } @@ -410,15 +548,22 @@ static ssize_t ws_enc_write_head(struct Curl_easy *data, size_t hlen; ssize_t n; + if(payload_len < 0) { + failf(data, "WS: starting new frame with negative payload length %" + FMT_OFF_T, payload_len); + *err = CURLE_SEND_ERROR; + return -1; + } + if(enc->payload_remain > 0) { /* trying to write a new frame before the previous one is finished */ - failf(data, "WS: starting new frame with %zd bytes from last one" + failf(data, "WS: starting new frame with %zd bytes from last one " "remaining to be sent", (ssize_t)enc->payload_remain); *err = CURLE_SEND_ERROR; return -1; } - opcode = ws_frame_flags2op(flags); + opcode = ws_frame_flags2op((int)flags & ~CURLWS_CONT); if(!opcode) { failf(data, "WS: provided flags not recognized '%x'", flags); *err = CURLE_SEND_ERROR; @@ -437,7 +582,7 @@ static ssize_t ws_enc_write_head(struct Curl_easy *data, enc->contfragment = FALSE; } else if(enc->contfragment) { - /* the previous fragment was not a final one and this isn't either, keep a + /* the previous fragment was not a final one and this is not either, keep a CONT opcode and no FIN bit */ firstbyte |= WSBIT_OPCODE_CONT; } @@ -576,8 +721,10 @@ CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req) if(result) return result; DEBUGASSERT(randlen < sizeof(keyval)); - if(randlen >= sizeof(keyval)) + if(randlen >= sizeof(keyval)) { + free(randstr); return CURLE_FAILED_INIT; + } strcpy(keyval, randstr); free(randstr); for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) { @@ -607,17 +754,32 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, { struct SingleRequest *k = &data->req; struct websocket *ws; + struct Curl_cwriter *ws_dec_writer; CURLcode result; DEBUGASSERT(data->conn); ws = data->conn->proto.ws; if(!ws) { + size_t chunk_size = WS_CHUNK_SIZE; ws = calloc(1, sizeof(*ws)); if(!ws) return CURLE_OUT_OF_MEMORY; data->conn->proto.ws = ws; - Curl_bufq_init(&ws->recvbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT); - Curl_bufq_init2(&ws->sendbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT, +#ifdef DEBUGBUILD + { + char *p = getenv("CURL_WS_CHUNK_SIZE"); + if(p) { + long l = strtol(p, NULL, 10); + if(l > 0 && l <= (1*1024*1024)) { + chunk_size = (size_t)l; + } + } + } +#endif + CURL_TRC_WS(data, "WS, using chunk size %zu", chunk_size); + Curl_bufq_init2(&ws->recvbuf, chunk_size, WS_CHUNK_COUNT, + BUFQ_OPT_SOFT_LIMIT); + Curl_bufq_init2(&ws->sendbuf, chunk_size, WS_CHUNK_COUNT, BUFQ_OPT_SOFT_LIMIT); ws_dec_init(&ws->dec); ws_enc_init(&ws->enc); @@ -655,6 +817,18 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x", ws->enc.mask[0], ws->enc.mask[1], ws->enc.mask[2], ws->enc.mask[3]); + /* Install our client writer that decodes WS frames payload */ + result = Curl_cwriter_create(&ws_dec_writer, data, &ws_cw_decode, + CURL_CW_CONTENT_DECODE); + if(result) + return result; + + result = Curl_cwriter_add(data, ws_dec_writer); + if(result) { + Curl_cwriter_free(data, ws_dec_writer); + return result; + } + if(data->set.connect_only) { ssize_t nwritten; /* In CONNECT_ONLY setup, the payloads from `mem` need to be received @@ -666,110 +840,20 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, return result; infof(data, "%zu bytes websocket payload", nread); } - k->upgr101 = UPGR101_RECEIVED; - - return result; -} - -static ssize_t ws_client_write(const unsigned char *buf, size_t buflen, - int frame_age, int frame_flags, - curl_off_t payload_offset, - curl_off_t payload_len, - void *userp, - CURLcode *err) -{ - struct Curl_easy *data = userp; - struct websocket *ws; - size_t wrote; - curl_off_t remain = (payload_len - (payload_offset + buflen)); - - (void)frame_age; - if(!data->conn || !data->conn->proto.ws) { - *err = CURLE_FAILED_INIT; - return -1; - } - ws = data->conn->proto.ws; - - if((frame_flags & CURLWS_PING) && !remain) { - /* auto-respond to PINGs, only works for single-frame payloads atm */ - size_t bytes; - infof(data, "WS: auto-respond to PING with a PONG"); - /* send back the exact same content as a PONG */ - *err = curl_ws_send(data, buf, buflen, &bytes, 0, CURLWS_PONG); - if(*err) - return -1; - } - else if(buflen || !remain) { - /* deliver the decoded frame to the user callback. The application - * may invoke curl_ws_meta() to access frame information. */ - update_meta(ws, frame_age, frame_flags, payload_offset, - payload_len, buflen); - Curl_set_in_callback(data, true); - wrote = data->set.fwrite_func((char *)buf, 1, - buflen, data->set.out); - Curl_set_in_callback(data, false); - if(wrote != buflen) { - *err = CURLE_RECV_ERROR; - return -1; + else { /* !connect_only */ + /* And pass any additional data to the writers */ + if(nread) { + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)mem, nread); } } - *err = CURLE_OK; - return (ssize_t)buflen; -} - -/* Curl_ws_writecb() is the write callback for websocket traffic. The - websocket data is provided to this raw, in chunks. This function should - handle/decode the data and call the "real" underlying callback accordingly. -*/ -size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */, - size_t nitems, void *userp) -{ - struct Curl_easy *data = userp; - - if(data->set.ws_raw_mode) - return data->set.fwrite_func(buffer, size, nitems, data->set.out); - else if(nitems) { - struct websocket *ws; - CURLcode result; - - if(!data->conn || !data->conn->proto.ws) { - failf(data, "WS: not a websocket transfer"); - return nitems - 1; - } - ws = data->conn->proto.ws; - - if(buffer) { - ssize_t nwritten; - - nwritten = Curl_bufq_write(&ws->recvbuf, (const unsigned char *)buffer, - nitems, &result); - if(nwritten < 0) { - infof(data, "WS: error adding data to buffer %d", (int)result); - return nitems - 1; - } - buffer = NULL; - } - - while(!Curl_bufq_is_empty(&ws->recvbuf)) { + k->upgr101 = UPGR101_RECEIVED; - result = ws_dec_pass(&ws->dec, data, &ws->recvbuf, - ws_client_write, data); - if(result == CURLE_AGAIN) - /* insufficient amount of data, keep it for later. - * we pretend to have written all since we have a copy */ - return nitems; - else if(result) { - infof(data, "WS: decode error %d", (int)result); - return nitems - 1; - } - } - } - return nitems; + return result; } struct ws_collect { struct Curl_easy *data; - void *buffer; + unsigned char *buffer; size_t buflen; size_t bufidx; int frame_age; @@ -821,7 +905,7 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen, return -1; } *err = CURLE_OK; - memcpy(ctx->buffer, buf, nwritten); + memcpy(ctx->buffer + ctx->bufidx, buf, nwritten); ctx->bufidx += nwritten; } return nwritten; @@ -840,15 +924,17 @@ static ssize_t nw_in_recv(void *reader_ctx, return (ssize_t)nread; } -CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, +CURL_EXTERN CURLcode curl_ws_recv(CURL *d, void *buffer, size_t buflen, size_t *nread, const struct curl_ws_frame **metap) { + struct Curl_easy *data = d; struct connectdata *conn = data->conn; struct websocket *ws; - bool done = FALSE; /* not filled passed buffer yet */ struct ws_collect ctx; - CURLcode result; + + *nread = 0; + *metap = NULL; if(!conn) { /* Unhappy hack with lifetimes of transfers and connection */ @@ -869,19 +955,15 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, return CURLE_BAD_FUNCTION_ARGUMENT; } - *nread = 0; - *metap = NULL; - /* get a download buffer */ - result = Curl_preconnect(data); - if(result) - return result; memset(&ctx, 0, sizeof(ctx)); ctx.data = data; ctx.buffer = buffer; ctx.buflen = buflen; - while(!done) { + while(1) { + CURLcode result; + /* receive more when our buffer is empty */ if(Curl_bufq_is_empty(&ws->recvbuf)) { ssize_t n = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &result); @@ -893,8 +975,8 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, infof(data, "connection expectedly closed?"); return CURLE_GOT_NOTHING; } - DEBUGF(infof(data, "curl_ws_recv, added %zu bytes from network", - Curl_bufq_len(&ws->recvbuf))); + CURL_TRC_WS(data, "curl_ws_recv, added %zu bytes from network", + Curl_bufq_len(&ws->recvbuf)); } result = ws_dec_pass(&ws->dec, data, &ws->recvbuf, @@ -904,7 +986,6 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, ws_dec_info(&ws->dec, data, "need more input"); continue; /* nothing written, try more input */ } - done = TRUE; break; } else if(result) { @@ -914,7 +995,6 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, /* The decoded frame is passed back to our caller. * There are frames like PING were we auto-respond to and * that we do not return. For these `ctx.written` is not set. */ - done = TRUE; break; } } @@ -924,111 +1004,184 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, ctx.payload_len, ctx.bufidx); *metap = &ws->frame; *nread = ws->frame.len; - /* infof(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %" - CURL_FORMAT_CURL_OFF_T ", %" CURL_FORMAT_CURL_OFF_T " left)", - buflen, *nread, ws->frame.offset, ws->frame.bytesleft); */ + CURL_TRC_WS(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %" + FMT_OFF_T ", %" FMT_OFF_T " left)", + buflen, *nread, ws->frame.offset, ws->frame.bytesleft); return CURLE_OK; } static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, - bool complete) + bool blocking) { if(!Curl_bufq_is_empty(&ws->sendbuf)) { CURLcode result; const unsigned char *out; - size_t outlen; - ssize_t n; + size_t outlen, n; while(Curl_bufq_peek(&ws->sendbuf, &out, &outlen)) { - if(data->set.connect_only) + if(blocking) { + result = ws_send_raw_blocking(data, ws, (char *)out, outlen); + n = result ? 0 : outlen; + } + else if(data->set.connect_only || Curl_is_in_callback(data)) result = Curl_senddata(data, out, outlen, &n); - else - result = Curl_write(data, data->conn->writesockfd, out, outlen, &n); - if(result) { - if(result == CURLE_AGAIN) { - if(!complete) { - infof(data, "WS: flush EAGAIN, %zu bytes remain in buffer", - Curl_bufq_len(&ws->sendbuf)); - return result; - } - /* TODO: the current design does not allow for buffered writes. - * We need to flush the buffer now. There is no ws_flush() later */ - n = 0; - continue; - } - else if(result) { - failf(data, "WS: flush, write error %d", result); - return result; - } + else { + result = Curl_xfer_send(data, out, outlen, FALSE, &n); + if(!result && !n && outlen) + result = CURLE_AGAIN; + } + + if(result == CURLE_AGAIN) { + CURL_TRC_WS(data, "flush EAGAIN, %zu bytes remain in buffer", + Curl_bufq_len(&ws->sendbuf)); + return result; + } + else if(result) { + failf(data, "WS: flush, write error %d", result); + return result; } else { - infof(data, "WS: flushed %zu bytes", (size_t)n); - Curl_bufq_skip(&ws->sendbuf, (size_t)n); + infof(data, "WS: flushed %zu bytes", n); + Curl_bufq_skip(&ws->sendbuf, n); } } } return CURLE_OK; } -CURL_EXTERN CURLcode curl_ws_send(CURL *data, const void *buffer, +static CURLcode ws_send_raw_blocking(CURL *d, struct websocket *ws, + const char *buffer, size_t buflen) +{ + CURLcode result = CURLE_OK; + size_t nwritten; + struct Curl_easy *data = d; + + (void)ws; + while(buflen) { + result = Curl_xfer_send(data, buffer, buflen, FALSE, &nwritten); + if(result) + return result; + DEBUGASSERT(nwritten <= buflen); + buffer += nwritten; + buflen -= nwritten; + if(buflen) { + curl_socket_t sock = data->conn->sock[FIRSTSOCKET]; + timediff_t left_ms; + int ev; + + CURL_TRC_WS(data, "ws_send_raw_blocking() partial, %zu left to send", + buflen); + left_ms = Curl_timeleft(data, NULL, FALSE); + if(left_ms < 0) { + failf(data, "Timeout waiting for socket becoming writable"); + return CURLE_SEND_ERROR; + } + + /* POLLOUT socket */ + if(sock == CURL_SOCKET_BAD) + return CURLE_SEND_ERROR; + ev = Curl_socket_check(CURL_SOCKET_BAD, CURL_SOCKET_BAD, sock, + left_ms ? left_ms : 500); + if(ev < 0) { + failf(data, "Error while waiting for socket becoming writable"); + return CURLE_SEND_ERROR; + } + } + } + return result; +} + +static CURLcode ws_send_raw(struct Curl_easy *data, const void *buffer, + size_t buflen, size_t *pnwritten) +{ + struct websocket *ws = data->conn->proto.ws; + CURLcode result; + + if(!ws) { + failf(data, "Not a websocket transfer"); + return CURLE_SEND_ERROR; + } + if(!buflen) + return CURLE_OK; + + if(Curl_is_in_callback(data)) { + /* When invoked from inside callbacks, we do a blocking send as the + * callback will probably not implement partial writes that may then + * mess up the ws framing subsequently. + * We need any pending data to be flushed before sending. */ + result = ws_flush(data, ws, TRUE); + if(result) + return result; + result = ws_send_raw_blocking(data, ws, buffer, buflen); + } + else { + /* We need any pending data to be sent or EAGAIN this call. */ + result = ws_flush(data, ws, FALSE); + if(result) + return result; + result = Curl_senddata(data, buffer, buflen, pnwritten); + } + + CURL_TRC_WS(data, "ws_send_raw(len=%zu) -> %d, %zu", + buflen, result, *pnwritten); + return result; +} + +CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer, size_t buflen, size_t *sent, curl_off_t fragsize, unsigned int flags) { struct websocket *ws; - ssize_t nwritten, n; - size_t space; + ssize_t n; + size_t space, payload_added; CURLcode result; + struct Curl_easy *data = d; + CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T + ", flags=%x), raw=%d", + buflen, fragsize, flags, data->set.ws_raw_mode); *sent = 0; if(!data->conn && data->set.connect_only) { result = Curl_connect_only_attach(data); if(result) - return result; + goto out; } if(!data->conn) { failf(data, "No associated connection"); - return CURLE_SEND_ERROR; + result = CURLE_SEND_ERROR; + goto out; } if(!data->conn->proto.ws) { failf(data, "Not a websocket transfer"); - return CURLE_SEND_ERROR; + result = CURLE_SEND_ERROR; + goto out; } ws = data->conn->proto.ws; + /* try flushing any content still waiting to be sent. */ + result = ws_flush(data, ws, FALSE); + if(result) + goto out; + if(data->set.ws_raw_mode) { - if(fragsize || flags) + /* In raw mode, we write directly to the connection */ + if(fragsize || flags) { + failf(data, "ws_send, raw mode: fragsize and flags cannot be non-zero"); return CURLE_BAD_FUNCTION_ARGUMENT; - if(!buflen) - /* nothing to do */ - return CURLE_OK; - /* raw mode sends exactly what was requested, and this is from within - the write callback */ - if(Curl_is_in_callback(data)) { - result = Curl_write(data, data->conn->writesockfd, buffer, buflen, - &nwritten); } - else - result = Curl_senddata(data, buffer, buflen, &nwritten); - - infof(data, "WS: wanted to send %zu bytes, sent %zu bytes", - buflen, nwritten); - *sent = (nwritten >= 0)? (size_t)nwritten : 0; - return result; + result = ws_send_raw(data, buffer, buflen, sent); + goto out; } /* Not RAW mode, buf we do the frame encoding */ - result = ws_flush(data, ws, FALSE); - if(result) - return result; - - /* TODO: the current design does not allow partial writes, afaict. - * It is not clear who the application is supposed to react. */ space = Curl_bufq_space(&ws->sendbuf); - DEBUGF(infof(data, "curl_ws_send(len=%zu), sendbuf len=%zu space %zu", - buflen, Curl_bufq_len(&ws->sendbuf), space)); - if(space < 14) - return CURLE_AGAIN; + CURL_TRC_WS(data, "curl_ws_send(len=%zu), sendbuf=%zu space_left=%zu", + buflen, Curl_bufq_len(&ws->sendbuf), space); + if(space < 14) { + result = CURLE_AGAIN; + goto out; + } if(flags & CURLWS_OFFSET) { if(fragsize) { @@ -1036,12 +1189,12 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *data, const void *buffer, n = ws_enc_write_head(data, &ws->enc, flags, fragsize, &ws->sendbuf, &result); if(n < 0) - return result; + goto out; } else { if((curl_off_t)buflen > ws->enc.payload_remain) { infof(data, "WS: unaligned frame size (sending %zu instead of %" - CURL_FORMAT_CURL_OFF_T ")", + FMT_OFF_T ")", buflen, ws->enc.payload_remain); } } @@ -1050,16 +1203,66 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *data, const void *buffer, n = ws_enc_write_head(data, &ws->enc, flags, (curl_off_t)buflen, &ws->sendbuf, &result); if(n < 0) - return result; + goto out; } n = ws_enc_write_payload(&ws->enc, data, buffer, buflen, &ws->sendbuf, &result); if(n < 0) - return result; + goto out; + payload_added = (size_t)n; + + while(!result && (buflen || !Curl_bufq_is_empty(&ws->sendbuf))) { + /* flush, blocking when in callback */ + result = ws_flush(data, ws, Curl_is_in_callback(data)); + if(!result) { + DEBUGASSERT(payload_added <= buflen); + /* all buffered data sent. Try sending the rest if there is any. */ + *sent += payload_added; + buffer = (const char *)buffer + payload_added; + buflen -= payload_added; + payload_added = 0; + if(buflen) { + n = ws_enc_write_payload(&ws->enc, data, + buffer, buflen, &ws->sendbuf, &result); + if(n < 0) + goto out; + payload_added = Curl_bufq_len(&ws->sendbuf); + } + } + else if(result == CURLE_AGAIN) { + /* partially sent. how much of the call data has been part of it? what + * should we report to out caller so it can retry/send the rest? */ + if(payload_added < buflen) { + /* We did not add everything the caller wanted. Return just + * the partial write to our buffer. */ + *sent = payload_added; + result = CURLE_OK; + goto out; + } + else if(!buflen) { + /* We have no payload to report a partial write. EAGAIN would make + * the caller repeat this and add the frame again. + * Flush blocking seems the only way out of this. */ + *sent = (size_t)n; + result = ws_flush(data, ws, TRUE); + goto out; + } + /* We added the complete data to our sendbuf. Report one byte less as + * sent. This partial success should make the caller invoke us again + * with the last byte. */ + *sent = payload_added - 1; + result = Curl_bufq_unwrite(&ws->sendbuf, 1); + if(!result) + result = CURLE_AGAIN; + } + } - *sent = (size_t)n; - return ws_flush(data, ws, TRUE); +out: + CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T + ", flags=%x, raw=%d) -> %d, %zu", + buflen, fragsize, flags, data->set.ws_raw_mode, result, *sent); + return result; } static void ws_free(struct connectdata *conn) @@ -1071,14 +1274,18 @@ static void ws_free(struct connectdata *conn) } } -void Curl_ws_done(struct Curl_easy *data) +static CURLcode ws_setup_conn(struct Curl_easy *data, + struct connectdata *conn) { - (void)data; + /* WebSockets is 1.1 only (for now) */ + data->state.httpwant = CURL_HTTP_VERSION_1_1; + return Curl_http_setup_conn(data, conn); } -CURLcode Curl_ws_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection) + +static CURLcode ws_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) { (void)data; (void)dead_connection; @@ -1086,16 +1293,70 @@ CURLcode Curl_ws_disconnect(struct Curl_easy *data, return CURLE_OK; } -CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data) +CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *d) { /* we only return something for websocket, called from within the callback when not using raw mode */ + struct Curl_easy *data = d; if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && data->conn && data->conn->proto.ws && !data->set.ws_raw_mode) return &data->conn->proto.ws->frame; return NULL; } +const struct Curl_handler Curl_handler_ws = { + "WS", /* scheme */ + ws_setup_conn, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + Curl_http_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + Curl_http_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ws_disconnect, /* disconnect */ + Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_HTTP, /* defport */ + CURLPROTO_WS, /* protocol */ + CURLPROTO_HTTP, /* family */ + PROTOPT_CREDSPERREQUEST | /* flags */ + PROTOPT_USERPWDCTRL +}; + +#ifdef USE_SSL +const struct Curl_handler Curl_handler_wss = { + "WSS", /* scheme */ + ws_setup_conn, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + Curl_http_connect, /* connect_it */ + NULL, /* connecting */ + ZERO_NULL, /* doing */ + NULL, /* proto_getsock */ + Curl_http_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ws_disconnect, /* disconnect */ + Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_HTTPS, /* defport */ + CURLPROTO_WSS, /* protocol */ + CURLPROTO_HTTP, /* family */ + PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */ + PROTOPT_USERPWDCTRL +}; +#endif + + #else CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen, @@ -1124,9 +1385,9 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer, return CURLE_NOT_BUILT_IN; } -CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data) +CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *data) { (void)data; return NULL; } -#endif /* USE_WEBSOCKETS */ +#endif /* !CURL_DISABLE_WEBSOCKETS */ diff --git a/deps/curl/lib/ws.h b/deps/curl/lib/ws.h index 0308a42545b..186cc2c63a8 100644 --- a/deps/curl/lib/ws.h +++ b/deps/curl/lib/ws.h @@ -25,7 +25,7 @@ ***************************************************************************/ #include "curl_setup.h" -#ifdef USE_WEBSOCKETS +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) #ifdef USE_HYPER #define REQTYPE void @@ -57,7 +57,7 @@ struct ws_encoder { curl_off_t payload_len; /* payload length of current frame */ curl_off_t payload_remain; /* remaining payload of current */ unsigned int xori; /* xor index */ - unsigned char mask[4]; /* 32 bit mask for this connection */ + unsigned char mask[4]; /* 32-bit mask for this connection */ unsigned char firstbyte; /* first byte of frame we encode */ bool contfragment; /* set TRUE if the previous fragment sent was not final */ }; @@ -75,14 +75,15 @@ struct websocket { CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req); CURLcode Curl_ws_accept(struct Curl_easy *data, const char *mem, size_t len); -size_t Curl_ws_writecb(char *buffer, size_t size, size_t nitems, void *userp); -void Curl_ws_done(struct Curl_easy *data); -CURLcode Curl_ws_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection); + +extern const struct Curl_handler Curl_handler_ws; +#ifdef USE_SSL +extern const struct Curl_handler Curl_handler_wss; +#endif + + #else #define Curl_ws_request(x,y) CURLE_OK -#define Curl_ws_done(x) Curl_nop_stmt #define Curl_ws_free(x) Curl_nop_stmt #endif