diff --git a/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake b/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake index 31a97cb107..217c8832c9 100644 --- a/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake +++ b/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake @@ -29,7 +29,7 @@ if(WIN32 AND (ENABLE_DEBUG OR ENABLE_CURLDEBUG)) # e.g. curl_easy_perform_ev() or curl_dbg_*(), # so disable symbol hiding for debug builds and for memory tracking. set(CURL_HIDDEN_SYMBOLS OFF) -elseif(DOS OR AMIGA) +elseif(DOS OR AMIGA OR MINGW32CE) set(CURL_HIDDEN_SYMBOLS OFF) endif() @@ -42,18 +42,18 @@ if(CURL_HIDDEN_SYMBOLS) set(CURL_HIDES_PRIVATE_SYMBOLS TRUE) set(CURL_EXTERN_SYMBOL "__attribute__((__visibility__(\"default\")))") set(CURL_CFLAG_SYMBOLS_HIDE "-fvisibility=hidden") - elseif(CMAKE_COMPILER_IS_GNUCC) - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.4) + elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU") + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 3.4) # Note: This is considered buggy prior to 4.0 but the autotools do not care, so let us ignore that fact set(CURL_HIDES_PRIVATE_SYMBOLS TRUE) set(CURL_EXTERN_SYMBOL "__attribute__((__visibility__(\"default\")))") set(CURL_CFLAG_SYMBOLS_HIDE "-fvisibility=hidden") endif() - elseif(CMAKE_C_COMPILER_ID MATCHES "SunPro" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 8.0) + elseif(CMAKE_C_COMPILER_ID MATCHES "SunPro" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 8.0) set(CURL_HIDES_PRIVATE_SYMBOLS TRUE) set(CURL_EXTERN_SYMBOL "__global") set(CURL_CFLAG_SYMBOLS_HIDE "-xldscope=hidden") - elseif(CMAKE_C_COMPILER_ID MATCHES "Intel" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 9.0) # Requires 9.1.045 + elseif(CMAKE_C_COMPILER_ID MATCHES "Intel" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 9.0) # Requires 9.1.045 set(CURL_HIDES_PRIVATE_SYMBOLS TRUE) set(CURL_EXTERN_SYMBOL "__attribute__((__visibility__(\"default\")))") set(CURL_CFLAG_SYMBOLS_HIDE "-fvisibility=hidden") diff --git a/Utilities/cmcurl/CMake/CurlTests.c b/Utilities/cmcurl/CMake/CurlTests.c index c5a5257672..de2313080d 100644 --- a/Utilities/cmcurl/CMake/CurlTests.c +++ b/Utilities/cmcurl/CMake/CurlTests.c @@ -30,14 +30,14 @@ /* */ #if defined(sun) || defined(__sun__) || \ defined(__SUNPRO_C) || defined(__SUNPRO_CC) -# if defined(__SVR4) || defined(__srv4__) -# define PLATFORM_SOLARIS -# else -# define PLATFORM_SUNOS4 -# endif +# if defined(__SVR4) || defined(__srv4__) +# define PLATFORM_SOLARIS +# else +# define PLATFORM_SUNOS4 +# endif #endif #if (defined(_AIX) || defined(__xlC__)) && !defined(_AIX41) -# define PLATFORM_AIX_V3 +# define PLATFORM_AIX_V3 #endif /* */ #if defined(PLATFORM_SUNOS4) || defined(PLATFORM_AIX_V3) @@ -55,72 +55,52 @@ int main(void) #endif /* tests for gethostbyname_r */ -#if defined(HAVE_GETHOSTBYNAME_R_3_REENTRANT) || \ - defined(HAVE_GETHOSTBYNAME_R_5_REENTRANT) || \ - defined(HAVE_GETHOSTBYNAME_R_6_REENTRANT) -# define _REENTRANT - /* no idea whether _REENTRANT is always set, just invent a new flag */ -# define TEST_GETHOSTBYFOO_REENTRANT -#endif #if defined(HAVE_GETHOSTBYNAME_R_3) || \ + defined(HAVE_GETHOSTBYNAME_R_3_REENTRANT) || \ defined(HAVE_GETHOSTBYNAME_R_5) || \ + defined(HAVE_GETHOSTBYNAME_R_5_REENTRANT) || \ defined(HAVE_GETHOSTBYNAME_R_6) || \ - defined(TEST_GETHOSTBYFOO_REENTRANT) + defined(HAVE_GETHOSTBYNAME_R_6_REENTRANT) #include #include int main(void) { const char *address = "example.com"; - int length = 0; - int type = 0; struct hostent h; int rc = 0; -#if defined(HAVE_GETHOSTBYNAME_R_3) || \ - defined(HAVE_GETHOSTBYNAME_R_3_REENTRANT) +#if defined(HAVE_GETHOSTBYNAME_R_3) || \ + defined(HAVE_GETHOSTBYNAME_R_3_REENTRANT) struct hostent_data hdata; #elif defined(HAVE_GETHOSTBYNAME_R_5) || \ defined(HAVE_GETHOSTBYNAME_R_5_REENTRANT) || \ defined(HAVE_GETHOSTBYNAME_R_6) || \ defined(HAVE_GETHOSTBYNAME_R_6_REENTRANT) char buffer[8192]; - int h_errnop; struct hostent *hp; + int h_errnop; #endif #if defined(HAVE_GETHOSTBYNAME_R_3) || \ defined(HAVE_GETHOSTBYNAME_R_3_REENTRANT) rc = gethostbyname_r(address, &h, &hdata); + (void)hdata; #elif defined(HAVE_GETHOSTBYNAME_R_5) || \ defined(HAVE_GETHOSTBYNAME_R_5_REENTRANT) rc = gethostbyname_r(address, &h, buffer, 8192, &h_errnop); (void)hp; /* not used for test */ + (void)h_errnop; #elif defined(HAVE_GETHOSTBYNAME_R_6) || \ defined(HAVE_GETHOSTBYNAME_R_6_REENTRANT) rc = gethostbyname_r(address, &h, buffer, 8192, &hp, &h_errnop); + (void)hp; + (void)h_errnop; #endif - - (void)length; - (void)type; + (void)h; (void)rc; return 0; } #endif -#ifdef HAVE_IN_ADDR_T -#include -#include -#include -int main(void) -{ - if((in_addr_t *) 0) - return 0; - if(sizeof(in_addr_t)) - return 0; - ; - return 0; -} -#endif - #ifdef HAVE_BOOL_T #ifdef HAVE_SYS_TYPES_H #include @@ -130,10 +110,7 @@ int main(void) #endif int main(void) { - if(sizeof(bool *)) - return 0; - ; - return 0; + return (int)sizeof(bool *); } #endif @@ -146,18 +123,20 @@ int main(void) { return 0; } #endif #ifdef HAVE_FILE_OFFSET_BITS -#undef _FILE_OFFSET_BITS -#define _FILE_OFFSET_BITS 64 #include /* Check that off_t can represent 2**63 - 1 correctly. We cannot simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) -int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 - && LARGE_OFF_T % 2147483647 == 1) - ? 1 : -1]; -int main(void) { return 0; } +static int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && + LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int main(void) +{ + (void)off_t_is_large; + return 0; +} #endif #ifdef HAVE_IOCTLSOCKET @@ -169,7 +148,7 @@ int main(void) /* ioctlsocket source code */ int socket = -1; unsigned long flags = ioctlsocket(socket, FIONBIO, &flags); - ; + (void)flags; return 0; } @@ -182,7 +161,6 @@ int main(void) /* IoctlSocket source code */ if(0 != IoctlSocket(0, 0, 0)) return 1; - ; return 0; } #endif @@ -198,7 +176,7 @@ int main(void) long flags = 0; if(0 != IoctlSocket(0, FIONBIO, &flags)) return 1; - ; + (void)flags; return 0; } #endif @@ -212,7 +190,7 @@ int main(void) unsigned long flags = 0; if(0 != ioctlsocket(0, FIONBIO, &flags)) return 1; - ; + (void)flags; return 0; } #endif @@ -239,7 +217,7 @@ int main(void) int flags = 0; if(0 != ioctl(0, FIONBIO, &flags)) return 1; - ; + (void)flags; return 0; } #endif @@ -267,7 +245,7 @@ int main(void) struct ifreq ifr; if(0 != ioctl(0, SIOCGIFADDR, &ifr)) return 1; - ; + (void)ifr; return 0; } #endif @@ -286,7 +264,6 @@ int main(void) { if(0 != setsockopt(0, SOL_SOCKET, SO_NONBLOCK, 0, 0)) return 1; - ; return 0; } #endif @@ -295,12 +272,13 @@ int main(void) #include #include -void check(char c) {} +static void check(char c) { (void)c; } int main(void) { char buffer[1024]; /* This will not compile if strerror_r does not return a char* */ + /* !checksrc! disable ERRNOVAR 1 */ check(strerror_r(EACCES, buffer, sizeof(buffer))[0]); return 0; } @@ -311,12 +289,13 @@ int main(void) #include /* Float, because a pointer cannot be implicitly cast to float */ -void check(float f) {} +static void check(float f) { (void)f; } int main(void) { char buffer[1024]; /* This will not compile if strerror_r does not return an int */ + /* !checksrc! disable ERRNOVAR 1 */ check(strerror_r(EACCES, buffer, sizeof(buffer))); return 0; } @@ -335,7 +314,7 @@ int main(void) #include /* header from libc, not from libattr */ int main(void) { - fsetxattr(0, 0, 0, 0, 0); + fsetxattr(0, "", 0, 0, 0); return 0; } #endif @@ -344,8 +323,9 @@ int main(void) #include int main(void) { - struct timespec ts = {0, 0}; - clock_gettime(CLOCK_MONOTONIC, &ts); + struct timespec ts; + (void)clock_gettime(CLOCK_MONOTONIC, &ts); + (void)ts; return 0; } #endif diff --git a/Utilities/cmcurl/CMake/FindBrotli.cmake b/Utilities/cmcurl/CMake/FindBrotli.cmake index b72f1906ff..690b5a9c27 100644 --- a/Utilities/cmcurl/CMake/FindBrotli.cmake +++ b/Utilities/cmcurl/CMake/FindBrotli.cmake @@ -39,24 +39,19 @@ # - `BROTLI_CFLAGS`: Required compiler flags. # - `BROTLI_VERSION`: Version of brotli. -set(BROTLI_PC_REQUIRES "libbrotlidec") +set(BROTLI_PC_REQUIRES "libbrotlidec" "libbrotlicommon") # order is significant: brotlidec then brotlicommon if(CURL_USE_PKGCONFIG AND NOT DEFINED BROTLI_INCLUDE_DIR AND NOT DEFINED BROTLICOMMON_LIBRARY AND NOT DEFINED BROTLIDEC_LIBRARY) find_package(PkgConfig QUIET) - pkg_check_modules(BROTLI "libbrotlicommon") - pkg_check_modules(BROTLIDEC ${BROTLI_PC_REQUIRES}) + pkg_check_modules(BROTLI ${BROTLI_PC_REQUIRES}) endif() -if(BROTLI_FOUND AND BROTLIDEC_FOUND) +if(BROTLI_FOUND) set(Brotli_FOUND TRUE) - list(APPEND BROTLIDEC_LIBRARIES ${BROTLI_LIBRARIES}) # order is significant: brotlidec then brotlicommon - list(REVERSE BROTLIDEC_LIBRARIES) - list(REMOVE_DUPLICATES BROTLIDEC_LIBRARIES) - list(REVERSE BROTLIDEC_LIBRARIES) - set(BROTLI_LIBRARIES ${BROTLIDEC_LIBRARIES}) + set(BROTLI_VERSION "${BROTLI_libbrotlicommon_VERSION}") string(REPLACE ";" " " BROTLI_CFLAGS "${BROTLI_CFLAGS}") message(STATUS "Found Brotli (via pkg-config): ${BROTLI_INCLUDE_DIRS} (found version \"${BROTLI_VERSION}\")") else() diff --git a/Utilities/cmcurl/CMake/FindLDAP.cmake b/Utilities/cmcurl/CMake/FindLDAP.cmake index 4f18e04b59..fdc6d7be94 100644 --- a/Utilities/cmcurl/CMake/FindLDAP.cmake +++ b/Utilities/cmcurl/CMake/FindLDAP.cmake @@ -39,7 +39,7 @@ # - `LDAP_CFLAGS`: Required compiler flags. # - `LDAP_VERSION`: Version of ldap. -set(LDAP_PC_REQUIRES "ldap") +set(LDAP_PC_REQUIRES "ldap" "lber") if(CURL_USE_PKGCONFIG AND NOT DEFINED LDAP_INCLUDE_DIR AND @@ -47,14 +47,10 @@ if(CURL_USE_PKGCONFIG AND NOT DEFINED LDAP_LBER_LIBRARY) find_package(PkgConfig QUIET) pkg_check_modules(LDAP ${LDAP_PC_REQUIRES}) - pkg_check_modules(LDAP_LBER "lber") endif() -if(LDAP_FOUND AND LDAP_LBER_FOUND) - list(APPEND LDAP_LIBRARIES ${LDAP_LBER_LIBRARIES}) - list(REVERSE LDAP_LIBRARIES) - list(REMOVE_DUPLICATES LDAP_LIBRARIES) - list(REVERSE LDAP_LIBRARIES) +if(LDAP_FOUND) + set(LDAP_VERSION "${LDAP_ldap_VERSION}") string(REPLACE ";" " " LDAP_CFLAGS "${LDAP_CFLAGS}") message(STATUS "Found LDAP (via pkg-config): ${LDAP_INCLUDE_DIRS} (found version \"${LDAP_VERSION}\")") else() diff --git a/Utilities/cmcurl/CMake/FindMbedTLS.cmake b/Utilities/cmcurl/CMake/FindMbedTLS.cmake index 83f13712e4..fcd6afb3ab 100644 --- a/Utilities/cmcurl/CMake/FindMbedTLS.cmake +++ b/Utilities/cmcurl/CMake/FindMbedTLS.cmake @@ -46,7 +46,7 @@ if(DEFINED MBEDTLS_INCLUDE_DIRS AND NOT DEFINED MBEDTLS_INCLUDE_DIR) unset(MBEDTLS_INCLUDE_DIRS) endif() -set(MBEDTLS_PC_REQUIRES "mbedtls") +set(MBEDTLS_PC_REQUIRES "mbedtls" "mbedx509" "mbedcrypto") if(CURL_USE_PKGCONFIG AND NOT DEFINED MBEDTLS_INCLUDE_DIR AND @@ -55,16 +55,11 @@ if(CURL_USE_PKGCONFIG AND NOT DEFINED MBEDCRYPTO_LIBRARY) find_package(PkgConfig QUIET) pkg_check_modules(MBEDTLS ${MBEDTLS_PC_REQUIRES}) - pkg_check_modules(MBEDX509 "mbedx509") - pkg_check_modules(MBEDCRYPTO "mbedcrypto") endif() -if(MBEDTLS_FOUND AND MBEDX509_FOUND AND MBEDCRYPTO_FOUND) +if(MBEDTLS_FOUND) set(MbedTLS_FOUND TRUE) - list(APPEND MBEDTLS_LIBRARIES ${MBEDX509_LIBRARIES} ${MBEDCRYPTO_LIBRARIES}) - list(REVERSE MBEDTLS_LIBRARIES) - list(REMOVE_DUPLICATES MBEDTLS_LIBRARIES) - list(REVERSE MBEDTLS_LIBRARIES) + set(MBEDTLS_VERSION "${MBEDTLS_mbedtls_VERSION}") string(REPLACE ";" " " MBEDTLS_CFLAGS "${MBEDTLS_CFLAGS}") message(STATUS "Found MbedTLS (via pkg-config): ${MBEDTLS_INCLUDE_DIRS} (found version \"${MBEDTLS_VERSION}\")") else() diff --git a/Utilities/cmcurl/CMake/FindNGTCP2.cmake b/Utilities/cmcurl/CMake/FindNGTCP2.cmake index 0cbb3484d1..3cbd408c22 100644 --- a/Utilities/cmcurl/CMake/FindNGTCP2.cmake +++ b/Utilities/cmcurl/CMake/FindNGTCP2.cmake @@ -30,6 +30,7 @@ # - BoringSSL: Use `libngtcp2_crypto_boringssl`. (choose this for AWS-LC) # - wolfSSL: Use `libngtcp2_crypto_wolfssl`. # - GnuTLS: Use `libngtcp2_crypto_gnutls`. +# - ossl: Use `libngtcp2_crypto_ossl`. # # Input variables: # @@ -49,7 +50,7 @@ if(NGTCP2_FIND_COMPONENTS) set(_ngtcp2_crypto_backend "") foreach(_component IN LISTS NGTCP2_FIND_COMPONENTS) - if(_component MATCHES "^(BoringSSL|quictls|wolfSSL|GnuTLS)") + if(_component MATCHES "^(BoringSSL|quictls|wolfSSL|GnuTLS|ossl)") if(_ngtcp2_crypto_backend) message(FATAL_ERROR "NGTCP2: Only one crypto library can be selected") endif() @@ -65,7 +66,7 @@ endif() set(NGTCP2_PC_REQUIRES "libngtcp2") if(_ngtcp2_crypto_backend) - set(NGTCP2_CRYPTO_PC_REQUIRES "lib${_crypto_library_lower}") + list(APPEND NGTCP2_PC_REQUIRES "lib${_crypto_library_lower}") endif() if(CURL_USE_PKGCONFIG AND @@ -73,18 +74,10 @@ if(CURL_USE_PKGCONFIG AND NOT DEFINED NGTCP2_LIBRARY) find_package(PkgConfig QUIET) pkg_check_modules(NGTCP2 ${NGTCP2_PC_REQUIRES}) - if(_ngtcp2_crypto_backend) - pkg_check_modules("${_crypto_library_upper}" ${NGTCP2_CRYPTO_PC_REQUIRES}) - else() - set("${_crypto_library_upper}_FOUND" TRUE) - endif() endif() -list(APPEND NGTCP2_PC_REQUIRES ${NGTCP2_CRYPTO_PC_REQUIRES}) - -if(NGTCP2_FOUND AND "${${_crypto_library_upper}_FOUND}") - list(APPEND NGTCP2_LIBRARIES "${${_crypto_library_upper}_LIBRARIES}") - list(REMOVE_DUPLICATES NGTCP2_LIBRARIES) +if(NGTCP2_FOUND) + set(NGTCP2_VERSION "${NGTCP2_libngtcp2_VERSION}") string(REPLACE ";" " " NGTCP2_CFLAGS "${NGTCP2_CFLAGS}") message(STATUS "Found NGTCP2 (via pkg-config): ${NGTCP2_INCLUDE_DIRS} (found version \"${NGTCP2_VERSION}\")") else() diff --git a/Utilities/cmcurl/CMake/Macros.cmake b/Utilities/cmcurl/CMake/Macros.cmake index 8653f36b0a..e3a654b5f3 100644 --- a/Utilities/cmcurl/CMake/Macros.cmake +++ b/Utilities/cmcurl/CMake/Macros.cmake @@ -52,7 +52,7 @@ macro(curl_internal_test _curl_test) ${PROJECT_BINARY_DIR} "${CMAKE_CURRENT_SOURCE_DIR}/CMake/CurlTests.c" CMAKE_FLAGS - "-DCOMPILE_DEFINITIONS:STRING=-D${_curl_test} ${CURL_TEST_DEFINES} ${_cmake_required_definitions}" + "-DCOMPILE_DEFINITIONS:STRING=-D${_curl_test} ${CURL_TEST_DEFINES} ${CMAKE_REQUIRED_FLAGS} ${_cmake_required_definitions}" "${_curl_test_add_libraries}" OUTPUT_VARIABLE CURL_TEST_OUTPUT) if(${_curl_test}) @@ -87,3 +87,10 @@ macro(curl_required_libpaths _libpaths_arg) list(APPEND CMAKE_REQUIRED_LINK_DIRECTORIES "${_libpaths_arg}") endif() endmacro() + +# Pre-fill variables set by a check_type_size() call. +macro(curl_prefill_type_size _type _size) + set(HAVE_SIZEOF_${_type} TRUE) + set(SIZEOF_${_type} ${_size}) + set(SIZEOF_${_type}_CODE "#define SIZEOF_${_type} ${_size}") +endmacro() diff --git a/Utilities/cmcurl/CMake/OtherTests.cmake b/Utilities/cmcurl/CMake/OtherTests.cmake index 8a7faaf657..26e9d821c2 100644 --- a/Utilities/cmcurl/CMake/OtherTests.cmake +++ b/Utilities/cmcurl/CMake/OtherTests.cmake @@ -107,11 +107,10 @@ if(NOT DEFINED HAVE_GETADDRINFO_THREADSAFE) check_c_source_compiles("${_source_epilogue} int main(void) { - #ifdef h_errno - return 0; - #else + #ifndef h_errno #error force compilation error #endif + return 0; }" HAVE_H_ERRNO) if(NOT HAVE_H_ERRNO) @@ -127,12 +126,11 @@ if(NOT DEFINED HAVE_GETADDRINFO_THREADSAFE) int main(void) { #if defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200809L) - return 0; #elif defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE >= 700) - return 0; #else #error force compilation error #endif + return 0; }" HAVE_H_ERRNO_SBS_ISSUE_7) endif() endif() diff --git a/Utilities/cmcurl/CMake/PickyWarnings.cmake b/Utilities/cmcurl/CMake/PickyWarnings.cmake index 15f771b3e3..be26d05cb8 100644 --- a/Utilities/cmcurl/CMake/PickyWarnings.cmake +++ b/Utilities/cmcurl/CMake/PickyWarnings.cmake @@ -24,28 +24,45 @@ include(CheckCCompilerFlag) set(_picky "") +set(_picky_nocheck "") # not to pass to feature checks -if(CURL_WERROR AND - ((CMAKE_COMPILER_IS_GNUCC AND - NOT DOS AND # Watt-32 headers use the '#include_next' GCC extension - NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 5.0 AND - NOT CMAKE_VERSION VERSION_LESS 3.23.0) OR # to avoid check_symbol_exists() conflicting with GCC -pedantic-errors - CMAKE_C_COMPILER_ID MATCHES "Clang")) - list(APPEND _picky "-pedantic-errors") +if(CURL_WERROR) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) + set(CMAKE_COMPILE_WARNING_AS_ERROR ON) + else() + if(MSVC) + list(APPEND _picky_nocheck "-WX") + else() # llvm/clang and gcc style options + list(APPEND _picky_nocheck "-Werror") + endif() + endif() + + if((CMAKE_C_COMPILER_ID STREQUAL "GNU" AND + NOT DOS AND # Watt-32 headers use the '#include_next' GCC extension + CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 5.0) OR + CMAKE_C_COMPILER_ID MATCHES "Clang") + list(APPEND _picky_nocheck "-pedantic-errors") + endif() endif() if(APPLE AND - (CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.6) OR - (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 6.3)) + (CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 3.6) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 6.3)) list(APPEND _picky "-Werror=partial-availability") # clang 3.6 appleclang 6.3 endif() -if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang") +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") list(APPEND _picky "-Werror-implicit-function-declaration") # clang 1.0 gcc 2.95 endif() +if(MSVC) + list(APPEND _picky "-W4") # Use the highest warning level for Visual Studio. +elseif(BORLAND) + list(APPEND _picky "-w-") # Disable warnings on Borland to avoid changing 3rd party code. +endif() + if(PICKY_COMPILER) - if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang") + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") # https://clang.llvm.org/docs/DiagnosticsReference.html # https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html @@ -93,6 +110,7 @@ if(PICKY_COMPILER) -Waddress # clang 2.7 gcc 4.3 -Wattributes # clang 2.7 gcc 4.1 -Wcast-align # clang 1.0 gcc 4.2 + -Wcast-qual # clang 3.0 gcc 3.4.6 -Wdeclaration-after-statement # clang 1.0 gcc 3.4 -Wdiv-by-zero # clang 2.7 gcc 4.1 -Wempty-body # clang 2.7 gcc 4.3 @@ -113,6 +131,7 @@ if(PICKY_COMPILER) -Wtype-limits # clang 2.7 gcc 4.3 -Wunreachable-code # clang 2.7 gcc 4.1 # -Wunused-macros # clang 2.7 gcc 4.1 # Not practical + # -Wno-error=unused-macros # clang 2.7 gcc 4.1 -Wunused-parameter # clang 2.7 gcc 4.1 -Wvla # clang 2.8 gcc 4.3 ) @@ -130,8 +149,8 @@ if(PICKY_COMPILER) ) endif() # Enable based on compiler version - if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.6) OR - (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 6.3)) + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 3.6) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 6.3)) list(APPEND _picky_enable -Wdouble-promotion # clang 3.6 gcc 4.6 appleclang 6.3 -Wenum-conversion # clang 3.2 gcc 10.0 appleclang 4.6 g++ 11.0 @@ -142,29 +161,30 @@ if(PICKY_COMPILER) -Wunused-const-variable # clang 3.4 gcc 6.0 appleclang 5.1 ) endif() - if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.9) OR - (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 8.3)) + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 3.9) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 8.3)) list(APPEND _picky_enable -Wcomma # clang 3.9 appleclang 8.3 -Wmissing-variable-declarations # clang 3.2 appleclang 4.6 ) endif() - if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 7.0) OR - (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 10.3)) + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 7.0) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 10.3)) list(APPEND _picky_enable -Wassign-enum # clang 7.0 appleclang 10.3 -Wextra-semi-stmt # clang 7.0 appleclang 10.3 ) endif() - if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 10.0) OR - (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 12.4)) + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 10.0) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 12.4)) list(APPEND _picky_enable -Wimplicit-fallthrough # clang 4.0 gcc 7.0 appleclang 12.4 # We do silencing for clang 10.0 and above only + -Wxor-used-as-pow # clang 10.0 gcc 13.0 ) endif() else() # gcc # Enable based on compiler version - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.3) + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.3) list(APPEND _picky_enable ${_picky_common_old} -Wclobbered # gcc 4.3 @@ -172,26 +192,32 @@ if(PICKY_COMPILER) -Wold-style-declaration # gcc 4.3 -Wpragmas # clang 3.5 gcc 4.1 appleclang 6.0 -Wstrict-aliasing=3 # gcc 4.0 + -ftree-vrp # gcc 4.3 (required for -Warray-bounds, included in -Wall) ) endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.5 AND MINGW) + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.5) list(APPEND _picky_enable - -Wno-pedantic-ms-format # gcc 4.5 (MinGW-only) + -Wjump-misses-init # gcc 4.5 ) + if(MINGW) + list(APPEND _picky_enable + -Wno-pedantic-ms-format # gcc 4.5 (MinGW-only) + ) + endif() endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.8) + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.8) list(APPEND _picky_enable -Wdouble-promotion # clang 3.6 gcc 4.6 appleclang 6.3 -Wformat=2 # clang 3.0 gcc 4.8 -Wtrampolines # gcc 4.6 ) endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 5.0) + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 5.0) list(APPEND _picky_enable - -Warray-bounds=2 -ftree-vrp # clang 3.0 gcc 5.0 (clang default: -Warray-bounds) + -Warray-bounds=2 # clang 3.0 gcc 5.0 (clang default: -Warray-bounds) ) endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 6.0) + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 6.0) list(APPEND _picky_enable -Wduplicated-cond # gcc 6.0 -Wnull-dereference # clang 3.0 gcc 6.0 (clang default) @@ -201,7 +227,7 @@ if(PICKY_COMPILER) -Wunused-const-variable # clang 3.4 gcc 6.0 appleclang 5.1 ) endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 7.0) + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 7.0) list(APPEND _picky_enable -Walloc-zero # gcc 7.0 -Wduplicated-branches # gcc 7.0 @@ -210,19 +236,42 @@ if(PICKY_COMPILER) -Wrestrict # gcc 7.0 ) endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 10.0) + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 10.0) list(APPEND _picky_enable -Warith-conversion # gcc 10.0 -Wenum-conversion # clang 3.2 gcc 10.0 appleclang 4.6 g++ 11.0 ) endif() + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 13.0) + list(APPEND _picky_enable + -Warray-compare # clang 20.0 gcc 12.0 + -Wenum-int-mismatch # gcc 13.0 + -Wxor-used-as-pow # clang 10.0 gcc 13.0 + ) + endif() + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 15.0) + list(APPEND _picky_enable + -Wleading-whitespace=spaces # gcc 15.0 + -Wtrailing-whitespace=any # gcc 15.0 + -Wunterminated-string-initialization # gcc 15.0 + ) + endif() endif() # + set(_picky_skipped "") foreach(_ccopt IN LISTS _picky_enable) - list(APPEND _picky "${_ccopt}") + string(REGEX MATCH "-W([a-z0-9-]+)" _ccmatch "${_ccopt}") + if(_ccmatch AND CMAKE_C_FLAGS MATCHES "-Wno-${CMAKE_MATCH_1}" AND NOT _ccopt STREQUAL "-Wall" AND NOT _ccopt MATCHES "^-Wno-") + string(APPEND _picky_skipped " ${_ccopt}") + else() + list(APPEND _picky "${_ccopt}") + endif() endforeach() + if(_picky_skipped) + message(STATUS "Picky compiler options skipped due to CMAKE_C_FLAGS override:${_picky_skipped}") + endif() foreach(_ccopt IN LISTS _picky_detect) # Use a unique variable name 1. for meaningful log output 2. to have a fresh, undefined variable for each detection @@ -236,22 +285,43 @@ if(PICKY_COMPILER) endif() endforeach() - if(CMAKE_COMPILER_IS_GNUCC) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU") if(CMAKE_C_COMPILER_VERSION VERSION_LESS 4.5) # Avoid false positives list(APPEND _picky "-Wno-shadow") list(APPEND _picky "-Wno-unreachable-code") endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.2 AND CMAKE_C_COMPILER_VERSION VERSION_LESS 4.6) + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.2 AND CMAKE_C_COMPILER_VERSION VERSION_LESS 4.6) # GCC <4.6 do not support #pragma to suppress warnings locally. Disable them globally instead. list(APPEND _picky "-Wno-overlength-strings") endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.0 AND CMAKE_C_COMPILER_VERSION VERSION_LESS 4.7) + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.0 AND CMAKE_C_COMPILER_VERSION VERSION_LESS 4.7) list(APPEND _picky "-Wno-missing-field-initializers") # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36750 endif() - if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.3 AND CMAKE_C_COMPILER_VERSION VERSION_LESS 4.8) + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.3 AND CMAKE_C_COMPILER_VERSION VERSION_LESS 4.8) list(APPEND _picky "-Wno-type-limits") # Avoid false positives endif() + if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 5.1 AND CMAKE_C_COMPILER_VERSION VERSION_LESS 5.5) + list(APPEND _picky "-Wno-conversion") # Avoid false positives + endif() + endif() + elseif(MSVC AND MSVC_VERSION LESS_EQUAL 1943) # Skip for untested/unreleased newer versions + list(APPEND _picky "-Wall") + list(APPEND _picky "-wd4061") # enumerator 'A' in switch of enum 'B' is not explicitly handled by a case label + list(APPEND _picky "-wd4191") # 'type cast': unsafe conversion from 'FARPROC' to 'void (__cdecl *)(void)' + list(APPEND _picky "-wd4255") # no function prototype given: converting '()' to '(void)' (in winuser.h) + list(APPEND _picky "-wd4464") # relative include path contains '..' + list(APPEND _picky "-wd4548") # expression before comma has no effect; expected expression with side-effect (in FD_SET()) + list(APPEND _picky "-wd4574") # 'M' is defined to be '0': did you mean to use '#if M'? (in ws2tcpip.h) + list(APPEND _picky "-wd4668") # 'M' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' (in winbase.h) + list(APPEND _picky "-wd4710") # 'snprintf': function not inlined + list(APPEND _picky "-wd4711") # function 'A' selected for automatic inline expansion + list(APPEND _picky "-wd4746") # volatile access of '' is subject to /volatile: setting; + # consider using __iso_volatile_load/store intrinsic functions (ARM64) + list(APPEND _picky "-wd4774") # 'snprintf': format string expected in argument 3 is not a string literal + list(APPEND _picky "-wd4820") # 'A': 'N' bytes padding added after data member 'B' + if(MSVC_VERSION GREATER_EQUAL 1900) + list(APPEND _picky "-wd5045") # Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified endif() endif() endif() @@ -260,20 +330,31 @@ endif() if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND MSVC) list(APPEND _picky "-Wno-language-extension-token") # Allow __int64 - set(_picky_tmp "") - foreach(_ccopt IN LISTS _picky) - # Prefix -Wall, otherwise clang-cl interprets it as an MSVC option and translates it to -Weverything - if(_ccopt MATCHES "^-W" AND NOT _ccopt STREQUAL "-Wall") - list(APPEND _picky_tmp ${_ccopt}) - else() - list(APPEND _picky_tmp "-clang:${_ccopt}") - endif() + foreach(_wlist IN ITEMS _picky_nocheck _picky) + set(_picky_tmp "") + foreach(_ccopt IN LISTS "${_wlist}") + # Prefix -Wall, otherwise clang-cl interprets it as an MSVC option and translates it to -Weverything + if(_ccopt MATCHES "^-W" AND NOT _ccopt STREQUAL "-Wall") + list(APPEND _picky_tmp ${_ccopt}) + else() + list(APPEND _picky_tmp "-clang:${_ccopt}") + endif() + endforeach() + set("${_wlist}" ${_picky_tmp}) endforeach() - set(_picky ${_picky_tmp}) endif() -if(_picky) - string(REPLACE ";" " " _picky "${_picky}") - string(APPEND CMAKE_C_FLAGS " ${_picky}") - message(STATUS "Picky compiler options: ${_picky}") +if(_picky_nocheck OR _picky) + set(_picky_tmp "${_picky_nocheck}" "${_picky}") + string(REPLACE ";" " " _picky_tmp "${_picky_tmp}") + string(STRIP "${_picky_tmp}" _picky_tmp) + message(STATUS "Picky compiler options: ${_picky_tmp}") + set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "${_picky_nocheck}" "${_picky}") + + # Apply to all feature checks + string(REPLACE ";" " " _picky_tmp "${_picky}") + string(APPEND CMAKE_REQUIRED_FLAGS " ${_picky_tmp}") + + unset(_picky) + unset(_picky_tmp) endif() diff --git a/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in b/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in index 4df85542d5..e4f3eae34c 100644 --- a/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in +++ b/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in @@ -35,10 +35,11 @@ string(REGEX REPLACE "\n" ";" _files "${_files}") foreach(_file ${_files}) message(STATUS "Uninstalling $ENV{DESTDIR}${_file}") if(IS_SYMLINK "$ENV{DESTDIR}${_file}" OR EXISTS "$ENV{DESTDIR}${_file}") - exec_program( - "@CMAKE_COMMAND@" ARGS "-E rm -f \"$ENV{DESTDIR}${_file}\"" - OUTPUT_VARIABLE rm_out - RETURN_VALUE rm_retval + execute_process( + COMMAND "@CMAKE_COMMAND@" -E rm -f "$ENV{DESTDIR}${_file}" + RESULT_VARIABLE rm_retval + OUTPUT_QUIET + ERROR_QUIET ) if(NOT "${rm_retval}" STREQUAL 0) message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${_file}") diff --git a/Utilities/cmcurl/CMake/curl-config.cmake.in b/Utilities/cmcurl/CMake/curl-config.cmake.in index a4df052c2a..d1582b8d41 100644 --- a/Utilities/cmcurl/CMake/curl-config.cmake.in +++ b/Utilities/cmcurl/CMake/curl-config.cmake.in @@ -25,7 +25,11 @@ include(CMakeFindDependencyMacro) if("@USE_OPENSSL@") - find_dependency(OpenSSL "@OPENSSL_VERSION_MAJOR@") + if("@OPENSSL_VERSION_MAJOR@") + find_dependency(OpenSSL "@OPENSSL_VERSION_MAJOR@") + else() + find_dependency(OpenSSL) + endif() endif() if("@HAVE_LIBZ@") find_dependency(ZLIB "@ZLIB_VERSION_MAJOR@") @@ -34,13 +38,16 @@ endif() include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") # Alias for either shared or static library -if(NOT TARGET @PROJECT_NAME@::libcurl) - add_library(@PROJECT_NAME@::libcurl ALIAS @PROJECT_NAME@::@LIB_SELECTED@) +if(NOT TARGET @PROJECT_NAME@::@LIB_NAME@) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.11 AND CMAKE_VERSION VERSION_LESS 3.18) + set_target_properties(@PROJECT_NAME@::@LIB_SELECTED@ PROPERTIES IMPORTED_GLOBAL TRUE) + endif() + add_library(@PROJECT_NAME@::@LIB_NAME@ ALIAS @PROJECT_NAME@::@LIB_SELECTED@) endif() # For compatibility with CMake's FindCURL.cmake set(CURL_VERSION_STRING "@CURLVERSION@") -set(CURL_LIBRARIES @PROJECT_NAME@::libcurl) +set(CURL_LIBRARIES @PROJECT_NAME@::@LIB_NAME@) set_and_check(CURL_INCLUDE_DIRS "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@") set(CURL_SUPPORTED_PROTOCOLS "@CURL_SUPPORTED_PROTOCOLS_LIST@") diff --git a/Utilities/cmcurl/CMake/unix-cache.cmake b/Utilities/cmcurl/CMake/unix-cache.cmake new file mode 100644 index 0000000000..58da04e222 --- /dev/null +++ b/Utilities/cmcurl/CMake/unix-cache.cmake @@ -0,0 +1,316 @@ +#*************************************************************************** +# _ _ ____ _ +# 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 +# +########################################################################### +# Based on CI runs for Cygwin/MSYS2, Linux, macOS, FreeBSD, NetBSD, OpenBSD +if(NOT UNIX) + message(FATAL_ERROR "This file should be included on Unix platforms only") +endif() + +if(APPLE OR + CYGWIN) + set(HAVE_ACCEPT4 0) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_ACCEPT4 1) +endif() +set(HAVE_ALARM 1) +if(ANDROID) + set(HAVE_ARC4RANDOM 1) +else() + set(HAVE_ARC4RANDOM 0) +endif() +set(HAVE_ARPA_INET_H 1) +set(HAVE_ATOMIC 1) +set(HAVE_BASENAME 1) +set(HAVE_BOOL_T 1) +if(NOT APPLE) + set(HAVE_CLOCK_GETTIME_MONOTONIC 1) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_CLOCK_GETTIME_MONOTONIC_RAW 1) + else() + set(HAVE_CLOCK_GETTIME_MONOTONIC_RAW 0) + endif() +endif() +set(HAVE_CLOSESOCKET 0) +set(HAVE_DECL_FSEEKO 1) +set(HAVE_DIRENT_H 1) +if(APPLE OR + CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_EVENTFD 0) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + set(HAVE_EVENTFD 1) +endif() +set(HAVE_FCNTL 1) +set(HAVE_FCNTL_H 1) +set(HAVE_FCNTL_O_NONBLOCK 1) +set(HAVE_FILE_OFFSET_BITS 1) +set(HAVE_FNMATCH 1) +set(HAVE_FREEADDRINFO 1) +set(HAVE_FSEEKO 1) +if(APPLE) + set(HAVE_FSETXATTR 1) + set(HAVE_FSETXATTR_5 0) + set(HAVE_FSETXATTR_6 1) +elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_FSETXATTR 0) + set(HAVE_FSETXATTR_5 0) + set(HAVE_FSETXATTR_6 0) +elseif(CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + set(HAVE_FSETXATTR 1) + set(HAVE_FSETXATTR_5 1) + set(HAVE_FSETXATTR_6 0) +endif() +set(HAVE_FTRUNCATE 1) +set(HAVE_GETADDRINFO 1) +if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_GETADDRINFO_THREADSAFE 0) +elseif(CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + set(HAVE_GETADDRINFO_THREADSAFE 1) +endif() +set(HAVE_GETEUID 1) +if(APPLE OR + CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_GETHOSTBYNAME_R 0) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(HAVE_GETHOSTBYNAME_R 1) +endif() +set(HAVE_GETHOSTBYNAME_R_3 0) +set(HAVE_GETHOSTBYNAME_R_3_REENTRANT 0) +set(HAVE_GETHOSTBYNAME_R_5 0) +set(HAVE_GETHOSTBYNAME_R_5_REENTRANT 0) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_GETHOSTBYNAME_R_6 1) + set(HAVE_GETHOSTBYNAME_R_6_REENTRANT 1) +else() + set(HAVE_GETHOSTBYNAME_R_6 0) + set(HAVE_GETHOSTBYNAME_R_6_REENTRANT 0) +endif() +set(HAVE_GETHOSTNAME 1) +if(NOT ANDROID OR ANDROID_PLATFORM_LEVEL GREATER_EQUAL 24) + set(HAVE_GETIFADDRS 1) +else() + set(HAVE_GETIFADDRS 0) +endif() +if(APPLE OR + CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_GETPASS_R 0) +elseif(CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + set(HAVE_GETPASS_R 1) +endif() +set(HAVE_GETPEERNAME 1) +set(HAVE_GETPPID 1) +set(HAVE_GETPWUID 1) +set(HAVE_GETPWUID_R 1) +set(HAVE_GETRLIMIT 1) +set(HAVE_GETSOCKNAME 1) +set(HAVE_GETTIMEOFDAY 1) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_GLIBC_STRERROR_R 1) +else() + set(HAVE_GLIBC_STRERROR_R 0) +endif() +set(HAVE_GMTIME_R 1) +set(HAVE_IFADDRS_H 1) +set(HAVE_IF_NAMETOINDEX 1) +set(HAVE_INET_NTOP 1) +set(HAVE_INET_PTON 1) +set(HAVE_IOCTLSOCKET 0) +set(HAVE_IOCTLSOCKET_CAMEL 0) +set(HAVE_IOCTLSOCKET_CAMEL_FIONBIO 0) +set(HAVE_IOCTLSOCKET_FIONBIO 0) +set(HAVE_IOCTL_FIONBIO 1) +set(HAVE_IOCTL_SIOCGIFADDR 1) +if(CYGWIN) + set(HAVE_IO_H 1) +else() + set(HAVE_IO_H 0) +endif() +set(HAVE_LIBGEN_H 1) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_LINUX_TCP_H 1) +else() + set(HAVE_LINUX_TCP_H 0) +endif() +set(HAVE_LOCALE_H 1) +set(HAVE_LONGLONG 1) +if(APPLE) + set(HAVE_MACH_ABSOLUTE_TIME 1) +endif() +if(APPLE OR + CYGWIN) + set(HAVE_MEMRCHR 0) +else() + set(HAVE_MEMRCHR 1) +endif() +set(HAVE_MSG_NOSIGNAL 1) +set(HAVE_NETDB_H 1) +if(ANDROID) + set(HAVE_NETINET_IN6_H 1) +else() + set(HAVE_NETINET_IN6_H 0) +endif() +set(HAVE_NETINET_IN_H 1) +set(HAVE_NETINET_TCP_H 1) +set(HAVE_NETINET_UDP_H 1) +set(HAVE_NET_IF_H 1) +set(HAVE_OPENDIR 1) +set(HAVE_PIPE 1) +if(APPLE OR + CYGWIN) + set(HAVE_PIPE2 0) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_PIPE2 1) +endif() +set(HAVE_POLL 1) +set(HAVE_POLL_H 1) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_POSIX_STRERROR_R 0) +else() + set(HAVE_POSIX_STRERROR_R 1) +endif() +set(HAVE_PWD_H 1) +set(HAVE_REALPATH 1) +set(HAVE_RECV 1) +set(HAVE_SA_FAMILY_T 1) +set(HAVE_SCHED_YIELD 1) +set(HAVE_SELECT 1) +set(HAVE_SEND 1) +if(APPLE OR + CYGWIN) + set(HAVE_SENDMMSG 0) +else() + set(HAVE_SENDMMSG 1) +endif() +set(HAVE_SENDMSG 1) +set(HAVE_SETLOCALE 1) +if(CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_SETMODE 0) +else() + set(HAVE_SETMODE 1) +endif() +set(HAVE_SETRLIMIT 1) +set(HAVE_SETSOCKOPT_SO_NONBLOCK 0) +set(HAVE_SIGACTION 1) +set(HAVE_SIGINTERRUPT 1) +set(HAVE_SIGNAL 1) +set(HAVE_SIGSETJMP 1) +set(HAVE_SNPRINTF 1) +set(HAVE_SOCKADDR_IN6_SIN6_ADDR 1) +set(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1) +set(HAVE_SOCKET 1) +set(HAVE_SOCKETPAIR 1) +set(HAVE_STDATOMIC_H 1) +set(HAVE_STDBOOL_H 1) +set(HAVE_STDDEF_H 1) +set(HAVE_STDINT_H 1) +set(HAVE_STRCASECMP 1) +set(HAVE_STRCMPI 0) +set(HAVE_STRDUP 1) +set(HAVE_STRERROR_R 1) +set(HAVE_STRICMP 0) +set(HAVE_STRINGS_H 1) +if(_CURL_OLD_LINUX) + set(HAVE_STROPTS_H 1) +else() + set(HAVE_STROPTS_H 0) # glibc 2.30 or newer. https://sourceware.org/legacy-ml/libc-alpha/2019-08/msg00029.html +endif() +set(HAVE_STRUCT_SOCKADDR_STORAGE 1) +set(HAVE_STRUCT_TIMEVAL 1) +if(ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "iOS") + set(HAVE_SUSECONDS_T 1) +endif() +if(APPLE OR + CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + set(HAVE_SYS_EVENTFD_H 0) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + set(HAVE_SYS_EVENTFD_H 1) +endif() +if(CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_SYS_FILIO_H 0) +else() + set(HAVE_SYS_FILIO_H 1) +endif() +set(HAVE_SYS_IOCTL_H 1) +set(HAVE_SYS_PARAM_H 1) +set(HAVE_SYS_POLL_H 1) +set(HAVE_SYS_RESOURCE_H 1) +set(HAVE_SYS_SELECT_H 1) +set(HAVE_SYS_SOCKET_H 1) +if(CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_SYS_SOCKIO_H 0) +else() + set(HAVE_SYS_SOCKIO_H 1) +endif() +set(HAVE_SYS_STAT_H 1) +set(HAVE_SYS_TIME_H 1) +set(HAVE_SYS_TYPES_H 1) +set(HAVE_SYS_UN_H 1) +if(CYGWIN) + set(HAVE_SYS_UTIME_H 1) +else() + set(HAVE_SYS_UTIME_H 0) +endif() +set(HAVE_TERMIOS_H 1) +if(CYGWIN OR + CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(HAVE_TERMIO_H 1) +else() + set(HAVE_TERMIO_H 0) +endif() +set(HAVE_TIME_T_UNSIGNED 0) +set(HAVE_UNISTD_H 1) +set(HAVE_UTIME 1) +set(HAVE_UTIMES 1) +set(HAVE_UTIME_H 1) +set(HAVE_WRITABLE_ARGV 1) +if(CYGWIN) + set(HAVE__SETMODE 1) +endif() +set(STDC_HEADERS 1) +set(USE_UNIX_SOCKETS 1) diff --git a/Utilities/cmcurl/CMake/win32-cache.cmake b/Utilities/cmcurl/CMake/win32-cache.cmake index 272f5134d1..163a310fda 100644 --- a/Utilities/cmcurl/CMake/win32-cache.cmake +++ b/Utilities/cmcurl/CMake/win32-cache.cmake @@ -25,55 +25,36 @@ if(NOT WIN32) message(FATAL_ERROR "This file should be included on Windows platform only") endif() -set(HAVE_LOCALE_H 1) - if(MINGW) - set(HAVE_SNPRINTF 1) - set(HAVE_UNISTD_H 1) + set(HAVE_BASENAME 1) + set(HAVE_BOOL_T 1) # = HAVE_STDBOOL_H + set(HAVE_DIRENT_H 1) + set(HAVE_FTRUNCATE 1) + set(HAVE_GETTIMEOFDAY 1) set(HAVE_LIBGEN_H 1) + set(HAVE_OPENDIR 1) + set(HAVE_SNPRINTF 1) + set(HAVE_STDBOOL_H 1) set(HAVE_STDDEF_H 1) # detected by CMake internally in check_type_size() set(HAVE_STDINT_H 1) # detected by CMake internally in check_type_size() - set(HAVE_STDBOOL_H 1) - set(HAVE_BOOL_T "${HAVE_STDBOOL_H}") - set(HAVE_STRTOLL 1) - set(HAVE_BASENAME 1) - set(HAVE_FTRUNCATE 1) + set(HAVE_STRINGS_H 1) # wrapper to string.h set(HAVE_SYS_PARAM_H 1) set(HAVE_SYS_TIME_H 1) - set(HAVE_GETTIMEOFDAY 1) - set(HAVE_STRINGS_H 1) # wrapper to string.h + set(HAVE_UNISTD_H 1) set(HAVE_UTIME_H 1) # wrapper to sys/utime.h - set(HAVE_DIRENT_H 1) - set(HAVE_OPENDIR 1) - if(MINGW64_VERSION) - if(NOT MINGW64_VERSION VERSION_LESS 4.0) - set(HAVE_STRTOK_R 1) - else() - set(HAVE_STRTOK_R 0) - endif() - endif() - if((CMAKE_COMPILER_IS_GNUCC AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.9) OR - (CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.6)) - set(HAVE_STDATOMIC_H 1) - set(HAVE_ATOMIC 1) - else() - set(HAVE_STDATOMIC_H 0) - set(HAVE_ATOMIC 0) - endif() else() - set(HAVE_LIBGEN_H 0) + set(HAVE_DIRENT_H 0) set(HAVE_FTRUNCATE 0) + set(HAVE_GETTIMEOFDAY 0) + set(HAVE_LIBGEN_H 0) + set(HAVE_OPENDIR 0) + set(HAVE_STRINGS_H 0) set(HAVE_SYS_PARAM_H 0) set(HAVE_SYS_TIME_H 0) - set(HAVE_GETTIMEOFDAY 0) - set(HAVE_STRINGS_H 0) set(HAVE_UTIME_H 0) - set(HAVE_DIRENT_H 0) - set(HAVE_OPENDIR 0) if(MSVC) set(HAVE_UNISTD_H 0) set(HAVE_STDDEF_H 1) # detected by CMake internally in check_type_size() - set(HAVE_STDATOMIC_H 0) if(MSVC_VERSION GREATER_EQUAL 1600) set(HAVE_STDINT_H 1) # detected by CMake internally in check_type_size() else() @@ -81,10 +62,8 @@ else() endif() if(MSVC_VERSION GREATER_EQUAL 1800) set(HAVE_STDBOOL_H 1) - set(HAVE_STRTOLL 1) else() set(HAVE_STDBOOL_H 0) - set(HAVE_STRTOLL 0) endif() set(HAVE_BOOL_T "${HAVE_STDBOOL_H}") if(MSVC_VERSION GREATER_EQUAL 1900) @@ -93,58 +72,99 @@ else() set(HAVE_SNPRINTF 0) endif() set(HAVE_BASENAME 0) - set(HAVE_STRTOK_R 0) - set(HAVE_FILE_OFFSET_BITS 0) - set(HAVE_ATOMIC 0) endif() endif() -# Available in Windows XP and newer -set(HAVE_GETADDRINFO 1) -set(HAVE_FREEADDRINFO 1) +if((CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.9) OR + (CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 3.6)) + # MinGW or clang-cl + set(HAVE_STDATOMIC_H 1) + set(HAVE_ATOMIC 1) +else() + set(HAVE_STDATOMIC_H 0) + set(HAVE_ATOMIC 0) +endif() -set(HAVE_SOCKETPAIR 0) -set(HAVE_SENDMSG 0) -set(HAVE_SENDMMSG 0) +set(HAVE_ACCEPT4 0) set(HAVE_ALARM 0) -set(HAVE_FCNTL 0) -set(HAVE_GETPPID 0) -set(HAVE_UTIMES 0) -set(HAVE_GETPWUID_R 0) -set(HAVE_STRERROR_R 0) -set(HAVE_SIGINTERRUPT 0) -set(HAVE_PIPE 0) -set(HAVE_EVENTFD 0) -set(HAVE_IF_NAMETOINDEX 0) -set(HAVE_GETRLIMIT 0) -set(HAVE_SETRLIMIT 0) -set(HAVE_FSETXATTR 0) -set(HAVE_SETLOCALE 1) -set(HAVE_SETMODE 1) -set(HAVE__SETMODE 1) -set(HAVE_GETPEERNAME 1) -set(HAVE_GETSOCKNAME 1) -set(HAVE_GETHOSTNAME 1) - -set(HAVE_RECV 1) -set(HAVE_SEND 1) -set(HAVE_STROPTS_H 0) set(HAVE_ARC4RANDOM 0) -set(HAVE_FNMATCH 0) set(HAVE_ARPA_INET_H 0) +set(HAVE_CLOSESOCKET 1) +set(HAVE_EVENTFD 0) +set(HAVE_FCNTL 0) set(HAVE_FCNTL_H 1) +set(HAVE_FCNTL_O_NONBLOCK 0) +set(HAVE_FNMATCH 0) +set(HAVE_FREEADDRINFO 1) # Available in Windows XP and newer +set(HAVE_FSETXATTR 0) +set(HAVE_GETADDRINFO 1) # Available in Windows XP and newer +set(HAVE_GETEUID 0) +set(HAVE_GETHOSTBYNAME_R 0) +set(HAVE_GETHOSTBYNAME_R_3 0) +set(HAVE_GETHOSTBYNAME_R_3_REENTRANT 0) +set(HAVE_GETHOSTBYNAME_R_5 0) +set(HAVE_GETHOSTBYNAME_R_5_REENTRANT 0) +set(HAVE_GETHOSTBYNAME_R_6 0) +set(HAVE_GETHOSTBYNAME_R_6_REENTRANT 0) +set(HAVE_GETHOSTNAME 1) +set(HAVE_GETIFADDRS 0) +set(HAVE_GETPASS_R 0) +set(HAVE_GETPEERNAME 1) +set(HAVE_GETPPID 0) +set(HAVE_GETPWUID 0) +set(HAVE_GETPWUID_R 0) +set(HAVE_GETRLIMIT 0) +set(HAVE_GETSOCKNAME 1) +set(HAVE_GLIBC_STRERROR_R 0) +set(HAVE_GMTIME_R 0) set(HAVE_IFADDRS_H 0) +set(HAVE_IF_NAMETOINDEX 0) +set(HAVE_INET_NTOP 0) +set(HAVE_INET_PTON 0) +set(HAVE_IOCTLSOCKET 1) +set(HAVE_IOCTLSOCKET_CAMEL 0) +set(HAVE_IOCTLSOCKET_CAMEL_FIONBIO 0) +set(HAVE_IOCTLSOCKET_FIONBIO 1) +set(HAVE_IOCTL_FIONBIO 0) +set(HAVE_IOCTL_SIOCGIFADDR 0) set(HAVE_IO_H 1) +set(HAVE_LINUX_TCP_H 0) +set(HAVE_LOCALE_H 1) +set(HAVE_MEMRCHR 0) +set(HAVE_MSG_NOSIGNAL 0) set(HAVE_NETDB_H 0) -set(HAVE_NETINET_IN_H 0) set(HAVE_NETINET_IN6_H 0) +set(HAVE_NETINET_IN_H 0) set(HAVE_NETINET_TCP_H 0) set(HAVE_NETINET_UDP_H 0) set(HAVE_NET_IF_H 0) -set(HAVE_IOCTL_SIOCGIFADDR 0) -set(HAVE_POLL_H 0) +set(HAVE_PIPE 0) +set(HAVE_PIPE2 0) set(HAVE_POLL 0) +set(HAVE_POLL_H 0) +set(HAVE_POSIX_STRERROR_R 0) set(HAVE_PWD_H 0) +set(HAVE_RECV 1) +set(HAVE_SELECT 1) +set(HAVE_SEND 1) +set(HAVE_SENDMMSG 0) +set(HAVE_SENDMSG 0) +set(HAVE_SETLOCALE 1) +set(HAVE_SETMODE 1) +set(HAVE_SETRLIMIT 0) +set(HAVE_SETSOCKOPT_SO_NONBLOCK 0) +set(HAVE_SIGACTION 0) +set(HAVE_SIGINTERRUPT 0) +set(HAVE_SIGNAL 1) +set(HAVE_SIGSETJMP 0) +set(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1) +set(HAVE_SOCKET 1) +set(HAVE_SOCKETPAIR 0) +set(HAVE_STRDUP 1) +set(HAVE_STRERROR_R 0) +set(HAVE_STROPTS_H 0) +set(HAVE_STRUCT_SOCKADDR_STORAGE 1) +set(HAVE_STRUCT_TIMEVAL 1) set(HAVE_SYS_EVENTFD_H 0) set(HAVE_SYS_FILIO_H 0) set(HAVE_SYS_IOCTL_H 0) @@ -159,47 +179,67 @@ set(HAVE_SYS_UN_H 0) set(HAVE_SYS_UTIME_H 1) set(HAVE_TERMIOS_H 0) set(HAVE_TERMIO_H 0) -set(HAVE_LINUX_TCP_H 0) - -set(HAVE_SOCKET 1) -set(HAVE_SELECT 1) -set(HAVE_STRDUP 1) -set(HAVE_MEMRCHR 0) -set(HAVE_CLOSESOCKET 1) -set(HAVE_SIGSETJMP 0) -set(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1) -set(HAVE_GETPASS_R 0) -set(HAVE_GETPWUID 0) -set(HAVE_GETEUID 0) -set(HAVE_UTIME 1) -set(HAVE_GMTIME_R 0) -set(HAVE_GETHOSTBYNAME_R 0) -set(HAVE_SIGNAL 1) -set(HAVE_SIGACTION 0) -set(HAVE_GLIBC_STRERROR_R 0) -set(HAVE_GETIFADDRS 0) -set(HAVE_FCNTL_O_NONBLOCK 0) -set(HAVE_IOCTLSOCKET 1) -set(HAVE_IOCTLSOCKET_CAMEL 0) -set(HAVE_IOCTLSOCKET_CAMEL_FIONBIO 0) -set(HAVE_IOCTLSOCKET_FIONBIO 1) -set(HAVE_IOCTL_FIONBIO 0) -set(HAVE_SETSOCKOPT_SO_NONBLOCK 0) -set(HAVE_POSIX_STRERROR_R 0) -set(HAVE_MSG_NOSIGNAL 0) -set(HAVE_STRUCT_TIMEVAL 1) -set(HAVE_STRUCT_SOCKADDR_STORAGE 1) set(HAVE_TIME_T_UNSIGNED 0) - -set(HAVE_GETHOSTBYNAME_R_3 0) -set(HAVE_GETHOSTBYNAME_R_3_REENTRANT 0) -set(HAVE_GETHOSTBYNAME_R_5 0) -set(HAVE_GETHOSTBYNAME_R_5_REENTRANT 0) -set(HAVE_GETHOSTBYNAME_R_6 0) -set(HAVE_GETHOSTBYNAME_R_6_REENTRANT 0) - -set(HAVE_IN_ADDR_T 0) +set(HAVE_UTIME 1) +set(HAVE_UTIMES 0) +set(HAVE__SETMODE 1) set(STDC_HEADERS 1) -set(HAVE_SIZEOF_SUSECONDS_T 0) +# Types and sizes + set(HAVE_SIZEOF_SA_FAMILY_T 0) +set(HAVE_SIZEOF_SUSECONDS_T 0) + +if(MINGW OR MSVC) + curl_prefill_type_size("INT" 4) + curl_prefill_type_size("LONG" 4) + curl_prefill_type_size("LONG_LONG" 8) + curl_prefill_type_size("__INT64" 8) + curl_prefill_type_size("CURL_OFF_T" 8) + # CURL_SOCKET_T, SIZE_T: 8 for _WIN64, 4 otherwise + # TIME_T: 8 for _WIN64 or UCRT or MSVC and not Windows CE, 4 otherwise + # Also 4 for non-UCRT 32-bit when _USE_32BIT_TIME_T is set. + # mingw-w64 sets _USE_32BIT_TIME_T unless __MINGW_USE_VC2005_COMPAT is explicit defined. + if(MSVC) + set(HAVE_SIZEOF_SSIZE_T 0) + set(HAVE_FILE_OFFSET_BITS 0) + curl_prefill_type_size("OFF_T" 4) + curl_prefill_type_size("ADDRESS_FAMILY" 2) + else() + # SSIZE_T: 8 for _WIN64, 4 otherwise + if(MINGW64_VERSION) + if(MINGW64_VERSION VERSION_GREATER_EQUAL 3.0) + set(HAVE_FILE_OFFSET_BITS 1) + curl_prefill_type_size("OFF_T" 8) + endif() + if(MINGW64_VERSION VERSION_GREATER_EQUAL 2.0) + curl_prefill_type_size("ADDRESS_FAMILY" 2) + else() + set(HAVE_SIZEOF_ADDRESS_FAMILY 0) + endif() + endif() + endif() +endif() + +# Windows CE exceptions + +if(WINCE) + set(HAVE_FREEADDRINFO 0) + set(HAVE_GETADDRINFO 0) + set(HAVE_LOCALE_H 0) + set(HAVE_SETLOCALE 0) + set(HAVE_SETMODE 0) + set(HAVE_SIGNAL 0) + set(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 0) + curl_prefill_type_size("CURL_SOCKET_T" 4) + curl_prefill_type_size("TIME_T" 4) + curl_prefill_type_size("SIZE_T" 4) + if(MINGW32CE) + set(HAVE_STRTOK_R 0) + set(HAVE__SETMODE 0) + set(HAVE_FILE_OFFSET_BITS 0) + set(HAVE_SIZEOF_ADDRESS_FAMILY 0) + curl_prefill_type_size("SSIZE_T" 4) + curl_prefill_type_size("OFF_T" 4) + endif() +endif() diff --git a/Utilities/cmcurl/CMakeLists.txt b/Utilities/cmcurl/CMakeLists.txt index 75b5102059..e936159f80 100644 --- a/Utilities/cmcurl/CMakeLists.txt +++ b/Utilities/cmcurl/CMakeLists.txt @@ -197,26 +197,6 @@ endif() ########################################################################### # by Tetetest and Sukender (Benoit Neil) -# Note: By default this CMake build script detects the version of some -# dependencies using `check_symbol_exists`. Those checks do not work in -# the case that both CURL and its dependency are included as sub-projects -# in a larger build using `FetchContent`. To support that case, additional -# variables may be defined by the parent project, ideally in the "extra" -# find package redirect file: -# https://cmake.org/cmake/help/latest/module/FetchContent.html#integrating-with-find-package -# -# The following variables are available: -# HAVE_SSL_SET0_WBIO: `SSL_set0_wbio` present in OpenSSL/wolfSSL -# HAVE_OPENSSL_SRP: `SSL_CTX_set_srp_username` present in OpenSSL/wolfSSL -# HAVE_GNUTLS_SRP: `gnutls_srp_verifier` present in GnuTLS -# HAVE_SSL_SET_QUIC_USE_LEGACY_CODEPOINT: `SSL_set_quic_use_legacy_codepoint` present in OpenSSL/wolfSSL -# HAVE_QUICHE_CONN_SET_QLOG_FD: `quiche_conn_set_qlog_fd` present in quiche -# HAVE_ECH: ECH API checks for OpenSSL, BoringSSL or wolfSSL -# -# For each of the above variables, if the variable is DEFINED (either -# to ON or OFF), the symbol detection is skipped. If the variable is -# NOT DEFINED, the symbol detection is performed. - if(0) # XXX(cmake): not needed for build within cmake cmake_minimum_required(VERSION 3.7...3.16 FATAL_ERROR) message(STATUS "Using CMake version ${CMAKE_VERSION}") @@ -276,7 +256,38 @@ if(WINDOWS_STORE AND MINGW) # mingw UWP build # CMake (as of v3.31.2) gets confused and applies the MSVC rc.exe command-line # template to windres. Reset it to the windres template via 'Modules/Platform/Windows-windres.cmake': set(CMAKE_RC_COMPILE_OBJECT " -O coff ") -elseif(DOS AND CMAKE_COMPILER_IS_GNUCC) # DJGPP +elseif(WIN32 AND WINCE AND CMAKE_C_COMPILER_ID STREQUAL "GNU") # mingw32ce build + if(NOT MINGW32CE_LIBRARY_DIR) + message(FATAL_ERROR "Set MINGW32CE_LIBRARY_DIR variable to the mingw32ce platform library directory.") + endif() + + set(MINGW 1) + set(MINGW32CE 1) + + # Build implib with libcurl DLL. Copied from CMake's 'Modules/Platform/Windows-GNU.cmake'. + set(CMAKE_C_CREATE_SHARED_LIBRARY " ") + string(APPEND CMAKE_C_CREATE_SHARED_LIBRARY " -o -Wl,--out-implib,") + string(APPEND CMAKE_C_CREATE_SHARED_LIBRARY " ${CMAKE_GNULD_IMAGE_VERSION} ") + + # Build resources. Copied from CMake's 'Modules/Platform/Windows-windres.cmake'. + set(CMAKE_RC_COMPILE_OBJECT " -O coff ") + enable_language(RC) + + # To compile long long integer literals + set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "-std=gnu99") + string(APPEND CMAKE_REQUIRED_FLAGS " -std=gnu99") + + set(CMAKE_C_COMPILE_OPTIONS_PIC "") # CMake sets it to '-fPIC', confusing the toolchain and breaking builds. Zap it. + + set(CMAKE_STATIC_LIBRARY_PREFIX "lib") + set(CMAKE_STATIC_LIBRARY_SUFFIX ".a") + set(CMAKE_SHARED_LIBRARY_PREFIX "lib") + set(CMAKE_SHARED_LIBRARY_SUFFIX ".dll") + set(CMAKE_IMPORT_LIBRARY_PREFIX "lib") + set(CMAKE_IMPORT_LIBRARY_SUFFIX ".dll.a") + set(CMAKE_FIND_LIBRARY_PREFIXES "lib" "") + set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll.a" ".a" ".lib") +elseif(DOS AND CMAKE_C_COMPILER_ID STREQUAL "GNU") # DJGPP set(CMAKE_STATIC_LIBRARY_PREFIX "lib") set(CMAKE_STATIC_LIBRARY_SUFFIX ".a") set(CMAKE_FIND_LIBRARY_PREFIXES "lib") @@ -304,22 +315,22 @@ endif() if(WIN32) string(APPEND _target_flags " WIN32") endif() +if(WINCE) + string(APPEND _target_flags " WINCE") +endif() if(WINDOWS_STORE) string(APPEND _target_flags " UWP") endif() if(CYGWIN) string(APPEND _target_flags " CYGWIN") endif() -if(MSYS) - string(APPEND _target_flags " MSYS") -endif() if(DOS) string(APPEND _target_flags " DOS") endif() if(AMIGA) string(APPEND _target_flags " AMIGA") endif() -if(CMAKE_COMPILER_IS_GNUCC) +if(CMAKE_C_COMPILER_ID STREQUAL "GNU") string(APPEND _target_flags " GCC") endif() if(MINGW) @@ -350,7 +361,9 @@ else() set(CURL_OS "\"${CMAKE_SYSTEM_NAME}\"") endif() -include_directories("${PROJECT_SOURCE_DIR}/include") +set(LIB_NAME "libcurl") + +set_property(DIRECTORY APPEND PROPERTY INCLUDE_DIRECTORIES "${PROJECT_SOURCE_DIR}/include") if(NOT DEFINED CMAKE_UNITY_BUILD_BATCH_SIZE) set(CMAKE_UNITY_BUILD_BATCH_SIZE 0) @@ -377,21 +390,14 @@ option(ENABLE_ARES "Enable c-ares support" OFF) option(CURL_DISABLE_INSTALL "Disable installation targets" OFF) if(WIN32) - option(CURL_STATIC_CRT "Build libcurl with static CRT with MSVC (/MT)" OFF) - if(CURL_STATIC_CRT AND MSVC) - set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - string(APPEND CMAKE_C_FLAGS_RELEASE " -MT") - string(APPEND CMAKE_C_FLAGS_DEBUG " -MTd") - endif() - option(ENABLE_UNICODE "Use the Unicode version of the Windows API functions" OFF) - if(WINDOWS_STORE) + if(WINDOWS_STORE OR WINCE) set(ENABLE_UNICODE ON) endif() if(ENABLE_UNICODE) - add_definitions("-DUNICODE" "-D_UNICODE") - if(MINGW) - add_compile_options("-municode") + set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "UNICODE" "_UNICODE") + if(MINGW AND NOT MINGW32CE) + set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "-municode") endif() endif() @@ -404,7 +410,7 @@ if(WIN32) set(CURL_TARGET_WINDOWS_VERSION "" CACHE STRING "Minimum target Windows version as hex string") if(CURL_TARGET_WINDOWS_VERSION) - add_definitions("-D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}") + set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}") list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}") # Apply to all feature checks endif() endif() # XXX(cmake): end @@ -426,7 +432,9 @@ if(WIN32) if(MINGW64_VERSION) string(REGEX MATCH "MINGW64_VERSION=[0-9]+\.[0-9]+" CURL_TEST_OUTPUT "${CURL_TEST_OUTPUT}") string(REGEX REPLACE "MINGW64_VERSION=" "" MINGW64_VERSION "${CURL_TEST_OUTPUT}") - message(STATUS "Found MINGW64_VERSION=${MINGW64_VERSION}") + if(MINGW64_VERSION) + message(STATUS "Found MINGW64_VERSION=${MINGW64_VERSION}") + endif() endif() unset(MINGW64_VERSION CACHE) # Avoid storing in CMake cache endif() @@ -450,28 +458,35 @@ endif() include(PickyWarnings) endif() # XXX(cmake): end -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - string(APPEND CMAKE_C_FLAGS " -D_GNU_SOURCE") # Required for sendmmsg() +if(CYGWIN OR CMAKE_SYSTEM_NAME STREQUAL "Linux") + set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "_GNU_SOURCE") # Required for accept4(), pipe2(), sendmmsg() + list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D_GNU_SOURCE") # Apply to all feature checks endif() option(ENABLE_DEBUG "Enable curl debug features (for developing curl itself)" OFF) if(ENABLE_DEBUG) - message(WARNING "This curl build is Debug-enabled, do not use in production.") + message(WARNING "This curl build is Debug-enabled and insecure, do not use in production.") endif() option(ENABLE_CURLDEBUG "Enable TrackMemory debug feature" ${ENABLE_DEBUG}) +option(ENABLE_SERVER_DEBUG "Apply curl debug options to test servers" OFF) +set(CURL_DEBUG_MACROS "") if(ENABLE_DEBUG) - set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "DEBUGBUILD") + list(APPEND CURL_DEBUG_MACROS "DEBUGBUILD") endif() - if(ENABLE_CURLDEBUG) - set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "CURLDEBUG") + list(APPEND CURL_DEBUG_MACROS "CURLDEBUG") endif() if(0) # XXX(cmake): not needed for build within cmake +option(CURL_TEST_BUNDLES "Build tests into single-binary bundles" OFF) + option(CURL_CLANG_TIDY "Run the build through clang-tidy" OFF) if(CURL_CLANG_TIDY) + # clang-tidy is not looking into #included sources, thus not compatible with + # unity builds and test bundles. set(CMAKE_UNITY_BUILD OFF) + set(CURL_TEST_BUNDLES OFF) set(_tidy_checks "") list(APPEND _tidy_checks "-clang-analyzer-security.insecureAPI.strcpy") list(APPEND _tidy_checks "-clang-analyzer-optin.performance.Padding") @@ -520,6 +535,19 @@ else() set(LIB_SELECTED ${LIB_STATIC}) endif() +if(WIN32) + option(CURL_STATIC_CRT "Build libcurl with static CRT with MSVC (/MT)" OFF) + if(CURL_STATIC_CRT AND MSVC) + if(MSVC_VERSION GREATER_EQUAL 1900 OR BUILD_STATIC_CURL OR NOT BUILD_CURL_EXE) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "$<$:-MT>") + set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "$<$:-MTd>") + else() + message(WARNING "Static CRT requires UCRT, static libcurl or no curl executable.") + endif() + endif() +endif() + # Override to force-disable or force-enable the use of pkg-config. if((UNIX AND NOT ANDROID AND (NOT APPLE OR CMAKE_SYSTEM_NAME MATCHES "Darwin")) OR VCPKG_TOOLCHAIN OR @@ -627,9 +655,9 @@ mark_as_advanced(CURL_DISABLE_PROXY) option(CURL_DISABLE_IPFS "Disable IPFS" OFF) mark_as_advanced(CURL_DISABLE_IPFS) option(CURL_DISABLE_RTSP "Disable RTSP" OFF) -mark_as_advanced(CURL_DISABLE_SHA512_256) -option(CURL_DISABLE_SHA512_256 "Disable SHA-512/256 hash algorithm" OFF) mark_as_advanced(CURL_DISABLE_RTSP) +option(CURL_DISABLE_SHA512_256 "Disable SHA-512/256 hash algorithm" OFF) +mark_as_advanced(CURL_DISABLE_SHA512_256) option(CURL_DISABLE_SHUFFLE_DNS "Disable shuffle DNS feature" OFF) mark_as_advanced(CURL_DISABLE_SHUFFLE_DNS) option(CURL_DISABLE_SMB "Disable SMB" OFF) @@ -676,7 +704,7 @@ if(HTTP_ONLY) set(CURL_DISABLE_TFTP ON) endif() -if(WINDOWS_STORE) +if(WINDOWS_STORE OR WINCE) set(CURL_DISABLE_TELNET ON) # telnet code needs fixing to compile for UWP. endif() @@ -710,14 +738,9 @@ if(ENABLE_CURL_MANUAL OR BUILD_LIBCURL_DOCS) endif() endif() # XXX(cmake): end -# Disable warnings on Borland to avoid changing 3rd party code. -if(BORLAND) - string(APPEND CMAKE_C_FLAGS " -w-") -endif() - # If we are on AIX, do the _ALL_SOURCE magic if(CMAKE_SYSTEM_NAME STREQUAL "AIX") - add_definitions("-D_ALL_SOURCE") + set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "_ALL_SOURCE") endif() # If we are on Haiku, make sure that the network library is brought in. @@ -738,15 +761,21 @@ include(CheckSymbolExists) include(CheckTypeSize) include(CheckCSourceCompiles) -option(_CURL_QUICK_DETECT "Fast-track known feature detection results (Windows, some Apple)" ON) -if(_CURL_QUICK_DETECT) +option(_CURL_PREFILL "Fast-track known feature detection results (Windows, some Apple)" "${WIN32}") +if(_CURL_PREFILL) if(WIN32) include("${CMAKE_CURRENT_SOURCE_DIR}/CMake/win32-cache.cmake") - elseif(APPLE) - set(HAVE_EVENTFD 0) - set(HAVE_GETPASS_R 0) - set(HAVE_SENDMMSG 0) + elseif(UNIX) + include("${CMAKE_CURRENT_SOURCE_DIR}/CMake/unix-cache.cmake") + message(STATUS "Pre-filling feature detection results for UNIX") endif() +elseif(WIN32) + message(STATUS "Pre-filling feature detection results disabled.") +elseif(APPLE) + set(HAVE_EVENTFD 0) + set(HAVE_GETPASS_R 0) + set(HAVE_WRITABLE_ARGV 1) + set(HAVE_SENDMMSG 0) endif() if(AMIGA) @@ -768,7 +797,19 @@ if(ENABLE_THREADED_RESOLVER) endif() # Check for all needed libraries -if(DOS) +if(WIN32) + if(WINCE) + set(_win32_winsock "ws2") + else() + set(_win32_winsock "ws2_32") + endif() + set(_win32_crypt32 "crypt32") + + if(MINGW32CE) # FIXME upstream: must specify the full path to avoid CMake converting "ws2" to "ws2.lib" + set(_win32_winsock "${MINGW32CE_LIBRARY_DIR}/lib${_win32_winsock}.a") + set(_win32_crypt32 "${MINGW32CE_LIBRARY_DIR}/lib${_win32_crypt32}.a") + endif() +elseif(DOS) if(WATT_ROOT) set(USE_WATT32 ON) # FIXME upstream: must specify the full path to avoid CMake converting "watt" to "watt.lib" @@ -789,7 +830,7 @@ elseif(AMIGA) set(CURL_USE_OPENSSL ON) set(CURL_CA_FALLBACK ON CACHE BOOL "") endif() -elseif(NOT WIN32 AND NOT APPLE) +elseif(NOT APPLE) check_library_exists("socket" "connect" "" HAVE_LIBSOCKET) if(HAVE_LIBSOCKET) set(CURL_LIBS "socket" ${CURL_LIBS}) @@ -803,8 +844,8 @@ if(ENABLE_IPV6) if(WIN32) check_struct_has_member("struct sockaddr_in6" "sin6_scope_id" "winsock2.h;ws2tcpip.h" HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) else() - check_struct_has_member("struct sockaddr_in6" "sin6_addr" "netinet/in.h" HAVE_SOCKADDR_IN6_SIN6_ADDR) check_struct_has_member("struct sockaddr_in6" "sin6_scope_id" "netinet/in.h" HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) + check_struct_has_member("struct sockaddr_in6" "sin6_addr" "netinet/in.h" HAVE_SOCKADDR_IN6_SIN6_ADDR) if(NOT HAVE_SOCKADDR_IN6_SIN6_ADDR) if(NOT DOS AND NOT AMIGA) message(WARNING "struct sockaddr_in6 not available, disabling IPv6 support") @@ -824,7 +865,7 @@ if(ENABLE_IPV6) endif() endif() endif() -if(ENABLE_IPV6) +if(ENABLE_IPV6 AND NOT WINCE) set(USE_IPV6 ON) endif() @@ -889,7 +930,7 @@ endif() if(CURL_USE_SCHANNEL) set(_ssl_enabled ON) set(USE_SCHANNEL ON) # Windows native SSL/TLS support - set(USE_WINDOWS_SSPI ON) # CURL_USE_SCHANNEL implies CURL_WINDOWS_SSPI + set(USE_WINDOWS_SSPI ON) # CURL_USE_SCHANNEL requires CURL_WINDOWS_SSPI if(CURL_DEFAULT_SSL_BACKEND AND CURL_DEFAULT_SSL_BACKEND STREQUAL "schannel") set(_valid_default_ssl_backend TRUE) @@ -996,11 +1037,6 @@ if(CURL_USE_OPENSSL) set(_openssl "AmiSSL") else() set(_openssl "OpenSSL") - if(0) # XXX(cmake): not needed for build within cmake - if(OPENSSL_VERSION VERSION_LESS 1.1.1) - message(WARNING "OpenSSL ${OPENSSL_VERSION} does not support TLS 1.3.") - endif() - endif() # XXX(cmake): end endif() endif() @@ -1093,8 +1129,8 @@ if(CURL_USE_GNUTLS) if(NOT DEFINED HAVE_GNUTLS_SRP AND NOT CURL_DISABLE_SRP) cmake_push_check_state() - list(APPEND CMAKE_REQUIRED_INCLUDES ${GNUTLS_INCLUDE_DIRS}) - list(APPEND CMAKE_REQUIRED_LIBRARIES ${GNUTLS_LIBRARIES}) + list(APPEND CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIRS}") + list(APPEND CMAKE_REQUIRED_LIBRARIES "${GNUTLS_LIBRARIES}") check_symbol_exists("gnutls_srp_verifier" "gnutls/gnutls.h" HAVE_GNUTLS_SRP) cmake_pop_check_state() endif() @@ -1113,6 +1149,22 @@ if(CURL_USE_RUSTLS) string(APPEND CMAKE_C_FLAGS " ${RUSTLS_CFLAGS}") endif() + if(NOT DEFINED HAVE_RUSTLS_SUPPORTED_HPKE) + if(RUSTLS_VERSION AND RUSTLS_VERSION VERSION_GREATER_EQUAL 0.15) + set(HAVE_RUSTLS_SUPPORTED_HPKE TRUE) + elseif(NOT RUSTLS_VERSION) + cmake_push_check_state() + list(APPEND CMAKE_REQUIRED_INCLUDES "${RUSTLS_INCLUDE_DIRS}") + list(APPEND CMAKE_REQUIRED_LIBRARIES "${RUSTLS_LIBRARIES}") + curl_required_libpaths("${RUSTLS_LIBRARY_DIRS}") + check_symbol_exists("rustls_supported_hpke" "rustls.h" HAVE_RUSTLS_SUPPORTED_HPKE) + cmake_pop_check_state() + endif() + endif() + if(NOT HAVE_RUSTLS_SUPPORTED_HPKE) + message(FATAL_ERROR "rustls-ffi library does not provide rustls_supported_hpke function. Required version is 0.15 or newer.") + endif() + if(CURL_DEFAULT_SSL_BACKEND AND CURL_DEFAULT_SSL_BACKEND STREQUAL "rustls") set(_valid_default_ssl_backend TRUE) endif() @@ -1124,7 +1176,7 @@ if(CURL_DEFAULT_SSL_BACKEND AND NOT _valid_default_ssl_backend) endif() # Keep ZLIB detection after TLS detection, -# and before calling curl_openssl_check_symbol_exists(). +# and before calling curl_openssl_check_exists(). set(HAVE_LIBZ OFF) curl_dependency_option(CURL_ZLIB ZLIB "ZLIB") @@ -1157,7 +1209,7 @@ endif() set(HAVE_ZSTD OFF) curl_dependency_option(CURL_ZSTD Zstd "zstd") if(ZSTD_FOUND) - if(NOT ZSTD_VERSION VERSION_LESS 1.0.0) + if(ZSTD_VERSION VERSION_GREATER_EQUAL 1.0.0) set(HAVE_ZSTD ON) list(APPEND CURL_LIBS ${ZSTD_LIBRARIES}) list(APPEND CURL_LIBDIRS ${ZSTD_LIBRARY_DIRS}) @@ -1172,8 +1224,8 @@ if(ZSTD_FOUND) endif() endif() -# Check symbol in an OpenSSL-like TLS backend. -macro(curl_openssl_check_symbol_exists _symbol _files _variable) +# Check function in an OpenSSL-like TLS backend. +macro(curl_openssl_check_exists) cmake_push_check_state() if(USE_OPENSSL) list(APPEND CMAKE_REQUIRED_LIBRARIES OpenSSL::SSL OpenSSL::Crypto) @@ -1185,78 +1237,88 @@ macro(curl_openssl_check_symbol_exists _symbol _files _variable) list(APPEND CMAKE_REQUIRED_LIBRARIES cmzlib) endif() endif() - if(WIN32) - list(APPEND CMAKE_REQUIRED_LIBRARIES "ws2_32") + if(WIN32 AND NOT WINCE) list(APPEND CMAKE_REQUIRED_LIBRARIES "bcrypt") # for OpenSSL/LibreSSL endif() endif() if(USE_WOLFSSL) - list(APPEND CMAKE_REQUIRED_INCLUDES "${WOLFSSL_INCLUDE_DIRS}") - list(APPEND CMAKE_REQUIRED_LIBRARIES "${WOLFSSL_LIBRARIES}") + list(APPEND CMAKE_REQUIRED_INCLUDES "${WOLFSSL_INCLUDE_DIRS}") + list(APPEND CMAKE_REQUIRED_LIBRARIES "${WOLFSSL_LIBRARIES}") curl_required_libpaths("${WOLFSSL_LIBRARY_DIRS}") if(HAVE_LIBZ) list(APPEND CMAKE_REQUIRED_LIBRARIES ZLIB::ZLIB) # Public wolfSSL headers also require zlib headers endif() - if(WIN32) - list(APPEND CMAKE_REQUIRED_LIBRARIES "ws2_32" "crypt32") - endif() list(APPEND CMAKE_REQUIRED_DEFINITIONS "-DHAVE_UINTPTR_T") # to pull in stdint.h (as of wolfSSL v5.5.4) endif() - check_symbol_exists("${_symbol}" "${_files}" "${_variable}") + if(WIN32) + list(APPEND CMAKE_REQUIRED_LIBRARIES "${_win32_winsock}" "${_win32_crypt32}") # for OpenSSL/wolfSSL + endif() + if(${ARGC} EQUAL 2) + check_function_exists(${ARGN}) + else() + check_symbol_exists(${ARGN}) # Uses CMAKE_REQUIRED_INCLUDES and CMAKE_REQUIRED_DEFINITIONS + endif() cmake_pop_check_state() endmacro() -# Ensure that the OpenSSL fork actually supports QUIC. +# Ensure that OpenSSL (or fork) or wolfSSL actually supports QUICTLS API. macro(curl_openssl_check_quic) - if(NOT DEFINED HAVE_SSL_SET_QUIC_USE_LEGACY_CODEPOINT) - if(USE_OPENSSL) - curl_openssl_check_symbol_exists("SSL_set_quic_use_legacy_codepoint" "openssl/ssl.h" HAVE_SSL_SET_QUIC_USE_LEGACY_CODEPOINT) - endif() - if(USE_WOLFSSL) - curl_openssl_check_symbol_exists("wolfSSL_set_quic_use_legacy_codepoint" "wolfssl/options.h;wolfssl/openssl/ssl.h" - HAVE_SSL_SET_QUIC_USE_LEGACY_CODEPOINT) + if(USE_OPENSSL AND NOT USE_OPENSSL_QUIC) + if(OPENSSL_VERSION VERSION_GREATER_EQUAL 3.5.0) + if(NOT DEFINED HAVE_SSL_SET_QUIC_TLS_CBS) + curl_openssl_check_exists("SSL_set_quic_tls_cbs" HAVE_SSL_SET_QUIC_TLS_CBS) + endif() + else() + if(NOT DEFINED HAVE_SSL_SET_QUIC_USE_LEGACY_CODEPOINT) + curl_openssl_check_exists("SSL_set_quic_use_legacy_codepoint" HAVE_SSL_SET_QUIC_USE_LEGACY_CODEPOINT) + endif() endif() endif() - if(NOT HAVE_SSL_SET_QUIC_USE_LEGACY_CODEPOINT) - message(FATAL_ERROR "QUIC support is missing in OpenSSL fork. Try setting -DOPENSSL_ROOT_DIR") + if(USE_WOLFSSL AND NOT DEFINED HAVE_WOLFSSL_SET_QUIC_USE_LEGACY_CODEPOINT) + curl_openssl_check_exists("wolfSSL_set_quic_use_legacy_codepoint" HAVE_WOLFSSL_SET_QUIC_USE_LEGACY_CODEPOINT) + endif() + if(NOT HAVE_SSL_SET_QUIC_TLS_CBS AND + NOT HAVE_SSL_SET_QUIC_USE_LEGACY_CODEPOINT AND + NOT HAVE_WOLFSSL_SET_QUIC_USE_LEGACY_CODEPOINT) + message(FATAL_ERROR "QUICTLS API support is missing from OpenSSL/fork/wolfSSL. Try setting -DOPENSSL_ROOT_DIR") endif() endmacro() if(USE_WOLFSSL) - curl_openssl_check_symbol_exists("wolfSSL_DES_ecb_encrypt" "wolfssl/options.h;wolfssl/openssl/des.h" HAVE_WOLFSSL_DES_ECB_ENCRYPT) - curl_openssl_check_symbol_exists("wolfSSL_BIO_new" "wolfssl/options.h;wolfssl/ssl.h" HAVE_WOLFSSL_BIO) - curl_openssl_check_symbol_exists("wolfSSL_BIO_set_shutdown" "wolfssl/options.h;wolfssl/ssl.h" HAVE_WOLFSSL_FULL_BIO) + curl_openssl_check_exists("wolfSSL_get_peer_certificate" HAVE_WOLFSSL_GET_PEER_CERTIFICATE) + curl_openssl_check_exists("wolfSSL_UseALPN" HAVE_WOLFSSL_USEALPN) + curl_openssl_check_exists("wolfSSL_DES_ecb_encrypt" HAVE_WOLFSSL_DES_ECB_ENCRYPT) + curl_openssl_check_exists("wolfSSL_BIO_new" HAVE_WOLFSSL_BIO_NEW) + curl_openssl_check_exists("wolfSSL_BIO_set_shutdown" HAVE_WOLFSSL_BIO_SET_SHUTDOWN) endif() -if(USE_OPENSSL OR USE_WOLFSSL) +if(USE_OPENSSL) if(NOT DEFINED HAVE_SSL_SET0_WBIO) - curl_openssl_check_symbol_exists("SSL_set0_wbio" "openssl/ssl.h" HAVE_SSL_SET0_WBIO) + curl_openssl_check_exists("SSL_set0_wbio" HAVE_SSL_SET0_WBIO) endif() if(NOT DEFINED HAVE_OPENSSL_SRP AND NOT CURL_DISABLE_SRP) - curl_openssl_check_symbol_exists("SSL_CTX_set_srp_username" "openssl/ssl.h" HAVE_OPENSSL_SRP) + curl_openssl_check_exists("SSL_CTX_set_srp_username" "openssl/ssl.h" HAVE_OPENSSL_SRP) endif() endif() option(USE_HTTPSRR "Enable HTTPS RR support" OFF) option(USE_ECH "Enable ECH support" OFF) if(USE_ECH) - if(USE_OPENSSL OR USE_WOLFSSL) + if(USE_OPENSSL OR USE_WOLFSSL OR USE_RUSTLS) # Be sure that the TLS library actually supports ECH. if(USE_WOLFSSL) - curl_openssl_check_symbol_exists("wolfSSL_CTX_GenerateEchConfig" "wolfssl/options.h;wolfssl/ssl.h" - HAVE_WOLFSSL_CTX_GENERATEECHCONFIG) + curl_openssl_check_exists("wolfSSL_CTX_GenerateEchConfig" HAVE_WOLFSSL_CTX_GENERATEECHCONFIG) endif() - if(HAVE_BORINGSSL OR HAVE_AWSLC) - curl_openssl_check_symbol_exists("SSL_set1_ech_config_list" "openssl/ssl.h" HAVE_SSL_SET1_ECH_CONFIG_LIST) - elseif(HAVE_OPENSSL) - curl_openssl_check_symbol_exists("SSL_set1_ech_config_list" "openssl/ech.h" HAVE_SSL_SET1_ECH_CONFIG_LIST) + if(USE_OPENSSL) + curl_openssl_check_exists("SSL_set1_ech_config_list" HAVE_SSL_SET1_ECH_CONFIG_LIST) endif() if(HAVE_WOLFSSL_CTX_GENERATEECHCONFIG OR - HAVE_SSL_SET1_ECH_CONFIG_LIST) + HAVE_SSL_SET1_ECH_CONFIG_LIST OR + USE_RUSTLS) set(HAVE_ECH 1) endif() if(NOT HAVE_ECH) - message(FATAL_ERROR "ECH support missing in OpenSSL/BoringSSL/AWS-LC/wolfSSL") + message(FATAL_ERROR "ECH support missing in OpenSSL/BoringSSL/AWS-LC/wolfSSL/rustls-ffi") else() message(STATUS "ECH enabled") # ECH wants HTTPSRR @@ -1264,7 +1326,7 @@ if(USE_ECH) message(STATUS "HTTPSRR enabled") endif() else() - message(FATAL_ERROR "ECH requires ECH-enablded OpenSSL, BoringSSL, AWS-LC or wolfSSL") + message(FATAL_ERROR "ECH requires ECH-enabled OpenSSL, BoringSSL, AWS-LC, wolfSSL or rustls-ffi") endif() endif() @@ -1301,6 +1363,12 @@ if(USE_NGTCP2) find_package(NGTCP2 REQUIRED "wolfSSL") elseif(HAVE_BORINGSSL OR HAVE_AWSLC) find_package(NGTCP2 REQUIRED "BoringSSL") + elseif(OPENSSL_VERSION VERSION_GREATER_EQUAL 3.5.0 AND NOT USE_OPENSSL_QUIC) + find_package(NGTCP2 REQUIRED "ossl") + if(NGTCP2_VERSION VERSION_LESS 1.12.0) + message(FATAL_ERROR "ngtcp2 1.12.0 or upper required for OpenSSL") + endif() + set(OPENSSL_QUIC_API2 1) else() find_package(NGTCP2 REQUIRED "quictls") if(NOT HAVE_LIBRESSL) @@ -1311,7 +1379,7 @@ if(USE_NGTCP2) elseif(USE_GNUTLS) find_package(NGTCP2 REQUIRED "GnuTLS") else() - message(FATAL_ERROR "ngtcp2 requires OpenSSL, wolfSSL or GnuTLS") + message(FATAL_ERROR "ngtcp2 requires a supported TLS-backend") endif() list(APPEND CURL_LIBS ${NGTCP2_LIBRARIES}) list(APPEND CURL_LIBDIRS ${NGTCP2_LIBRARY_DIRS}) @@ -1354,8 +1422,8 @@ if(USE_QUICHE) endif() if(NOT DEFINED HAVE_QUICHE_CONN_SET_QLOG_FD) cmake_push_check_state() - list(APPEND CMAKE_REQUIRED_INCLUDES "${QUICHE_INCLUDE_DIRS}") - list(APPEND CMAKE_REQUIRED_LIBRARIES "${QUICHE_LIBRARIES}") + list(APPEND CMAKE_REQUIRED_INCLUDES "${QUICHE_INCLUDE_DIRS}") + list(APPEND CMAKE_REQUIRED_LIBRARIES "${QUICHE_LIBRARIES}") check_symbol_exists("quiche_conn_set_qlog_fd" "quiche.h" HAVE_QUICHE_CONN_SET_QLOG_FD) cmake_pop_check_state() endif() @@ -1391,9 +1459,14 @@ if(USE_OPENSSL_QUIC) find_package(NGHTTP3 REQUIRED) set(USE_NGHTTP3 ON) - include_directories(SYSTEM ${NGHTTP3_INCLUDE_DIRS}) list(APPEND CURL_LIBS ${NGHTTP3_LIBRARIES}) + list(APPEND CURL_LIBDIRS ${NGHTTP3_LIBRARY_DIRS}) list(APPEND LIBCURL_PC_REQUIRES_PRIVATE ${NGHTTP3_PC_REQUIRES}) + include_directories(SYSTEM ${NGHTTP3_INCLUDE_DIRS}) + link_directories(${NGHTTP3_LIBRARY_DIRS}) + if(NGHTTP3_CFLAGS) + string(APPEND CMAKE_C_FLAGS " ${NGHTTP3_CFLAGS}") + endif() endif() if(CURL_WITH_MULTI_SSL AND (USE_NGTCP2 OR USE_QUICHE OR USE_MSH3 OR USE_OPENSSL_QUIC)) @@ -1405,7 +1478,7 @@ if(NOT CURL_DISABLE_SRP AND (HAVE_GNUTLS_SRP OR HAVE_OPENSSL_SRP)) endif() if(NOT CURL_DISABLE_LDAP) - if(WIN32 AND NOT WINDOWS_STORE) + if(WIN32 AND NOT WINDOWS_STORE AND NOT WINCE) option(USE_WIN32_LDAP "Use Windows LDAP implementation" ON) if(USE_WIN32_LDAP) list(APPEND CURL_LIBS "wldap32") @@ -1439,7 +1512,7 @@ if(NOT CURL_DISABLE_LDAP) # LDAP feature checks list(APPEND CMAKE_REQUIRED_DEFINITIONS "-DLDAP_DEPRECATED=1") - list(APPEND CMAKE_REQUIRED_LIBRARIES ${LDAP_LIBRARIES}) + list(APPEND CMAKE_REQUIRED_LIBRARIES "${LDAP_LIBRARIES}") curl_required_libpaths("${LDAP_LIBRARY_DIRS}") check_function_exists("ldap_url_parse" HAVE_LDAP_URL_PARSE) @@ -1622,7 +1695,7 @@ if(CURL_USE_GSSAPI) set(HAVE_GSSGNU 1) else() cmake_push_check_state() - list(APPEND CMAKE_REQUIRED_INCLUDES ${GSS_INCLUDE_DIRS}) + list(APPEND CMAKE_REQUIRED_INCLUDES "${GSS_INCLUDE_DIRS}") set(_include_list "") check_include_file("gssapi/gssapi.h" HAVE_GSSAPI_GSSAPI_H) @@ -1642,7 +1715,7 @@ if(CURL_USE_GSSAPI) if(NOT DEFINED HAVE_GSS_C_NT_HOSTBASED_SERVICE) string(APPEND CMAKE_REQUIRED_FLAGS " ${GSS_CFLAGS}") - list(APPEND CMAKE_REQUIRED_LIBRARIES ${GSS_LIBRARIES}) + list(APPEND CMAKE_REQUIRED_LIBRARIES "${GSS_LIBRARIES}") curl_required_libpaths("${GSS_LIBRARY_DIRS}") check_symbol_exists("GSS_C_NT_HOSTBASED_SERVICE" "${_include_list}" HAVE_GSS_C_NT_HOSTBASED_SERVICE) endif() @@ -1691,7 +1764,7 @@ if(USE_LIBRTMP) endif() option(ENABLE_UNIX_SOCKETS "Enable Unix domain sockets support" ON) -if(ENABLE_UNIX_SOCKETS) +if(ENABLE_UNIX_SOCKETS AND NOT WINCE) if(WIN32 OR DOS) set(USE_UNIX_SOCKETS ON) else() @@ -1803,24 +1876,9 @@ if(WIN32) list(APPEND CURL_INCLUDES "winsock2.h") list(APPEND CURL_INCLUDES "ws2tcpip.h") - if(HAVE_WIN32_WINNT) - if(HAVE_WIN32_WINNT LESS 0x0501) - # Windows XP is required for freeaddrinfo, getaddrinfo - message(FATAL_ERROR "Building for Windows XP or newer is required.") - endif() - - # Pre-fill detection results based on target OS version - if(MINGW OR MSVC) - if(HAVE_WIN32_WINNT LESS 0x0600) - set(HAVE_INET_NTOP 0) - set(HAVE_INET_PTON 0) - else() # Windows Vista or newer - set(HAVE_INET_NTOP 1) - set(HAVE_INET_PTON 1) - endif() - unset(HAVE_INET_NTOP CACHE) - unset(HAVE_INET_PTON CACHE) - endif() + if(HAVE_WIN32_WINNT AND HAVE_WIN32_WINNT LESS 0x0501 AND NOT WINCE) + # Windows XP is required for freeaddrinfo, getaddrinfo + message(FATAL_ERROR "Building for Windows XP or newer is required.") endif() endif() @@ -1862,6 +1920,7 @@ check_include_file("poll.h" HAVE_POLL_H) check_include_file("pwd.h" HAVE_PWD_H) check_include_file("stdatomic.h" HAVE_STDATOMIC_H) check_include_file("stdbool.h" HAVE_STDBOOL_H) +check_include_file("stdint.h" HAVE_STDINT_H) check_include_file("strings.h" HAVE_STRINGS_H) check_include_file("stropts.h" HAVE_STROPTS_H) check_include_file("termio.h" HAVE_TERMIO_H) @@ -1902,7 +1961,7 @@ endif() # Apply to all feature checks if(WIN32) - list(APPEND CMAKE_REQUIRED_LIBRARIES "ws2_32") + list(APPEND CMAKE_REQUIRED_LIBRARIES "${_win32_winsock}") elseif(HAVE_LIBSOCKET) list(APPEND CMAKE_REQUIRED_LIBRARIES "socket") elseif(HAVE_LIBNETWORK) @@ -1911,6 +1970,7 @@ elseif(DOS) list(APPEND CMAKE_REQUIRED_LIBRARIES "${WATT_ROOT}/lib/libwatt.a") endif() +check_function_exists("accept4" HAVE_ACCEPT4) check_function_exists("fnmatch" HAVE_FNMATCH) check_symbol_exists("basename" "${CURL_INCLUDES};string.h" HAVE_BASENAME) # libgen.h unistd.h check_symbol_exists("opendir" "dirent.h" HAVE_OPENDIR) @@ -1923,7 +1983,6 @@ check_function_exists("sendmsg" HAVE_SENDMSG) check_function_exists("sendmmsg" HAVE_SENDMMSG) check_symbol_exists("select" "${CURL_INCLUDES}" HAVE_SELECT) # proto/bsdsocket.h sys/select.h sys/socket.h check_symbol_exists("strdup" "string.h" HAVE_STRDUP) -check_symbol_exists("strtok_r" "string.h" HAVE_STRTOK_R) check_symbol_exists("memrchr" "string.h" HAVE_MEMRCHR) check_symbol_exists("alarm" "unistd.h" HAVE_ALARM) check_symbol_exists("fcntl" "fcntl.h" HAVE_FCNTL) @@ -1944,7 +2003,6 @@ check_symbol_exists("gethostbyname_r" "netdb.h" HAVE_GETHOSTBYNAME_R) check_symbol_exists("gethostname" "${CURL_INCLUDES}" HAVE_GETHOSTNAME) # winsock2.h unistd.h proto/bsdsocket.h check_symbol_exists("signal" "signal.h" HAVE_SIGNAL) -check_symbol_exists("strtoll" "stdlib.h" HAVE_STRTOLL) check_symbol_exists("strerror_r" "stdlib.h;string.h" HAVE_STRERROR_R) check_symbol_exists("sigaction" "signal.h" HAVE_SIGACTION) check_symbol_exists("siginterrupt" "signal.h" HAVE_SIGINTERRUPT) @@ -1952,13 +2010,13 @@ check_symbol_exists("getaddrinfo" "${CURL_INCLUDES};stdlib.h;string.h" HAVE_ check_symbol_exists("getifaddrs" "${CURL_INCLUDES};stdlib.h" HAVE_GETIFADDRS) # ifaddrs.h check_symbol_exists("freeaddrinfo" "${CURL_INCLUDES}" HAVE_FREEADDRINFO) # ws2tcpip.h sys/socket.h netdb.h check_function_exists("pipe" HAVE_PIPE) +check_function_exists("pipe2" HAVE_PIPE2) check_function_exists("eventfd" HAVE_EVENTFD) check_symbol_exists("ftruncate" "unistd.h" HAVE_FTRUNCATE) check_symbol_exists("getpeername" "${CURL_INCLUDES}" HAVE_GETPEERNAME) # winsock2.h unistd.h proto/bsdsocket.h check_symbol_exists("getsockname" "${CURL_INCLUDES}" HAVE_GETSOCKNAME) # winsock2.h unistd.h proto/bsdsocket.h check_function_exists("getrlimit" HAVE_GETRLIMIT) check_function_exists("setlocale" HAVE_SETLOCALE) -check_function_exists("setmode" HAVE_SETMODE) check_function_exists("setrlimit" HAVE_SETRLIMIT) if(NOT WIN32) @@ -1970,8 +2028,11 @@ if(NOT WIN32) check_symbol_exists("strcmpi" "string.h" HAVE_STRCMPI) endif() -if(WIN32 OR CYGWIN) - check_function_exists("_setmode" HAVE__SETMODE) +if(NOT MINGW32CE) # Avoid false detections + check_function_exists("setmode" HAVE_SETMODE) + if(WIN32 OR CYGWIN) + check_function_exists("_setmode" HAVE__SETMODE) + endif() endif() if(AMIGA) @@ -1990,8 +2051,10 @@ endif() if(APPLE) check_function_exists("mach_absolute_time" HAVE_MACH_ABSOLUTE_TIME) endif() -check_symbol_exists("inet_ntop" "${CURL_INCLUDES};stdlib.h;string.h" HAVE_INET_NTOP) # arpa/inet.h netinet/in.h sys/socket.h -check_symbol_exists("inet_pton" "${CURL_INCLUDES};stdlib.h;string.h" HAVE_INET_PTON) # arpa/inet.h netinet/in.h sys/socket.h +if(NOT WIN32) + check_symbol_exists("inet_ntop" "${CURL_INCLUDES};stdlib.h;string.h" HAVE_INET_NTOP) # arpa/inet.h netinet/in.h sys/socket.h + check_symbol_exists("inet_pton" "${CURL_INCLUDES};stdlib.h;string.h" HAVE_INET_PTON) # arpa/inet.h netinet/in.h sys/socket.h +endif() check_symbol_exists("fsetxattr" "sys/xattr.h" HAVE_FSETXATTR) if(HAVE_FSETXATTR) @@ -2024,18 +2087,44 @@ foreach(_curl_test IN ITEMS HAVE_GETHOSTBYNAME_R_3 HAVE_GETHOSTBYNAME_R_5 HAVE_GETHOSTBYNAME_R_6 - HAVE_GETHOSTBYNAME_R_3_REENTRANT - HAVE_GETHOSTBYNAME_R_5_REENTRANT - HAVE_GETHOSTBYNAME_R_6_REENTRANT - HAVE_IN_ADDR_T HAVE_BOOL_T STDC_HEADERS - HAVE_FILE_OFFSET_BITS HAVE_ATOMIC ) curl_internal_test(${_curl_test}) endforeach() +# Check for reentrant +cmake_push_check_state() +list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D_REENTRANT") +foreach(_curl_test IN ITEMS + HAVE_GETHOSTBYNAME_R_3 + HAVE_GETHOSTBYNAME_R_5 + HAVE_GETHOSTBYNAME_R_6) + curl_internal_test(${_curl_test}_REENTRANT) + if(NOT ${_curl_test} AND ${_curl_test}_REENTRANT) + set(NEED_REENTRANT 1) + endif() +endforeach() +cmake_pop_check_state() + +if(NEED_REENTRANT) + foreach(_curl_test IN ITEMS + HAVE_GETHOSTBYNAME_R_3 + HAVE_GETHOSTBYNAME_R_5 + HAVE_GETHOSTBYNAME_R_6) + set(${_curl_test} 0) + if(${_curl_test}_REENTRANT) + set(${_curl_test} 1) + endif() + endforeach() +endif() + +cmake_push_check_state() +list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D_FILE_OFFSET_BITS=64") +curl_internal_test(HAVE_FILE_OFFSET_BITS) +cmake_pop_check_state() + cmake_push_check_state() if(HAVE_FILE_OFFSET_BITS) set(_FILE_OFFSET_BITS 64) @@ -2091,30 +2180,6 @@ endif() curl_internal_test(HAVE_GLIBC_STRERROR_R) curl_internal_test(HAVE_POSIX_STRERROR_R) -# Check for reentrant -foreach(_curl_test IN ITEMS - HAVE_GETHOSTBYNAME_R_3 - HAVE_GETHOSTBYNAME_R_5 - HAVE_GETHOSTBYNAME_R_6) - if(NOT ${_curl_test}) - if(${_curl_test}_REENTRANT) - set(NEED_REENTRANT 1) - endif() - endif() -endforeach() - -if(NEED_REENTRANT) - foreach(_curl_test IN ITEMS - HAVE_GETHOSTBYNAME_R_3 - HAVE_GETHOSTBYNAME_R_5 - HAVE_GETHOSTBYNAME_R_6) - set(${_curl_test} 0) - if(${_curl_test}_REENTRANT) - set(${_curl_test} 1) - endif() - endforeach() -endif() - if(NOT WIN32) curl_internal_test(HAVE_CLOCK_GETTIME_MONOTONIC) # Check clock_gettime(CLOCK_MONOTONIC, x) support endif() @@ -2125,26 +2190,6 @@ endif() # Some other minor tests -if(NOT HAVE_IN_ADDR_T) - set(in_addr_t "unsigned long") -endif() - -if(CMAKE_COMPILER_IS_GNUCC AND APPLE) - include(CheckCCompilerFlag) - check_c_compiler_flag("-Wno-long-double" HAVE_C_FLAG_Wno_long_double) - if(HAVE_C_FLAG_Wno_long_double) - # The Mac version of GCC warns about use of long double. Disable it. - get_source_file_property(_mprintf_compile_flags "mprintf.c" COMPILE_FLAGS) - if(_mprintf_compile_flags) - string(APPEND _mprintf_compile_flags " -Wno-long-double") - else() - set(_mprintf_compile_flags "-Wno-long-double") - endif() - set_source_files_properties("mprintf.c" PROPERTIES - COMPILE_FLAGS ${_mprintf_compile_flags}) - endif() -endif() - if(_cmake_try_compile_target_type_save) set(CMAKE_TRY_COMPILE_TARGET_TYPE ${_cmake_try_compile_target_type_save}) unset(_cmake_try_compile_target_type_save) @@ -2152,18 +2197,17 @@ endif() include(CMake/OtherTests.cmake) -add_definitions("-DHAVE_CONFIG_H") +set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "HAVE_CONFIG_H") if(WIN32) - list(APPEND CURL_LIBS "ws2_32" "bcrypt") - - # _fseeki64() requires VS2005 - if(NOT MSVC OR (MSVC_VERSION GREATER_EQUAL 1400)) - set(USE_WIN32_LARGE_FILES ON) + list(APPEND CURL_LIBS "${_win32_winsock}") + if(NOT WINCE) + list(APPEND CURL_LIBS "bcrypt") endif() - # Use the manifest embedded in the Windows Resource - string(APPEND CMAKE_RC_FLAGS " -DCURL_EMBED_MANIFEST") + if(NOT WINCE) + set(USE_WIN32_LARGE_FILES ON) + endif() # We use crypto functions that are not available for UWP apps if(NOT WINDOWS_STORE) @@ -2172,32 +2216,15 @@ if(WIN32) # Link required libraries for USE_WIN32_CRYPTO or USE_SCHANNEL if(USE_WIN32_CRYPTO OR USE_SCHANNEL) - list(APPEND CURL_LIBS "advapi32" "crypt32") + if(NOT WINCE) + list(APPEND CURL_LIBS "advapi32") + endif() + list(APPEND CURL_LIBS "${_win32_crypt32}") endif() endif() -if(MSVC) - # Disable default manifest added by CMake - string(APPEND CMAKE_EXE_LINKER_FLAGS " -MANIFEST:NO") - - if(CMAKE_C_FLAGS MATCHES "[/-]W[0-4]") - string(REGEX REPLACE "[/-]W[0-4]" "-W4" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - else() - string(APPEND CMAKE_C_FLAGS " -W4") - endif() - - # Use multithreaded compilation on VS2008+ - if(CMAKE_C_COMPILER_ID STREQUAL "MSVC" AND MSVC_VERSION GREATER_EQUAL 1500) - string(APPEND CMAKE_C_FLAGS " -MP") - endif() -endif() - -if(CURL_WERROR) - if(MSVC) - string(APPEND CMAKE_C_FLAGS " -WX") - else() - string(APPEND CMAKE_C_FLAGS " -Werror") # This assumes clang or gcc style options - endif() +if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") # MSVC but exclude clang-cl + set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "-MP") # Parallel compilation endif() if(CURL_LTO) @@ -2224,9 +2251,9 @@ function(curl_transform_makefile_inc _input_file _output_file) string(REPLACE "$(top_srcdir)" "\${PROJECT_SOURCE_DIR}" _makefile_inc_text ${_makefile_inc_text}) string(REPLACE "$(top_builddir)" "\${PROJECT_BINARY_DIR}" _makefile_inc_text ${_makefile_inc_text}) - string(REGEX REPLACE "\\\\\n" "!Ï€!α!" _makefile_inc_text ${_makefile_inc_text}) + string(REGEX REPLACE "\\\\\n" "!^!^!" _makefile_inc_text ${_makefile_inc_text}) string(REGEX REPLACE "([a-zA-Z_][a-zA-Z0-9_]*)[\t ]*=[\t ]*([^\n]*)" "set(\\1 \\2)" _makefile_inc_text ${_makefile_inc_text}) - string(REPLACE "!Ï€!α!" "\n" _makefile_inc_text ${_makefile_inc_text}) + string(REPLACE "!^!^!" "\n" _makefile_inc_text ${_makefile_inc_text}) # Replace $() with ${} string(REGEX REPLACE "\\$\\(([a-zA-Z_][a-zA-Z0-9_]*)\\)" "\${\\1}" _makefile_inc_text ${_makefile_inc_text}) @@ -2263,7 +2290,7 @@ set(_project_config "${_generated_dir}/${PROJECT_NAME}Config.cmake") set(_version_config "${_generated_dir}/${PROJECT_NAME}ConfigVersion.cmake") option(BUILD_TESTING "Build tests" ON) -if(BUILD_TESTING AND PERL_FOUND AND NOT CURL_DISABLE_TESTS) +if(BUILD_TESTING AND PERL_FOUND) set(CURL_BUILD_TESTING ON) else() set(CURL_BUILD_TESTING OFF) @@ -2275,6 +2302,10 @@ if(HAVE_MANUAL_TOOLS) add_subdirectory(docs) endif() +add_subdirectory(scripts) # for shell completions + +list(REMOVE_DUPLICATES CURL_LIBDIRS) + add_subdirectory(lib) if(BUILD_CURL_EXE) @@ -2379,16 +2410,16 @@ curl_add_if("SPNEGO" NOT CURL_DISABLE_NEGOTIATE_AUTH AND (HAVE_GSSAPI OR USE_WINDOWS_SSPI)) curl_add_if("Kerberos" NOT CURL_DISABLE_KERBEROS_AUTH AND (HAVE_GSSAPI OR USE_WINDOWS_SSPI)) -curl_add_if("NTLM" NOT (CURL_DISABLE_NTLM) AND +curl_add_if("NTLM" NOT CURL_DISABLE_NTLM AND (_use_curl_ntlm_core OR USE_WINDOWS_SSPI)) curl_add_if("TLS-SRP" USE_TLS_SRP) curl_add_if("HTTP2" USE_NGHTTP2) curl_add_if("HTTP3" USE_NGTCP2 OR USE_QUICHE OR USE_MSH3 OR USE_OPENSSL_QUIC) curl_add_if("MultiSSL" CURL_WITH_MULTI_SSL) -curl_add_if("HTTPS-proxy" _ssl_enabled AND (USE_OPENSSL OR USE_GNUTLS +curl_add_if("HTTPS-proxy" NOT CURL_DISABLE_PROXY AND _ssl_enabled AND (USE_OPENSSL OR USE_GNUTLS OR USE_SCHANNEL OR USE_RUSTLS OR USE_BEARSSL OR USE_MBEDTLS OR USE_SECTRANSP OR - (USE_WOLFSSL AND HAVE_WOLFSSL_BIO))) + (USE_WOLFSSL AND HAVE_WOLFSSL_BIO_NEW))) curl_add_if("Unicode" ENABLE_UNICODE) curl_add_if("threadsafe" HAVE_ATOMIC OR (USE_THREADS_POSIX AND HAVE_PTHREAD_H) OR @@ -2401,7 +2432,7 @@ curl_add_if("PSL" USE_LIBPSL) curl_add_if("CAcert" CURL_CA_EMBED_SET) curl_add_if("SSLS-EXPORT" _ssl_enabled AND USE_SSLS_EXPORT) if(_items) - if(NOT CMAKE_VERSION VERSION_LESS 3.13) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13) list(SORT _items CASE INSENSITIVE) else() list(SORT _items) @@ -2415,7 +2446,7 @@ message(STATUS "Features: ${SUPPORT_FEATURES}") set(_items "") curl_add_if("Schannel" _ssl_enabled AND USE_SCHANNEL) curl_add_if("${_openssl}" _ssl_enabled AND USE_OPENSSL AND OPENSSL_VERSION VERSION_LESS 3.0.0) -curl_add_if("${_openssl} v3+" _ssl_enabled AND USE_OPENSSL AND NOT OPENSSL_VERSION VERSION_LESS 3.0.0) +curl_add_if("${_openssl} v3+" _ssl_enabled AND USE_OPENSSL AND OPENSSL_VERSION VERSION_GREATER_EQUAL 3.0.0) curl_add_if("Secure Transport" _ssl_enabled AND USE_SECTRANSP) curl_add_if("mbedTLS" _ssl_enabled AND USE_MBEDTLS) curl_add_if("BearSSL" _ssl_enabled AND USE_BEARSSL) @@ -2424,7 +2455,7 @@ curl_add_if("GnuTLS" _ssl_enabled AND USE_GNUTLS) curl_add_if("rustls" _ssl_enabled AND USE_RUSTLS) if(_items) - if(NOT CMAKE_VERSION VERSION_LESS 3.13) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13) list(SORT _items CASE INSENSITIVE) else() list(SORT _items) @@ -2502,7 +2533,7 @@ if(NOT CURL_DISABLE_INSTALL) endforeach() foreach(_libdir IN LISTS _custom_libdirs CURL_LIBDIRS) - if(NOT CMAKE_VERSION VERSION_LESS 3.20) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20) cmake_path(SET _libdir NORMALIZE "${_libdir}") endif() list(FIND _sys_libdirs "${_libdir}" _libdir_index) @@ -2539,7 +2570,7 @@ if(NOT CURL_DISABLE_INSTALL) get_filename_component(_libdir ${_lib} DIRECTORY) get_filename_component(_libname ${_lib} NAME_WE) if(_libname MATCHES "^lib") - if(NOT CMAKE_VERSION VERSION_LESS 3.20) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20) cmake_path(SET _libdir NORMALIZE "${_libdir}") endif() list(FIND _sys_libdirs "${_libdir}" _libdir_index) @@ -2668,6 +2699,7 @@ if(NOT CURL_DISABLE_INSTALL) # Consumed custom variables: # CURLVERSION + # LIB_NAME # LIB_SELECTED # TARGETS_EXPORT_NAME # USE_OPENSSL OPENSSL_VERSION_MAJOR @@ -2698,7 +2730,7 @@ if(NOT CURL_DISABLE_INSTALL) COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/CMake/cmake_uninstall.cmake") endif() - install(FILES "${PROJECT_SOURCE_DIR}/scripts/mk-ca-bundle.pl" + install(FILES "${PROJECT_SOURCE_DIR}/scripts/wcurl" DESTINATION ${CMAKE_INSTALL_BINDIR} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE diff --git a/Utilities/cmcurl/include/curl/curl.h b/Utilities/cmcurl/include/curl/curl.h index c713368c28..d769108f91 100644 --- a/Utilities/cmcurl/include/curl/curl.h +++ b/Utilities/cmcurl/include/curl/curl.h @@ -42,7 +42,7 @@ !defined(CURL_DISABLE_DEPRECATION) && !defined(BUILDING_LIBCURL) #define CURL_DEPRECATED(version, message) \ __attribute__((deprecated("since " # version ". " message))) -#if defined(__IAR_SYSTEMS_ICC__) +#ifdef __IAR_SYSTEMS_ICC__ #define CURL_IGNORE_DEPRECATION(statements) \ _Pragma("diag_suppress=Pe1444") \ statements \ @@ -97,19 +97,11 @@ #include #endif -#if !defined(_WIN32) && !defined(_WIN32_WCE) +#ifndef _WIN32 #include -#endif - -#if !defined(_WIN32) #include #endif -/* Compatibility for non-Clang compilers */ -#ifndef __has_declspec_attribute -# define __has_declspec_attribute(x) 0 -#endif - #ifdef __cplusplus extern "C" { #endif @@ -121,11 +113,17 @@ typedef void CURLSH; * libcurl external API function linkage decorations. */ +#ifdef __has_declspec_attribute +#define CURL_HAS_DECLSPEC_ATTRIBUTE(x) __has_declspec_attribute(x) +#else +#define CURL_HAS_DECLSPEC_ATTRIBUTE(x) 0 +#endif + #ifdef CURL_STATICLIB # define CURL_EXTERN #elif defined(_WIN32) || \ - (__has_declspec_attribute(dllexport) && \ - __has_declspec_attribute(dllimport)) + (CURL_HAS_DECLSPEC_ATTRIBUTE(dllexport) && \ + CURL_HAS_DECLSPEC_ATTRIBUTE(dllimport)) # if defined(BUILDING_LIBCURL) # define CURL_EXTERN __declspec(dllexport) # else @@ -177,6 +175,16 @@ typedef enum { #define CURLSSLBACKEND_CYASSL CURLSSLBACKEND_WOLFSSL #define CURLSSLBACKEND_DARWINSSL CURLSSLBACKEND_SECURETRANSPORT +/* bits for the CURLOPT_FOLLOWLOCATION option */ +#define CURLFOLLOW_ALL 1L /* generic follow redirects */ + +/* Do not use the custom method in the follow-up request if the HTTP code + instructs so (301, 302, 303). */ +#define CURLFOLLOW_OBEYCODE 2L + +/* Only use the custom method in the first request, always reset in the next */ +#define CURLFOLLOW_FIRSTONLY 3L + struct curl_httppost { struct curl_httppost *next; /* next entry in the list */ char *name; /* pointer to allocated name */ @@ -637,7 +645,20 @@ typedef enum { 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! */ + CURL_LAST, /* never use! */ + + CURLE_RESERVED115 = 115, /* 115-126 - used in tests */ + CURLE_RESERVED116 = 116, + CURLE_RESERVED117 = 117, + CURLE_RESERVED118 = 118, + CURLE_RESERVED119 = 119, + CURLE_RESERVED120 = 120, + CURLE_RESERVED121 = 121, + CURLE_RESERVED122 = 122, + CURLE_RESERVED123 = 123, + CURLE_RESERVED124 = 124, + CURLE_RESERVED125 = 125, + CURLE_RESERVED126 = 126 } CURLcode; #ifndef CURL_NO_OLDIES /* define this to test if your app builds with all @@ -906,12 +927,13 @@ typedef int /* parameter for the CURLOPT_USE_SSL option */ +#define CURLUSESSL_NONE 0L /* do not attempt to use SSL */ +#define CURLUSESSL_TRY 1L /* try using SSL, proceed anyway otherwise */ +#define CURLUSESSL_CONTROL 2L /* SSL for the control connection or fail */ +#define CURLUSESSL_ALL 3L /* SSL for all communication or fail */ + typedef enum { - CURLUSESSL_NONE, /* do not attempt to use SSL */ - CURLUSESSL_TRY, /* try using SSL, proceed anyway otherwise */ - CURLUSESSL_CONTROL, /* SSL for the control connection or fail */ - CURLUSESSL_ALL, /* SSL for all communication or fail */ - CURLUSESSL_LAST /* not an option, never use */ + CURLUSESSL_LAST = 4 /* not an option, never use */ } curl_usessl; /* Definition of bits for the CURLOPT_SSL_OPTIONS argument: */ @@ -1015,6 +1037,12 @@ typedef enum { #define CURLALTSVC_H2 (1<<4) #define CURLALTSVC_H3 (1<<5) +/* bitmask values for CURLOPT_UPLOAD_FLAGS */ +#define CURLULFLAG_ANSWERED (1L<<0) +#define CURLULFLAG_DELETED (1L<<1) +#define CURLULFLAG_DRAFT (1L<<2) +#define CURLULFLAG_FLAGGED (1L<<3) +#define CURLULFLAG_SEEN (1L<<4) struct curl_hstsentry { char *name; @@ -2228,6 +2256,11 @@ typedef enum { /* maximum number of keepalive probes (Linux, *BSD, macOS, etc.) */ CURLOPT(CURLOPT_TCP_KEEPCNT, CURLOPTTYPE_LONG, 326), + CURLOPT(CURLOPT_UPLOAD_FLAGS, CURLOPTTYPE_LONG, 327), + + /* set TLS supported signature algorithms */ + CURLOPT(CURLOPT_SSL_SIGNATURE_ALGORITHMS, CURLOPTTYPE_STRINGPOINT, 328), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; @@ -2276,26 +2309,25 @@ typedef enum { /* Convenient "aliases" */ #define CURLOPT_RTSPHEADER CURLOPT_HTTPHEADER - /* These enums are for use with the CURLOPT_HTTP_VERSION option. */ -enum { - 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 */ - CURL_HTTP_VERSION_2TLS, /* use version 2 for HTTPS, version 1.1 for HTTP */ - CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE, /* please use HTTP 2 without HTTP/1.1 - Upgrade */ - CURL_HTTP_VERSION_3 = 30, /* Use HTTP/3, fallback to HTTP/2 or HTTP/1 if - needed. For HTTPS only. For HTTP, this option - makes libcurl return error. */ - CURL_HTTP_VERSION_3ONLY = 31, /* Use HTTP/3 without fallback. For HTTPS - only. For HTTP, this makes libcurl - return error. */ - - CURL_HTTP_VERSION_LAST /* *ILLEGAL* http version */ -}; +/* These constants are for use with the CURLOPT_HTTP_VERSION option. */ +#define CURL_HTTP_VERSION_NONE 0L /* setting this means we do not care, and + that we would like the library to choose + the best possible for us! */ +#define CURL_HTTP_VERSION_1_0 1L /* please use HTTP 1.0 in the request */ +#define CURL_HTTP_VERSION_1_1 2L /* please use HTTP 1.1 in the request */ +#define CURL_HTTP_VERSION_2_0 3L /* please use HTTP 2 in the request */ +#define CURL_HTTP_VERSION_2TLS 4L /* use version 2 for HTTPS, version 1.1 for + HTTP */ +#define CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE 5L /* please use HTTP 2 without + HTTP/1.1 Upgrade */ +#define CURL_HTTP_VERSION_3 30L /* Use HTTP/3, fallback to HTTP/2 or + HTTP/1 if needed. For HTTPS only. For + HTTP, this option makes libcurl + return error. */ +#define CURL_HTTP_VERSION_3ONLY 31L /* Use HTTP/3 without fallback. For + HTTPS only. For HTTP, this makes + libcurl return error. */ +#define CURL_HTTP_VERSION_LAST 32L /* *ILLEGAL* http version */ /* Convenience definition simple because the name of the version is HTTP/2 and not 2.0. The 2_0 version of the enum name was set while the version was @@ -2305,32 +2337,33 @@ enum { /* * Public API enums for RTSP requests */ -enum { - CURL_RTSPREQ_NONE, /* first in list */ - CURL_RTSPREQ_OPTIONS, - CURL_RTSPREQ_DESCRIBE, - CURL_RTSPREQ_ANNOUNCE, - CURL_RTSPREQ_SETUP, - CURL_RTSPREQ_PLAY, - CURL_RTSPREQ_PAUSE, - CURL_RTSPREQ_TEARDOWN, - CURL_RTSPREQ_GET_PARAMETER, - CURL_RTSPREQ_SET_PARAMETER, - CURL_RTSPREQ_RECORD, - CURL_RTSPREQ_RECEIVE, - CURL_RTSPREQ_LAST /* last in list */ -}; + +#define CURL_RTSPREQ_NONE 0L +#define CURL_RTSPREQ_OPTIONS 1L +#define CURL_RTSPREQ_DESCRIBE 2L +#define CURL_RTSPREQ_ANNOUNCE 3L +#define CURL_RTSPREQ_SETUP 4L +#define CURL_RTSPREQ_PLAY 5L +#define CURL_RTSPREQ_PAUSE 6L +#define CURL_RTSPREQ_TEARDOWN 7L +#define CURL_RTSPREQ_GET_PARAMETER 8L +#define CURL_RTSPREQ_SET_PARAMETER 9L +#define CURL_RTSPREQ_RECORD 10L +#define CURL_RTSPREQ_RECEIVE 11L +#define CURL_RTSPREQ_LAST 12L /* not used */ /* These enums are for use with the CURLOPT_NETRC option. */ +#define CURL_NETRC_IGNORED 0L /* The .netrc will never be read. + This is the default. */ +#define CURL_NETRC_OPTIONAL 1L /* A user:password in the URL will be preferred + to one in the .netrc. */ +#define CURL_NETRC_REQUIRED 2L /* A user:password in the URL will be ignored. + Unless one is set programmatically, the + .netrc will be queried. */ enum CURL_NETRC_OPTION { - CURL_NETRC_IGNORED, /* The .netrc will never be read. - * This is the default. */ - CURL_NETRC_OPTIONAL, /* A user:password in the URL will be preferred - * to one in the .netrc. */ - CURL_NETRC_REQUIRED, /* A user:password in the URL will be ignored. - * Unless one is set programmatically, the .netrc - * will be queried. */ - CURL_NETRC_LAST + /* we set a single member here, just to make sure we still provide the enum, + but the values to use are defined above with L suffixes */ + CURL_NETRC_LAST = 3 }; #define CURL_SSLVERSION_DEFAULT 0 @@ -2354,10 +2387,13 @@ enum CURL_NETRC_OPTION { /* never use, keep last */ #define CURL_SSLVERSION_MAX_LAST (CURL_SSLVERSION_LAST << 16) +#define CURL_TLSAUTH_NONE 0L +#define CURL_TLSAUTH_SRP 1L + enum CURL_TLSAUTH { - CURL_TLSAUTH_NONE, - CURL_TLSAUTH_SRP, - CURL_TLSAUTH_LAST /* never use, keep last */ + /* we set a single member here, just to make sure we still provide the enum, + but the values to use are defined above with L suffixes */ + CURL_TLSAUTH_LAST = 2 }; /* symbols to use with CURLOPT_POSTREDIR. @@ -2372,14 +2408,16 @@ enum CURL_TLSAUTH { #define CURL_REDIR_POST_ALL \ (CURL_REDIR_POST_301|CURL_REDIR_POST_302|CURL_REDIR_POST_303) +#define CURL_TIMECOND_NONE 0L +#define CURL_TIMECOND_IFMODSINCE 1L +#define CURL_TIMECOND_IFUNMODSINCE 2L +#define CURL_TIMECOND_LASTMOD 3L + typedef enum { - CURL_TIMECOND_NONE, - - CURL_TIMECOND_IFMODSINCE, - CURL_TIMECOND_IFUNMODSINCE, - CURL_TIMECOND_LASTMOD, - - CURL_TIMECOND_LAST + /* we set a single member here, just to make sure we still provide + the enum typedef, but the values to use are defined above with L + suffixes */ + CURL_TIMECOND_LAST = 4 } curl_TimeCond; /* Special size_t value signaling a null-terminated string. */ @@ -2775,17 +2813,17 @@ struct curl_slist { * *before* curl_global_init(). * * The backend can be identified by the id (e.g. CURLSSLBACKEND_OPENSSL). The - * backend can also be specified via the name parameter (passing -1 as id). - * If both id and name are specified, the name will be ignored. If neither id - * nor name are specified, the function will fail with - * CURLSSLSET_UNKNOWN_BACKEND and set the "avail" pointer to the - * NULL-terminated list of available backends. + * backend can also be specified via the name parameter (passing -1 as id). If + * both id and name are specified, the name will be ignored. If neither id nor + * name are specified, the function will fail with CURLSSLSET_UNKNOWN_BACKEND + * and set the "avail" pointer to the NULL-terminated list of available + * backends. * * Upon success, the function returns CURLSSLSET_OK. * * If the specified SSL backend is not available, the function returns - * CURLSSLSET_UNKNOWN_BACKEND and sets the "avail" pointer to a NULL-terminated - * list of available SSL backends. + * CURLSSLSET_UNKNOWN_BACKEND and sets the "avail" pointer to a + * NULL-terminated list of available SSL backends. * * The SSL backend can be set only once. If it has already been set, a * subsequent attempt to change it will result in a CURLSSLSET_TOO_LATE. @@ -3288,9 +3326,7 @@ CURL_EXTERN CURLcode curl_easy_ssls_export(CURL *handle, #include "options.h" #include "header.h" #include "websockets.h" -#ifndef CURL_SKIP_INCLUDE_MPRINTF #include "mprintf.h" -#endif /* the typechecker does not work in C++ (yet) */ #if defined(__GNUC__) && defined(__GNUC_MINOR__) && \ diff --git a/Utilities/cmcurl/include/curl/curlver.h b/Utilities/cmcurl/include/curl/curlver.h index 1c0057209f..cce4b2027b 100644 --- a/Utilities/cmcurl/include/curl/curlver.h +++ b/Utilities/cmcurl/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.12.1" +#define LIBCURL_VERSION "8.14.1" /* The numeric version number is also available "in parts" by using these defines: */ #define LIBCURL_VERSION_MAJOR 8 -#define LIBCURL_VERSION_MINOR 12 +#define LIBCURL_VERSION_MINOR 14 #define LIBCURL_VERSION_PATCH 1 /* This is the numeric version of the libcurl version number, meant for easier @@ -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 0x080c01 +#define LIBCURL_VERSION_NUM 0x080e01 /* * This is the date and time when the full source package was created. The diff --git a/Utilities/cmcurl/include/curl/system.h b/Utilities/cmcurl/include/curl/system.h index 820fe96b39..f1c2719cfe 100644 --- a/Utilities/cmcurl/include/curl/system.h +++ b/Utilities/cmcurl/include/curl/system.h @@ -36,13 +36,10 @@ * curl_off_t * ---------- * - * For any given platform/compiler curl_off_t must be typedef'ed to a 64-bit + * For any given platform/compiler curl_off_t MUST be typedef'ed to a 64-bit * wide signed integral data type. The width of this data type must remain * constant and independent of any possible large file support settings. * - * As an exception to the above, curl_off_t shall be typedef'ed to a 32-bit - * wide signed integral data type if there is no 64-bit type. - * * 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 @@ -52,7 +49,7 @@ * */ -#if defined(__DJGPP__) +#ifdef __DJGPP__ # define CURL_TYPEOF_CURL_OFF_T long long # define CURL_FORMAT_CURL_OFF_T "lld" # define CURL_FORMAT_CURL_OFF_TU "llu" @@ -137,13 +134,22 @@ # define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int # endif -#elif defined(_WIN32_WCE) -# define CURL_TYPEOF_CURL_OFF_T __int64 -# define CURL_FORMAT_CURL_OFF_T "I64d" -# define CURL_FORMAT_CURL_OFF_TU "I64u" -# define CURL_SUFFIX_CURL_OFF_T i64 -# define CURL_SUFFIX_CURL_OFF_TU ui64 -# define CURL_TYPEOF_CURL_SOCKLEN_T int +#elif defined(UNDER_CE) +# if defined(__MINGW32CE__) +# 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 +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# else +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T "I64d" +# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# endif #elif defined(__MINGW32__) # include @@ -330,6 +336,8 @@ # define CURL_FORMAT_CURL_OFF_TU "llu" # define CURL_SUFFIX_CURL_OFF_T LL # define CURL_SUFFIX_CURL_OFF_TU ULL +# define CURL_POPCOUNT64(x) __builtin_popcountll(x) +# define CURL_CTZ64(x) __builtin_ctzll(x) # elif defined(__LP64__) || \ defined(__x86_64__) || defined(__ppc64__) || defined(__sparc64__) || \ defined(__e2k__) || \ @@ -340,6 +348,8 @@ # define CURL_FORMAT_CURL_OFF_TU "lu" # define CURL_SUFFIX_CURL_OFF_T L # define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_POPCOUNT64(x) __builtin_popcountl(x) +# define CURL_CTZ64(x) __builtin_ctzl(x) # endif # define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t # define CURL_PULL_SYS_TYPES_H 1 diff --git a/Utilities/cmcurl/include/curl/typecheck-gcc.h b/Utilities/cmcurl/include/curl/typecheck-gcc.h index e532e6997d..ca0c0ef9d6 100644 --- a/Utilities/cmcurl/include/curl/typecheck-gcc.h +++ b/Utilities/cmcurl/include/curl/typecheck-gcc.h @@ -40,115 +40,157 @@ * 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__({ \ - CURLoption _curl_opt = (option); \ - if(__builtin_constant_p(_curl_opt)) { \ + if(__builtin_constant_p(option)) { \ CURL_IGNORE_DEPRECATION( \ - if(curlcheck_long_option(_curl_opt)) \ + if(curlcheck_long_option(option)) \ if(!curlcheck_long(value)) \ _curl_easy_setopt_err_long(); \ - if(curlcheck_off_t_option(_curl_opt)) \ + if(curlcheck_off_t_option(option)) \ if(!curlcheck_off_t(value)) \ _curl_easy_setopt_err_curl_off_t(); \ - if(curlcheck_string_option(_curl_opt)) \ + if(curlcheck_string_option(option)) \ if(!curlcheck_string(value)) \ _curl_easy_setopt_err_string(); \ - if(curlcheck_write_cb_option(_curl_opt)) \ + if((option) == CURLOPT_PRIVATE) { } \ + if(curlcheck_write_cb_option(option)) \ if(!curlcheck_write_cb(value)) \ _curl_easy_setopt_err_write_callback(); \ - if((_curl_opt) == CURLOPT_RESOLVER_START_FUNCTION) \ + if(curlcheck_curl_option(option)) \ + if(!curlcheck_curl(value)) \ + _curl_easy_setopt_err_curl(); \ + if((option) == CURLOPT_RESOLVER_START_FUNCTION) \ if(!curlcheck_resolver_start_callback(value)) \ _curl_easy_setopt_err_resolver_start_callback(); \ - if((_curl_opt) == CURLOPT_READFUNCTION) \ + if((option) == CURLOPT_READFUNCTION) \ if(!curlcheck_read_cb(value)) \ _curl_easy_setopt_err_read_cb(); \ - if((_curl_opt) == CURLOPT_IOCTLFUNCTION) \ + if((option) == CURLOPT_IOCTLFUNCTION) \ if(!curlcheck_ioctl_cb(value)) \ _curl_easy_setopt_err_ioctl_cb(); \ - if((_curl_opt) == CURLOPT_SOCKOPTFUNCTION) \ + if((option) == CURLOPT_SOCKOPTFUNCTION) \ if(!curlcheck_sockopt_cb(value)) \ _curl_easy_setopt_err_sockopt_cb(); \ - if((_curl_opt) == CURLOPT_OPENSOCKETFUNCTION) \ + if((option) == CURLOPT_OPENSOCKETFUNCTION) \ if(!curlcheck_opensocket_cb(value)) \ _curl_easy_setopt_err_opensocket_cb(); \ - if((_curl_opt) == CURLOPT_PROGRESSFUNCTION) \ + if((option) == CURLOPT_PROGRESSFUNCTION) \ if(!curlcheck_progress_cb(value)) \ _curl_easy_setopt_err_progress_cb(); \ - if((_curl_opt) == CURLOPT_DEBUGFUNCTION) \ + if((option) == CURLOPT_XFERINFOFUNCTION) \ + if(!curlcheck_xferinfo_cb(value)) \ + _curl_easy_setopt_err_xferinfo_cb(); \ + if((option) == CURLOPT_DEBUGFUNCTION) \ if(!curlcheck_debug_cb(value)) \ _curl_easy_setopt_err_debug_cb(); \ - if((_curl_opt) == CURLOPT_SSL_CTX_FUNCTION) \ + if((option) == CURLOPT_SSL_CTX_FUNCTION) \ if(!curlcheck_ssl_ctx_cb(value)) \ _curl_easy_setopt_err_ssl_ctx_cb(); \ - if(curlcheck_conv_cb_option(_curl_opt)) \ + if(curlcheck_conv_cb_option(option)) \ if(!curlcheck_conv_cb(value)) \ _curl_easy_setopt_err_conv_cb(); \ - if((_curl_opt) == CURLOPT_SEEKFUNCTION) \ + if((option) == CURLOPT_SEEKFUNCTION) \ if(!curlcheck_seek_cb(value)) \ _curl_easy_setopt_err_seek_cb(); \ - if(curlcheck_cb_data_option(_curl_opt)) \ + if((option) == CURLOPT_CHUNK_BGN_FUNCTION) \ + if(!curlcheck_chunk_bgn_cb(value)) \ + _curl_easy_setopt_err_chunk_bgn_cb(); \ + if((option) == CURLOPT_CHUNK_END_FUNCTION) \ + if(!curlcheck_chunk_end_cb(value)) \ + _curl_easy_setopt_err_chunk_end_cb(); \ + if((option) == CURLOPT_CLOSESOCKETFUNCTION) \ + if(!curlcheck_close_socket_cb(value)) \ + _curl_easy_setopt_err_close_socket_cb(); \ + if((option) == CURLOPT_FNMATCH_FUNCTION) \ + if(!curlcheck_fnmatch_cb(value)) \ + _curl_easy_setopt_err_fnmatch_cb(); \ + if((option) == CURLOPT_HSTSREADFUNCTION) \ + if(!curlcheck_hstsread_cb(value)) \ + _curl_easy_setopt_err_hstsread_cb(); \ + if((option) == CURLOPT_HSTSWRITEFUNCTION) \ + if(!curlcheck_hstswrite_cb(value)) \ + _curl_easy_setopt_err_hstswrite_cb(); \ + if((option) == CURLOPT_SSH_HOSTKEYFUNCTION) \ + if(!curlcheck_ssh_hostkey_cb(value)) \ + _curl_easy_setopt_err_ssh_hostkey_cb(); \ + if((option) == CURLOPT_SSH_KEYFUNCTION) \ + if(!curlcheck_ssh_key_cb(value)) \ + _curl_easy_setopt_err_ssh_key_cb(); \ + if((option) == CURLOPT_INTERLEAVEFUNCTION) \ + if(!curlcheck_interleave_cb(value)) \ + _curl_easy_setopt_err_interleave_cb(); \ + if((option) == CURLOPT_PREREQFUNCTION) \ + if(!curlcheck_prereq_cb(value)) \ + _curl_easy_setopt_err_prereq_cb(); \ + if((option) == CURLOPT_TRAILERFUNCTION) \ + if(!curlcheck_trailer_cb(value)) \ + _curl_easy_setopt_err_trailer_cb(); \ + if(curlcheck_cb_data_option(option)) \ if(!curlcheck_cb_data(value)) \ _curl_easy_setopt_err_cb_data(); \ - if((_curl_opt) == CURLOPT_ERRORBUFFER) \ + if((option) == CURLOPT_ERRORBUFFER) \ if(!curlcheck_error_buffer(value)) \ _curl_easy_setopt_err_error_buffer(); \ - if((_curl_opt) == CURLOPT_STDERR) \ + if((option) == CURLOPT_CURLU) \ + if(!curlcheck_ptr((value), CURLU)) \ + _curl_easy_setopt_err_curlu(); \ + if((option) == CURLOPT_STDERR) \ if(!curlcheck_FILE(value)) \ _curl_easy_setopt_err_FILE(); \ - if(curlcheck_postfields_option(_curl_opt)) \ + if(curlcheck_postfields_option(option)) \ if(!curlcheck_postfields(value)) \ _curl_easy_setopt_err_postfields(); \ - if((_curl_opt) == CURLOPT_HTTPPOST) \ + if((option) == CURLOPT_HTTPPOST) \ if(!curlcheck_arr((value), struct curl_httppost)) \ _curl_easy_setopt_err_curl_httpost(); \ - if((_curl_opt) == CURLOPT_MIMEPOST) \ + if((option) == CURLOPT_MIMEPOST) \ if(!curlcheck_ptr((value), curl_mime)) \ _curl_easy_setopt_err_curl_mimepost(); \ - if(curlcheck_slist_option(_curl_opt)) \ + if(curlcheck_slist_option(option)) \ if(!curlcheck_arr((value), struct curl_slist)) \ _curl_easy_setopt_err_curl_slist(); \ - if((_curl_opt) == CURLOPT_SHARE) \ + if((option) == CURLOPT_SHARE) \ if(!curlcheck_ptr((value), CURLSH)) \ _curl_easy_setopt_err_CURLSH(); \ - ) \ - } \ - curl_easy_setopt(handle, _curl_opt, value); \ + ) \ + } \ + curl_easy_setopt(handle, option, value); \ }) /* wraps curl_easy_getinfo() with typechecking */ #define curl_easy_getinfo(handle, info, arg) \ __extension__({ \ - CURLINFO _curl_info = (info); \ - if(__builtin_constant_p(_curl_info)) { \ + if(__builtin_constant_p(info)) { \ CURL_IGNORE_DEPRECATION( \ - if(curlcheck_string_info(_curl_info)) \ + if(curlcheck_string_info(info)) \ if(!curlcheck_arr((arg), char *)) \ _curl_easy_getinfo_err_string(); \ - if(curlcheck_long_info(_curl_info)) \ + if(curlcheck_long_info(info)) \ if(!curlcheck_arr((arg), long)) \ _curl_easy_getinfo_err_long(); \ - if(curlcheck_double_info(_curl_info)) \ + if(curlcheck_double_info(info)) \ if(!curlcheck_arr((arg), double)) \ _curl_easy_getinfo_err_double(); \ - if(curlcheck_slist_info(_curl_info)) \ + if(curlcheck_slist_info(info)) \ if(!curlcheck_arr((arg), struct curl_slist *)) \ _curl_easy_getinfo_err_curl_slist(); \ - if(curlcheck_tlssessioninfo_info(_curl_info)) \ + if(curlcheck_tlssessioninfo_info(info)) \ if(!curlcheck_arr((arg), struct curl_tlssessioninfo *)) \ - _curl_easy_getinfo_err_curl_tlssesssioninfo(); \ - if(curlcheck_certinfo_info(_curl_info)) \ + _curl_easy_getinfo_err_curl_tlssessioninfo(); \ + if(curlcheck_certinfo_info(info)) \ if(!curlcheck_arr((arg), struct curl_certinfo *)) \ _curl_easy_getinfo_err_curl_certinfo(); \ - if(curlcheck_socket_info(_curl_info)) \ + if(curlcheck_socket_info(info)) \ if(!curlcheck_arr((arg), curl_socket_t)) \ _curl_easy_getinfo_err_curl_socket(); \ - if(curlcheck_off_t_info(_curl_info)) \ + if(curlcheck_off_t_info(info)) \ if(!curlcheck_arr((arg), curl_off_t)) \ _curl_easy_getinfo_err_curl_off_t(); \ - ) \ - } \ - curl_easy_getinfo(handle, _curl_info, arg); \ + ) \ + } \ + curl_easy_getinfo(handle, info, arg); \ }) /* @@ -157,7 +199,6 @@ #define curl_share_setopt(share,opt,param) curl_share_setopt(share,opt,param) #define curl_multi_setopt(handle,opt,param) curl_multi_setopt(handle,opt,param) - /* the actual warnings, triggered by calling the _curl_easy_setopt_err* * functions */ @@ -168,187 +209,212 @@ id(void) { __asm__(""); } CURLWARNING(_curl_easy_setopt_err_long, - "curl_easy_setopt expects a long argument for this option") + "curl_easy_setopt expects a long argument") CURLWARNING(_curl_easy_setopt_err_curl_off_t, - "curl_easy_setopt expects a curl_off_t argument for this option") + "curl_easy_setopt expects a curl_off_t argument") CURLWARNING(_curl_easy_setopt_err_string, - "curl_easy_setopt expects a " - "string ('char *' or char[]) argument for this option" - ) + "curl_easy_setopt expects a " + "string ('char *' or char[]) argument") CURLWARNING(_curl_easy_setopt_err_write_callback, - "curl_easy_setopt expects a curl_write_callback argument for this option") + "curl_easy_setopt expects a curl_write_callback argument") CURLWARNING(_curl_easy_setopt_err_resolver_start_callback, - "curl_easy_setopt expects a " - "curl_resolver_start_callback argument for this option" - ) + "curl_easy_setopt expects a " + "curl_resolver_start_callback argument") CURLWARNING(_curl_easy_setopt_err_read_cb, - "curl_easy_setopt expects a curl_read_callback argument for this option") + "curl_easy_setopt expects a curl_read_callback argument") CURLWARNING(_curl_easy_setopt_err_ioctl_cb, - "curl_easy_setopt expects a curl_ioctl_callback argument for this option") + "curl_easy_setopt expects a curl_ioctl_callback argument") CURLWARNING(_curl_easy_setopt_err_sockopt_cb, - "curl_easy_setopt expects a curl_sockopt_callback argument for this option") + "curl_easy_setopt expects a curl_sockopt_callback argument") CURLWARNING(_curl_easy_setopt_err_opensocket_cb, - "curl_easy_setopt expects a " - "curl_opensocket_callback argument for this option" - ) + "curl_easy_setopt expects a " + "curl_opensocket_callback argument") CURLWARNING(_curl_easy_setopt_err_progress_cb, - "curl_easy_setopt expects a curl_progress_callback argument for this option") + "curl_easy_setopt expects a curl_progress_callback argument") +CURLWARNING(_curl_easy_setopt_err_xferinfo_cb, + "curl_easy_setopt expects a curl_xferinfo_callback argument") CURLWARNING(_curl_easy_setopt_err_debug_cb, - "curl_easy_setopt expects a curl_debug_callback argument for this option") + "curl_easy_setopt expects a curl_debug_callback argument") CURLWARNING(_curl_easy_setopt_err_ssl_ctx_cb, - "curl_easy_setopt expects a curl_ssl_ctx_callback argument for this option") + "curl_easy_setopt expects a curl_ssl_ctx_callback argument") CURLWARNING(_curl_easy_setopt_err_conv_cb, - "curl_easy_setopt expects a curl_conv_callback argument for this option") + "curl_easy_setopt expects a curl_conv_callback argument") CURLWARNING(_curl_easy_setopt_err_seek_cb, - "curl_easy_setopt expects a curl_seek_callback argument for this option") + "curl_easy_setopt expects a curl_seek_callback argument") CURLWARNING(_curl_easy_setopt_err_cb_data, - "curl_easy_setopt expects a " - "private data pointer as argument for this option") + "curl_easy_setopt expects a " + "private data pointer as argument") +CURLWARNING(_curl_easy_setopt_err_chunk_bgn_cb, + "curl_easy_setopt expects a curl_chunk_bgn_callback argument") +CURLWARNING(_curl_easy_setopt_err_chunk_end_cb, + "curl_easy_setopt expects a curl_chunk_end_callback argument") +CURLWARNING(_curl_easy_setopt_err_close_socket_cb, + "curl_easy_setopt expects a curl_closesocket_callback argument") +CURLWARNING(_curl_easy_setopt_err_fnmatch_cb, + "curl_easy_setopt expects a curl_fnmatch_callback argument") +CURLWARNING(_curl_easy_setopt_err_hstsread_cb, + "curl_easy_setopt expects a curl_hstsread_callback argument") +CURLWARNING(_curl_easy_setopt_err_hstswrite_cb, + "curl_easy_setopt expects a curl_hstswrite_callback argument") +CURLWARNING(_curl_easy_setopt_err_ssh_key_cb, + "curl_easy_setopt expects a curl_sshkeycallback argument") +CURLWARNING(_curl_easy_setopt_err_ssh_hostkey_cb, + "curl_easy_setopt expects a curl_sshhostkeycallback argument") +CURLWARNING(_curl_easy_setopt_err_interleave_cb, + "curl_easy_setopt expects a curl_interleave_callback argument") +CURLWARNING(_curl_easy_setopt_err_prereq_cb, + "curl_easy_setopt expects a curl_prereq_callback argument") +CURLWARNING(_curl_easy_setopt_err_trailer_cb, + "curl_easy_setopt expects a curl_trailerfunc_ok argument") CURLWARNING(_curl_easy_setopt_err_error_buffer, - "curl_easy_setopt expects a " - "char buffer of CURL_ERROR_SIZE as argument for this option") + "curl_easy_setopt expects a " + "char buffer of CURL_ERROR_SIZE as argument") +CURLWARNING(_curl_easy_setopt_err_curlu, + "curl_easy_setopt expects a 'CURLU *' argument") +CURLWARNING(_curl_easy_setopt_err_curl, + "curl_easy_setopt expects a 'CURL *' argument") CURLWARNING(_curl_easy_setopt_err_FILE, - "curl_easy_setopt expects a 'FILE *' argument for this option") + "curl_easy_setopt expects a 'FILE *' argument") CURLWARNING(_curl_easy_setopt_err_postfields, - "curl_easy_setopt expects a 'void *' or 'char *' argument for this option") + "curl_easy_setopt expects a 'void *' or 'char *' argument") CURLWARNING(_curl_easy_setopt_err_curl_httpost, - "curl_easy_setopt expects a 'struct curl_httppost *' " - "argument for this option") + "curl_easy_setopt expects a 'struct curl_httppost *' " + "argument") CURLWARNING(_curl_easy_setopt_err_curl_mimepost, - "curl_easy_setopt expects a 'curl_mime *' " - "argument for this option") + "curl_easy_setopt expects a 'curl_mime *' " + "argument") CURLWARNING(_curl_easy_setopt_err_curl_slist, - "curl_easy_setopt expects a 'struct curl_slist *' argument for this option") + "curl_easy_setopt expects a 'struct curl_slist *' argument") CURLWARNING(_curl_easy_setopt_err_CURLSH, - "curl_easy_setopt expects a CURLSH* argument for this option") - + "curl_easy_setopt expects a CURLSH* argument") CURLWARNING(_curl_easy_getinfo_err_string, - "curl_easy_getinfo expects a pointer to 'char *' for this info") + "curl_easy_getinfo expects a pointer to 'char *'") CURLWARNING(_curl_easy_getinfo_err_long, - "curl_easy_getinfo expects a pointer to long for this info") + "curl_easy_getinfo expects a pointer to long") CURLWARNING(_curl_easy_getinfo_err_double, - "curl_easy_getinfo expects a pointer to double for this info") + "curl_easy_getinfo expects a pointer to double") CURLWARNING(_curl_easy_getinfo_err_curl_slist, - "curl_easy_getinfo expects a pointer to 'struct curl_slist *' for this info") -CURLWARNING(_curl_easy_getinfo_err_curl_tlssesssioninfo, - "curl_easy_getinfo expects a pointer to " - "'struct curl_tlssessioninfo *' for this info") + "curl_easy_getinfo expects a pointer to 'struct curl_slist *'") +CURLWARNING(_curl_easy_getinfo_err_curl_tlssessioninfo, + "curl_easy_getinfo expects a pointer to " + "'struct curl_tlssessioninfo *'") CURLWARNING(_curl_easy_getinfo_err_curl_certinfo, - "curl_easy_getinfo expects a pointer to " - "'struct curl_certinfo *' for this info") + "curl_easy_getinfo expects a pointer to " + "'struct curl_certinfo *'") CURLWARNING(_curl_easy_getinfo_err_curl_socket, - "curl_easy_getinfo expects a pointer to curl_socket_t for this info") + "curl_easy_getinfo expects a pointer to curl_socket_t") CURLWARNING(_curl_easy_getinfo_err_curl_off_t, - "curl_easy_getinfo expects a pointer to curl_off_t for this info") + "curl_easy_getinfo expects a pointer to curl_off_t") /* groups of curl_easy_setops options that take the same type of argument */ -/* 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 do not - * have to do anything - */ - /* evaluates to true if option takes a long argument */ #define curlcheck_long_option(option) \ (0 < (option) && (option) < CURLOPTTYPE_OBJECTPOINT) -#define curlcheck_off_t_option(option) \ +#define curlcheck_off_t_option(option) \ (((option) > CURLOPTTYPE_OFF_T) && ((option) < CURLOPTTYPE_BLOB)) +/* option takes a CURL * argument */ +#define curlcheck_curl_option(option) \ + ((option) == CURLOPT_STREAM_DEPENDS || \ + (option) == CURLOPT_STREAM_DEPENDS_E || \ + 0) + /* evaluates to true if option takes a char* argument */ -#define curlcheck_string_option(option) \ - ((option) == CURLOPT_ABSTRACT_UNIX_SOCKET || \ - (option) == CURLOPT_ACCEPT_ENCODING || \ - (option) == CURLOPT_ALTSVC || \ - (option) == CURLOPT_CAINFO || \ - (option) == CURLOPT_CAPATH || \ - (option) == CURLOPT_COOKIE || \ - (option) == CURLOPT_COOKIEFILE || \ - (option) == CURLOPT_COOKIEJAR || \ - (option) == CURLOPT_COOKIELIST || \ - (option) == CURLOPT_CRLFILE || \ - (option) == CURLOPT_CUSTOMREQUEST || \ - (option) == CURLOPT_DEFAULT_PROTOCOL || \ - (option) == CURLOPT_DNS_INTERFACE || \ - (option) == CURLOPT_DNS_LOCAL_IP4 || \ - (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 || \ - (option) == CURLOPT_FTPPORT || \ - (option) == CURLOPT_HSTS || \ - (option) == CURLOPT_HAPROXY_CLIENT_IP || \ - (option) == CURLOPT_INTERFACE || \ - (option) == CURLOPT_ISSUERCERT || \ - (option) == CURLOPT_KEYPASSWD || \ - (option) == CURLOPT_KRBLEVEL || \ - (option) == CURLOPT_LOGIN_OPTIONS || \ - (option) == CURLOPT_MAIL_AUTH || \ - (option) == CURLOPT_MAIL_FROM || \ - (option) == CURLOPT_NETRC_FILE || \ - (option) == CURLOPT_NOPROXY || \ - (option) == CURLOPT_PASSWORD || \ - (option) == CURLOPT_PINNEDPUBLICKEY || \ - (option) == CURLOPT_PRE_PROXY || \ - (option) == CURLOPT_PROTOCOLS_STR || \ - (option) == CURLOPT_PROXY || \ - (option) == CURLOPT_PROXY_CAINFO || \ - (option) == CURLOPT_PROXY_CAPATH || \ - (option) == CURLOPT_PROXY_CRLFILE || \ - (option) == CURLOPT_PROXY_ISSUERCERT || \ - (option) == CURLOPT_PROXY_KEYPASSWD || \ - (option) == CURLOPT_PROXY_PINNEDPUBLICKEY || \ - (option) == CURLOPT_PROXY_SERVICE_NAME || \ - (option) == CURLOPT_PROXY_SSL_CIPHER_LIST || \ - (option) == CURLOPT_PROXY_SSLCERT || \ - (option) == CURLOPT_PROXY_SSLCERTTYPE || \ - (option) == CURLOPT_PROXY_SSLKEY || \ - (option) == CURLOPT_PROXY_SSLKEYTYPE || \ - (option) == CURLOPT_PROXY_TLS13_CIPHERS || \ - (option) == CURLOPT_PROXY_TLSAUTH_PASSWORD || \ - (option) == CURLOPT_PROXY_TLSAUTH_TYPE || \ - (option) == CURLOPT_PROXY_TLSAUTH_USERNAME || \ - (option) == CURLOPT_PROXYPASSWORD || \ - (option) == CURLOPT_PROXYUSERNAME || \ - (option) == CURLOPT_PROXYUSERPWD || \ - (option) == CURLOPT_RANDOM_FILE || \ - (option) == CURLOPT_RANGE || \ - (option) == CURLOPT_REDIR_PROTOCOLS_STR || \ - (option) == CURLOPT_REFERER || \ - (option) == CURLOPT_REQUEST_TARGET || \ - (option) == CURLOPT_RTSP_SESSION_ID || \ - (option) == CURLOPT_RTSP_STREAM_URI || \ - (option) == CURLOPT_RTSP_TRANSPORT || \ - (option) == CURLOPT_SASL_AUTHZID || \ - (option) == CURLOPT_SERVICE_NAME || \ - (option) == CURLOPT_SOCKS5_GSSAPI_SERVICE || \ - (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 || \ - (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 || \ - (option) == CURLOPT_SSH_KNOWNHOSTS || \ - (option) == CURLOPT_SSH_PRIVATE_KEYFILE || \ - (option) == CURLOPT_SSH_PUBLIC_KEYFILE || \ - (option) == CURLOPT_SSLCERT || \ - (option) == CURLOPT_SSLCERTTYPE || \ - (option) == CURLOPT_SSLENGINE || \ - (option) == CURLOPT_SSLKEY || \ - (option) == CURLOPT_SSLKEYTYPE || \ - (option) == CURLOPT_SSL_CIPHER_LIST || \ - (option) == CURLOPT_TLS13_CIPHERS || \ - (option) == CURLOPT_TLSAUTH_PASSWORD || \ - (option) == CURLOPT_TLSAUTH_TYPE || \ - (option) == CURLOPT_TLSAUTH_USERNAME || \ - (option) == CURLOPT_UNIX_SOCKET_PATH || \ - (option) == CURLOPT_URL || \ - (option) == CURLOPT_USERAGENT || \ - (option) == CURLOPT_USERNAME || \ - (option) == CURLOPT_AWS_SIGV4 || \ - (option) == CURLOPT_USERPWD || \ - (option) == CURLOPT_XOAUTH2_BEARER || \ - (option) == CURLOPT_SSL_EC_CURVES || \ +#define curlcheck_string_option(option) \ + ((option) == CURLOPT_ABSTRACT_UNIX_SOCKET || \ + (option) == CURLOPT_ACCEPT_ENCODING || \ + (option) == CURLOPT_ALTSVC || \ + (option) == CURLOPT_CAINFO || \ + (option) == CURLOPT_CAPATH || \ + (option) == CURLOPT_COOKIE || \ + (option) == CURLOPT_COOKIEFILE || \ + (option) == CURLOPT_COOKIEJAR || \ + (option) == CURLOPT_COOKIELIST || \ + (option) == CURLOPT_CRLFILE || \ + (option) == CURLOPT_CUSTOMREQUEST || \ + (option) == CURLOPT_DEFAULT_PROTOCOL || \ + (option) == CURLOPT_DNS_INTERFACE || \ + (option) == CURLOPT_DNS_LOCAL_IP4 || \ + (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 || \ + (option) == CURLOPT_FTPPORT || \ + (option) == CURLOPT_HAPROXY_CLIENT_IP || \ + (option) == CURLOPT_HSTS || \ + (option) == CURLOPT_INTERFACE || \ + (option) == CURLOPT_ISSUERCERT || \ + (option) == CURLOPT_KEYPASSWD || \ + (option) == CURLOPT_KRBLEVEL || \ + (option) == CURLOPT_LOGIN_OPTIONS || \ + (option) == CURLOPT_MAIL_AUTH || \ + (option) == CURLOPT_MAIL_FROM || \ + (option) == CURLOPT_NETRC_FILE || \ + (option) == CURLOPT_NOPROXY || \ + (option) == CURLOPT_PASSWORD || \ + (option) == CURLOPT_PINNEDPUBLICKEY || \ + (option) == CURLOPT_PRE_PROXY || \ + (option) == CURLOPT_PROTOCOLS_STR || \ + (option) == CURLOPT_PROXY || \ + (option) == CURLOPT_PROXY_CAINFO || \ + (option) == CURLOPT_PROXY_CAPATH || \ + (option) == CURLOPT_PROXY_CRLFILE || \ + (option) == CURLOPT_PROXY_ISSUERCERT || \ + (option) == CURLOPT_PROXY_KEYPASSWD || \ + (option) == CURLOPT_PROXY_PINNEDPUBLICKEY || \ + (option) == CURLOPT_PROXY_SERVICE_NAME || \ + (option) == CURLOPT_PROXY_SSL_CIPHER_LIST || \ + (option) == CURLOPT_PROXY_SSLCERT || \ + (option) == CURLOPT_PROXY_SSLCERTTYPE || \ + (option) == CURLOPT_PROXY_SSLKEY || \ + (option) == CURLOPT_PROXY_SSLKEYTYPE || \ + (option) == CURLOPT_PROXY_TLS13_CIPHERS || \ + (option) == CURLOPT_PROXY_TLSAUTH_PASSWORD || \ + (option) == CURLOPT_PROXY_TLSAUTH_TYPE || \ + (option) == CURLOPT_PROXY_TLSAUTH_USERNAME || \ + (option) == CURLOPT_PROXYPASSWORD || \ + (option) == CURLOPT_PROXYUSERNAME || \ + (option) == CURLOPT_PROXYUSERPWD || \ + (option) == CURLOPT_RANDOM_FILE || \ + (option) == CURLOPT_RANGE || \ + (option) == CURLOPT_REDIR_PROTOCOLS_STR || \ + (option) == CURLOPT_REFERER || \ + (option) == CURLOPT_REQUEST_TARGET || \ + (option) == CURLOPT_RTSP_SESSION_ID || \ + (option) == CURLOPT_RTSP_STREAM_URI || \ + (option) == CURLOPT_RTSP_TRANSPORT || \ + (option) == CURLOPT_SASL_AUTHZID || \ + (option) == CURLOPT_SERVICE_NAME || \ + (option) == CURLOPT_SOCKS5_GSSAPI_SERVICE || \ + (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 || \ + (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 || \ + (option) == CURLOPT_SSH_KNOWNHOSTS || \ + (option) == CURLOPT_SSH_PRIVATE_KEYFILE || \ + (option) == CURLOPT_SSH_PUBLIC_KEYFILE || \ + (option) == CURLOPT_SSLCERT || \ + (option) == CURLOPT_SSLCERTTYPE || \ + (option) == CURLOPT_SSLENGINE || \ + (option) == CURLOPT_SSLKEY || \ + (option) == CURLOPT_SSLKEYTYPE || \ + (option) == CURLOPT_SSL_CIPHER_LIST || \ + (option) == CURLOPT_SSL_EC_CURVES || \ + (option) == CURLOPT_SSL_SIGNATURE_ALGORITHMS || \ + (option) == CURLOPT_TLS13_CIPHERS || \ + (option) == CURLOPT_TLSAUTH_PASSWORD || \ + (option) == CURLOPT_TLSAUTH_TYPE || \ + (option) == CURLOPT_TLSAUTH_USERNAME || \ + (option) == CURLOPT_UNIX_SOCKET_PATH || \ + (option) == CURLOPT_URL || \ + (option) == CURLOPT_USERAGENT || \ + (option) == CURLOPT_USERNAME || \ + (option) == CURLOPT_AWS_SIGV4 || \ + (option) == CURLOPT_USERPWD || \ + (option) == CURLOPT_XOAUTH2_BEARER || \ 0) /* evaluates to true if option takes a curl_write_callback argument */ @@ -375,7 +441,7 @@ CURLWARNING(_curl_easy_getinfo_err_curl_off_t, (option) == CURLOPT_IOCTLDATA || \ (option) == CURLOPT_OPENSOCKETDATA || \ (option) == CURLOPT_PREREQDATA || \ - (option) == CURLOPT_PROGRESSDATA || \ + (option) == CURLOPT_XFERINFODATA || \ (option) == CURLOPT_READDATA || \ (option) == CURLOPT_SEEKDATA || \ (option) == CURLOPT_SOCKOPTDATA || \ @@ -479,22 +545,36 @@ CURLWARNING(_curl_easy_getinfo_err_curl_off_t, curlcheck_arr((expr), signed char) || \ curlcheck_arr((expr), unsigned char)) +/* evaluates to true if expr is a CURL * */ +#define curlcheck_curl(expr) \ + (curlcheck_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), CURL *)) + + /* evaluates to true if expr is a long (no matter the signedness) * XXX: for now, int is also accepted (and therefore short and char, which * are promoted to int when passed to a variadic function) */ -#define curlcheck_long(expr) \ - (__builtin_types_compatible_p(__typeof__(expr), long) || \ - __builtin_types_compatible_p(__typeof__(expr), signed long) || \ - __builtin_types_compatible_p(__typeof__(expr), unsigned long) || \ - __builtin_types_compatible_p(__typeof__(expr), int) || \ - __builtin_types_compatible_p(__typeof__(expr), signed int) || \ - __builtin_types_compatible_p(__typeof__(expr), unsigned int) || \ - __builtin_types_compatible_p(__typeof__(expr), short) || \ - __builtin_types_compatible_p(__typeof__(expr), signed short) || \ - __builtin_types_compatible_p(__typeof__(expr), unsigned short) || \ - __builtin_types_compatible_p(__typeof__(expr), char) || \ - __builtin_types_compatible_p(__typeof__(expr), signed char) || \ - __builtin_types_compatible_p(__typeof__(expr), unsigned char)) +#define curlcheck_long(expr) \ + ( \ + ((sizeof(long) != sizeof(int)) && \ + (__builtin_types_compatible_p(__typeof__(expr), long) || \ + __builtin_types_compatible_p(__typeof__(expr), signed long) || \ + __builtin_types_compatible_p(__typeof__(expr), unsigned long))) \ + || \ + ((sizeof(long) == sizeof(int)) && \ + (__builtin_types_compatible_p(__typeof__(expr), long) || \ + __builtin_types_compatible_p(__typeof__(expr), signed long) || \ + __builtin_types_compatible_p(__typeof__(expr), unsigned long) || \ + __builtin_types_compatible_p(__typeof__(expr), int) || \ + __builtin_types_compatible_p(__typeof__(expr), signed int) || \ + __builtin_types_compatible_p(__typeof__(expr), unsigned int) || \ + __builtin_types_compatible_p(__typeof__(expr), short) || \ + __builtin_types_compatible_p(__typeof__(expr), signed short) || \ + __builtin_types_compatible_p(__typeof__(expr), unsigned short) || \ + __builtin_types_compatible_p(__typeof__(expr), char) || \ + __builtin_types_compatible_p(__typeof__(expr), signed char) || \ + __builtin_types_compatible_p(__typeof__(expr), unsigned char))) \ + ) /* evaluates to true if expr is of type curl_off_t */ #define curlcheck_off_t(expr) \ @@ -629,6 +709,11 @@ typedef int (*_curl_progress_callback1)(void *, typedef int (*_curl_progress_callback2)(const void *, double, double, double, double); +/* evaluates to true if expr is of type curl_xferinfo_callback */ +#define curlcheck_xferinfo_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_xferinfo_callback)) + /* evaluates to true if expr is of type curl_debug_callback or "similar" */ #define curlcheck_debug_cb(expr) \ (curlcheck_NULL(expr) || \ @@ -714,5 +799,69 @@ typedef CURLcode (*_curl_conv_callback4)(const void *, size_t length); typedef CURLcode (*_curl_seek_callback1)(void *, curl_off_t, int); typedef CURLcode (*_curl_seek_callback2)(const void *, curl_off_t, int); +/* evaluates to true if expr is of type curl_chunk_bgn_callback */ +#define curlcheck_chunk_bgn_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_chunk_bgn_callback) || \ + curlcheck_cb_compatible((expr), _curl_chunk_bgn_callback1) || \ + curlcheck_cb_compatible((expr), _curl_chunk_bgn_callback2)) +typedef long (*_curl_chunk_bgn_callback1)(struct curl_fileinfo *, + void *, int); +typedef long (*_curl_chunk_bgn_callback2)(void *, void *, int); + +/* evaluates to true if expr is of type curl_chunk_end_callback */ +#define curlcheck_chunk_end_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_chunk_end_callback)) + +/* evaluates to true if expr is of type curl_closesocket_callback */ +#define curlcheck_close_socket_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_closesocket_callback)) + +/* evaluates to true if expr is of type curl_fnmatch_callback */ +#define curlcheck_fnmatch_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_fnmatch_callback)) + +/* evaluates to true if expr is of type curl_hstsread_callback */ +#define curlcheck_hstsread_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_hstsread_callback)) + +/* evaluates to true if expr is of type curl_hstswrite_callback */ +#define curlcheck_hstswrite_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_hstswrite_callback)) + +/* evaluates to true if expr is of type curl_sshhostkeycallback */ +#define curlcheck_ssh_hostkey_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_sshhostkeycallback)) + +/* evaluates to true if expr is of type curl_sshkeycallback */ +#define curlcheck_ssh_key_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_sshkeycallback)) + +/* evaluates to true if expr is of type curl_interleave_callback */ +#define curlcheck_interleave_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), _curl_interleave_callback1) || \ + curlcheck_cb_compatible((expr), _curl_interleave_callback2)) +typedef size_t (*_curl_interleave_callback1)(void *p, size_t s, + size_t n, void *u); +typedef size_t (*_curl_interleave_callback2)(char *p, size_t s, + size_t n, void *u); + +/* evaluates to true if expr is of type curl_prereq_callback */ +#define curlcheck_prereq_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_prereq_callback)) + +/* evaluates to true if expr is of type curl_trailer_callback */ +#define curlcheck_trailer_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_trailer_callback)) #endif /* CURLINC_TYPECHECK_GCC_H */ diff --git a/Utilities/cmcurl/include/curl/websockets.h b/Utilities/cmcurl/include/curl/websockets.h index 6ef6a2bc92..afb86b4ebc 100644 --- a/Utilities/cmcurl/include/curl/websockets.h +++ b/Utilities/cmcurl/include/curl/websockets.h @@ -73,7 +73,8 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer, unsigned int flags); /* bits for the CURLOPT_WS_OPTIONS bitmask: */ -#define CURLWS_RAW_MODE (1<<0) +#define CURLWS_RAW_MODE (1<<0) +#define CURLWS_NOAUTOPONG (1<<1) CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *curl); diff --git a/Utilities/cmcurl/lib/CMakeLists.txt b/Utilities/cmcurl/lib/CMakeLists.txt index d61d398ed5..6a0798de70 100644 --- a/Utilities/cmcurl/lib/CMakeLists.txt +++ b/Utilities/cmcurl/lib/CMakeLists.txt @@ -21,9 +21,11 @@ # 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(LIBCURL_OUTPUT_NAME "${LIB_NAME}" CACHE STRING "Basename of the curl library") + +set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "BUILDING_LIBCURL") +set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "${CURL_DEBUG_MACROS}") configure_file("curl_config.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/curl_config.h") @@ -31,17 +33,14 @@ configure_file("curl_config.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/curl_config.h" curl_transform_makefile_inc("Makefile.inc" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake") include("${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake") -# DllMain is added later for DLL builds only. -list(REMOVE_ITEM CSOURCES "dllmain.c") - list(APPEND HHEADERS "${CMAKE_CURRENT_BINARY_DIR}/curl_config.h") # The rest of the build -include_directories( - "${PROJECT_BINARY_DIR}/lib" # for "curl_config.h" - "${PROJECT_SOURCE_DIR}/lib" # for "curl_setup.h" +set_property(DIRECTORY APPEND PROPERTY INCLUDE_DIRECTORIES + "${PROJECT_BINARY_DIR}/lib" # for "curl_config.h" ) + if(USE_ARES) include_directories(SYSTEM ${CARES_INCLUDE_DIRS}) endif() @@ -92,7 +91,7 @@ if(CURL_BUILD_TESTING) EXCLUDE_FROM_ALL ${HHEADERS} ${CSOURCES} ) - target_compile_definitions(curlu PUBLIC "UNITTESTS" "CURL_STATICLIB") + target_compile_definitions(curlu PUBLIC "CURL_STATICLIB" "UNITTESTS") target_link_libraries(curlu PRIVATE ${CURL_LIBS}) # There is plenty of parallelism when building the testdeps target. # Override the curlu batch size with the maximum to optimize performance. @@ -100,26 +99,35 @@ if(CURL_BUILD_TESTING) endif() 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) + # We must compile this source separately to avoid memdebug.h redefinitions + # applying to it. + set_source_files_properties("memdebug.c" PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) endif() ## Library definition if(NOT DEFINED IMPORT_LIB_SUFFIX) - set(IMPORT_LIB_SUFFIX "") + # Suffix implib name with "_imp" by default, to avoid conflicting with + # the generated static "libcurl.lib" (typically with MSVC). + if(WIN32 AND BUILD_SHARED_LIBS AND + CMAKE_IMPORT_LIBRARY_SUFFIX STREQUAL CMAKE_STATIC_LIBRARY_SUFFIX) + set(IMPORT_LIB_SUFFIX "_imp") + else() + set(IMPORT_LIB_SUFFIX "") + endif() endif() if(NOT DEFINED STATIC_LIB_SUFFIX) set(STATIC_LIB_SUFFIX "") endif() -# Add "_imp" as a suffix before the extension to avoid conflicting with -# the statically linked "libcurl.lib" (typically with MSVC) -if(WIN32 AND - NOT IMPORT_LIB_SUFFIX AND - CMAKE_STATIC_LIBRARY_SUFFIX STREQUAL CMAKE_IMPORT_LIBRARY_SUFFIX) - set(IMPORT_LIB_SUFFIX "_imp") +# Detect implib static lib filename collision +if(WIN32 AND BUILD_STATIC_LIBS AND BUILD_SHARED_LIBS AND + "${IMPORT_LIB_SUFFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX}" STREQUAL + "${STATIC_LIB_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}") + message(FATAL_ERROR "Library suffix is the same ('${STATIC_LIB_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}') " + "for the import and static '${LIBCURL_OUTPUT_NAME}' library. " + "Set IMPORT_LIB_SUFFIX and/or STATIC_LIB_SUFFIX to different values, " + "or disable building either the shared or static library to avoid the filename collision.") endif() # Whether to do a single compilation pass for libcurl sources and reuse these @@ -136,9 +144,9 @@ if(NOT DEFINED SHARE_LIB_OBJECT) endif() endif() -if(SHARE_LIB_OBJECT) +if(SHARE_LIB_OBJECT AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.12) set(LIB_OBJECT "libcurl_object") - add_library(${LIB_OBJECT} OBJECT ${HHEADERS} ${CSOURCES}) + add_library(${LIB_OBJECT} OBJECT ${HHEADERS} ${CSOURCES}) # Requires CMake 3.12 if(WIN32) # Define CURL_STATICLIB always, to disable __declspec(dllexport) for # exported libcurl symbols. We handle exports via libcurl.def instead. @@ -154,7 +162,13 @@ if(SHARE_LIB_OBJECT) set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS") endif() if(CURL_HAS_LTO) - set_target_properties(${LIB_OBJECT} PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + if(CMAKE_CONFIGURATION_TYPES) + set_target_properties(${LIB_OBJECT} PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE + INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE) + else() + set_target_properties(${LIB_OBJECT} PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() endif() target_include_directories(${LIB_OBJECT} INTERFACE @@ -179,13 +193,20 @@ if(BUILD_STATIC_LIBS) set_target_properties(${LIB_STATIC} PROPERTIES PREFIX "" OUTPUT_NAME "${LIBCURL_OUTPUT_NAME}" SUFFIX "${STATIC_LIB_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}" - INTERFACE_COMPILE_DEFINITIONS "CURL_STATICLIB") + INTERFACE_COMPILE_DEFINITIONS "CURL_STATICLIB" + INTERFACE_LINK_DIRECTORIES "${CURL_LIBDIRS}") 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 TRUE) + if(CMAKE_CONFIGURATION_TYPES) + set_target_properties(${LIB_OBJECT} PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE + INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE) + else() + set_target_properties(${LIB_OBJECT} PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() endif() target_include_directories(${LIB_STATIC} INTERFACE @@ -197,15 +218,8 @@ 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 "dllmain.c") 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") @@ -222,7 +236,13 @@ if(BUILD_SHARED_LIBS) 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 TRUE) + if(CMAKE_CONFIGURATION_TYPES) + set_target_properties(${LIB_OBJECT} PROPERTIES + INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE + INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE) + else() + set_target_properties(${LIB_OBJECT} PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() endif() target_include_directories(${LIB_SHARED} INTERFACE @@ -294,8 +314,11 @@ if(BUILD_SHARED_LIBS) 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}") + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13) + set_target_properties(${LIB_SHARED} PROPERTIES LINK_OPTIONS "${CMAKE_REQUIRED_LINK_OPTIONS}") + else() + set_target_properties(${LIB_SHARED} PROPERTIES LINK_FLAGS "${CMAKE_REQUIRED_LINK_OPTIONS}") + endif() else() message(WARNING "Versioned symbols requested, but not supported by the toolchain.") endif() diff --git a/Utilities/cmcurl/lib/Makefile.inc b/Utilities/cmcurl/lib/Makefile.inc index 25c6df84ec..b384e09623 100644 --- a/Utilities/cmcurl/lib/Makefile.inc +++ b/Utilities/cmcurl/lib/Makefile.inc @@ -22,6 +22,33 @@ # ########################################################################### +LIB_CURLX_CFILES = \ + curlx/base64.c \ + curlx/dynbuf.c \ + curlx/inet_pton.c \ + curlx/multibyte.c \ + curlx/nonblock.c \ + curlx/strparse.c \ + curlx/timediff.c \ + curlx/timeval.c \ + curlx/version_win32.c \ + curlx/warnless.c \ + curlx/winapi.c + +LIB_CURLX_HFILES = \ + curlx/base64.h \ + curlx/curlx.h \ + curlx/dynbuf.h \ + curlx/inet_pton.h \ + curlx/multibyte.h \ + curlx/nonblock.h \ + curlx/strparse.h \ + curlx/timediff.h \ + curlx/timeval.h \ + curlx/version_win32.h \ + curlx/warnless.h \ + curlx/winapi.h + LIB_VAUTH_CFILES = \ vauth/cleartext.c \ vauth/cram.c \ @@ -39,7 +66,6 @@ LIB_VAUTH_CFILES = \ LIB_VAUTH_HFILES = \ vauth/digest.h \ - vauth/ntlm.h \ vauth/vauth.h LIB_VTLS_CFILES = \ @@ -112,8 +138,8 @@ LIB_CFILES = \ altsvc.c \ amigaos.c \ asyn-ares.c \ - asyn-thread.c \ - base64.c \ + asyn-base.c \ + asyn-thrdd.c \ bufq.c \ bufref.c \ cf-h1-proxy.c \ @@ -126,6 +152,7 @@ LIB_CFILES = \ connect.c \ content_encoding.c \ cookie.c \ + cshutdn.c \ curl_addrinfo.c \ curl_des.c \ curl_endian.c \ @@ -134,7 +161,6 @@ LIB_CFILES = \ curl_gethostname.c \ curl_gssapi.c \ curl_memrchr.c \ - curl_multibyte.c \ curl_ntlm_core.c \ curl_range.c \ curl_rtmp.c \ @@ -144,15 +170,15 @@ LIB_CFILES = \ curl_threads.c \ curl_trc.c \ cw-out.c \ + cw-pause.c \ dict.c \ - dllmain.c \ doh.c \ - dynbuf.c \ dynhds.c \ easy.c \ easygetopt.c \ easyoptions.c \ escape.c \ + fake_addrinfo.c \ file.c \ fileinfo.c \ fopen.c \ @@ -165,11 +191,9 @@ LIB_CFILES = \ hash.c \ headers.c \ hmac.c \ - hostasyn.c \ hostip.c \ hostip4.c \ hostip6.c \ - hostsyn.c \ hsts.c \ http.c \ http1.c \ @@ -185,7 +209,6 @@ LIB_CFILES = \ if2ip.c \ imap.c \ inet_ntop.c \ - inet_pton.c \ krb5.c \ ldap.c \ llist.c \ @@ -197,8 +220,8 @@ LIB_CFILES = \ mprintf.c \ mqtt.c \ multi.c \ + multi_ev.c \ netrc.c \ - nonblock.c \ noproxy.c \ openldap.c \ parsedate.c \ @@ -226,21 +249,19 @@ LIB_CFILES = \ splay.c \ strcase.c \ strdup.c \ + strequal.c \ strerror.c \ - strparse.c \ - strtok.c \ - strtoofft.c \ system_win32.c \ telnet.c \ tftp.c \ - timediff.c \ - timeval.c \ transfer.c \ + uint-bset.c \ + uint-hash.c \ + uint-spbset.c \ + uint-table.c \ url.c \ urlapi.c \ version.c \ - version_win32.c \ - warnless.c \ ws.c LIB_HFILES = \ @@ -257,11 +278,11 @@ LIB_HFILES = \ cf-socket.h \ cfilters.h \ conncache.h \ + cshutdn.h \ connect.h \ content_encoding.h \ cookie.h \ curl_addrinfo.h \ - curl_base64.h \ curl_ctype.h \ curl_des.h \ curl_endian.h \ @@ -276,7 +297,6 @@ LIB_HFILES = \ curl_md5.h \ curl_memory.h \ curl_memrchr.h \ - curl_multibyte.h \ curl_ntlm_core.h \ curl_printf.h \ curl_range.h \ @@ -289,16 +309,16 @@ LIB_HFILES = \ curl_sspi.h \ curl_threads.h \ curl_trc.h \ - curlx.h \ cw-out.h \ + cw-pause.h \ dict.h \ doh.h \ - dynbuf.h \ dynhds.h \ easy_lock.h \ easyif.h \ easyoptions.h \ escape.h \ + fake_addrinfo.h \ file.h \ fileinfo.h \ fopen.h \ @@ -326,16 +346,15 @@ LIB_HFILES = \ if2ip.h \ imap.h \ inet_ntop.h \ - inet_pton.h \ llist.h \ macos.h \ memdebug.h \ mime.h \ mqtt.h \ multihandle.h \ + multi_ev.h \ multiif.h \ netrc.h \ - nonblock.h \ noproxy.h \ parsedate.h \ pingpong.h \ @@ -365,25 +384,22 @@ LIB_HFILES = \ strcase.h \ strdup.h \ strerror.h \ - strparse.h \ - strtok.h \ - strtoofft.h \ system_win32.h \ telnet.h \ tftp.h \ - timediff.h \ - timeval.h \ transfer.h \ + uint-bset.h \ + uint-hash.h \ + uint-spbset.h \ + uint-table.h \ url.h \ urlapi-int.h \ urldata.h \ - version_win32.h \ - warnless.h \ ws.h LIB_RCFILES = libcurl.rc CSOURCES = $(LIB_CFILES) $(LIB_VAUTH_CFILES) $(LIB_VTLS_CFILES) \ - $(LIB_VQUIC_CFILES) $(LIB_VSSH_CFILES) + $(LIB_VQUIC_CFILES) $(LIB_VSSH_CFILES) $(LIB_CURLX_CFILES) HHEADERS = $(LIB_HFILES) $(LIB_VAUTH_HFILES) $(LIB_VTLS_HFILES) \ - $(LIB_VQUIC_HFILES) $(LIB_VSSH_HFILES) + $(LIB_VQUIC_HFILES) $(LIB_VSSH_HFILES) $(LIB_CURLX_HFILES) diff --git a/Utilities/cmcurl/lib/altsvc.c b/Utilities/cmcurl/lib/altsvc.c index 095b3c168a..602ef61def 100644 --- a/Utilities/cmcurl/lib/altsvc.c +++ b/Utilities/cmcurl/lib/altsvc.c @@ -35,12 +35,12 @@ #include "strcase.h" #include "parsedate.h" #include "sendf.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "fopen.h" #include "rename.h" #include "strdup.h" -#include "inet_pton.h" -#include "strparse.h" +#include "curlx/inet_pton.h" +#include "curlx/strparse.h" #include "connect.h" /* The last 3 #include files should be in this order */ @@ -138,18 +138,20 @@ static struct altsvc *altsvc_create(struct Curl_str *srchost, size_t srcport, size_t dstport) { - enum alpnid dstalpnid = Curl_alpn2alpnid(dstalpn->str, dstalpn->len); - enum alpnid srcalpnid = Curl_alpn2alpnid(srcalpn->str, srcalpn->len); + enum alpnid dstalpnid = + Curl_alpn2alpnid(curlx_str(dstalpn), curlx_strlen(dstalpn)); + enum alpnid srcalpnid = + Curl_alpn2alpnid(curlx_str(srcalpn), curlx_strlen(srcalpn)); if(!srcalpnid || !dstalpnid) return NULL; - return altsvc_createid(srchost->str, srchost->len, - dsthost->str, dsthost->len, + return altsvc_createid(curlx_str(srchost), curlx_strlen(srchost), + curlx_str(dsthost), curlx_strlen(dsthost), srcalpnid, dstalpnid, srcport, dstport); } /* only returns SERIOUS errors */ -static CURLcode altsvc_add(struct altsvcinfo *asi, char *line) +static CURLcode altsvc_add(struct altsvcinfo *asi, const char *line) { /* Example line: h2 example.com 443 h3 shiny.example.com 8443 "20191231 10:00:00" 1 @@ -159,42 +161,42 @@ static CURLcode altsvc_add(struct altsvcinfo *asi, char *line) struct Curl_str srcalpn; struct Curl_str dstalpn; struct Curl_str date; - size_t srcport; - size_t dstport; - size_t persist; - size_t prio; + curl_off_t srcport; + curl_off_t dstport; + curl_off_t persist; + curl_off_t prio; - if(Curl_str_word(&line, &srcalpn, MAX_ALTSVC_ALPNLEN) || - Curl_str_singlespace(&line) || - Curl_str_word(&line, &srchost, MAX_ALTSVC_HOSTLEN) || - Curl_str_singlespace(&line) || - Curl_str_number(&line, &srcport, 65535) || - Curl_str_singlespace(&line) || - Curl_str_word(&line, &dstalpn, MAX_ALTSVC_ALPNLEN) || - Curl_str_singlespace(&line) || - Curl_str_word(&line, &dsthost, MAX_ALTSVC_HOSTLEN) || - Curl_str_singlespace(&line) || - Curl_str_number(&line, &dstport, 65535) || - Curl_str_singlespace(&line) || - Curl_str_quotedword(&line, &date, MAX_ALTSVC_DATELEN) || - Curl_str_singlespace(&line) || - Curl_str_number(&line, &persist, 1) || - Curl_str_singlespace(&line) || - Curl_str_number(&line, &prio, 0) || - Curl_str_newline(&line)) + if(curlx_str_word(&line, &srcalpn, MAX_ALTSVC_ALPNLEN) || + curlx_str_singlespace(&line) || + curlx_str_word(&line, &srchost, MAX_ALTSVC_HOSTLEN) || + curlx_str_singlespace(&line) || + curlx_str_number(&line, &srcport, 65535) || + curlx_str_singlespace(&line) || + curlx_str_word(&line, &dstalpn, MAX_ALTSVC_ALPNLEN) || + curlx_str_singlespace(&line) || + curlx_str_word(&line, &dsthost, MAX_ALTSVC_HOSTLEN) || + curlx_str_singlespace(&line) || + curlx_str_number(&line, &dstport, 65535) || + curlx_str_singlespace(&line) || + curlx_str_quotedword(&line, &date, MAX_ALTSVC_DATELEN) || + curlx_str_singlespace(&line) || + curlx_str_number(&line, &persist, 1) || + curlx_str_singlespace(&line) || + curlx_str_number(&line, &prio, 0) || + curlx_str_newline(&line)) ; else { struct altsvc *as; char dbuf[MAX_ALTSVC_DATELEN + 1]; time_t expires; - /* The date parser works on a null terminated string. The maximum length - is upheld by Curl_str_quotedword(). */ - memcpy(dbuf, date.str, date.len); - dbuf[date.len] = 0; + /* The date parser works on a null-terminated string. The maximum length + is upheld by curlx_str_quotedword(). */ + memcpy(dbuf, curlx_str(&date), curlx_strlen(&date)); + dbuf[curlx_strlen(&date)] = 0; expires = Curl_getdate_capped(dbuf); - as = altsvc_create(&srchost, &dsthost, &srcalpn, &dstalpn, srcport, - dstport); + as = altsvc_create(&srchost, &dsthost, &srcalpn, &dstalpn, + (size_t)srcport, (size_t)dstport); if(as) { as->expires = expires; as->prio = 0; /* not supported to just set zero */ @@ -229,18 +231,14 @@ static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file) fp = fopen(file, FOPEN_READTEXT); if(fp) { struct dynbuf buf; - Curl_dyn_init(&buf, MAX_ALTSVC_LINE); + curlx_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 == '#') - /* skip commented lines */ - continue; - - altsvc_add(asi, lineptr); + const char *lineptr = curlx_dyn_ptr(&buf); + curlx_str_passblanks(&lineptr); + if(curlx_str_single(&lineptr, '#')) + altsvc_add(asi, lineptr); } - Curl_dyn_free(&buf); /* free the line buffer */ + curlx_dyn_free(&buf); /* free the line buffer */ fclose(fp); } return result; @@ -263,11 +261,11 @@ static CURLcode altsvc_out(struct altsvc *as, FILE *fp) #ifdef USE_IPV6 else { char ipv6_unused[16]; - if(1 == Curl_inet_pton(AF_INET6, as->dst.host, ipv6_unused)) { + if(1 == curlx_inet_pton(AF_INET6, as->dst.host, ipv6_unused)) { dst6_pre = "["; dst6_post = "]"; } - if(1 == Curl_inet_pton(AF_INET6, as->src.host, ipv6_unused)) { + if(1 == curlx_inet_pton(AF_INET6, as->src.host, ipv6_unused)) { src6_pre = "["; src6_post = "]"; } @@ -405,26 +403,6 @@ CURLcode Curl_altsvc_save(struct Curl_easy *data, return result; } -static CURLcode getalnum(const char **ptr, char *alpnbuf, size_t buflen) -{ - size_t len; - const char *protop; - const char *p = *ptr; - while(*p && ISBLANK(*p)) - p++; - protop = p; - while(*p && !ISBLANK(*p) && (*p != ';') && (*p != '=')) - p++; - len = p - protop; - *ptr = p; - - if(!len || (len >= buflen)) - return CURLE_BAD_FUNCTION_ARGUMENT; - memcpy(alpnbuf, protop, len); - alpnbuf[len] = 0; - return CURLE_OK; -} - /* hostcompare() returns true if 'host' matches 'check'. The first host * argument may have a trailing dot present that will be ignored. */ @@ -460,15 +438,16 @@ static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid, } } -#ifdef DEBUGBUILD +#if defined(DEBUGBUILD) || defined(UNITTESTS) /* to play well with debug builds, we can *set* a fixed time this will return */ static time_t altsvc_debugtime(void *unused) { - char *timestr = getenv("CURL_TIME"); + const char *timestr = getenv("CURL_TIME"); (void)unused; if(timestr) { - long val = strtol(timestr, NULL, 10); + curl_off_t val; + curlx_str_number(×tr, &val, TIME_T_MAX); return (time_t)val; } return time(NULL); @@ -494,153 +473,124 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data, unsigned short srcport) { const char *p = value; - char alpnbuf[MAX_ALTSVC_ALPNLEN] = ""; struct altsvc *as; unsigned short dstport = srcport; /* the same by default */ - CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf)); size_t entries = 0; - size_t alpnlen = strlen(alpnbuf); - size_t srchostlen = strlen(srchost); + struct Curl_str alpn; + const char *sp; + time_t maxage = 24 * 3600; /* default is 24 hours */ + bool persist = FALSE; #ifdef CURL_DISABLE_VERBOSE_STRINGS (void)data; #endif - if(result) { - infof(data, "Excessive alt-svc header, ignoring."); - return CURLE_OK; - } DEBUGASSERT(asi); - /* "clear" is a magic keyword */ - if(strcasecompare(alpnbuf, "clear")) { - /* Flush cached alternatives for this source origin */ - altsvc_flush(asi, srcalpnid, srchost, srcport); - return CURLE_OK; + /* initial check for "clear" */ + if(!curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, ';') && + !curlx_str_single(&p, ';')) { + curlx_str_trimblanks(&alpn); + /* "clear" is a magic keyword */ + if(curlx_str_casecompare(&alpn, "clear")) { + /* Flush cached alternatives for this source origin */ + altsvc_flush(asi, srcalpnid, srchost, srcport); + return CURLE_OK; + } + } + + p = value; + + if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '=')) + return CURLE_OK; /* strange line */ + + curlx_str_trimblanks(&alpn); + + /* Handle the optional 'ma' and 'persist' flags once first, as they need to + be known for each alternative service. Unknown flags are skipped. */ + sp = strchr(p, ';'); + if(sp) { + sp++; /* pass the semicolon */ + for(;;) { + struct Curl_str name; + struct Curl_str val; + const char *vp; + curl_off_t num; + bool quoted; + /* allow some extra whitespaces around name and value */ + if(curlx_str_until(&sp, &name, 20, '=') || + curlx_str_single(&sp, '=') || + curlx_str_until(&sp, &val, 80, ';')) + break; + curlx_str_trimblanks(&name); + curlx_str_trimblanks(&val); + /* the value might be quoted */ + vp = curlx_str(&val); + quoted = (*vp == '\"'); + if(quoted) + vp++; + if(!curlx_str_number(&vp, &num, TIME_T_MAX)) { + if(curlx_str_casecompare(&name, "ma")) + maxage = (time_t)num; + else if(curlx_str_casecompare(&name, "persist") && (num == 1)) + persist = TRUE; + } + if(quoted && curlx_str_single(&sp, '\"')) + break; + if(curlx_str_single(&sp, ';')) + break; + } } do { - if(*p == '=') { - /* [protocol]="[host][:port]" */ - enum alpnid dstalpnid = Curl_alpn2alpnid(alpnbuf, alpnlen); - p++; - if(*p == '\"') { - const char *dsthost = ""; - size_t dstlen = 0; /* destination hostname length */ - const char *value_ptr; - char option[32]; - unsigned long num; - char *end_ptr; - bool quoted = FALSE; - time_t maxage = 24 * 3600; /* default is 24 hours */ - bool persist = FALSE; - bool valid = TRUE; - p++; - if(*p != ':') { + if(!curlx_str_single(&p, '=')) { + /* [protocol]="[host][:port], [protocol]="[host][:port]" */ + enum alpnid dstalpnid = + Curl_alpn2alpnid(curlx_str(&alpn), curlx_strlen(&alpn)); + if(!curlx_str_single(&p, '\"')) { + struct Curl_str dsthost; + curl_off_t port = 0; + if(curlx_str_single(&p, ':')) { /* hostname starts here */ - const char *hostp = p; - if(*p == '[') { - /* pass all valid IPv6 letters - does not handle zone id */ - dstlen = strspn(++p, "0123456789abcdefABCDEF:."); - if(p[dstlen] != ']') - /* invalid host syntax, bail out */ + if(curlx_str_single(&p, '[')) { + if(curlx_str_until(&p, &dsthost, MAX_ALTSVC_HOSTLEN, ':')) { + infof(data, "Bad alt-svc hostname, ignoring."); break; - /* we store the IPv6 numerical address *with* brackets */ - dstlen += 2; - p = &p[dstlen-1]; + } } else { - while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-'))) - p++; - dstlen = p - hostp; - } - if(!dstlen || (dstlen >= MAX_ALTSVC_HOSTLEN)) { - infof(data, "Excessive alt-svc hostname, ignoring."); - valid = FALSE; - } - else { - dsthost = hostp; + /* IPv6 host name */ + if(curlx_str_until(&p, &dsthost, MAX_IPADR_LEN, ']') || + curlx_str_single(&p, ']')) { + infof(data, "Bad alt-svc IPv6 hostname, ignoring."); + break; + } } + if(curlx_str_single(&p, ':')) + break; } - else { + else /* no destination name, use source host */ - dsthost = srchost; - dstlen = strlen(srchost); - } - if(*p == ':') { - unsigned long port = 0; - p++; - if(ISDIGIT(*p)) - /* a port number */ - port = strtoul(p, &end_ptr, 10); - else - end_ptr = (char *)p; /* not left uninitialized */ - if(!port || port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') { - infof(data, "Unknown alt-svc port number, ignoring."); - valid = FALSE; - } - else { - dstport = curlx_ultous(port); - p = end_ptr; - } - } - if(*p++ != '\"') + curlx_str_assign(&dsthost, srchost, strlen(srchost)); + + if(curlx_str_number(&p, &port, 0xffff)) { + infof(data, "Unknown alt-svc port number, ignoring."); break; - /* Handle the optional 'ma' and 'persist' flags. Unknown flags - are skipped. */ - for(;;) { - while(ISBLANK(*p)) - p++; - if(*p != ';') - break; - p++; /* pass the semicolon */ - if(!*p || ISNEWLINE(*p)) - break; - result = getalnum(&p, option, sizeof(option)); - if(result) { - /* skip option if name is too long */ - option[0] = '\0'; - } - while(*p && ISBLANK(*p)) - p++; - if(*p != '=') - return CURLE_OK; - p++; - while(*p && ISBLANK(*p)) - p++; - if(!*p) - return CURLE_OK; - if(*p == '\"') { - /* quoted value */ - p++; - quoted = TRUE; - } - value_ptr = p; - if(quoted) { - while(*p && *p != '\"') - p++; - if(!*p++) - return CURLE_OK; - } - else { - while(*p && !ISBLANK(*p) && *p!= ';' && *p != ',') - p++; - } - num = strtoul(value_ptr, &end_ptr, 10); - if((end_ptr != value_ptr) && (num < ULONG_MAX)) { - if(strcasecompare("ma", option)) - maxage = (time_t)num; - else if(strcasecompare("persist", option) && (num == 1)) - persist = TRUE; - } } - if(dstalpnid && valid) { + + dstport = (unsigned short)port; + + if(curlx_str_single(&p, '\"')) + break; + + if(dstalpnid) { if(!entries++) /* Flush cached alternatives for this source origin, if any - when this is the first entry of the line. */ altsvc_flush(asi, srcalpnid, srchost, srcport); - as = altsvc_createid(srchost, srchostlen, - dsthost, dstlen, + as = altsvc_createid(srchost, strlen(srchost), + curlx_str(&dsthost), + curlx_strlen(&dsthost), srcalpnid, dstalpnid, srcport, dstport); if(as) { @@ -653,26 +603,28 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data, as->expires = maxage + secs; as->persist = persist; Curl_llist_append(&asi->list, as, &as->node); - infof(data, "Added alt-svc: %s:%d over %s", dsthost, dstport, - Curl_alpnid2str(dstalpnid)); + infof(data, "Added alt-svc: %.*s:%d over %s", + (int)curlx_strlen(&dsthost), curlx_str(&dsthost), + dstport, Curl_alpnid2str(dstalpnid)); } } } else break; + /* 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 */ - p++; - result = getalnum(&p, alpnbuf, sizeof(alpnbuf)); - if(result) - break; - } + if(curlx_str_single(&p, ',')) + break; + + /* comma means another alternative is present */ + if(curlx_str_until(&p, &alpn, MAX_ALTSVC_LINE, '=')) + break; + curlx_str_trimblanks(&alpn); } else break; - } while(*p && (*p != ';') && (*p != '\n') && (*p != '\r')); + } while(1); return CURLE_OK; } diff --git a/Utilities/cmcurl/lib/altsvc.h b/Utilities/cmcurl/lib/altsvc.h index 5f94f832b4..831cd09743 100644 --- a/Utilities/cmcurl/lib/altsvc.h +++ b/Utilities/cmcurl/lib/altsvc.h @@ -39,9 +39,9 @@ struct altsvc { struct althost src; struct althost dst; time_t expires; - bool persist; - unsigned int prio; struct Curl_llist_node node; + unsigned int prio; + BIT(persist); }; struct altsvcinfo { diff --git a/Utilities/cmcurl/lib/amigaos.c b/Utilities/cmcurl/lib/amigaos.c index c4872f248d..ac6d6b4193 100644 --- a/Utilities/cmcurl/lib/amigaos.c +++ b/Utilities/cmcurl/lib/amigaos.c @@ -184,7 +184,7 @@ int Curl_amiga_select(int nfds, fd_set *readfds, fd_set *writefds, { int r = WaitSelect(nfds, readfds, writefds, errorfds, timeout, 0); /* Ensure Ctrl-C signal is actioned */ - if((r == -1) && (SOCKERRNO == EINTR)) + if((r == -1) && (SOCKERRNO == SOCKEINTR)) raise(SIGINT); return r; } diff --git a/Utilities/cmcurl/lib/asyn-ares.c b/Utilities/cmcurl/lib/asyn-ares.c index 640c8a9b65..10f870a15a 100644 --- a/Utilities/cmcurl/lib/asyn-ares.c +++ b/Utilities/cmcurl/lib/asyn-ares.c @@ -24,7 +24,7 @@ #include "curl_setup.h" -#ifdef USE_ARES +#ifdef CURLRES_ARES /*********************************************************************** * Only for ares-enabled builds @@ -54,11 +54,11 @@ #include "share.h" #include "url.h" #include "multiif.h" -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "connect.h" #include "select.h" #include "progress.h" -#include "timediff.h" +#include "curlx/timediff.h" #include "httpsrr.h" #include "strdup.h" @@ -66,102 +66,6 @@ #include /* really old c-ares did not include this by itself */ -/* - * Curl_ares_getsock() is called when the outside world (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. - * - * Returns: sockets-in-use-bitmap - */ - -int Curl_ares_getsock(struct Curl_easy *data, - ares_channel channel, - curl_socket_t *socks) -{ - struct timeval maxtime = { CURL_TIMEOUT_RESOLVE, 0 }; - struct timeval timebuf; - int max = ares_getsock(channel, - (ares_socket_t *)socks, MAX_SOCKSPEREASYHANDLE); - struct timeval *timeout = ares_timeout(channel, &maxtime, &timebuf); - timediff_t milli = curlx_tvtoms(timeout); - Curl_expire(data, milli, EXPIRE_ASYNC_NAME); - return max; -} - -/* - * Curl_ares_perform() - * - * 1) Ask ares what sockets it currently plays with, then - * 2) wait for the timeout period to check for action on ares' sockets. - * 3) tell ares to act on all the sockets marked as "with action" - * - * return number of sockets it worked on, or -1 on error - */ - -int Curl_ares_perform(ares_channel channel, - timediff_t timeout_ms) -{ - int nfds; - int bitmask; - ares_socket_t socks[ARES_GETSOCK_MAXNUM]; - struct pollfd pfd[ARES_GETSOCK_MAXNUM]; - int i; - int num = 0; - - if(!channel) - return 0; - - bitmask = ares_getsock(channel, socks, ARES_GETSOCK_MAXNUM); - - for(i = 0; i < ARES_GETSOCK_MAXNUM; i++) { - pfd[i].events = 0; - pfd[i].revents = 0; - if(ARES_GETSOCK_READABLE(bitmask, i)) { - pfd[i].fd = socks[i]; - pfd[i].events |= POLLRDNORM|POLLIN; - } - if(ARES_GETSOCK_WRITABLE(bitmask, i)) { - pfd[i].fd = socks[i]; - pfd[i].events |= POLLWRNORM|POLLOUT; - } - if(pfd[i].events) - num++; - else - break; - } - - if(num) { - nfds = Curl_poll(pfd, (unsigned int)num, timeout_ms); - if(nfds < 0) - return -1; - } - else - nfds = 0; - - if(!nfds) - /* Call ares_process() unconditionally here, even if we simply timed out - above, as otherwise the ares name resolve will not timeout! */ - ares_process_fd(channel, 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(channel, - (pfd[i].revents & (POLLRDNORM|POLLIN)) ? - pfd[i].fd : ARES_SOCKET_BAD, - (pfd[i].revents & (POLLWRNORM|POLLOUT)) ? - pfd[i].fd : ARES_SOCKET_BAD); - } - return nfds; -} - -#ifdef CURLRES_ARES - -#if ARES_VERSION >= 0x010500 -/* c-ares 1.5.0 or later, the callback proto is modified */ -#define HAVE_CARES_CALLBACK_TIMEOUTS 1 -#endif - #if ARES_VERSION >= 0x010601 /* IPv6 supported since 1.6.1 */ #define HAVE_CARES_IPV6 1 @@ -182,12 +86,11 @@ int Curl_ares_perform(ares_channel channel, #define HAVE_CARES_GETADDRINFO 1 #endif -#if ARES_VERSION >= 0x011c00 -/* 1.28.0 and later have ares_query_dnsrec */ -#define HAVE_ARES_QUERY_DNSREC 1 #ifdef USE_HTTPSRR -#define USE_HTTPSRR_ARES 1 +#if ARES_VERSION < 0x011c00 +#error "requires c-ares 1.28.0 or newer for HTTPSRR" #endif +#define HTTPSRR_WORKS #endif /* The last 3 #include files should be in this order */ @@ -210,12 +113,15 @@ int Curl_ares_perform(ares_channel channel, static int ares_ver = 0; +static CURLcode async_ares_set_dns_servers(struct Curl_easy *data, + bool reset_on_null); + /* - * Curl_resolver_global_init() - the generic low-level asynchronous name + * Curl_async_global_init() - the generic low-level asynchronous name * resolve API. Called from curl_global_init() to initialize global resolver * environment. Initializes ares library. */ -int Curl_resolver_global_init(void) +int Curl_async_global_init(void) { #ifdef CARES_HAVE_ARES_LIBRARY_INIT if(ares_library_init(ARES_LIB_INIT_ALL)) { @@ -227,12 +133,12 @@ int Curl_resolver_global_init(void) } /* - * Curl_resolver_global_cleanup() + * Curl_async_global_cleanup() * * Called from curl_global_cleanup() to destroy global resolver environment. * Deinitializes ares library. */ -void Curl_resolver_global_cleanup(void) +void Curl_async_global_cleanup(void) { #ifdef CARES_HAVE_ARES_LIBRARY_CLEANUP ares_library_cleanup(); @@ -246,25 +152,22 @@ static void sock_state_cb(void *data, ares_socket_t socket_fd, struct Curl_easy *easy = data; if(!readable && !writable) { DEBUGASSERT(easy); - Curl_multi_closed(easy, socket_fd); + Curl_multi_will_close(easy, socket_fd); } } -/* - * Curl_resolver_init() - * - * 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. - */ -CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) +static CURLcode async_ares_init(struct Curl_easy *data) { + struct async_ares_ctx *ares = &data->state.async.ares; int status; struct ares_options options; int optmask = ARES_OPT_SOCK_STATE_CB; - options.sock_state_cb = sock_state_cb; - options.sock_state_cb_data = easy; + CURLcode rc = CURLE_OK; + options.sock_state_cb = sock_state_cb; + options.sock_state_cb_data = data; + + DEBUGASSERT(!ares->channel); /* if c ares < 1.20.0: curl set timeout to CARES_TIMEOUT_PER_ATTEMPT (2s) @@ -280,175 +183,193 @@ CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) optmask |= ARES_OPT_TIMEOUTMS; } - status = ares_init_options((ares_channel*)resolver, &options, optmask); + status = ares_init_options(&ares->channel, &options, optmask); if(status != ARES_SUCCESS) { - if(status == ARES_ENOMEM) - return CURLE_OUT_OF_MEMORY; - else - return CURLE_FAILED_INIT; + ares->channel = NULL; + rc = (status == ARES_ENOMEM) ? + CURLE_OUT_OF_MEMORY : CURLE_FAILED_INIT; + goto out; } + + rc = async_ares_set_dns_servers(data, FALSE); + if(rc && rc != CURLE_NOT_BUILT_IN) + goto out; + + rc = Curl_async_ares_set_dns_interface(data); + if(rc && rc != CURLE_NOT_BUILT_IN) + goto out; + + rc = Curl_async_ares_set_dns_local_ip4(data); + if(rc && rc != CURLE_NOT_BUILT_IN) + goto out; + + rc = Curl_async_ares_set_dns_local_ip6(data); + if(rc && rc != CURLE_NOT_BUILT_IN) + goto out; + + rc = CURLE_OK; + +out: + if(rc && ares->channel) { + ares_destroy(ares->channel); + ares->channel = NULL; + } + return rc; +} + +static CURLcode async_ares_init_lazy(struct Curl_easy *data) +{ + struct async_ares_ctx *ares = &data->state.async.ares; + if(!ares->channel) + return async_ares_init(data); return CURLE_OK; - /* make sure that all other returns from this function should destroy the - ares channel before returning error! */ } -/* - * Curl_resolver_cleanup() - * - * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). Destroys the ares channel. - */ -void Curl_resolver_cleanup(void *resolver) +CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl) { - ares_destroy((ares_channel)resolver); + struct async_ares_ctx *ares = &data->state.async.ares; + CURLcode result = CURLE_OK; + if(!ares->channel) { + result = async_ares_init(data); + } + *impl = ares->channel; + return result; } -/* - * Curl_resolver_duphandle() - * - * Called from curl_easy_duphandle() to duplicate resolver URL-state specific - * 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) +static void async_ares_cleanup(struct Curl_easy *data); + +void Curl_async_ares_shutdown(struct Curl_easy *data) { - (void)from; - /* - * it would be better to call ares_dup instead, but right now - * it is not possible to set 'sock_state_cb_data' outside of - * ares_init_options - */ - return Curl_resolver_init(easy, to); + struct async_ares_ctx *ares = &data->state.async.ares; + if(ares->channel) + ares_cancel(ares->channel); + async_ares_cleanup(data); } -static void destroy_async_data(struct Curl_async *async); - -/* - * Cancel all possibly still on-going resolves for this connection. - */ -void Curl_resolver_cancel(struct Curl_easy *data) +void Curl_async_ares_destroy(struct Curl_easy *data) { - DEBUGASSERT(data); - if(data->state.async.resolver) - ares_cancel((ares_channel)data->state.async.resolver); - destroy_async_data(&data->state.async); -} - -/* - * We are equivalent to Curl_resolver_cancel() for the c-ares resolver. We - * never block. - */ -void Curl_resolver_kill(struct Curl_easy *data) -{ - /* 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); -} - -/* - * destroy_async_data() cleans up async resolver data. - */ -static void destroy_async_data(struct Curl_async *async) -{ - if(async->tdata) { - struct thread_data *res = async->tdata; - if(res) { - if(res->temp_ai) { - Curl_freeaddrinfo(res->temp_ai); - res->temp_ai = NULL; - } - free(res); - } - async->tdata = NULL; + struct async_ares_ctx *ares = &data->state.async.ares; + Curl_async_ares_shutdown(data); + if(ares->channel) { + ares_destroy(ares->channel); + ares->channel = NULL; } } /* - * Curl_resolver_getsock() is called when someone from the outside world + * async_ares_cleanup() cleans up async resolver data. + */ +static void async_ares_cleanup(struct Curl_easy *data) +{ + struct async_ares_ctx *ares = &data->state.async.ares; + if(ares->temp_ai) { + Curl_freeaddrinfo(ares->temp_ai); + ares->temp_ai = NULL; + } +#ifdef USE_HTTPSRR + Curl_httpsrr_cleanup(&ares->hinfo); +#endif +} + +/* + * Curl_async_getsock() is called when someone from the outside world * (using curl_multi_fdset()) wants to get our fd_set setup. */ -int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *socks) +int Curl_async_getsock(struct Curl_easy *data, curl_socket_t *socks) { - return Curl_ares_getsock(data, (ares_channel)data->state.async.resolver, - socks); + struct async_ares_ctx *ares = &data->state.async.ares; + DEBUGASSERT(ares->channel); + return Curl_ares_getsock(data, ares->channel, socks); } /* - * Curl_resolver_is_resolved() is called repeatedly to check if a previous + * Curl_async_is_resolved() is called repeatedly to check if a previous * name resolve request has completed. It should also make sure to time-out if * the operation seems to take too long. * * Returns normal CURLcode errors. */ -CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, - struct Curl_dns_entry **dns) +CURLcode Curl_async_is_resolved(struct Curl_easy *data, + struct Curl_dns_entry **dns) { - struct thread_data *res = data->state.async.tdata; + struct async_ares_ctx *ares = &data->state.async.ares; CURLcode result = CURLE_OK; DEBUGASSERT(dns); *dns = NULL; - if(Curl_ares_perform((ares_channel)data->state.async.resolver, 0) < 0) + if(data->state.async.done) { + *dns = data->state.async.dns; + return CURLE_OK; + } + + if(Curl_ares_perform(ares->channel, 0) < 0) return CURLE_UNRECOVERABLE_POLL; #ifndef HAVE_CARES_GETADDRINFO /* 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 + if(ares->num_pending /* This is only set to non-zero if the timer was started. */ - && (res->happy_eyeballs_dns_time.tv_sec - || res->happy_eyeballs_dns_time.tv_usec) - && (Curl_timediff(Curl_now(), res->happy_eyeballs_dns_time) + && (ares->happy_eyeballs_dns_time.tv_sec + || ares->happy_eyeballs_dns_time.tv_usec) + && (curlx_timediff(curlx_now(), ares->happy_eyeballs_dns_time) >= HAPPY_EYEBALLS_DNS_TIMEOUT)) { /* Remember that the EXPIRE_HAPPY_EYEBALLS_DNS timer is no longer running. */ - memset( - &res->happy_eyeballs_dns_time, 0, sizeof(res->happy_eyeballs_dns_time)); + memset(&ares->happy_eyeballs_dns_time, 0, + sizeof(ares->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 leave us with res->num_pending == 0, which is perfect for the next block. */ - ares_cancel((ares_channel)data->state.async.resolver); - DEBUGASSERT(res->num_pending == 0); + ares_cancel(ares->channel); + DEBUGASSERT(ares->num_pending == 0); } #endif - if(res && !res->num_pending) { - (void)Curl_addrinfo_callback(data, res->last_status, res->temp_ai); - /* temp_ai ownership is moved to the connection, so we need not free-up - them */ - res->temp_ai = NULL; - - if(!data->state.async.dns) - result = Curl_resolver_error(data); - else { - *dns = data->state.async.dns; -#ifdef USE_HTTPSRR_ARES - { - struct Curl_https_rrinfo *lhrr = - Curl_memdup(&res->hinfo, sizeof(struct Curl_https_rrinfo)); - if(!lhrr) - result = CURLE_OUT_OF_MEMORY; - else - (*dns)->hinfo = lhrr; - } + if(!ares->num_pending) { + /* all c-ares operations done, what is the result to report? */ + Curl_resolv_unlink(data, &data->state.async.dns); + data->state.async.done = TRUE; + result = ares->result; + if(ares->last_status == CURL_ASYNC_SUCCESS && !result) { + data->state.async.dns = + Curl_dnscache_mk_entry(data, ares->temp_ai, + data->state.async.hostname, 0, + data->state.async.port, FALSE); + ares->temp_ai = NULL; /* temp_ai now owned by entry */ +#ifdef HTTPSRR_WORKS + if(data->state.async.dns) { + struct Curl_https_rrinfo *lhrr = Curl_httpsrr_dup_move(&ares->hinfo); + if(!lhrr) + result = CURLE_OUT_OF_MEMORY; + else + data->state.async.dns->hinfo = lhrr; + } #endif + if(!result && data->state.async.dns) + result = Curl_dnscache_add(data, data->state.async.dns); } - - destroy_async_data(&data->state.async); + /* if we have not found anything, report the proper + * CURLE_COULDNT_RESOLVE_* code */ + if(!result && !data->state.async.dns) + result = Curl_resolver_error(data); + if(result) + Curl_resolv_unlink(data, &data->state.async.dns); + *dns = data->state.async.dns; + CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound", + result, *dns ? "" : "not "); + async_ares_cleanup(data); } - return result; } /* - * Curl_resolver_wait_resolv() + * Curl_async_await() * * Waits for a resolve to finish. This function should be avoided since using * this risk getting the multi interface to "hang". @@ -458,12 +379,13 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. */ -CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, - struct Curl_dns_entry **entry) +CURLcode Curl_async_await(struct Curl_easy *data, + struct Curl_dns_entry **entry) { + struct async_ares_ctx *ares = &data->state.async.ares; CURLcode result = CURLE_OK; timediff_t timeout; - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); DEBUGASSERT(entry); *entry = NULL; /* clear on entry */ @@ -492,7 +414,7 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, store.tv_sec = itimeout/1000; store.tv_usec = (itimeout%1000)*1000; - tvp = ares_timeout((ares_channel)data->state.async.resolver, &store, &tv); + tvp = ares_timeout(ares->channel, &store, &tv); /* use the timeout period ares returned to us above if less than one second is left, otherwise just use 1000ms to make sure the progress @@ -502,19 +424,18 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, else timeout_ms = 1000; - if(Curl_ares_perform((ares_channel)data->state.async.resolver, - timeout_ms) < 0) + if(Curl_ares_perform(ares->channel, timeout_ms) < 0) return CURLE_UNRECOVERABLE_POLL; - result = Curl_resolver_is_resolved(data, entry); + result = Curl_async_is_resolved(data, entry); if(result || data->state.async.done) break; if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else { - struct curltime now2 = Curl_now(); - timediff_t timediff = Curl_timediff(now2, now); /* spent time */ + struct curltime now2 = curlx_now(); + timediff_t timediff = curlx_timediff(now2, now); /* spent time */ if(timediff <= 0) timeout -= 1; /* always deduct at least 1 */ else if(timediff > timeout) @@ -526,163 +447,155 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, if(timeout < 0) result = CURLE_OPERATION_TIMEDOUT; } - if(result) - /* failure, so we cancel the ares operation */ - ares_cancel((ares_channel)data->state.async.resolver); /* Operation complete, if the lookup was successful we now have the entry in the cache. */ + data->state.async.done = TRUE; if(entry) *entry = data->state.async.dns; if(result) - /* close the connection, since we cannot return failure here without - cleaning up this connection properly. */ - connclose(data->conn, "c-ares resolve failed"); - + ares_cancel(ares->channel); return result; } #ifndef HAVE_CARES_GETADDRINFO /* Connects results to the list */ -static void compound_results(struct thread_data *res, - struct Curl_addrinfo *ai) +static void async_addr_concat(struct Curl_addrinfo **pbase, + struct Curl_addrinfo *ai) { if(!ai) return; + /* When adding `ai` to an existing address list, we prefer ipv6 + * to be in front. */ #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. */ - struct Curl_addrinfo *temp_ai_tail = res->temp_ai; - - while(temp_ai_tail->ai_next) - temp_ai_tail = temp_ai_tail->ai_next; - - temp_ai_tail->ai_next = ai; + if(*pbase && (*pbase)->ai_family == PF_INET6) { + /* ipv6 already in front, append `ai` */ + struct Curl_addrinfo *tail = *pbase; + while(tail->ai_next) + tail = tail->ai_next; + tail->ai_next = ai; } else #endif /* CURLRES_IPV6 */ { - /* Add the new results to the list of old results. */ - struct Curl_addrinfo *ai_tail = ai; - while(ai_tail->ai_next) - ai_tail = ai_tail->ai_next; - - ai_tail->ai_next = res->temp_ai; - res->temp_ai = ai; + /* prepend to the (possibly) existing list. */ + struct Curl_addrinfo *tail = ai; + while(tail->ai_next) + tail = tail->ai_next; + tail->ai_next = *pbase; + *pbase = ai; } } /* * ares_query_completed_cb() is the callback that ares will call when - * the host query initiated by ares_gethostbyname() from Curl_getaddrinfo(), - * when using ares, is completed either successfully or with failure. + * the host query initiated by ares_gethostbyname() from + * Curl_async_getaddrinfo(), when using ares, is completed either + * successfully or with failure. */ -static void query_completed_cb(void *arg, /* (struct connectdata *) */ - int status, -#ifdef HAVE_CARES_CALLBACK_TIMEOUTS - int timeouts, -#endif - struct hostent *hostent) +static void async_ares_hostbyname_cb(void *user_data, + int status, + int timeouts, + struct hostent *hostent) { - struct Curl_easy *data = (struct Curl_easy *)arg; - struct thread_data *res; + struct Curl_easy *data = (struct Curl_easy *)user_data; + struct async_ares_ctx *ares = &data->state.async.ares; -#ifdef HAVE_CARES_CALLBACK_TIMEOUTS (void)timeouts; /* ignored */ -#endif if(ARES_EDESTRUCTION == status) /* when this ares handle is getting destroyed, the 'arg' pointer may not be valid so only defer it when we know the 'status' says its fine! */ return; - res = data->state.async.tdata; - if(res) { - res->num_pending--; + if(CURL_ASYNC_SUCCESS == status) { + ares->last_status = status; /* one success overrules any error */ + async_addr_concat(&ares->temp_ai, + Curl_he2ai(hostent, data->state.async.port)); + } + else if(ares->last_status != ARES_SUCCESS) { + /* no success so far, remember error */ + ares->last_status = status; + } - if(CURL_ASYNC_SUCCESS == status) { - struct Curl_addrinfo *ai = Curl_he2ai(hostent, data->state.async.port); - if(ai) { - compound_results(res, ai); - } - } - /* A successful result overwrites any previous error */ - if(res->last_status != ARES_SUCCESS) - res->last_status = status; + ares->num_pending--; - /* 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 have got a - "definitive" response from one of a set of parallel queries, we need to - 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 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 do not - bother handling any other cases. */ - DEBUGASSERT(res->num_pending == 1); + CURL_TRC_DNS(data, "ares: hostbyname done, status=%d, pending=%d, " + "addr=%sfound", + status, ares->num_pending, ares->temp_ai ? "" : "not "); + /* If there are responses still pending, we presume they must be the + complementary IPv4 or IPv6 lookups that we started in parallel in + Curl_async_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 are willing to wait for more responses. */ + if(ares->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 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 do not + bother handling any other cases. */ + DEBUGASSERT(ares->num_pending == 1); - /* 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 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 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 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). + 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 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 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. + 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 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 - 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 - 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 do not even know where in the - c-ares retry cycle each request is. - */ - res->happy_eyeballs_dns_time = Curl_now(); - Curl_expire(data, HAPPY_EYEBALLS_DNS_TIMEOUT, - EXPIRE_HAPPY_EYEBALLS_DNS); - } + 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 + 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 + 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 do not even know where in the + c-ares retry cycle each request is. + */ + ares->happy_eyeballs_dns_time = curlx_now(); + Curl_expire(data, HAPPY_EYEBALLS_DNS_TIMEOUT, + EXPIRE_HAPPY_EYEBALLS_DNS); } } + #else /* c-ares 1.16.0 or later */ /* - * ares2addr() converts an address list provided by c-ares to an internal - * libcurl compatible list + * async_ares_node2addr() converts an address list provided by c-ares + * to an internal libcurl compatible list. */ -static struct Curl_addrinfo *ares2addr(struct ares_addrinfo_node *node) +static struct Curl_addrinfo * +async_ares_node2addr(struct ares_addrinfo_node *node) { /* traverse the ares_addrinfo_node list */ struct ares_addrinfo_node *ai; @@ -752,152 +665,183 @@ static struct Curl_addrinfo *ares2addr(struct ares_addrinfo_node *node) return cafirst; } -static void addrinfo_cb(void *arg, int status, int timeouts, - struct ares_addrinfo *result) +static void async_ares_addrinfo_cb(void *user_data, int status, int timeouts, + struct ares_addrinfo *result) { - struct Curl_easy *data = (struct Curl_easy *)arg; - struct thread_data *res = data->state.async.tdata; + struct Curl_easy *data = (struct Curl_easy *)user_data; + struct async_ares_ctx *ares = &data->state.async.ares; (void)timeouts; + CURL_TRC_DNS(data, "asyn-ares: addrinfo callback, status=%d", status); if(ARES_SUCCESS == status) { - res->temp_ai = ares2addr(result->nodes); - res->last_status = CURL_ASYNC_SUCCESS; + ares->temp_ai = async_ares_node2addr(result->nodes); + ares->last_status = CURL_ASYNC_SUCCESS; ares_freeaddrinfo(result); } - res->num_pending--; + ares->num_pending--; + CURL_TRC_DNS(data, "ares: addrinfo done, status=%d, pending=%d, " + "addr=%sfound", + status, ares->num_pending, ares->temp_ai ? "" : "not "); } #endif +#ifdef USE_HTTPSRR +static void async_ares_rr_done(void *user_data, ares_status_t status, + size_t timeouts, + const ares_dns_record_t *dnsrec) +{ + struct Curl_easy *data = user_data; + struct async_ares_ctx *ares = &data->state.async.ares; + + (void)timeouts; + --ares->num_pending; + CURL_TRC_DNS(data, "ares: httpsrr done, status=%d, pending=%d, " + "dnsres=%sfound", + status, ares->num_pending, + (dnsrec && + ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER)) ? + "" : "not "); + if((ARES_SUCCESS != status) || !dnsrec) + return; + ares->result = Curl_httpsrr_from_ares(data, dnsrec, &ares->hinfo); +} +#endif /* USE_HTTPSRR */ + /* - * Curl_resolver_getaddrinfo() - when using ares + * Curl_async_getaddrinfo() - when using ares * * Returns name information about the given hostname and port number. If * successful, the 'hostent' is returned and the fourth argument will point to * memory we need to free after use. That memory *MUST* be freed with * Curl_freeaddrinfo(), nothing else. */ -struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_async_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + int *waitp) { - struct thread_data *res = NULL; - size_t namelen = strlen(hostname); + struct async_ares_ctx *ares = &data->state.async.ares; *waitp = 0; /* default to synchronous response */ - res = calloc(1, sizeof(struct thread_data) + namelen); - if(res) { - strcpy(res->hostname, hostname); - data->state.async.hostname = res->hostname; - data->state.async.port = port; - data->state.async.done = FALSE; /* not done */ - data->state.async.status = 0; /* clear */ - data->state.async.dns = NULL; /* clear */ - data->state.async.tdata = res; + if(async_ares_init_lazy(data)) + return NULL; - /* initial status - failed */ - res->last_status = ARES_ENOTFOUND; + data->state.async.done = FALSE; /* not done */ + data->state.async.dns = NULL; /* clear */ + data->state.async.port = port; + data->state.async.ip_version = ip_version; + data->state.async.hostname = strdup(hostname); + if(!data->state.async.hostname) + return NULL; + + /* initial status - failed */ + ares->last_status = ARES_ENOTFOUND; #ifdef HAVE_CARES_GETADDRINFO - { - struct ares_addrinfo_hints hints; - char service[12]; - int pf = PF_INET; - memset(&hints, 0, sizeof(hints)); + { + struct ares_addrinfo_hints hints; + char service[12]; + int pf = PF_INET; + memset(&hints, 0, sizeof(hints)); #ifdef CURLRES_IPV6 - if((data->conn->ip_version != CURL_IPRESOLVE_V4) && - Curl_ipv6works(data)) { - /* The stack seems to be IPv6-enabled */ - if(data->conn->ip_version == CURL_IPRESOLVE_V6) - pf = PF_INET6; - else - pf = PF_UNSPEC; - } -#endif /* CURLRES_IPV6 */ - hints.ai_family = pf; - 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 - */ - hints.ai_flags = ARES_AI_NUMERICSERV; - msnprintf(service, sizeof(service), "%d", port); - res->num_pending = 1; - ares_getaddrinfo((ares_channel)data->state.async.resolver, hostname, - service, &hints, addrinfo_cb, data); + if((ip_version != CURL_IPRESOLVE_V4) && + Curl_ipv6works(data)) { + /* The stack seems to be IPv6-enabled */ + if(ip_version == CURL_IPRESOLVE_V6) + pf = PF_INET6; + else + pf = PF_UNSPEC; } +#endif /* CURLRES_IPV6 */ + CURL_TRC_DNS(data, "asyn-ares: fire off getaddrinfo for %s", + (pf == PF_UNSPEC) ? "A+AAAA" : + ((pf == PF_INET) ? "A" : "AAAA")); + hints.ai_family = pf; + 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 + */ + hints.ai_flags = ARES_AI_NUMERICSERV; + msnprintf(service, sizeof(service), "%d", port); + ares->num_pending = 1; + ares_getaddrinfo(ares->channel, data->state.async.hostname, + service, &hints, async_ares_addrinfo_cb, data); + } #else #ifdef HAVE_CARES_IPV6 - if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { - /* The stack seems to be IPv6-enabled */ - res->num_pending = 2; - - /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->state.async.resolver, hostname, - PF_INET, query_completed_cb, data); - ares_gethostbyname((ares_channel)data->state.async.resolver, hostname, - PF_INET6, query_completed_cb, data); - } - else -#endif - { - res->num_pending = 1; - - /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->state.async.resolver, - hostname, PF_INET, - query_completed_cb, data); - } -#endif -#ifdef USE_HTTPSRR_ARES - { - res->num_pending++; /* one more */ - memset(&res->hinfo, 0, sizeof(struct Curl_https_rrinfo)); - ares_query_dnsrec((ares_channel)data->state.async.resolver, - hostname, ARES_CLASS_IN, - ARES_REC_TYPE_HTTPS, - Curl_dnsrec_done_cb, data, NULL); - } -#endif - *waitp = 1; /* expect asynchronous response */ + if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { + /* The stack seems to be IPv6-enabled */ + /* areschannel is already setup in the Curl_open() function */ + CURL_TRC_DNS(data, "asyn-ares: fire off query for A"); + ares_gethostbyname(ares->channel, hostname, PF_INET, + async_ares_hostbyname_cb, data); + CURL_TRC_DNS(data, "asyn-ares: fire off query for AAAA"); + ares->num_pending = 2; + ares_gethostbyname(ares->channel, data->state.async.hostname, PF_INET6, + async_ares_hostbyname_cb, data); } + else +#endif + { + /* areschannel is already setup in the Curl_open() function */ + CURL_TRC_DNS(data, "asyn-ares: fire off query for A"); + ares->num_pending = 1; + ares_gethostbyname(ares->channel, data->state.async.hostname, PF_INET, + async_ares_hostbyname_cb, data); + } +#endif +#ifdef USE_HTTPSRR + { + CURL_TRC_DNS(data, "asyn-ares: fire off query for HTTPSRR"); + memset(&ares->hinfo, 0, sizeof(ares->hinfo)); + ares->hinfo.port = -1; + ares->num_pending++; /* one more */ + ares_query_dnsrec(ares->channel, data->state.async.hostname, + ARES_CLASS_IN, ARES_REC_TYPE_HTTPS, + async_ares_rr_done, data, NULL); + } +#endif + *waitp = 1; /* expect asynchronous response */ + return NULL; /* no struct yet */ } -CURLcode Curl_set_dns_servers(struct Curl_easy *data, - char *servers) +/* Set what DNS server are is to use. This is called in 2 situations: + * 1. when the application does 'CURLOPT_DNS_SERVERS' and passing NULL + * means any previous set value should be unset. Which means + * we need to destroy and create the are channel anew, if there is one. + * 2. When we lazy init the ares channel and NULL means that there + * are no preferences and we do not reset any existing channel. */ +static CURLcode async_ares_set_dns_servers(struct Curl_easy *data, + bool reset_on_null) { + struct async_ares_ctx *ares = &data->state.async.ares; CURLcode result = CURLE_NOT_BUILT_IN; - int ares_result; + const char *servers = data->set.str[STRING_DNS_SERVERS]; + int ares_result = ARES_SUCCESS; + +#if defined(CURLDEBUG) && defined(HAVE_CARES_SERVERS_CSV) + if(getenv("CURL_DNS_SERVER")) + servers = getenv("CURL_DNS_SERVER"); +#endif - /* If server is NULL, this purges all DNS servers from c-ares. Reset it to - * default. - */ if(!servers) { - Curl_resolver_cleanup(data->state.async.resolver); - result = Curl_resolver_init(data, &data->state.async.resolver); - if(!result) { - /* this now needs to restore the other options set to c-ares */ - if(data->set.str[STRING_DNS_INTERFACE]) - (void)Curl_set_dns_interface(data, - data->set.str[STRING_DNS_INTERFACE]); - if(data->set.str[STRING_DNS_LOCAL_IP4]) - (void)Curl_set_dns_local_ip4(data, - data->set.str[STRING_DNS_LOCAL_IP4]); - if(data->set.str[STRING_DNS_LOCAL_IP6]) - (void)Curl_set_dns_local_ip6(data, - data->set.str[STRING_DNS_LOCAL_IP6]); + if(reset_on_null) { + Curl_async_destroy(data); } - return result; + return CURLE_OK; } #ifdef HAVE_CARES_SERVERS_CSV + /* if channel is not there, this is just a parameter check */ + if(ares->channel) #ifdef HAVE_CARES_PORTS_CSV - ares_result = ares_set_servers_ports_csv(data->state.async.resolver, - servers); + ares_result = ares_set_servers_ports_csv(ares->channel, servers); #else - ares_result = ares_set_servers_csv(data->state.async.resolver, servers); + ares_result = ares_set_servers_csv(ares->channel, servers); #endif switch(ares_result) { case ARES_SUCCESS: @@ -921,14 +865,23 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data, return result; } -CURLcode Curl_set_dns_interface(struct Curl_easy *data, - const char *interf) +CURLcode Curl_async_ares_set_dns_servers(struct Curl_easy *data) +{ + return async_ares_set_dns_servers(data, TRUE); +} + +CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data) { #ifdef HAVE_CARES_LOCAL_DEV + struct async_ares_ctx *ares = &data->state.async.ares; + const char *interf = data->set.str[STRING_DNS_INTERFACE]; + if(!interf) interf = ""; - ares_set_local_dev((ares_channel)data->state.async.resolver, interf); + /* if channel is not there, this is just a parameter check */ + if(ares->channel) + ares_set_local_dev(ares->channel, interf); return CURLE_OK; #else /* c-ares version too old! */ @@ -938,24 +891,26 @@ CURLcode Curl_set_dns_interface(struct Curl_easy *data, #endif } -CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, - const char *local_ip4) +CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data) { #ifdef HAVE_CARES_SET_LOCAL + struct async_ares_ctx *ares = &data->state.async.ares; struct in_addr a4; + const char *local_ip4 = data->set.str[STRING_DNS_LOCAL_IP4]; if((!local_ip4) || (local_ip4[0] == 0)) { a4.s_addr = 0; /* disabled: do not bind to a specific address */ } else { - if(Curl_inet_pton(AF_INET, local_ip4, &a4) != 1) { + if(curlx_inet_pton(AF_INET, local_ip4, &a4) != 1) { DEBUGF(infof(data, "bad DNS IPv4 address")); return CURLE_BAD_FUNCTION_ARGUMENT; } } - ares_set_local_ip4((ares_channel)data->state.async.resolver, - ntohl(a4.s_addr)); + /* if channel is not there yet, this is just a parameter check */ + if(ares->channel) + ares_set_local_ip4(ares->channel, ntohl(a4.s_addr)); return CURLE_OK; #else /* c-ares version too old! */ @@ -965,32 +920,33 @@ CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, #endif } -CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, - const char *local_ip6) +CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data) { #if defined(HAVE_CARES_SET_LOCAL) && defined(USE_IPV6) + struct async_ares_ctx *ares = &data->state.async.ares; unsigned char a6[INET6_ADDRSTRLEN]; + const char *local_ip6 = data->set.str[STRING_DNS_LOCAL_IP6]; if((!local_ip6) || (local_ip6[0] == 0)) { /* disabled: do not bind to a specific address */ memset(a6, 0, sizeof(a6)); } else { - if(Curl_inet_pton(AF_INET6, local_ip6, a6) != 1) { + if(curlx_inet_pton(AF_INET6, local_ip6, a6) != 1) { DEBUGF(infof(data, "bad DNS IPv6 address")); return CURLE_BAD_FUNCTION_ARGUMENT; } } - ares_set_local_ip6((ares_channel)data->state.async.resolver, a6); + /* if channel is not there, this is just a parameter check */ + if(ares->channel) + ares_set_local_ip6(ares->channel, a6); return CURLE_OK; #else /* c-ares version too old! */ (void)data; - (void)local_ip6; return CURLE_NOT_BUILT_IN; #endif } -#endif /* CURLRES_ARES */ -#endif /* USE_ARES */ +#endif /* CURLRES_ARES */ diff --git a/Utilities/cmcurl/lib/asyn-base.c b/Utilities/cmcurl/lib/asyn-base.c new file mode 100644 index 0000000000..ea89fba15c --- /dev/null +++ b/Utilities/cmcurl/lib/asyn-base.c @@ -0,0 +1,196 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#ifdef USE_ARES +#include +#include /* really old c-ares did not include this by + itself */ +#endif + +#include "urldata.h" +#include "asyn.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "multiif.h" +#include "select.h" +#include "share.h" +#include "url.h" +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +/*********************************************************************** + * Only for builds using asynchronous name resolves + **********************************************************************/ +#ifdef CURLRES_ASYNCH + + +#ifdef USE_ARES + +#if ARES_VERSION < 0x010600 +#error "requires c-ares 1.6.0 or newer" +#endif + +/* + * Curl_ares_getsock() is called when the outside world (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. + * + * Returns: sockets-in-use-bitmap + */ + +int Curl_ares_getsock(struct Curl_easy *data, + ares_channel channel, + curl_socket_t *socks) +{ + struct timeval maxtime = { CURL_TIMEOUT_RESOLVE, 0 }; + struct timeval timebuf; + int max = ares_getsock(channel, + (ares_socket_t *)socks, MAX_SOCKSPEREASYHANDLE); + struct timeval *timeout = ares_timeout(channel, &maxtime, &timebuf); + timediff_t milli = curlx_tvtoms(timeout); + Curl_expire(data, milli, EXPIRE_ASYNC_NAME); + return max; +} + +/* + * Curl_ares_perform() + * + * 1) Ask ares what sockets it currently plays with, then + * 2) wait for the timeout period to check for action on ares' sockets. + * 3) tell ares to act on all the sockets marked as "with action" + * + * return number of sockets it worked on, or -1 on error + */ +int Curl_ares_perform(ares_channel channel, + timediff_t timeout_ms) +{ + int nfds; + int bitmask; + ares_socket_t socks[ARES_GETSOCK_MAXNUM]; + struct pollfd pfd[ARES_GETSOCK_MAXNUM]; + int i; + int num = 0; + + if(!channel) + return 0; + + bitmask = ares_getsock(channel, socks, ARES_GETSOCK_MAXNUM); + + for(i = 0; i < ARES_GETSOCK_MAXNUM; i++) { + pfd[i].events = 0; + pfd[i].revents = 0; + if(ARES_GETSOCK_READABLE(bitmask, i)) { + pfd[i].fd = socks[i]; + pfd[i].events |= POLLRDNORM|POLLIN; + } + if(ARES_GETSOCK_WRITABLE(bitmask, i)) { + pfd[i].fd = socks[i]; + pfd[i].events |= POLLWRNORM|POLLOUT; + } + if(pfd[i].events) + num++; + else + break; + } + + if(num) { + nfds = Curl_poll(pfd, (unsigned int)num, timeout_ms); + if(nfds < 0) + return -1; + } + else + nfds = 0; + + if(!nfds) + /* Call ares_process() unconditionally here, even if we simply timed out + above, as otherwise the ares name resolve will not timeout! */ + ares_process_fd(channel, 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(channel, + (pfd[i].revents & (POLLRDNORM|POLLIN)) ? + pfd[i].fd : ARES_SOCKET_BAD, + (pfd[i].revents & (POLLWRNORM|POLLOUT)) ? + pfd[i].fd : ARES_SOCKET_BAD); + } + return nfds; +} + +#endif + +#endif /* CURLRES_ASYNCH */ + +#ifdef USE_CURL_ASYNC + +#include "doh.h" + +void Curl_async_shutdown(struct Curl_easy *data) +{ +#ifdef CURLRES_ARES + Curl_async_ares_shutdown(data); +#endif +#ifdef CURLRES_THREADED + Curl_async_thrdd_shutdown(data); +#endif +#ifndef CURL_DISABLE_DOH + Curl_doh_cleanup(data); +#endif + Curl_safefree(data->state.async.hostname); +} + +void Curl_async_destroy(struct Curl_easy *data) +{ +#ifdef CURLRES_ARES + Curl_async_ares_destroy(data); +#endif +#ifdef CURLRES_THREADED + Curl_async_thrdd_destroy(data); +#endif +#ifndef CURL_DISABLE_DOH + Curl_doh_cleanup(data); +#endif + Curl_safefree(data->state.async.hostname); +} + +#endif /* USE_CURL_ASYNC */ diff --git a/Utilities/cmcurl/lib/asyn-thrdd.c b/Utilities/cmcurl/lib/asyn-thrdd.c new file mode 100644 index 0000000000..9cd25dcd20 --- /dev/null +++ b/Utilities/cmcurl/lib/asyn-thrdd.c @@ -0,0 +1,760 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "socketpair.h" + +/*********************************************************************** + * Only for threaded name resolves builds + **********************************************************************/ +#ifdef CURLRES_THREADED + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) +# include +#endif + +#ifdef HAVE_GETADDRINFO +# define RESOLVER_ENOMEM EAI_MEMORY /* = WSA_NOT_ENOUGH_MEMORY on Windows */ +#else +# define RESOLVER_ENOMEM SOCKENOMEM +#endif + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "share.h" +#include "url.h" +#include "multiif.h" +#include "inet_ntop.h" +#include "curl_threads.h" +#include "strdup.h" + +#ifdef USE_ARES +#include +#ifdef USE_HTTPSRR +#define USE_HTTPSRR_ARES /* the combo */ +#endif +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +/* + * Curl_async_global_init() + * Called from curl_global_init() to initialize global resolver environment. + * Does nothing here. + */ +int Curl_async_global_init(void) +{ +#if defined(USE_ARES) && defined(CARES_HAVE_ARES_LIBRARY_INIT) + if(ares_library_init(ARES_LIB_INIT_ALL)) { + return CURLE_FAILED_INIT; + } +#endif + return CURLE_OK; +} + +/* + * Curl_async_global_cleanup() + * Called from curl_global_cleanup() to destroy global resolver environment. + * Does nothing here. + */ +void Curl_async_global_cleanup(void) +{ +#if defined(USE_ARES) && defined(CARES_HAVE_ARES_LIBRARY_INIT) + ares_library_cleanup(); +#endif +} + +static void async_thrdd_destroy(struct Curl_easy *); + +CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl) +{ + (void)data; + *impl = NULL; + return CURLE_OK; +} + +/* Destroy context of threaded resolver */ +static void addr_ctx_destroy(struct async_thrdd_addr_ctx *addr_ctx) +{ + if(addr_ctx) { + DEBUGASSERT(!addr_ctx->ref_count); + Curl_mutex_destroy(&addr_ctx->mutx); + free(addr_ctx->hostname); + if(addr_ctx->res) + Curl_freeaddrinfo(addr_ctx->res); +#ifndef CURL_DISABLE_SOCKETPAIR + /* + * close one end of the socket pair (may be done in resolver thread); + * the other end (for reading) is always closed in the parent thread. + */ +#ifndef USE_EVENTFD + if(addr_ctx->sock_pair[1] != CURL_SOCKET_BAD) { + wakeup_close(addr_ctx->sock_pair[1]); + } +#endif +#endif + free(addr_ctx); + } +} + +/* Initialize context for threaded resolver */ +static struct async_thrdd_addr_ctx * +addr_ctx_create(const char *hostname, int port, + const struct addrinfo *hints) +{ + struct async_thrdd_addr_ctx *addr_ctx = calloc(1, sizeof(*addr_ctx)); + if(!addr_ctx) + return NULL; + + addr_ctx->thread_hnd = curl_thread_t_null; + addr_ctx->port = port; +#ifndef CURL_DISABLE_SOCKETPAIR + addr_ctx->sock_pair[0] = CURL_SOCKET_BAD; + addr_ctx->sock_pair[1] = CURL_SOCKET_BAD; +#endif + addr_ctx->ref_count = 0; + +#ifdef HAVE_GETADDRINFO + DEBUGASSERT(hints); + addr_ctx->hints = *hints; +#else + (void) hints; +#endif + + Curl_mutex_init(&addr_ctx->mutx); + +#ifndef CURL_DISABLE_SOCKETPAIR + /* create socket pair or pipe */ + if(wakeup_create(addr_ctx->sock_pair, FALSE) < 0) { + addr_ctx->sock_pair[0] = CURL_SOCKET_BAD; + addr_ctx->sock_pair[1] = CURL_SOCKET_BAD; + goto err_exit; + } +#endif + addr_ctx->sock_error = CURL_ASYNC_SUCCESS; + + /* Copying hostname string because original can be destroyed by parent + * thread during gethostbyname execution. + */ + addr_ctx->hostname = strdup(hostname); + if(!addr_ctx->hostname) + goto err_exit; + + addr_ctx->ref_count = 1; + return addr_ctx; + +err_exit: +#ifndef CURL_DISABLE_SOCKETPAIR + if(addr_ctx->sock_pair[0] != CURL_SOCKET_BAD) { + wakeup_close(addr_ctx->sock_pair[0]); + addr_ctx->sock_pair[0] = CURL_SOCKET_BAD; + } +#endif + addr_ctx_destroy(addr_ctx); + return NULL; +} + +#ifdef HAVE_GETADDRINFO + +/* + * getaddrinfo_thread() resolves a name and then exits. + * + * For builds without ARES, but with USE_IPV6, create a resolver thread + * and wait on it. + */ +static +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) +DWORD +#else +unsigned int +#endif +CURL_STDCALL getaddrinfo_thread(void *arg) +{ + struct async_thrdd_addr_ctx *addr_ctx = arg; + char service[12]; + int rc; + bool all_gone; + + msnprintf(service, sizeof(service), "%d", addr_ctx->port); + + rc = Curl_getaddrinfo_ex(addr_ctx->hostname, service, + &addr_ctx->hints, &addr_ctx->res); + + if(rc) { + addr_ctx->sock_error = SOCKERRNO ? SOCKERRNO : rc; + if(addr_ctx->sock_error == 0) + addr_ctx->sock_error = RESOLVER_ENOMEM; + } + else { + Curl_addrinfo_set_port(addr_ctx->res, addr_ctx->port); + } + + Curl_mutex_acquire(&addr_ctx->mutx); + if(addr_ctx->ref_count > 1) { + /* Someone still waiting on our results. */ +#ifndef CURL_DISABLE_SOCKETPAIR + if(addr_ctx->sock_pair[1] != CURL_SOCKET_BAD) { +#ifdef USE_EVENTFD + const uint64_t buf[1] = { 1 }; +#else + const char buf[1] = { 1 }; +#endif + /* DNS has been resolved, signal client task */ + if(wakeup_write(addr_ctx->sock_pair[1], buf, sizeof(buf)) < 0) { + /* update sock_erro to errno */ + addr_ctx->sock_error = SOCKERRNO; + } + } +#endif + } + /* thread gives up its reference to the shared data now. */ + --addr_ctx->ref_count; + all_gone = !addr_ctx->ref_count; + Curl_mutex_release(&addr_ctx->mutx); + if(all_gone) + addr_ctx_destroy(addr_ctx); + + return 0; +} + +#else /* HAVE_GETADDRINFO */ + +/* + * gethostbyname_thread() resolves a name and then exits. + */ +static +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) +DWORD +#else +unsigned int +#endif +CURL_STDCALL gethostbyname_thread(void *arg) +{ + struct async_thrdd_addr_ctx *addr_ctx = arg; + bool all_gone; + + addr_ctx->res = Curl_ipv4_resolve_r(addr_ctx->hostname, addr_ctx->port); + + if(!addr_ctx->res) { + addr_ctx->sock_error = SOCKERRNO; + if(addr_ctx->sock_error == 0) + addr_ctx->sock_error = RESOLVER_ENOMEM; + } + + Curl_mutex_acquire(&addr_ctx->mutx); + /* thread gives up its reference to the shared data now. */ + --addr_ctx->ref_count; + all_gone = !addr_ctx->ref_count;; + Curl_mutex_release(&addr_ctx->mutx); + if(all_gone) + addr_ctx_destroy(addr_ctx); + + return 0; +} + +#endif /* HAVE_GETADDRINFO */ + +/* + * async_thrdd_destroy() cleans up async resolver data and thread handle. + */ +static void async_thrdd_destroy(struct Curl_easy *data) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + struct async_thrdd_addr_ctx *addr = thrdd->addr; +#ifdef USE_HTTPSRR_ARES + if(thrdd->rr.channel) { + ares_destroy(thrdd->rr.channel); + thrdd->rr.channel = NULL; + } + Curl_httpsrr_cleanup(&thrdd->rr.hinfo); +#endif + + if(addr) { +#ifndef CURL_DISABLE_SOCKETPAIR + curl_socket_t sock_rd = addr->sock_pair[0]; +#endif + bool done; + + /* Release our reference to the data shared with the thread. */ + Curl_mutex_acquire(&addr->mutx); + --addr->ref_count; + CURL_TRC_DNS(data, "resolve, destroy async data, shared ref=%d", + addr->ref_count); + done = !addr->ref_count; + /* we give up our reference to `addr`, so NULL our pointer. + * coverity analyses this as being a potential unsynched write, + * assuming two calls to this function could be invoked concurrently. + * Which they never are, as the transfer's side runs single-threaded. */ + thrdd->addr = NULL; + if(!done) { + /* thread is still running. Detach the thread while mutexed, it will + * trigger the cleanup when it releases its reference. */ + Curl_thread_destroy(&addr->thread_hnd); + } + Curl_mutex_release(&addr->mutx); + + if(done) { + /* thread has released its reference, join it and + * release the memory we shared with it. */ + if(addr->thread_hnd != curl_thread_t_null) + Curl_thread_join(&addr->thread_hnd); + addr_ctx_destroy(addr); + } +#ifndef CURL_DISABLE_SOCKETPAIR + /* + * ensure CURLMOPT_SOCKETFUNCTION fires CURL_POLL_REMOVE + * before the FD is invalidated to avoid EBADF on EPOLL_CTL_DEL + */ + Curl_multi_will_close(data, sock_rd); + wakeup_close(sock_rd); +#endif + } +} + +#ifdef USE_HTTPSRR_ARES + +static void async_thrdd_rr_done(void *user_data, ares_status_t status, + size_t timeouts, + const ares_dns_record_t *dnsrec) +{ + struct Curl_easy *data = user_data; + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + + (void)timeouts; + thrdd->rr.done = TRUE; + if((ARES_SUCCESS != status) || !dnsrec) + return; + thrdd->rr.result = Curl_httpsrr_from_ares(data, dnsrec, &thrdd->rr.hinfo); +} + +static CURLcode async_rr_start(struct Curl_easy *data) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + int status; + + DEBUGASSERT(!thrdd->rr.channel); + status = ares_init_options(&thrdd->rr.channel, NULL, 0); + if(status != ARES_SUCCESS) { + thrdd->rr.channel = NULL; + return CURLE_FAILED_INIT; + } + + memset(&thrdd->rr.hinfo, 0, sizeof(thrdd->rr.hinfo)); + thrdd->rr.hinfo.port = -1; + ares_query_dnsrec(thrdd->rr.channel, + data->conn->host.name, ARES_CLASS_IN, + ARES_REC_TYPE_HTTPS, + async_thrdd_rr_done, data, NULL); + return CURLE_OK; +} +#endif + +/* + * async_thrdd_init() starts a new thread that performs the actual + * resolve. This function returns before the resolve is done. + * + * Returns FALSE in case of failure, otherwise TRUE. + */ +static bool async_thrdd_init(struct Curl_easy *data, + const char *hostname, int port, int ip_version, + const struct addrinfo *hints) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + struct async_thrdd_addr_ctx *addr_ctx; + + /* !checksrc! disable ERRNOVAR 1 */ + int err = ENOMEM; + + if(thrdd->addr +#ifdef USE_HTTPSRR_ARES + || thrdd->rr.channel +#endif + ) { + CURL_TRC_DNS(data, "starting new resolve, with previous not cleaned up"); + async_thrdd_destroy(data); + DEBUGASSERT(!thrdd->addr); +#ifdef USE_HTTPSRR_ARES + DEBUGASSERT(!thrdd->rr.channel); +#endif + } + + data->state.async.dns = NULL; + data->state.async.done = FALSE; + data->state.async.port = port; + data->state.async.ip_version = ip_version; + data->state.async.hostname = strdup(hostname); + if(!data->state.async.hostname) + goto err_exit; + + addr_ctx = addr_ctx_create(hostname, port, hints); + if(!addr_ctx) + goto err_exit; + thrdd->addr = addr_ctx; + + Curl_mutex_acquire(&addr_ctx->mutx); + DEBUGASSERT(addr_ctx->ref_count == 1); + /* passing addr_ctx to the thread adds a reference */ + addr_ctx->start = curlx_now(); + ++addr_ctx->ref_count; +#ifdef HAVE_GETADDRINFO + addr_ctx->thread_hnd = Curl_thread_create(getaddrinfo_thread, addr_ctx); +#else + addr_ctx->thread_hnd = Curl_thread_create(gethostbyname_thread, addr_ctx); +#endif + if(addr_ctx->thread_hnd == curl_thread_t_null) { + /* The thread never started, remove its reference that never happened. */ + --addr_ctx->ref_count; + err = errno; + Curl_mutex_release(&addr_ctx->mutx); + goto err_exit; + } + Curl_mutex_release(&addr_ctx->mutx); + +#ifdef USE_HTTPSRR_ARES + if(async_rr_start(data)) + infof(data, "Failed HTTPS RR operation"); +#endif + CURL_TRC_DNS(data, "resolve thread started for of %s:%d", hostname, port); + return TRUE; + +err_exit: + CURL_TRC_DNS(data, "resolve thread failed init: %d", err); + async_thrdd_destroy(data); + CURL_SETERRNO(err); + return FALSE; +} + +/* + * 'entry' may be NULL and then no data is returned + */ +static CURLcode asyn_thrdd_await(struct Curl_easy *data, + struct async_thrdd_addr_ctx *addr_ctx, + struct Curl_dns_entry **entry) +{ + CURLcode result = CURLE_OK; + + DEBUGASSERT(addr_ctx->thread_hnd != curl_thread_t_null); + + CURL_TRC_DNS(data, "resolve, wait for thread to finish"); + /* wait for the thread to resolve the name */ + if(Curl_thread_join(&addr_ctx->thread_hnd)) { + if(entry) + result = Curl_async_is_resolved(data, entry); + } + else + DEBUGASSERT(0); + + data->state.async.done = TRUE; + if(entry) + *entry = data->state.async.dns; + + async_thrdd_destroy(data); + return result; +} + + +/* + * Until we gain a way to signal the resolver threads to stop early, we must + * simply wait for them and ignore their results. + */ +void Curl_async_thrdd_shutdown(struct Curl_easy *data) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + + /* 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(thrdd->addr && (thrdd->addr->thread_hnd != curl_thread_t_null) && + !data->set.quick_exit) + (void)asyn_thrdd_await(data, thrdd->addr, NULL); + else + async_thrdd_destroy(data); +} + +void Curl_async_thrdd_destroy(struct Curl_easy *data) +{ + Curl_async_thrdd_shutdown(data); +} + +/* + * Curl_async_await() + * + * Waits for a resolve to finish. This function should be avoided since using + * this risk getting the multi interface to "hang". + * + * If 'entry' is non-NULL, make it point to the resolved dns entry + * + * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, + * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. + * + * This is the version for resolves-in-a-thread. + */ +CURLcode Curl_async_await(struct Curl_easy *data, + struct Curl_dns_entry **entry) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + if(thrdd->addr) + return asyn_thrdd_await(data, thrdd->addr, entry); + return CURLE_FAILED_INIT; +} + +/* + * Curl_async_is_resolved() is called repeatedly to check if a previous + * name resolve request has completed. It should also make sure to time-out if + * the operation seems to take too long. + */ +CURLcode Curl_async_is_resolved(struct Curl_easy *data, + struct Curl_dns_entry **dns) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + bool done = FALSE; + + DEBUGASSERT(dns); + *dns = NULL; + + if(data->state.async.done) { + *dns = data->state.async.dns; + CURL_TRC_DNS(data, "threaded: is_resolved(), already done, dns=%sfound", + *dns ? "" : "not "); + return CURLE_OK; + } + +#ifdef USE_HTTPSRR_ARES + /* best effort, ignore errors */ + if(thrdd->rr.channel) + (void)Curl_ares_perform(thrdd->rr.channel, 0); +#endif + + DEBUGASSERT(thrdd->addr); + if(!thrdd->addr) + return CURLE_FAILED_INIT; + + Curl_mutex_acquire(&thrdd->addr->mutx); + done = (thrdd->addr->ref_count == 1); + Curl_mutex_release(&thrdd->addr->mutx); + + if(done) { + CURLcode result = CURLE_OK; + + data->state.async.done = TRUE; + Curl_resolv_unlink(data, &data->state.async.dns); + + if(thrdd->addr->res) { + data->state.async.dns = + Curl_dnscache_mk_entry(data, thrdd->addr->res, + data->state.async.hostname, 0, + data->state.async.port, FALSE); + thrdd->addr->res = NULL; + if(!data->state.async.dns) + result = CURLE_OUT_OF_MEMORY; + +#ifdef USE_HTTPSRR_ARES + if(thrdd->rr.channel) { + result = thrdd->rr.result; + if(!result) { + struct Curl_https_rrinfo *lhrr; + lhrr = Curl_httpsrr_dup_move(&thrdd->rr.hinfo); + if(!lhrr) + result = CURLE_OUT_OF_MEMORY; + else + data->state.async.dns->hinfo = lhrr; + } + } +#endif + if(!result && data->state.async.dns) + result = Curl_dnscache_add(data, data->state.async.dns); + } + + if(!result && !data->state.async.dns) + result = Curl_resolver_error(data); + if(result) + Curl_resolv_unlink(data, &data->state.async.dns); + *dns = data->state.async.dns; + CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound", + result, *dns ? "" : "not "); + async_thrdd_destroy(data); + return result; + } + else { + /* poll for name lookup done with exponential backoff up to 250ms */ + /* should be fine even if this converts to 32-bit */ + timediff_t elapsed = curlx_timediff(curlx_now(), + data->progress.t_startsingle); + if(elapsed < 0) + elapsed = 0; + + if(thrdd->addr->poll_interval == 0) + /* Start at 1ms poll interval */ + thrdd->addr->poll_interval = 1; + else if(elapsed >= thrdd->addr->interval_end) + /* Back-off exponentially if last interval expired */ + thrdd->addr->poll_interval *= 2; + + if(thrdd->addr->poll_interval > 250) + thrdd->addr->poll_interval = 250; + + thrdd->addr->interval_end = elapsed + thrdd->addr->poll_interval; + Curl_expire(data, thrdd->addr->poll_interval, EXPIRE_ASYNC_NAME); + return CURLE_OK; + } +} + +int Curl_async_getsock(struct Curl_easy *data, curl_socket_t *socks) +{ + struct async_thrdd_ctx *thrdd = &data->state.async.thrdd; + int ret_val = 0; +#if !defined(CURL_DISABLE_SOCKETPAIR) || defined(USE_HTTPSRR_ARES) + int socketi = 0; +#else + (void)socks; +#endif + +#ifdef USE_HTTPSRR_ARES + if(thrdd->rr.channel) { + ret_val = Curl_ares_getsock(data, thrdd->rr.channel, socks); + for(socketi = 0; socketi < (MAX_SOCKSPEREASYHANDLE - 1); socketi++) + if(!ARES_GETSOCK_READABLE(ret_val, socketi) && + !ARES_GETSOCK_WRITABLE(ret_val, socketi)) + break; + } +#endif + if(!thrdd->addr) + return ret_val; + +#ifndef CURL_DISABLE_SOCKETPAIR + if(thrdd->addr) { + /* return read fd to client for polling the DNS resolution status */ + socks[socketi] = thrdd->addr->sock_pair[0]; + ret_val |= GETSOCK_READSOCK(socketi); + } + else +#endif + { + timediff_t milli; + timediff_t ms = curlx_timediff(curlx_now(), thrdd->addr->start); + if(ms < 3) + milli = 0; + else if(ms <= 50) + milli = ms/3; + else if(ms <= 250) + milli = 50; + else + milli = 200; + Curl_expire(data, milli, EXPIRE_ASYNC_NAME); + } + + return ret_val; +} + +#ifndef HAVE_GETADDRINFO +/* + * Curl_async_getaddrinfo() - for platforms without getaddrinfo + */ +struct Curl_addrinfo *Curl_async_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + int *waitp) +{ + (void)ip_version; + *waitp = 0; /* default to synchronous response */ + + /* fire up a new resolver thread! */ + if(async_thrdd_init(data, hostname, port, ip_version, NULL)) { + *waitp = 1; /* expect asynchronous response */ + return NULL; + } + + failf(data, "getaddrinfo() thread failed"); + + return NULL; +} + +#else /* !HAVE_GETADDRINFO */ + +/* + * Curl_async_getaddrinfo() - for getaddrinfo + */ +struct Curl_addrinfo *Curl_async_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + int *waitp) +{ + struct addrinfo hints; + int pf = PF_INET; + *waitp = 0; /* default to synchronous response */ + + CURL_TRC_DNS(data, "init threaded resolve of %s:%d", hostname, port); +#ifdef CURLRES_IPV6 + if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { + /* The stack seems to be IPv6-enabled */ + if(ip_version == CURL_IPRESOLVE_V6) + pf = PF_INET6; + else + pf = PF_UNSPEC; + } +#else + (void)ip_version; +#endif /* CURLRES_IPV6 */ + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = pf; + hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP) ? + SOCK_STREAM : SOCK_DGRAM; + + /* fire up a new resolver thread! */ + if(async_thrdd_init(data, hostname, port, ip_version, &hints)) { + *waitp = 1; /* expect asynchronous response */ + return NULL; + } + + failf(data, "getaddrinfo() thread failed to start"); + return NULL; + +} + +#endif /* !HAVE_GETADDRINFO */ + +#endif /* CURLRES_THREADED */ diff --git a/Utilities/cmcurl/lib/asyn-thread.c b/Utilities/cmcurl/lib/asyn-thread.c deleted file mode 100644 index f98941596e..0000000000 --- a/Utilities/cmcurl/lib/asyn-thread.c +++ /dev/null @@ -1,812 +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" -#include "socketpair.h" - -/*********************************************************************** - * Only for threaded name resolves builds - **********************************************************************/ -#ifdef CURLRES_THREADED - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) -# include -#endif - -#ifdef HAVE_GETADDRINFO -# define RESOLVER_ENOMEM EAI_MEMORY -#else -# define RESOLVER_ENOMEM ENOMEM -#endif - -#include "urldata.h" -#include "sendf.h" -#include "hostip.h" -#include "hash.h" -#include "share.h" -#include "url.h" -#include "multiif.h" -#include "inet_ntop.h" -#include "curl_threads.h" -#include "connect.h" -#include "strdup.h" - -#ifdef USE_ARES -#include -#ifdef USE_HTTPSRR -#define USE_HTTPSRR_ARES 1 /* the combo */ -#endif -#endif - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -struct resdata { - struct curltime start; -}; - -/* - * Curl_resolver_global_init() - * Called from curl_global_init() to initialize global resolver environment. - * Does nothing here. - */ -int Curl_resolver_global_init(void) -{ - return CURLE_OK; -} - -/* - * Curl_resolver_global_cleanup() - * Called from curl_global_cleanup() to destroy global resolver environment. - * Does nothing here. - */ -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). - */ -CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) -{ - (void)easy; - *resolver = calloc(1, sizeof(struct resdata)); - if(!*resolver) - return CURLE_OUT_OF_MEMORY; - return CURLE_OK; -} - -/* - * Curl_resolver_cleanup() - * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). - */ -void Curl_resolver_cleanup(void *resolver) -{ - free(resolver); -} - -/* - * Curl_resolver_duphandle() - * Called from curl_easy_duphandle() to duplicate resolver URL state-specific - * environment ('resolver' member of the UrlState structure). - */ -CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from) -{ - (void)from; - return Curl_resolver_init(easy, to); -} - -static void destroy_async_data(struct Curl_easy *); - -/* - * Cancel all possibly still on-going resolves for this connection. - */ -void Curl_resolver_cancel(struct Curl_easy *data) -{ - destroy_async_data(data); -} - -/* This function is used to init a threaded resolve */ -static bool init_resolve_thread(struct Curl_easy *data, - const char *hostname, int port, - const struct addrinfo *hints); - - -static struct thread_sync_data *conn_thread_sync_data(struct Curl_easy *data) -{ - return &(data->state.async.tdata->tsd); -} - -/* Destroy resolver thread synchronization data */ -static -void destroy_thread_sync_data(struct thread_sync_data *tsd) -{ - if(tsd->mtx) { - Curl_mutex_destroy(tsd->mtx); - free(tsd->mtx); - } - - free(tsd->hostname); - - if(tsd->res) - Curl_freeaddrinfo(tsd->res); - -#ifndef CURL_DISABLE_SOCKETPAIR - /* - * close one end of the socket pair (may be done in resolver thread); - * the other end (for reading) is always closed in the parent thread. - */ -#ifndef USE_EVENTFD - if(tsd->sock_pair[1] != CURL_SOCKET_BAD) { - wakeup_close(tsd->sock_pair[1]); - } -#endif -#endif - memset(tsd, 0, sizeof(*tsd)); -} - -/* Initialize resolver thread synchronization data */ -static -int init_thread_sync_data(struct thread_data *td, - const char *hostname, - int port, - const struct addrinfo *hints) -{ - struct thread_sync_data *tsd = &td->tsd; - - memset(tsd, 0, sizeof(*tsd)); - - tsd->td = td; - tsd->port = port; - /* Treat the request as done until the thread actually starts so any early - * cleanup gets done properly. - */ - tsd->done = TRUE; -#ifdef HAVE_GETADDRINFO - DEBUGASSERT(hints); - tsd->hints = *hints; -#else - (void) hints; -#endif - - tsd->mtx = malloc(sizeof(curl_mutex_t)); - if(!tsd->mtx) - goto err_exit; - - Curl_mutex_init(tsd->mtx); - -#ifndef CURL_DISABLE_SOCKETPAIR - /* 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; - } -#endif - tsd->sock_error = CURL_ASYNC_SUCCESS; - - /* Copying hostname string because original can be destroyed by parent - * thread during gethostbyname execution. - */ - tsd->hostname = strdup(hostname); - if(!tsd->hostname) - goto err_exit; - - return 1; - -err_exit: -#ifndef CURL_DISABLE_SOCKETPAIR - if(tsd->sock_pair[0] != CURL_SOCKET_BAD) { - wakeup_close(tsd->sock_pair[0]); - tsd->sock_pair[0] = CURL_SOCKET_BAD; - } -#endif - destroy_thread_sync_data(tsd); - return 0; -} - -static CURLcode getaddrinfo_complete(struct Curl_easy *data) -{ - struct thread_sync_data *tsd = conn_thread_sync_data(data); - CURLcode result; - - 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 does not free it. - */ - tsd->res = NULL; - - return result; -} - - -#ifdef HAVE_GETADDRINFO - -/* - * getaddrinfo_thread() resolves a name and then exits. - * - * For builds without ARES, but with USE_IPV6, create a resolver thread - * and wait on it. - */ -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; - - msnprintf(service, sizeof(service), "%d", tsd->port); - - rc = Curl_getaddrinfo_ex(tsd->hostname, service, &tsd->hints, &tsd->res); - - if(rc) { - tsd->sock_error = SOCKERRNO ? SOCKERRNO : rc; - if(tsd->sock_error == 0) - tsd->sock_error = RESOLVER_ENOMEM; - } - else { - Curl_addrinfo_set_port(tsd->res, tsd->port); - } - - Curl_mutex_acquire(tsd->mtx); - if(tsd->done) { - /* too late, gotta clean up the mess */ - Curl_mutex_release(tsd->mtx); - destroy_thread_sync_data(tsd); - free(td); - } - else { -#ifndef CURL_DISABLE_SOCKETPAIR - if(tsd->sock_pair[1] != CURL_SOCKET_BAD) { -#ifdef USE_EVENTFD - const uint64_t buf[1] = { 1 }; -#else - const char buf[1] = { 1 }; -#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; - } - } -#endif - tsd->done = TRUE; - Curl_mutex_release(tsd->mtx); - } - - return 0; -} - -#else /* HAVE_GETADDRINFO */ - -/* - * gethostbyname_thread() resolves a name and then exits. - */ -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; - - tsd->res = Curl_ipv4_resolve_r(tsd->hostname, tsd->port); - - if(!tsd->res) { - tsd->sock_error = SOCKERRNO; - if(tsd->sock_error == 0) - tsd->sock_error = RESOLVER_ENOMEM; - } - - Curl_mutex_acquire(tsd->mtx); - if(tsd->done) { - /* too late, gotta clean up the mess */ - Curl_mutex_release(tsd->mtx); - destroy_thread_sync_data(tsd); - free(td); - } - else { - tsd->done = TRUE; - Curl_mutex_release(tsd->mtx); - } - - return 0; -} - -#endif /* HAVE_GETADDRINFO */ - -/* - * destroy_async_data() cleans up async resolver data and thread handle. - */ -static void destroy_async_data(struct Curl_easy *data) -{ - struct Curl_async *async; - DEBUGASSERT(data); - async = &data->state.async; - DEBUGASSERT(async); - if(async->tdata) { - struct thread_data *td = async->tdata; - bool done; -#ifndef CURL_DISABLE_SOCKETPAIR - curl_socket_t sock_rd = td->tsd.sock_pair[0]; -#endif - -#ifdef USE_HTTPSRR_ARES - if(data->state.async.tdata->channel) - ares_destroy(data->state.async.tdata->channel); -#endif - /* - * if the thread is still blocking in the resolve syscall, detach it and - * let the thread do the cleanup... - */ - Curl_mutex_acquire(td->tsd.mtx); - done = td->tsd.done; - td->tsd.done = TRUE; - Curl_mutex_release(td->tsd.mtx); - - if(!done) { - Curl_thread_destroy(td->thread_hnd); - } - else { - if(td->thread_hnd != curl_thread_t_null) - Curl_thread_join(&td->thread_hnd); - - destroy_thread_sync_data(&td->tsd); - - free(async->tdata); - } -#ifndef CURL_DISABLE_SOCKETPAIR - /* - * ensure CURLMOPT_SOCKETFUNCTION fires CURL_POLL_REMOVE - * before the FD is invalidated to avoid EBADF on EPOLL_CTL_DEL - */ - Curl_multi_closed(data, sock_rd); - wakeup_close(sock_rd); -#endif - } - async->tdata = NULL; - - free(async->hostname); - async->hostname = NULL; -} - -#ifdef USE_HTTPSRR_ARES -static CURLcode resolve_httpsrr(struct Curl_easy *data, - struct Curl_async *asp) -{ - int status = ares_init_options(&asp->tdata->channel, NULL, 0); - if(status != ARES_SUCCESS) - return CURLE_FAILED_INIT; - - memset(&asp->tdata->hinfo, 0, sizeof(struct Curl_https_rrinfo)); - ares_query_dnsrec(asp->tdata->channel, - asp->hostname, ARES_CLASS_IN, - ARES_REC_TYPE_HTTPS, - Curl_dnsrec_done_cb, data, NULL); - - return CURLE_OK; -} -#endif - -/* - * init_resolve_thread() starts a new thread that performs the actual - * resolve. This function returns before the resolve is done. - * - * Returns FALSE in case of failure, otherwise TRUE. - */ -static bool init_resolve_thread(struct Curl_easy *data, - const char *hostname, int port, - const struct addrinfo *hints) -{ - struct thread_data *td = calloc(1, sizeof(struct thread_data)); - int err = ENOMEM; - struct Curl_async *asp = &data->state.async; - - data->state.async.tdata = td; - if(!td) - goto errno_exit; - - asp->port = port; - asp->done = FALSE; - asp->status = 0; - asp->dns = NULL; - td->thread_hnd = curl_thread_t_null; - - if(!init_thread_sync_data(td, hostname, port, hints)) { - asp->tdata = NULL; - free(td); - goto errno_exit; - } - - free(asp->hostname); - asp->hostname = strdup(hostname); - if(!asp->hostname) - goto err_exit; - - /* The thread will set this TRUE when complete. */ - td->tsd.done = FALSE; - -#ifdef HAVE_GETADDRINFO - td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd); -#else - td->thread_hnd = Curl_thread_create(gethostbyname_thread, &td->tsd); -#endif - - if(td->thread_hnd == curl_thread_t_null) { - /* The thread never started, so mark it as done here for proper cleanup. */ - td->tsd.done = TRUE; - err = errno; - goto err_exit; - } -#ifdef USE_HTTPSRR_ARES - if(resolve_httpsrr(data, asp)) - infof(data, "Failed HTTPS RR operation"); -#endif - return TRUE; - -err_exit: - destroy_async_data(data); - -errno_exit: - errno = err; - return FALSE; -} - -/* - * 'entry' may be NULL and then no data is returned - */ -static CURLcode thread_wait_resolv(struct Curl_easy *data, - struct Curl_dns_entry **entry, - bool report) -{ - struct thread_data *td; - CURLcode result = CURLE_OK; - - DEBUGASSERT(data); - td = data->state.async.tdata; - DEBUGASSERT(td); - DEBUGASSERT(td->thread_hnd != curl_thread_t_null); - - /* wait for the thread to resolve the name */ - if(Curl_thread_join(&td->thread_hnd)) { - if(entry) - result = getaddrinfo_complete(data); - } - else - DEBUGASSERT(0); - - data->state.async.done = TRUE; - - if(entry) - *entry = data->state.async.dns; - - if(!data->state.async.dns && report) - /* a name was not resolved, report error */ - result = Curl_resolver_error(data); - - destroy_async_data(data); - - if(!data->state.async.dns && report) - connclose(data->conn, "asynch resolve failed"); - - return result; -} - - -/* - * Until we gain a way to signal the resolver threads to stop early, we must - * simply wait for them and ignore their results. - */ -void Curl_resolver_kill(struct Curl_easy *data) -{ - struct thread_data *td = data->state.async.tdata; - - /* 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)) - (void)thread_wait_resolv(data, NULL, FALSE); - else - Curl_resolver_cancel(data); -} - -/* - * Curl_resolver_wait_resolv() - * - * Waits for a resolve to finish. This function should be avoided since using - * this risk getting the multi interface to "hang". - * - * If 'entry' is non-NULL, make it point to the resolved dns entry - * - * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, - * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. - * - * This is the version for resolves-in-a-thread. - */ -CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, - struct Curl_dns_entry **entry) -{ - return thread_wait_resolv(data, entry, TRUE); -} - -/* - * Curl_resolver_is_resolved() is called repeatedly to check if a previous - * name resolve request has completed. It should also make sure to time-out if - * the operation seems to take too long. - */ -CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, - struct Curl_dns_entry **entry) -{ - struct thread_data *td = data->state.async.tdata; - bool done = FALSE; - - DEBUGASSERT(entry); - *entry = NULL; - - if(!td) { - DEBUGASSERT(td); - return CURLE_COULDNT_RESOLVE_HOST; - } -#ifdef USE_HTTPSRR_ARES - if(Curl_ares_perform(data->state.async.tdata->channel, 0) < 0) - return CURLE_UNRECOVERABLE_POLL; -#endif - - Curl_mutex_acquire(td->tsd.mtx); - done = td->tsd.done; - Curl_mutex_release(td->tsd.mtx); - - if(done) { - getaddrinfo_complete(data); - - if(!data->state.async.dns) { - CURLcode result = Curl_resolver_error(data); - destroy_async_data(data); - return result; - } -#ifdef USE_HTTPSRR_ARES - { - struct Curl_https_rrinfo *lhrr = - Curl_memdup(&td->hinfo, sizeof(struct Curl_https_rrinfo)); - if(!lhrr) { - destroy_async_data(data); - return CURLE_OUT_OF_MEMORY; - } - data->state.async.dns->hinfo = lhrr; - } -#endif - destroy_async_data(data); - *entry = data->state.async.dns; - } - else { - /* poll for name lookup done with exponential backoff up to 250ms */ - /* should be fine even if this converts to 32-bit */ - timediff_t elapsed = Curl_timediff(Curl_now(), - data->progress.t_startsingle); - if(elapsed < 0) - elapsed = 0; - - if(td->poll_interval == 0) - /* Start at 1ms poll interval */ - td->poll_interval = 1; - else if(elapsed >= td->interval_end) - /* Back-off exponentially if last interval expired */ - td->poll_interval *= 2; - - if(td->poll_interval > 250) - td->poll_interval = 250; - - td->interval_end = elapsed + td->poll_interval; - Curl_expire(data, td->poll_interval, EXPIRE_ASYNC_NAME); - } - - return CURLE_OK; -} - -int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *socks) -{ - int ret_val = 0; - timediff_t milli; - timediff_t ms; - struct resdata *reslv = (struct resdata *)data->state.async.resolver; -#ifndef CURL_DISABLE_SOCKETPAIR - struct thread_data *td = data->state.async.tdata; -#endif -#if !defined(CURL_DISABLE_SOCKETPAIR) || defined(USE_HTTPSRR_ARES) - int socketi = 0; -#else - (void)socks; -#endif - -#ifdef USE_HTTPSRR_ARES - if(data->state.async.tdata && data->state.async.tdata->channel) { - ret_val = Curl_ares_getsock(data, data->state.async.tdata->channel, socks); - for(socketi = 0; socketi < (MAX_SOCKSPEREASYHANDLE - 1); socketi++) - if(!ARES_GETSOCK_READABLE(ret_val, socketi) && - !ARES_GETSOCK_WRITABLE(ret_val, socketi)) - break; - } -#endif -#ifndef CURL_DISABLE_SOCKETPAIR - if(td) { - /* return read fd to client for polling the DNS resolution status */ - socks[socketi] = td->tsd.sock_pair[0]; - ret_val |= GETSOCK_READSOCK(socketi); - } - else { -#endif - ms = Curl_timediff(Curl_now(), reslv->start); - if(ms < 3) - milli = 0; - else if(ms <= 50) - milli = ms/3; - else if(ms <= 250) - milli = 50; - else - milli = 200; - Curl_expire(data, milli, EXPIRE_ASYNC_NAME); -#ifndef CURL_DISABLE_SOCKETPAIR - } -#endif - - - return ret_val; -} - -#ifndef HAVE_GETADDRINFO -/* - * Curl_getaddrinfo() - for platforms without getaddrinfo - */ -struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) -{ - struct resdata *reslv = (struct resdata *)data->state.async.resolver; - - *waitp = 0; /* default to synchronous response */ - - reslv->start = Curl_now(); - - /* fire up a new resolver thread! */ - if(init_resolve_thread(data, hostname, port, NULL)) { - *waitp = 1; /* expect asynchronous response */ - return NULL; - } - - failf(data, "getaddrinfo() thread failed"); - - return NULL; -} - -#else /* !HAVE_GETADDRINFO */ - -/* - * Curl_resolver_getaddrinfo() - for getaddrinfo - */ -struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) -{ - struct addrinfo hints; - int pf = PF_INET; - struct resdata *reslv = (struct resdata *)data->state.async.resolver; - - *waitp = 0; /* default to synchronous response */ - -#ifdef CURLRES_IPV6 - if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { - /* The stack seems to be IPv6-enabled */ - if(data->conn->ip_version == CURL_IPRESOLVE_V6) - pf = PF_INET6; - else - pf = PF_UNSPEC; - } -#endif /* CURLRES_IPV6 */ - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = pf; - hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP) ? - SOCK_STREAM : SOCK_DGRAM; - - reslv->start = Curl_now(); - /* fire up a new resolver thread! */ - if(init_resolve_thread(data, hostname, port, &hints)) { - *waitp = 1; /* expect asynchronous response */ - return NULL; - } - - failf(data, "getaddrinfo() thread failed to start"); - return NULL; - -} - -#endif /* !HAVE_GETADDRINFO */ - -CURLcode Curl_set_dns_servers(struct Curl_easy *data, - char *servers) -{ - (void)data; - (void)servers; - return CURLE_NOT_BUILT_IN; - -} - -CURLcode Curl_set_dns_interface(struct Curl_easy *data, - const char *interf) -{ - (void)data; - (void)interf; - return CURLE_NOT_BUILT_IN; -} - -CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, - const char *local_ip4) -{ - (void)data; - (void)local_ip4; - return CURLE_NOT_BUILT_IN; -} - -CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, - const char *local_ip6) -{ - (void)data; - (void)local_ip6; - return CURLE_NOT_BUILT_IN; -} - -#endif /* CURLRES_THREADED */ diff --git a/Utilities/cmcurl/lib/asyn.h b/Utilities/cmcurl/lib/asyn.h index 5a21329cf3..1cc7175beb 100644 --- a/Utilities/cmcurl/lib/asyn.h +++ b/Utilities/cmcurl/lib/asyn.h @@ -25,76 +25,22 @@ ***************************************************************************/ #include "curl_setup.h" + +struct Curl_easy; +struct Curl_dns_entry; + +#ifdef CURLRES_ASYNCH + #include "curl_addrinfo.h" #include "httpsrr.h" struct addrinfo; struct hostent; -struct Curl_easy; struct connectdata; -struct Curl_dns_entry; -#ifdef CURLRES_THREADED -#include "curl_threads.h" - -/* Data for synchronization between resolver thread and its parent */ -struct thread_sync_data { - curl_mutex_t *mtx; - char *hostname; /* hostname to resolve, Curl_async.hostname - duplicate */ -#ifndef CURL_DISABLE_SOCKETPAIR - curl_socket_t sock_pair[2]; /* eventfd/pipes/socket pair */ +#if defined(CURLRES_ARES) && defined(CURLRES_THREADED) +#error cannot have both CURLRES_ARES and CURLRES_THREADED defined #endif - struct Curl_addrinfo *res; -#ifdef HAVE_GETADDRINFO - struct addrinfo hints; -#endif - struct thread_data *td; /* for thread-self cleanup */ - int port; - int sock_error; - bool done; -}; - -struct thread_data { - curl_thread_t thread_hnd; - unsigned int poll_interval; - timediff_t interval_end; - struct thread_sync_data tsd; -#if defined(USE_HTTPSRR) && defined(USE_ARES) - struct Curl_https_rrinfo hinfo; - ares_channel channel; -#endif -}; - -#elif defined(CURLRES_ARES) /* CURLRES_THREADED */ - -struct thread_data { - int num_pending; /* number of outstanding c-ares requests */ - struct Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares - parts */ - int last_status; -#ifndef HAVE_CARES_GETADDRINFO - struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */ -#endif -#ifdef USE_HTTPSRR - struct Curl_https_rrinfo hinfo; -#endif - char hostname[1]; -}; - -#endif /* CURLRES_ARES */ - -#ifdef USE_ARES -#include - -/* for HTTPS RR purposes as well */ -int Curl_ares_getsock(struct Curl_easy *data, - ares_channel channel, - curl_socket_t *socks); -int Curl_ares_perform(ares_channel channel, - timediff_t timeout_ms); -#endif - /* * This header defines all functions in the internal asynch resolver interface. @@ -104,85 +50,38 @@ int Curl_ares_perform(ares_channel channel, */ /* - * Curl_resolver_global_init() + * Curl_async_global_init() * * Called from curl_global_init() to initialize global resolver environment. * Returning anything else than CURLE_OK fails curl_global_init(). */ -int Curl_resolver_global_init(void); +int Curl_async_global_init(void); /* - * Curl_resolver_global_cleanup() + * Curl_async_global_cleanup() * Called from curl_global_cleanup() to destroy global resolver environment. */ -void Curl_resolver_global_cleanup(void); +void Curl_async_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. - * Returning anything else than CURLE_OK fails curl_easy_init() with the - * correspondent code. + * Curl_async_get_impl() + * Get the resolver implementation instance (c-ares channel) or NULL + * for passing to application callback. */ -CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver); +CURLcode Curl_async_get_impl(struct Curl_easy *easy, void **impl); -/* - * 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 - * it. - */ -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 - * duplicate the 'from' handle and pass the resulting handle to the 'to' - * pointer. Returning anything else than CURLE_OK causes failed - * curl_easy_duphandle() call. - */ -CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, - void *from); - -/* - * Curl_resolver_cancel(). +/* Curl_async_getsock() * - * 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. - * - * It is safe to call this when conn is in any state. - */ -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 - * that cleans up an in-progress resolver completely (before its owner is about - * to die). - * - * It is safe to call this when conn is in any state. - */ -void Curl_resolver_kill(struct Curl_easy *data); - -/* Curl_resolver_getsock() - * - * This function is called from the multi_getsock() function. 'sock' is a + * This function is called from the Curl_multi_getsock() function. 'sock' is a * pointer to an array to hold the file descriptors, with 'numsock' being the * size of that array (in number of entries). This function is supposed to * return bitmask indicating what file descriptors (referring to array indexes * in the 'sock' array) to wait for, read/write. */ -int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *sock); +int Curl_async_getsock(struct Curl_easy *data, curl_socket_t *sock); /* - * Curl_resolver_is_resolved() + * Curl_async_is_resolved() * * Called repeatedly to check if a previous name resolve request has * completed. It should also make sure to time-out if the operation seems to @@ -190,25 +89,25 @@ int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *sock); * * Returns normal CURLcode errors. */ -CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, - struct Curl_dns_entry **dns); +CURLcode Curl_async_is_resolved(struct Curl_easy *data, + struct Curl_dns_entry **dns); /* - * Curl_resolver_wait_resolv() + * Curl_async_await() * * Waits for a resolve to finish. This function should be avoided since using * this risk getting the multi interface to "hang". * - * If 'entry' is non-NULL, make it point to the resolved dns entry + * On return 'entry' is assigned the resolved dns (CURLE_OK or NULL otherwise. * * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. */ -CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, - struct Curl_dns_entry **dnsentry); +CURLcode Curl_async_await(struct Curl_easy *data, + struct Curl_dns_entry **dnsentry); /* - * Curl_resolver_getaddrinfo() - when using this resolver + * Curl_async_getaddrinfo() - when using this resolver * * Returns name information about the given hostname and port number. If * successful, the 'hostent' is returned and the fourth argument will point to @@ -218,29 +117,157 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, * Each resolver backend must of course make sure to return data in the * correct format to comply with this. */ -struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp); +struct Curl_addrinfo *Curl_async_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + int *waitp); + +#ifdef USE_ARES +/* common functions for c-ares and threaded resolver with HTTPSRR */ +#include + +int Curl_ares_getsock(struct Curl_easy *data, + ares_channel channel, + curl_socket_t *socks); +int Curl_ares_perform(ares_channel channel, + timediff_t timeout_ms); +#endif + +#ifdef CURLRES_ARES +/* async resolving implementation using c-ares alone */ +struct async_ares_ctx { + ares_channel channel; + int num_pending; /* number of outstanding c-ares requests */ + struct Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares + parts */ + int last_status; + CURLcode result; /* CURLE_OK or error handling response */ +#ifndef HAVE_CARES_GETADDRINFO + struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */ +#endif +#ifdef USE_HTTPSRR + struct Curl_https_rrinfo hinfo; +#endif +}; + +void Curl_async_ares_shutdown(struct Curl_easy *data); +void Curl_async_ares_destroy(struct Curl_easy *data); + +/* Set the DNS server to use by ares, from `data` settings. */ +CURLcode Curl_async_ares_set_dns_servers(struct Curl_easy *data); + +/* Set the DNS interfacer to use by ares, from `data` settings. */ +CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data); + +/* Set the local ipv4 address to use by ares, from `data` settings. */ +CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data); + +/* Set the local ipv6 address to use by ares, from `data` settings. */ +CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data); + +#endif /* CURLRES_ARES */ + +#ifdef CURLRES_THREADED +/* async resolving implementation using POSIX threads */ +#include "curl_threads.h" + +/* Context for threaded address resolver */ +struct async_thrdd_addr_ctx { + curl_thread_t thread_hnd; + char *hostname; /* hostname to resolve, Curl_async.hostname + duplicate */ + curl_mutex_t mutx; +#ifndef CURL_DISABLE_SOCKETPAIR + curl_socket_t sock_pair[2]; /* eventfd/pipes/socket pair */ +#endif + struct Curl_addrinfo *res; +#ifdef HAVE_GETADDRINFO + struct addrinfo hints; +#endif + struct curltime start; + timediff_t interval_end; + unsigned int poll_interval; + int port; + int sock_error; + int ref_count; +}; + +/* Context for threaded resolver */ +struct async_thrdd_ctx { + /* `addr` is a pointer since this memory is shared with a started + * thread. Since threads cannot be killed, we use reference counting + * so that we can "release" our pointer to this memory while the + * thread is still running. */ + struct async_thrdd_addr_ctx *addr; +#if defined(USE_HTTPSRR) && defined(USE_ARES) + struct { + ares_channel channel; + struct Curl_https_rrinfo hinfo; + CURLcode result; + BIT(done); + } rr; +#endif +}; + +void Curl_async_thrdd_shutdown(struct Curl_easy *data); +void Curl_async_thrdd_destroy(struct Curl_easy *data); + +#endif /* CURLRES_THREADED */ + +#ifndef CURL_DISABLE_DOH +struct doh_probes; +#endif + +#else /* CURLRES_ASYNCH */ -#ifndef CURLRES_ASYNCH /* 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 -#define Curl_resolver_wait_resolv(x,y) CURLE_COULDNT_RESOLVE_HOST -#define Curl_resolver_duphandle(x,y,z) CURLE_OK -#define Curl_resolver_init(x,y) CURLE_OK -#define Curl_resolver_global_init() CURLE_OK -#define Curl_resolver_global_cleanup() Curl_nop_stmt -#define Curl_resolver_cleanup(x) Curl_nop_stmt +#define Curl_async_get_impl(x,y) (*(y) = NULL, CURLE_OK) +#define Curl_async_is_resolved(x,y) CURLE_COULDNT_RESOLVE_HOST +#define Curl_async_await(x,y) CURLE_COULDNT_RESOLVE_HOST +#define Curl_async_global_init() CURLE_OK +#define Curl_async_global_cleanup() Curl_nop_stmt + +#endif /* !CURLRES_ASYNCH */ + +#if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH) +#define USE_CURL_ASYNC #endif -#ifdef CURLRES_ASYNCH -#define Curl_resolver_asynch() 1 -#else -#define Curl_resolver_asynch() 0 +#ifdef USE_CURL_ASYNC +struct Curl_async { +#ifdef CURLRES_ARES /* */ + struct async_ares_ctx ares; +#elif defined(CURLRES_THREADED) + struct async_thrdd_ctx thrdd; #endif +#ifndef CURL_DISABLE_DOH + struct doh_probes *doh; /* DoH specific data for this request */ +#endif + struct Curl_dns_entry *dns; /* result of resolving on success */ + char *hostname; /* copy of the params resolv started with */ + int port; + int ip_version; + BIT(done); +}; + +/* + * Curl_async_shutdown(). + * + * This shuts down all ongoing operations. + */ +void Curl_async_shutdown(struct Curl_easy *data); + +/* + * Curl_async_destroy(). + * + * This frees the resources of any async resolve. + */ +void Curl_async_destroy(struct Curl_easy *data); +#else /* !USE_CURL_ASYNC */ +#define Curl_async_shutdown(x) Curl_nop_stmt +#define Curl_async_destroy(x) Curl_nop_stmt +#endif /* USE_CURL_ASYNC */ /********** end of generic resolver interface functions *****************/ diff --git a/Utilities/cmcurl/lib/bufref.c b/Utilities/cmcurl/lib/bufref.c index f048b57011..ac0612071d 100644 --- a/Utilities/cmcurl/lib/bufref.c +++ b/Utilities/cmcurl/lib/bufref.c @@ -30,7 +30,9 @@ #include "curl_memory.h" #include "memdebug.h" +#ifdef DEBUGBUILD #define SIGNATURE 0x5c48e9b2 /* Random pattern. */ +#endif /* * Init a bufref struct. @@ -59,7 +61,7 @@ void Curl_bufref_free(struct bufref *br) DEBUGASSERT(br->ptr || !br->len); if(br->ptr && br->dtor) - br->dtor((void *) br->ptr); + br->dtor(CURL_UNCONST(br->ptr)); br->dtor = NULL; br->ptr = NULL; diff --git a/Utilities/cmcurl/lib/cf-h1-proxy.c b/Utilities/cmcurl/lib/cf-h1-proxy.c index 9b75d086e5..df6f575454 100644 --- a/Utilities/cmcurl/lib/cf-h1-proxy.c +++ b/Utilities/cmcurl/lib/cf-h1-proxy.c @@ -28,7 +28,7 @@ #include #include "urldata.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "sendf.h" #include "http.h" #include "http1.h" @@ -40,10 +40,11 @@ #include "cf-h1-proxy.h" #include "connect.h" #include "curl_trc.h" -#include "curlx.h" +#include "strcase.h" #include "vtls/vtls.h" #include "transfer.h" #include "multiif.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -96,8 +97,8 @@ static CURLcode tunnel_reinit(struct Curl_cfilter *cf, (void)data; (void)cf; DEBUGASSERT(ts); - Curl_dyn_reset(&ts->rcvbuf); - Curl_dyn_reset(&ts->request_data); + curlx_dyn_reset(&ts->rcvbuf); + curlx_dyn_reset(&ts->request_data); ts->tunnel_state = H1_TUNNEL_INIT; ts->keepon = KEEPON_CONNECT; ts->cl = 0; @@ -122,8 +123,8 @@ static CURLcode tunnel_init(struct Curl_cfilter *cf, infof(data, "allocate connect buffer"); - Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS); - Curl_dyn_init(&ts->request_data, DYN_HTTP_REQUEST); + curlx_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS); + curlx_dyn_init(&ts->request_data, DYN_HTTP_REQUEST); Curl_httpchunk_init(data, &ts->ch, TRUE); *pts = ts; @@ -149,7 +150,7 @@ static void h1_tunnel_go_state(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "new tunnel state 'connect'"); ts->tunnel_state = H1_TUNNEL_CONNECT; ts->keepon = KEEPON_CONNECT; - Curl_dyn_reset(&ts->rcvbuf); + curlx_dyn_reset(&ts->rcvbuf); break; case H1_TUNNEL_RECEIVE: @@ -172,8 +173,8 @@ static void h1_tunnel_go_state(struct Curl_cfilter *cf, 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->request_data); + curlx_dyn_reset(&ts->rcvbuf); + curlx_dyn_reset(&ts->request_data); /* restore the protocol pointer */ data->info.httpcode = 0; /* clear it as it might've been used for the proxy */ @@ -192,8 +193,8 @@ static void tunnel_free(struct Curl_cfilter *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); + curlx_dyn_free(&ts->rcvbuf); + curlx_dyn_free(&ts->request_data); Curl_httpchunk_free(data, &ts->ch); free(ts); cf->ctx = NULL; @@ -225,7 +226,7 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, infof(data, "Establish HTTP proxy tunnel to %s", req->authority); - Curl_dyn_reset(&ts->request_data); + curlx_dyn_reset(&ts->request_data); ts->nsent = 0; ts->headerlines = 0; http_minor = (cf->conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? 0 : 1; @@ -247,8 +248,8 @@ static CURLcode send_CONNECT(struct Curl_cfilter *cf, struct h1_tunnel_state *ts, bool *done) { - char *buf = Curl_dyn_ptr(&ts->request_data); - size_t request_len = Curl_dyn_len(&ts->request_data); + char *buf = curlx_dyn_ptr(&ts->request_data); + size_t request_len = curlx_dyn_len(&ts->request_data); size_t blen = request_len; CURLcode result = CURLE_OK; ssize_t nwritten; @@ -314,8 +315,11 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, k->httpcode); } else { - (void)curlx_strtoofft(header + strlen("Content-Length:"), - NULL, 10, &ts->cl); + const char *p = header + strlen("Content-Length:"); + if(curlx_str_numblanks(&p, &ts->cl)) { + failf(data, "Unsupported Content-Length value"); + return CURLE_WEIRD_SERVER_REPLY; + } } } else if(Curl_compareheader(header, @@ -440,7 +444,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, continue; } - if(Curl_dyn_addn(&ts->rcvbuf, &byte, 1)) { + if(curlx_dyn_addn(&ts->rcvbuf, &byte, 1)) { failf(data, "CONNECT response too large"); return CURLE_RECV_ERROR; } @@ -450,8 +454,8 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, continue; ts->headerlines++; - linep = Curl_dyn_ptr(&ts->rcvbuf); - line_len = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */ + linep = curlx_dyn_ptr(&ts->rcvbuf); + line_len = curlx_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */ /* output debug if that is requested */ Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len); @@ -508,7 +512,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, if(result) return result; - Curl_dyn_reset(&ts->rcvbuf); + curlx_dyn_reset(&ts->rcvbuf); } /* while there is buffer left and loop is requested */ if(error) @@ -597,7 +601,7 @@ static CURLcode H1_CONNECT(struct Curl_cfilter *cf, infof(data, "Connect me again please"); Curl_conn_cf_close(cf, data); connkeep(conn, "HTTP proxy CONNECT"); - result = Curl_conn_cf_connect(cf->next, data, FALSE, &done); + result = Curl_conn_cf_connect(cf->next, data, &done); goto out; } else { @@ -637,7 +641,7 @@ out: static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { CURLcode result; struct h1_tunnel_state *ts = cf->ctx; @@ -648,7 +652,7 @@ static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf, } CURL_TRC_CF(data, cf, "connect"); - result = cf->next->cft->do_connect(cf->next, data, blocking, done); + result = cf->next->cft->do_connect(cf->next, data, done); if(result || !*done) return result; diff --git a/Utilities/cmcurl/lib/cf-h2-proxy.c b/Utilities/cmcurl/lib/cf-h2-proxy.c index d8b91280c2..d3bc4b0768 100644 --- a/Utilities/cmcurl/lib/cf-h2-proxy.c +++ b/Utilities/cmcurl/lib/cf-h2-proxy.c @@ -32,7 +32,7 @@ #include "connect.h" #include "curl_trc.h" #include "bufq.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "dynhds.h" #include "http1.h" #include "http2.h" @@ -619,7 +619,7 @@ static int proxy_h2_fr_print(const nghttp2_frame *frame, frame->hd.flags & NGHTTP2_FLAG_ACK); case NGHTTP2_GOAWAY: { char scratch[128]; - size_t s_len = sizeof(scratch)/sizeof(scratch[0]); + size_t s_len = CURL_ARRAYSIZE(scratch); size_t len = (frame->goaway.opaque_data_len < s_len) ? frame->goaway.opaque_data_len : s_len-1; if(len) @@ -1090,7 +1090,7 @@ out: static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_h2_proxy_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; @@ -1105,7 +1105,7 @@ static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf, /* Connect the lower filters first */ if(!cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, blocking, done); + result = Curl_conn_cf_connect(cf->next, data, done); if(result || !*done) return result; } diff --git a/Utilities/cmcurl/lib/cf-haproxy.c b/Utilities/cmcurl/lib/cf-haproxy.c index ae2402f224..7bc12dbbb1 100644 --- a/Utilities/cmcurl/lib/cf-haproxy.c +++ b/Utilities/cmcurl/lib/cf-haproxy.c @@ -54,13 +54,13 @@ static void cf_haproxy_ctx_reset(struct cf_haproxy_ctx *ctx) { DEBUGASSERT(ctx); ctx->state = HAPROXY_INIT; - Curl_dyn_reset(&ctx->data_out); + curlx_dyn_reset(&ctx->data_out); } static void cf_haproxy_ctx_free(struct cf_haproxy_ctx *ctx) { if(ctx) { - Curl_dyn_free(&ctx->data_out); + curlx_dyn_free(&ctx->data_out); free(ctx); } } @@ -79,7 +79,7 @@ static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf, #ifdef USE_UNIX_SOCKETS if(cf->conn->unix_domain_socket) /* the buffer is large enough to hold this! */ - result = Curl_dyn_addn(&ctx->data_out, STRCONST("PROXY UNKNOWN\r\n")); + result = curlx_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); @@ -92,10 +92,10 @@ static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf, else client_ip = ipquad.local_ip; - result = Curl_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n", - is_ipv6 ? "TCP6" : "TCP4", - client_ip, ipquad.remote_ip, - ipquad.local_port, ipquad.remote_port); + result = curlx_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n", + is_ipv6 ? "TCP6" : "TCP4", + client_ip, ipquad.remote_ip, + ipquad.local_port, ipquad.remote_port); #ifdef USE_UNIX_SOCKETS } @@ -105,7 +105,7 @@ static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf, static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_haproxy_ctx *ctx = cf->ctx; CURLcode result; @@ -117,7 +117,7 @@ static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf, return CURLE_OK; } - result = cf->next->cft->do_connect(cf->next, data, blocking, done); + result = cf->next->cft->do_connect(cf->next, data, done); if(result || !*done) return result; @@ -129,11 +129,11 @@ static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf, ctx->state = HAPROXY_SEND; FALLTHROUGH(); case HAPROXY_SEND: - len = Curl_dyn_len(&ctx->data_out); + len = curlx_dyn_len(&ctx->data_out); if(len > 0) { ssize_t nwritten; nwritten = Curl_conn_cf_send(cf->next, data, - Curl_dyn_ptr(&ctx->data_out), len, FALSE, + curlx_dyn_ptr(&ctx->data_out), len, FALSE, &result); if(nwritten < 0) { if(result != CURLE_AGAIN) @@ -141,8 +141,8 @@ static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf, result = CURLE_OK; nwritten = 0; } - Curl_dyn_tail(&ctx->data_out, len - (size_t)nwritten); - if(Curl_dyn_len(&ctx->data_out) > 0) { + curlx_dyn_tail(&ctx->data_out, len - (size_t)nwritten); + if(curlx_dyn_len(&ctx->data_out) > 0) { result = CURLE_OK; goto out; } @@ -150,7 +150,7 @@ static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf, ctx->state = HAPROXY_DONE; FALLTHROUGH(); default: - Curl_dyn_free(&ctx->data_out); + curlx_dyn_free(&ctx->data_out); break; } @@ -222,7 +222,7 @@ static CURLcode cf_haproxy_create(struct Curl_cfilter **pcf, goto out; } ctx->state = HAPROXY_INIT; - Curl_dyn_init(&ctx->data_out, DYN_HAXPROXY); + curlx_dyn_init(&ctx->data_out, DYN_HAXPROXY); result = Curl_cf_create(&cf, &Curl_cft_haproxy, ctx); if(result) diff --git a/Utilities/cmcurl/lib/cf-https-connect.c b/Utilities/cmcurl/lib/cf-https-connect.c index f073647e55..cd0d226efd 100644 --- a/Utilities/cmcurl/lib/cf-https-connect.c +++ b/Utilities/cmcurl/lib/cf-https-connect.c @@ -113,7 +113,6 @@ static CURLcode cf_hc_baller_cntrl(struct cf_hc_baller *b, struct cf_hc_ctx { cf_hc_state state; - const struct Curl_dns_entry *remotehost; struct curltime started; /* when connect started */ CURLcode result; /* overall result */ struct cf_hc_baller ballers[2]; @@ -147,11 +146,10 @@ static void cf_hc_baller_init(struct cf_hc_baller *b, struct Curl_easy *data, int transport) { - struct cf_hc_ctx *ctx = cf->ctx; struct Curl_cfilter *save = cf->next; cf->next = NULL; - b->started = Curl_now(); + b->started = curlx_now(); switch(b->alpn_id) { case ALPN_h3: transport = TRNSPRT_QUIC; @@ -161,8 +159,8 @@ static void cf_hc_baller_init(struct cf_hc_baller *b, } if(!b->result) - b->result = Curl_cf_setup_insert_after(cf, data, ctx->remotehost, - transport, CURL_CF_SSL_ENABLE); + b->result = Curl_cf_setup_insert_after(cf, data, transport, + CURL_CF_SSL_ENABLE); b->cf = cf->next; cf->next = save; } @@ -175,7 +173,7 @@ static CURLcode cf_hc_baller_connect(struct cf_hc_baller *b, struct Curl_cfilter *save = cf->next; cf->next = b->cf; - b->result = Curl_conn_cf_connect(cf->next, data, FALSE, done); + b->result = Curl_conn_cf_connect(cf->next, data, done); b->cf = cf->next; /* it might mutate */ cf->next = save; return b->result; @@ -192,7 +190,7 @@ static void cf_hc_reset(struct Curl_cfilter *cf, struct Curl_easy *data) ctx->state = CF_HC_INIT; ctx->result = CURLE_OK; ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout; - ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 2; + ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 4; } } @@ -213,11 +211,12 @@ static CURLcode baller_connected(struct Curl_cfilter *cf, 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); + winner->name, (int)curlx_timediff(curlx_now(), + winner->started), reply_ms); else CURL_TRC_CF(data, cf, "deferred handshake %s: %dms", - winner->name, (int)Curl_timediff(Curl_now(), winner->started)); + winner->name, (int)curlx_timediff(curlx_now(), + winner->started)); cf->next = winner->cf; winner->cf = NULL; @@ -263,11 +262,11 @@ static bool time_to_start_next(struct Curl_cfilter *cf, break; } if(i == idx) { - CURL_TRC_CF(data, cf, "all previous ballers have failed, time to start " - "baller %zu [%s]", idx, ctx->ballers[idx].name); + CURL_TRC_CF(data, cf, "all previous attempts failed, starting %s", + ctx->ballers[idx].name); return TRUE; } - elapsed_ms = Curl_timediff(now, ctx->started); + elapsed_ms = curlx_timediff(now, ctx->started); if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) { CURL_TRC_CF(data, cf, "hard timeout of %dms reached, starting %s", ctx->hard_eyeballs_timeout_ms, ctx->ballers[idx].name); @@ -291,21 +290,20 @@ static bool time_to_start_next(struct Curl_cfilter *cf, static CURLcode cf_hc_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_hc_ctx *ctx = cf->ctx; struct curltime now; CURLcode result = CURLE_OK; size_t i, failed_ballers; - (void)blocking; if(cf->connected) { *done = TRUE; return CURLE_OK; } *done = FALSE; - now = Curl_now(); + now = curlx_now(); switch(ctx->state) { case CF_HC_INIT: DEBUGASSERT(!cf->next); @@ -316,7 +314,7 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, cf_hc_baller_init(&ctx->ballers[0], cf, data, cf->conn->transport); if(ctx->baller_count > 1) { Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS); - CURL_TRC_CF(data, cf, "set expire for starting next baller in %ums", + CURL_TRC_CF(data, cf, "set next attempt to start in %ums", ctx->soft_eyeballs_timeout_ms); } ctx->state = CF_HC_CONNECT; @@ -352,7 +350,7 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, if(failed_ballers == ctx->baller_count) { /* all have failed. we give up */ - CURL_TRC_CF(data, cf, "connect, all failed"); + CURL_TRC_CF(data, cf, "connect, all attempts failed"); for(i = 0; i < ctx->baller_count; i++) { if(ctx->ballers[i].result) { result = ctx->ballers[i].result; @@ -451,7 +449,6 @@ static bool cf_hc_data_pending(struct Curl_cfilter *cf, if(cf->connected) return cf->next->cft->has_data_pending(cf->next, data); - CURL_TRC_CF((struct Curl_easy *)data, cf, "data_pending"); for(i = 0; i < ctx->baller_count; i++) if(cf_hc_baller_data_pending(&ctx->ballers[i], data)) return TRUE; @@ -471,7 +468,7 @@ static struct curltime cf_get_max_baller_time(struct Curl_cfilter *cf, struct Curl_cfilter *cfb = ctx->ballers[i].cf; memset(&t, 0, sizeof(t)); if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) { - if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) + if((t.tv_sec || t.tv_usec) && curlx_timediff_us(t, tmax) > 0) tmax = t; } } @@ -577,7 +574,6 @@ struct Curl_cftype Curl_cft_http_connect = { static CURLcode cf_hc_create(struct Curl_cfilter **pcf, struct Curl_easy *data, - const struct Curl_dns_entry *remotehost, enum alpnid *alpnids, size_t alpn_count) { struct Curl_cfilter *cf = NULL; @@ -599,7 +595,6 @@ static CURLcode cf_hc_create(struct Curl_cfilter **pcf, result = CURLE_OUT_OF_MEMORY; goto out; } - ctx->remotehost = remotehost; for(i = 0; i < alpn_count; ++i) cf_hc_baller_assign(&ctx->ballers[i], alpnids[i]); for(; i < CURL_ARRAYSIZE(ctx->ballers); ++i) @@ -607,8 +602,6 @@ static CURLcode cf_hc_create(struct Curl_cfilter **pcf, ctx->baller_count = alpn_count; result = Curl_cf_create(&cf, &Curl_cft_http_connect, ctx); - CURL_TRC_CF(data, cf, "created with %zu ALPNs -> %d", - ctx->baller_count, result); if(result) goto out; ctx = NULL; @@ -623,14 +616,13 @@ out: static CURLcode cf_http_connect_add(struct Curl_easy *data, struct connectdata *conn, int sockindex, - const struct Curl_dns_entry *remotehost, enum alpnid *alpn_ids, size_t alpn_count) { struct Curl_cfilter *cf; CURLcode result = CURLE_OK; DEBUGASSERT(data); - result = cf_hc_create(&cf, data, remotehost, alpn_ids, alpn_count); + result = cf_hc_create(&cf, data, alpn_ids, alpn_count); if(result) goto out; Curl_conn_cf_add(data, conn, sockindex, cf); @@ -638,88 +630,110 @@ out: return result; } +static bool cf_https_alpns_contain(enum alpnid id, + enum alpnid *list, size_t len) +{ + size_t i; + for(i = 0; i < len; ++i) { + if(id == list[i]) + return TRUE; + } + return FALSE; +} + CURLcode Curl_cf_https_setup(struct Curl_easy *data, struct connectdata *conn, - int sockindex, - const struct Curl_dns_entry *remotehost) + int sockindex) { enum alpnid alpn_ids[2]; size_t alpn_count = 0; CURLcode result = CURLE_OK; + struct Curl_cfilter cf_fake, *cf = NULL; (void)sockindex; - (void)remotehost; + /* we want to log for the filter before we create it, fake it. */ + memset(&cf_fake, 0, sizeof(cf_fake)); + cf_fake.cft = &Curl_cft_http_connect; + cf = &cf_fake; if(conn->bits.tls_enable_alpn) { - switch(data->state.httpwant) { - case CURL_HTTP_VERSION_NONE: - /* No preferences by transfer setup. Choose best defaults */ #ifdef USE_HTTPSRR - if(conn->dns_entry && conn->dns_entry->hinfo && - !conn->dns_entry->hinfo->no_def_alpn) { - size_t i, j; - for(i = 0; i < CURL_ARRAYSIZE(conn->dns_entry->hinfo->alpns) && - alpn_count < CURL_ARRAYSIZE(alpn_ids); ++i) { - bool present = FALSE; - enum alpnid alpn = conn->dns_entry->hinfo->alpns[i]; - for(j = 0; j < alpn_count; ++j) { - if(alpn == alpn_ids[j]) { - present = TRUE; - break; - } + /* Is there an HTTPSRR use its ALPNs here. + * We are here after having selected a connection to a host+port and + * can no longer change that. Any HTTPSRR advice for other hosts and ports + * we need to ignore. */ + struct Curl_dns_entry *dns = data->state.dns[sockindex]; + struct Curl_https_rrinfo *rr = dns ? dns->hinfo : NULL; + if(rr && !rr->no_def_alpn && /* ALPNs are defaults */ + (!rr->target || /* for same host */ + !rr->target[0] || + (rr->target[0] == '.' && + !rr->target[1])) && + (rr->port < 0 || /* for same port */ + rr->port == conn->remote_port)) { + size_t i; + for(i = 0; i < CURL_ARRAYSIZE(rr->alpns) && + alpn_count < CURL_ARRAYSIZE(alpn_ids); ++i) { + enum alpnid alpn = rr->alpns[i]; + if(cf_https_alpns_contain(alpn, alpn_ids, alpn_count)) + continue; + switch(alpn) { + case ALPN_h3: + if(Curl_conn_may_http3(data, conn)) + break; /* not possible */ + if(data->state.http_neg.allowed & CURL_HTTP_V3x) { + CURL_TRC_CF(data, cf, "adding h3 via HTTPS-RR"); + alpn_ids[alpn_count++] = alpn; } - if(!present) { - switch(alpn) { - case ALPN_h3: - if(Curl_conn_may_http3(data, conn)) - break; /* not possible */ - FALLTHROUGH(); - case ALPN_h2: - case ALPN_h1: - alpn_ids[alpn_count++] = alpn; - break; - default: /* ignore */ - break; - } + break; + case ALPN_h2: + if(data->state.http_neg.allowed & CURL_HTTP_V2x) { + CURL_TRC_CF(data, cf, "adding h2 via HTTPS-RR"); + alpn_ids[alpn_count++] = alpn; } + break; + case ALPN_h1: + if(data->state.http_neg.allowed & CURL_HTTP_V1x) { + CURL_TRC_CF(data, cf, "adding h1 via HTTPS-RR"); + alpn_ids[alpn_count++] = alpn; + } + break; + default: /* ignore */ + break; } } + } #endif - if(!alpn_count) - alpn_ids[alpn_count++] = ALPN_h2; - break; - case CURL_HTTP_VERSION_3ONLY: + + if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) && + (data->state.http_neg.wanted & CURL_HTTP_V3x) && + !cf_https_alpns_contain(ALPN_h3, alpn_ids, alpn_count)) { result = Curl_conn_may_http3(data, conn); - if(result) /* cannot do it */ - goto out; - alpn_ids[alpn_count++] = ALPN_h3; - break; - case CURL_HTTP_VERSION_3: - /* We assume that silently not even trying H3 is ok here */ - if(Curl_conn_may_http3(data, conn) == CURLE_OK) + if(!result) { + CURL_TRC_CF(data, cf, "adding wanted h3"); alpn_ids[alpn_count++] = ALPN_h3; + } + else if(data->state.http_neg.wanted == CURL_HTTP_V3x) + goto out; /* only h3 allowed, not possible, error out */ + } + if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) && + (data->state.http_neg.wanted & CURL_HTTP_V2x) && + !cf_https_alpns_contain(ALPN_h2, alpn_ids, alpn_count)) { + CURL_TRC_CF(data, cf, "adding wanted h2"); alpn_ids[alpn_count++] = ALPN_h2; - break; - case CURL_HTTP_VERSION_2_0: - case CURL_HTTP_VERSION_2TLS: - case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE: - alpn_ids[alpn_count++] = ALPN_h2; - break; - case CURL_HTTP_VERSION_1_0: - case CURL_HTTP_VERSION_1_1: + } + else if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) && + (data->state.http_neg.wanted & CURL_HTTP_V1x) && + !cf_https_alpns_contain(ALPN_h1, alpn_ids, alpn_count)) { + CURL_TRC_CF(data, cf, "adding wanted h1"); alpn_ids[alpn_count++] = ALPN_h1; - break; - default: - alpn_ids[alpn_count++] = ALPN_h2; - break; } } /* If we identified ALPNs to use, install our filter. Otherwise, * install nothing, so our call will use a default connect setup. */ if(alpn_count) { - result = cf_http_connect_add(data, conn, sockindex, remotehost, - alpn_ids, alpn_count); + result = cf_http_connect_add(data, conn, sockindex, alpn_ids, alpn_count); } out: diff --git a/Utilities/cmcurl/lib/cf-https-connect.h b/Utilities/cmcurl/lib/cf-https-connect.h index 4ff9ef8d37..c36726f0a2 100644 --- a/Utilities/cmcurl/lib/cf-https-connect.h +++ b/Utilities/cmcurl/lib/cf-https-connect.h @@ -38,20 +38,17 @@ extern struct Curl_cftype Curl_cft_http_connect; CURLcode Curl_cf_http_connect_add(struct Curl_easy *data, struct connectdata *conn, int sockindex, - const struct Curl_dns_entry *remotehost, bool try_h3, bool try_h21); CURLcode Curl_cf_http_connect_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - const struct Curl_dns_entry *remotehost, bool try_h3, bool try_h21); CURLcode Curl_cf_https_setup(struct Curl_easy *data, struct connectdata *conn, - int sockindex, - const struct Curl_dns_entry *remotehost); + int sockindex); #endif /* !defined(CURL_DISABLE_HTTP) */ diff --git a/Utilities/cmcurl/lib/cf-socket.c b/Utilities/cmcurl/lib/cf-socket.c index a6f98886c5..e31977201c 100644 --- a/Utilities/cmcurl/lib/cf-socket.c +++ b/Utilities/cmcurl/lib/cf-socket.c @@ -74,15 +74,17 @@ #include "multiif.h" #include "sockaddr.h" /* required for Curl_sockaddr_storage */ #include "inet_ntop.h" -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "progress.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "conncache.h" #include "multihandle.h" #include "rand.h" #include "share.h" #include "strdup.h" -#include "version_win32.h" +#include "system_win32.h" +#include "curlx/version_win32.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -113,8 +115,8 @@ static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd) int level = IPPROTO_TCP; char buffer[STRERROR_LEN]; - if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff, - sizeof(onoff)) < 0) + if(setsockopt(sockfd, level, TCP_NODELAY, + (void *)&onoff, sizeof(onoff)) < 0) infof(data, "Could not set TCP_NODELAY: %s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); #else @@ -133,8 +135,8 @@ static void nosigpipe(struct Curl_easy *data, { int onoff = 1; (void)data; - if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff, - sizeof(onoff)) < 0) { + if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, + (void *)&onoff, sizeof(onoff)) < 0) { #if !defined(CURL_DISABLE_VERBOSE_STRINGS) char buffer[STRERROR_LEN]; infof(data, "Could not set SO_NOSIGPIPE: %s", @@ -181,7 +183,7 @@ tcpkeepalive(struct Curl_easy *data, /* only set IDLE and INTVL if setting KEEPALIVE is successful */ if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, - (void *)&optval, sizeof(optval)) < 0) { + (void *)&optval, sizeof(optval)) < 0) { infof(data, "Failed to set SO_KEEPALIVE on fd " "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); @@ -234,7 +236,7 @@ tcpkeepalive(struct Curl_easy *data, optval = curlx_sltosi(data->set.tcp_keepidle); KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, - (void *)&optval, sizeof(optval)) < 0) { + (void *)&optval, sizeof(optval)) < 0) { infof(data, "Failed to set TCP_KEEPIDLE on fd " "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); @@ -244,7 +246,7 @@ tcpkeepalive(struct Curl_easy *data, optval = curlx_sltosi(data->set.tcp_keepidle); KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE, - (void *)&optval, sizeof(optval)) < 0) { + (void *)&optval, sizeof(optval)) < 0) { infof(data, "Failed to set TCP_KEEPALIVE on fd " "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); @@ -254,7 +256,7 @@ tcpkeepalive(struct Curl_easy *data, optval = curlx_sltosi(data->set.tcp_keepidle); KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_THRESHOLD, - (void *)&optval, sizeof(optval)) < 0) { + (void *)&optval, sizeof(optval)) < 0) { infof(data, "Failed to set TCP_KEEPALIVE_THRESHOLD on fd " "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); @@ -264,7 +266,7 @@ tcpkeepalive(struct Curl_easy *data, optval = curlx_sltosi(data->set.tcp_keepintvl); KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, - (void *)&optval, sizeof(optval)) < 0) { + (void *)&optval, sizeof(optval)) < 0) { infof(data, "Failed to set TCP_KEEPINTVL on fd " "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); @@ -285,7 +287,7 @@ tcpkeepalive(struct Curl_easy *data, curlx_sltosi(data->set.tcp_keepintvl); KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_ABORT_THRESHOLD, - (void *)&optval, sizeof(optval)) < 0) { + (void *)&optval, sizeof(optval)) < 0) { infof(data, "Failed to set TCP_KEEPALIVE_ABORT_THRESHOLD on fd " "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); } @@ -293,7 +295,7 @@ tcpkeepalive(struct Curl_easy *data, #ifdef TCP_KEEPCNT optval = curlx_sltosi(data->set.tcp_keepcnt); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, - (void *)&optval, sizeof(optval)) < 0) { + (void *)&optval, sizeof(optval)) < 0) { infof(data, "Failed to set TCP_KEEPCNT on fd " "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); } @@ -420,7 +422,7 @@ static int socket_close(struct Curl_easy *data, struct connectdata *conn, if(use_callback && conn && conn->fclosesocket) { int rc; - Curl_multi_closed(data, sock); + Curl_multi_will_close(data, sock); Curl_set_in_callback(data, TRUE); rc = conn->fclosesocket(conn->closesocket_client, sock); Curl_set_in_callback(data, FALSE); @@ -429,7 +431,7 @@ static int socket_close(struct Curl_easy *data, struct connectdata *conn, if(conn) /* tell the multi-socket code about this */ - Curl_multi_closed(data, sock); + Curl_multi_will_close(data, sock); sclose(sock); @@ -460,9 +462,6 @@ int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn, 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_sndbuf_init(curl_socket_t sockfd) { @@ -470,17 +469,7 @@ void Curl_sndbuf_init(curl_socket_t sockfd) int curval = 0; int curlen = sizeof(curval); - static int detectOsState = DETECT_OS_NONE; - - if(detectOsState == DETECT_OS_NONE) { - if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL)) - detectOsState = DETECT_OS_VISTA_OR_LATER; - else - detectOsState = DETECT_OS_PREVISTA; - } - - if(detectOsState == DETECT_OS_VISTA_OR_LATER) + if(Curl_isVistaOrGreater) return; if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0) @@ -685,21 +674,14 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, * of the connection. The resolve functions should really be changed * to take a type parameter instead. */ - unsigned char ipver = conn->ip_version; - int rc; - - if(af == AF_INET) - conn->ip_version = CURL_IPRESOLVE_V4; + int ip_version = (af == AF_INET) ? + CURL_IPRESOLVE_V4 : CURL_IPRESOLVE_WHATEVER; #ifdef USE_IPV6 - else if(af == AF_INET6) - conn->ip_version = CURL_IPRESOLVE_V6; + if(af == AF_INET6) + ip_version = CURL_IPRESOLVE_V6; #endif - rc = Curl_resolv(data, host, 80, FALSE, &h); - if(rc == CURLRESOLV_PENDING) - (void)Curl_resolver_wait_resolv(data, &h); - conn->ip_version = ipver; - + (void)Curl_resolv_blocking(data, host, 80, ip_version, &h); if(h) { int h_af = h->addr->ai_family; /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ @@ -732,7 +714,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, if(scope_ptr) *(scope_ptr++) = '\0'; #endif - if(Curl_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) { + if(curlx_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) { si6->sin6_family = AF_INET6; si6->sin6_port = htons(port); #ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID @@ -741,10 +723,10 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, Curl_printable_address. The latter returns only numeric scope 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) + curl_off_t scope_id; + if(curlx_str_number((const char **)CURL_UNCONST(&scope_ptr), + &scope_id, UINT_MAX)) return CURLE_UNSUPPORTED_PROTOCOL; - si6->sin6_scope_id = (unsigned int)scope_id; } #endif @@ -755,7 +737,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, #endif /* IPv4 address */ if((af == AF_INET) && - (Curl_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) { + (curlx_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) { si4->sin_family = AF_INET; si4->sin_port = htons(port); sizeof_sa = sizeof(struct sockaddr_in); @@ -855,7 +837,7 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) * Someone got to verify this on Win-NT 4.0, 2000." */ -#ifdef _WIN32_WCE +#ifdef UNDER_CE Sleep(0); #else SleepEx(0, FALSE); @@ -865,7 +847,7 @@ 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 +#ifdef UNDER_CE /* Old Windows CE versions do not support SO_ERROR */ if(WSAENOPROTOOPT == err) { SET_SOCKERRNO(0); @@ -879,7 +861,7 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) err = 0; } #endif - if((0 == err) || (EISCONN == err)) + if((0 == err) || (SOCKEISCONN == err)) /* we are connected, awesome! */ rc = TRUE; else @@ -902,10 +884,10 @@ static CURLcode socket_connect_result(struct Curl_easy *data, const char *ipaddress, int error) { switch(error) { - case EINPROGRESS: - case EWOULDBLOCK: + case SOCKEINPROGRESS: + case SOCKEWOULDBLOCK: #if defined(EAGAIN) -#if (EAGAIN) != (EWOULDBLOCK) +#if (EAGAIN) != (SOCKEWOULDBLOCK) /* On some platforms EAGAIN and EWOULDBLOCK are the * same value, and on others they are different, hence * the odd #if @@ -932,15 +914,6 @@ static CURLcode socket_connect_result(struct Curl_easy *data, } } -/* We have a recv buffer to enhance reads with len < NW_SMALL_READS. - * This happens often on TLS connections where the TLS implementation - * tries to read the head of a TLS record, determine the length of the - * full record and then make a subsequent read for that. - * On large reads, we will not fill the buffer to avoid the double copy. */ -#define NW_RECV_CHUNK_SIZE (64 * 1024) -#define NW_RECV_CHUNKS 1 -#define NW_SMALL_READS (1024) - struct cf_socket_ctx { int transport; struct Curl_sockaddr_ex addr; /* address to connect to */ @@ -983,28 +956,28 @@ static CURLcode cf_socket_ctx_init(struct cf_socket_ctx *ctx, #ifdef DEBUGBUILD { - char *p = getenv("CURL_DBG_SOCK_WBLOCK"); + const char *p = getenv("CURL_DBG_SOCK_WBLOCK"); if(p) { - long l = strtol(p, NULL, 10); - if(l >= 0 && l <= 100) + curl_off_t l; + if(!curlx_str_number(&p, &l, 100)) ctx->wblock_percent = (int)l; } p = getenv("CURL_DBG_SOCK_WPARTIAL"); if(p) { - long l = strtol(p, NULL, 10); - if(l >= 0 && l <= 100) + curl_off_t l; + if(!curlx_str_number(&p, &l, 100)) ctx->wpartial_percent = (int)l; } p = getenv("CURL_DBG_SOCK_RBLOCK"); if(p) { - long l = strtol(p, NULL, 10); - if(l >= 0 && l <= 100) + curl_off_t l; + if(!curlx_str_number(&p, &l, 100)) ctx->rblock_percent = (int)l; } p = getenv("CURL_DBG_SOCK_RMAX"); if(p) { - long l = strtol(p, NULL, 10); - if(l >= 0) + curl_off_t l; + if(!curlx_str_number(&p, &l, CURL_OFF_T_MAX)) ctx->recv_max = (size_t)l; } } @@ -1018,7 +991,7 @@ 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) { - CURL_TRC_CF(data, cf, "cf_socket_close(%" FMT_SOCKET_T ")", ctx->sock); + CURL_TRC_CF(data, cf, "cf_socket_close, fd=%" 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); @@ -1040,7 +1013,7 @@ static CURLcode cf_socket_shutdown(struct Curl_cfilter *cf, if(cf->connected) { struct cf_socket_ctx *ctx = cf->ctx; - CURL_TRC_CF(data, cf, "cf_socket_shutdown(%" FMT_SOCKET_T ")", ctx->sock); + CURL_TRC_CF(data, cf, "cf_socket_shutdown, fd=%" 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. */ @@ -1132,7 +1105,7 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, (void)data; DEBUGASSERT(ctx->sock == CURL_SOCKET_BAD); - ctx->started_at = Curl_now(); + ctx->started_at = curlx_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 @@ -1248,7 +1221,7 @@ out: } else if(isconnected) { set_local_ip(cf, data); - ctx->connected_at = Curl_now(); + ctx->connected_at = curlx_now(); cf->connected = TRUE; } CURL_TRC_CF(data, cf, "cf_socket_open() -> %d, fd=%" FMT_SOCKET_T, @@ -1313,7 +1286,7 @@ static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data, static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_socket_ctx *ctx = cf->ctx; CURLcode result = CURLE_COULDNT_CONNECT; @@ -1325,9 +1298,6 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, return CURLE_OK; } - if(blocking) - return CURLE_UNSUPPORTED_PROTOCOL; - *done = FALSE; /* a negative world view is best */ if(ctx->sock == CURL_SOCKET_BAD) { int error; @@ -1370,7 +1340,7 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, else if(rc == CURL_CSELECT_OUT || cf->conn->bits.tcp_fastopen) { if(verifyconnect(ctx->sock, &ctx->error)) { /* we are connected with TCP, awesome! */ - ctx->connected_at = Curl_now(); + ctx->connected_at = curlx_now(); set_local_ip(cf, data); *done = TRUE; cf->connected = TRUE; @@ -1471,9 +1441,9 @@ static void win_update_sndbuf_size(struct cf_socket_ctx *ctx) { ULONG ideal; DWORD ideallen; - struct curltime n = Curl_now(); + struct curltime n = curlx_now(); - if(Curl_timediff(n, ctx->last_sndbuf_query_at) > 1000) { + if(curlx_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 && @@ -1538,15 +1508,16 @@ static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, int sockerr = SOCKERRNO; if( -#ifdef WSAEWOULDBLOCK +#ifdef USE_WINSOCK /* This is how Windows does it */ - (WSAEWOULDBLOCK == sockerr) + (SOCKEWOULDBLOCK == 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) || - (EINPROGRESS == sockerr) + (SOCKEWOULDBLOCK == sockerr) || + (EAGAIN == sockerr) || (SOCKEINTR == sockerr) || + (SOCKEINPROGRESS == sockerr) #endif ) { /* this is just a case of EWOULDBLOCK */ @@ -1606,14 +1577,15 @@ static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data, int sockerr = SOCKERRNO; if( -#ifdef WSAEWOULDBLOCK +#ifdef USE_WINSOCK /* This is how Windows does it */ - (WSAEWOULDBLOCK == sockerr) + (SOCKEWOULDBLOCK == 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) + (SOCKEWOULDBLOCK == sockerr) || + (EAGAIN == sockerr) || (SOCKEINTR == sockerr) #endif ) { /* this is just a case of EWOULDBLOCK */ @@ -1632,7 +1604,7 @@ static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data, 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->first_byte_at = curlx_now(); ctx->got_first_byte = TRUE; } return nread; @@ -1743,7 +1715,7 @@ static CURLcode cf_socket_query(struct Curl_cfilter *cf, 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); + timediff_t ms = curlx_timediff(ctx->first_byte_at, ctx->started_at); *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; } else @@ -1849,7 +1821,9 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, /* QUIC needs a connected socket, nonblocking */ DEBUGASSERT(ctx->sock != CURL_SOCKET_BAD); - rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, /* NOLINT */ + /* error: The 1st argument to 'connect' is -1 but should be >= 0 + NOLINTNEXTLINE(clang-analyzer-unix.StdCLibraryFunctions) */ + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, (curl_socklen_t)ctx->addr.addrlen); if(-1 == rc) { return socket_connect_result(data, ctx->ip.remote_ip, SOCKERRNO); @@ -1867,8 +1841,9 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, * 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. */ +#ifdef __linux__ switch(ctx->addr.family) { -#if defined(__linux__) && defined(IP_MTU_DISCOVER) +#ifdef IP_MTU_DISCOVER case AF_INET: { int val = IP_PMTUDISC_DO; (void)setsockopt(ctx->sock, IPPROTO_IP, IP_MTU_DISCOVER, &val, @@ -1876,7 +1851,7 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, break; } #endif -#if defined(__linux__) && defined(IPV6_MTU_DISCOVER) +#ifdef IPV6_MTU_DISCOVER case AF_INET6: { int val = IPV6_PMTUDISC_DO; (void)setsockopt(ctx->sock, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val, @@ -1886,11 +1861,12 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, #endif } -#if defined(__linux__) && defined(UDP_GRO) && \ +#if 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 #endif return CURLE_OK; @@ -1898,12 +1874,11 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, static CURLcode cf_udp_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_socket_ctx *ctx = cf->ctx; CURLcode result = CURLE_COULDNT_CONNECT; - (void)blocking; if(cf->connected) { *done = TRUE; return CURLE_OK; @@ -2057,7 +2032,7 @@ static timediff_t cf_tcp_accept_timeleft(struct Curl_cfilter *cf, timeout_ms = data->set.accepttimeout; #endif - now = Curl_now(); + now = curlx_now(); /* check if the generic timeout possibly is set shorter */ other = Curl_timeleft(data, &now, FALSE); if(other && (other < timeout_ms)) @@ -2066,7 +2041,7 @@ static timediff_t cf_tcp_accept_timeleft(struct Curl_cfilter *cf, timeout_ms = other; else { /* subtract elapsed time */ - timeout_ms -= Curl_timediff(now, ctx->started_at); + timeout_ms -= curlx_timediff(now, ctx->started_at); if(!timeout_ms) /* avoid returning 0 as that means no timeout! */ timeout_ms = -1; @@ -2108,7 +2083,7 @@ static void cf_tcp_set_accepted_remote_ip(struct Curl_cfilter *cf, static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_socket_ctx *ctx = cf->ctx; #ifdef USE_IPV6 @@ -2124,7 +2099,6 @@ static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf, /* we start accepted, if we ever close, we cannot go on */ (void)data; - (void)blocking; if(cf->connected) { *done = TRUE; return CURLE_OK; @@ -2163,7 +2137,12 @@ static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf, if(0 == getsockname(ctx->sock, (struct sockaddr *) &add, &size)) { size = sizeof(add); +#ifdef HAVE_ACCEPT4 + s_accepted = accept4(ctx->sock, (struct sockaddr *) &add, &size, + SOCK_NONBLOCK | SOCK_CLOEXEC); +#else s_accepted = accept(ctx->sock, (struct sockaddr *) &add, &size); +#endif } if(CURL_SOCKET_BAD == s_accepted) { @@ -2172,7 +2151,9 @@ static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf, } infof(data, "Connection accepted from server"); +#ifndef HAVE_ACCEPT4 (void)curlx_nonblock(s_accepted, TRUE); /* enable non-blocking */ +#endif /* Replace any filter on SECONDARY with one listening on this socket */ ctx->listening = FALSE; ctx->accepted = TRUE; @@ -2183,7 +2164,7 @@ static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf, cf_tcp_set_accepted_remote_ip(cf, data); set_local_ip(cf, data); ctx->active = TRUE; - ctx->connected_at = Curl_now(); + ctx->connected_at = curlx_now(); cf->connected = TRUE; CURL_TRC_CF(data, cf, "accepted_set(sock=%" FMT_SOCKET_T ", remote=%s port=%d)", @@ -2201,6 +2182,7 @@ static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf, if(error) return CURLE_ABORTED_BY_CALLBACK; } + *done = TRUE; return CURLE_OK; } @@ -2249,7 +2231,7 @@ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, goto out; Curl_conn_cf_add(data, conn, sockindex, cf); - ctx->started_at = Curl_now(); + ctx->started_at = curlx_now(); conn->sock[sockindex] = ctx->sock; set_local_ip(cf, data); CURL_TRC_CF(data, cf, "set filter for listen socket fd=%" FMT_SOCKET_T diff --git a/Utilities/cmcurl/lib/cf-socket.h b/Utilities/cmcurl/lib/cf-socket.h index 1ce8404c07..d3e3509842 100644 --- a/Utilities/cmcurl/lib/cf-socket.h +++ b/Utilities/cmcurl/lib/cf-socket.h @@ -25,7 +25,7 @@ ***************************************************************************/ #include "curl_setup.h" -#include "nonblock.h" /* for curlx_nonblock(), formerly Curl_nonblock() */ +#include "curlx/nonblock.h" /* for curlx_nonblock() */ #include "sockaddr.h" struct Curl_addrinfo; diff --git a/Utilities/cmcurl/lib/cfilters.c b/Utilities/cmcurl/lib/cfilters.c index 6a894e8ce6..00090f0c71 100644 --- a/Utilities/cmcurl/lib/cfilters.c +++ b/Utilities/cmcurl/lib/cfilters.c @@ -28,13 +28,14 @@ #include "strerror.h" #include "cfilters.h" #include "connect.h" -#include "url.h" /* for Curl_safefree() */ +#include "url.h" #include "sendf.h" #include "sockaddr.h" /* required for Curl_sockaddr_storage */ #include "multiif.h" #include "progress.h" #include "select.h" -#include "warnless.h" +#include "curlx/warnless.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -197,11 +198,11 @@ CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done) } *done = FALSE; - now = Curl_now(); + now = curlx_now(); if(!Curl_shutdown_started(data, sockindex)) { - DEBUGF(infof(data, "shutdown start on%s connection", - sockindex ? " secondary" : "")); - Curl_shutdown_start(data, sockindex, &now); + CURL_TRC_M(data, "shutdown start on%s connection", + sockindex ? " secondary" : ""); + Curl_shutdown_start(data, sockindex, 0, &now); } else { timeout_ms = Curl_shutdown_timeleft(data->conn, sockindex, &now); @@ -367,10 +368,10 @@ bool Curl_conn_cf_discard_sub(struct Curl_cfilter *cf, CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { if(cf) - return cf->cft->do_connect(cf, data, blocking, done); + return cf->cft->do_connect(cf, data, done); return CURLE_FAILED_INIT; } @@ -404,6 +405,9 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, bool blocking, bool *done) { +#define CF_CONN_NUM_POLLS_ON_STACK 5 + struct pollfd a_few_on_stack[CF_CONN_NUM_POLLS_ON_STACK]; + struct curl_pollfds cpfds; struct Curl_cfilter *cf; CURLcode result = CURLE_OK; @@ -411,14 +415,17 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, DEBUGASSERT(data->conn); cf = data->conn->cfilter[sockindex]; - DEBUGASSERT(cf); if(!cf) { *done = FALSE; return CURLE_FAILED_INIT; } *done = cf->connected; - if(!*done) { + if(*done) + return CURLE_OK; + + Curl_pollfds_init(&cpfds, a_few_on_stack, CF_CONN_NUM_POLLS_ON_STACK); + while(!*done) { if(Curl_conn_needs_flush(data, sockindex)) { DEBUGF(infof(data, "Curl_conn_connect(index=%d), flush", sockindex)); result = Curl_conn_flush(data, sockindex); @@ -426,24 +433,75 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, return result; } - result = cf->cft->do_connect(cf, data, blocking, done); + result = cf->cft->do_connect(cf, data, done); + CURL_TRC_CF(data, cf, "Curl_conn_connect(block=%d) -> %d, done=%d", + blocking, result, *done); if(!result && *done) { /* 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(); + data->conn->keepalive = curlx_now(); Curl_verboseconnect(data, data->conn, sockindex); + goto out; } else if(result) { + CURL_TRC_CF(data, cf, "Curl_conn_connect(), filter returned %d", + result); conn_report_connect_stats(data, data->conn); + goto out; + } + + if(!blocking) + goto out; + else { + /* check allowed time left */ + const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + struct easy_pollset ps; + int rc; + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "connect timeout"); + result = CURLE_OPERATION_TIMEDOUT; + goto out; + } + + CURL_TRC_CF(data, cf, "Curl_conn_connect(block=1), do poll"); + Curl_pollfds_reset(&cpfds); + memset(&ps, 0, sizeof(ps)); + /* In general, we want to send after connect, wait on that. */ + if(sockfd != CURL_SOCKET_BAD) + Curl_pollset_set_out_only(data, &ps, sockfd); + Curl_conn_adjust_pollset(data, data->conn, &ps); + result = Curl_pollfds_add_ps(&cpfds, &ps); + if(result) + goto out; + + rc = Curl_poll(cpfds.pfds, cpfds.n, + CURLMIN(timeout_ms, (cpfds.n ? 1000 : 10))); + CURL_TRC_CF(data, cf, "Curl_conn_connect(block=1), Curl_poll() -> %d", + rc); + if(rc < 0) { + result = CURLE_COULDNT_CONNECT; + goto out; + } + /* continue iterating */ } } +out: + Curl_pollfds_cleanup(&cpfds); return result; } +bool Curl_conn_is_setup(struct connectdata *conn, int sockindex) +{ + return (conn->cfilter[sockindex] != NULL); +} + bool Curl_conn_is_connected(struct connectdata *conn, int sockindex) { struct Curl_cfilter *cf; @@ -496,13 +554,14 @@ bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex) return FALSE; } -unsigned char Curl_conn_http_version(struct Curl_easy *data) +unsigned char Curl_conn_http_version(struct Curl_easy *data, + struct connectdata *conn) { struct Curl_cfilter *cf; CURLcode result = CURLE_UNKNOWN_OPTION; unsigned char v = 0; - cf = data->conn ? data->conn->cfilter[FIRSTSOCKET] : NULL; + cf = conn->cfilter[FIRSTSOCKET]; for(; cf; cf = cf->next) { if(cf->cft->flags & CF_TYPE_HTTP) { int value = 0; @@ -571,14 +630,15 @@ void Curl_conn_cf_adjust_pollset(struct Curl_cfilter *cf, } void Curl_conn_adjust_pollset(struct Curl_easy *data, - struct easy_pollset *ps) + struct connectdata *conn, + struct easy_pollset *ps) { int i; DEBUGASSERT(data); - DEBUGASSERT(data->conn); + DEBUGASSERT(conn); for(i = 0; i < 2; ++i) { - Curl_conn_cf_adjust_pollset(data->conn->cfilter[i], data, ps); + Curl_conn_cf_adjust_pollset(conn->cfilter[i], data, ps); } } @@ -880,14 +940,14 @@ CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex, DEBUGASSERT(data->conn); conn = data->conn; #ifdef DEBUGBUILD - { + if(write_len) { /* Allow debug builds to override this logic to force short sends */ - char *p = getenv("CURL_SMALLSENDS"); + const char *p = getenv("CURL_SMALLSENDS"); if(p) { - size_t altsize = (size_t)strtoul(p, NULL, 10); - if(altsize) - write_len = CURLMIN(write_len, altsize); + curl_off_t altsize; + if(!curlx_str_number(&p, &altsize, write_len)) + write_len = (size_t)altsize; } } #endif diff --git a/Utilities/cmcurl/lib/cfilters.h b/Utilities/cmcurl/lib/cfilters.h index 2d5599a90a..4c604db38f 100644 --- a/Utilities/cmcurl/lib/cfilters.h +++ b/Utilities/cmcurl/lib/cfilters.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "timediff.h" +#include "curlx/timediff.h" struct Curl_cfilter; struct Curl_easy; @@ -51,7 +51,7 @@ typedef CURLcode Curl_cft_shutdown(struct Curl_cfilter *cf, typedef CURLcode Curl_cft_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done); + bool *done); /* Return the hostname and port the connection goes to. * This may change with the connection state of filters when tunneling @@ -65,10 +65,10 @@ typedef CURLcode Curl_cft_connect(struct Curl_cfilter *cf, * @param pport on return, contains the port number */ typedef void Curl_cft_get_host(struct Curl_cfilter *cf, - struct Curl_easy *data, - const char **phost, - const char **pdisplay_host, - int *pport); + struct Curl_easy *data, + const char **phost, + const char **pdisplay_host, + int *pport); struct easy_pollset; @@ -96,8 +96,8 @@ struct easy_pollset; * @param ps the pollset (inout) for the easy handle */ typedef void Curl_cft_adjust_pollset(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct easy_pollset *ps); + struct Curl_easy *data, + struct easy_pollset *ps); typedef bool Curl_cft_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data); @@ -245,8 +245,8 @@ void Curl_cf_def_get_host(struct Curl_cfilter *cf, struct Curl_easy *data, const char **phost, const char **pdisplay_host, int *pport); void Curl_cf_def_adjust_pollset(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct easy_pollset *ps); + 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, @@ -255,8 +255,8 @@ ssize_t Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data, 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, - struct Curl_easy *data, - int event, int arg1, void *arg2); + struct Curl_easy *data, + int event, int arg1, void *arg2); bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data, bool *input_pending); @@ -324,7 +324,7 @@ void Curl_conn_cf_discard_all(struct Curl_easy *data, CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done); + bool *done); void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data); ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *buf, size_t len, bool eos, @@ -370,6 +370,11 @@ bool Curl_conn_cf_needs_flush(struct Curl_cfilter *cf, CURLcode Curl_conn_connect(struct Curl_easy *data, int sockindex, bool blocking, bool *done); +/** + * Check if a filter chain at `sockindex` for connection `conn` exists. + */ +bool Curl_conn_is_setup(struct connectdata *conn, int sockindex); + /** * Check if the filter chain at `sockindex` for connection `conn` is * completely connected. @@ -399,7 +404,8 @@ bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex); * Return the HTTP version used on the FIRSTSOCKET connection filters * or 0 if unknown. Value otherwise is 09, 10, 11, etc. */ -unsigned char Curl_conn_http_version(struct Curl_easy *data); +unsigned char Curl_conn_http_version(struct Curl_easy *data, + struct connectdata *conn); /** * Close the filter chain at `sockindex` for connection `data->conn`. @@ -454,7 +460,8 @@ void Curl_conn_cf_adjust_pollset(struct Curl_cfilter *cf, * Adjust pollset from filters installed at transfer's connection. */ void Curl_conn_adjust_pollset(struct Curl_easy *data, - struct easy_pollset *ps); + struct connectdata *conn, + struct easy_pollset *ps); /** * Curl_poll() the filter chain at `cf` with timeout `timeout_ms`. @@ -654,7 +661,7 @@ struct cf_call_data { (save) = CF_CTX_CALL_DATA(cf); \ DEBUGASSERT((save).data == NULL || (save).depth > 0); \ CF_CTX_CALL_DATA(cf).depth++; \ - CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)data; \ + CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)CURL_UNCONST(data); \ } while(0) #define CF_DATA_RESTORE(cf, save) \ @@ -669,7 +676,7 @@ struct cf_call_data { #define CF_DATA_SAVE(save, cf, data) \ do { \ (save) = CF_CTX_CALL_DATA(cf); \ - CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)data; \ + CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)CURL_UNCONST(data); \ } while(0) #define CF_DATA_RESTORE(cf, save) \ diff --git a/Utilities/cmcurl/lib/conncache.c b/Utilities/cmcurl/lib/conncache.c index b8a0515276..f5e2ea8725 100644 --- a/Utilities/cmcurl/lib/conncache.c +++ b/Utilities/cmcurl/lib/conncache.c @@ -32,7 +32,9 @@ #include "cfilters.h" #include "progress.h" #include "multiif.h" +#include "multi_ev.h" #include "sendf.h" +#include "cshutdn.h" #include "conncache.h" #include "http_negotiate.h" #include "http_ntlm.h" @@ -41,6 +43,8 @@ #include "connect.h" #include "select.h" #include "strcase.h" +#include "curlx/strparse.h" +#include "uint-table.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -50,24 +54,24 @@ #define CPOOL_IS_LOCKED(c) ((c) && (c)->locked) -#define CPOOL_LOCK(c) \ +#define CPOOL_LOCK(c,d) \ do { \ if((c)) { \ if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ - Curl_share_lock(((c)->idata), CURL_LOCK_DATA_CONNECT, \ + Curl_share_lock((d), CURL_LOCK_DATA_CONNECT, \ CURL_LOCK_ACCESS_SINGLE); \ DEBUGASSERT(!(c)->locked); \ (c)->locked = TRUE; \ } \ } while(0) -#define CPOOL_UNLOCK(c) \ +#define CPOOL_UNLOCK(c,d) \ 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); \ + Curl_share_unlock((d), CURL_LOCK_DATA_CONNECT); \ } \ } while(0) @@ -84,35 +88,18 @@ 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 size_t cpool_shutdown_dest_count(struct cpool *cpool, - const char *destination); -static struct cpool_bundle *cpool_bundle_create(const char *dest, - size_t dest_len) +static struct cpool_bundle *cpool_bundle_create(const char *dest) { struct cpool_bundle *bundle; + size_t dest_len = strlen(dest); + 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); + bundle->dest_len = dest_len + 1; + memcpy(bundle->dest, dest, bundle->dest_len); return bundle; } @@ -146,57 +133,102 @@ static void cpool_bundle_free_entry(void *freethis) cpool_bundle_destroy((struct cpool_bundle *)freethis); } -int Curl_cpool_init(struct cpool *cpool, - Curl_cpool_disconnect_cb *disconnect_cb, - struct Curl_multi *multi, - struct Curl_share *share, - size_t size) +void Curl_cpool_init(struct cpool *cpool, + struct Curl_easy *idata, + 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); + curlx_str_key_compare, cpool_bundle_free_entry); - DEBUGASSERT(disconnect_cb); - if(!disconnect_cb) - return 1; + DEBUGASSERT(idata); - /* allocate a new easy handle to use when closing cached connections */ - cpool->idata = curl_easy_init(); - if(!cpool->idata) - return 1; /* bad */ - cpool->idata->state.internal = TRUE; - /* This is quirky. We need an internal handle for certain operations, but we - * do not add it to the multi (if there is one). 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 + cpool->idata = idata; + cpool->share = share; + cpool->initialised = TRUE; +} - cpool->disconnect_cb = disconnect_cb; - cpool->idata->multi = cpool->multi = multi; - cpool->idata->share = cpool->share = share; +/* Return the "first" connection in the pool or NULL. */ +static struct connectdata *cpool_get_first(struct cpool *cpool) +{ + struct Curl_hash_iterator iter; + struct Curl_hash_element *he; + struct cpool_bundle *bundle; + struct Curl_llist_node *conn_node; - return 0; /* good */ + Curl_hash_start_iterate(&cpool->dest2bundle, &iter); + for(he = Curl_hash_next_element(&iter); he; + he = Curl_hash_next_element(&iter)) { + bundle = he->ptr; + conn_node = Curl_llist_head(&bundle->conns); + if(conn_node) + return Curl_node_elem(conn_node); + } + return NULL; +} + + +static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool, + struct connectdata *conn) +{ + return Curl_hash_pick(&cpool->dest2bundle, + conn->destination, strlen(conn->destination) + 1); +} + + +static void cpool_remove_bundle(struct cpool *cpool, + struct cpool_bundle *bundle) +{ + if(!cpool) + return; + Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len); +} + + +static void cpool_remove_conn(struct cpool *cpool, + struct connectdata *conn) +{ + 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--; + } + else { + /* Should have been in the bundle list */ + DEBUGASSERT(NULL); + } + } } void Curl_cpool_destroy(struct cpool *cpool) { - 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); + if(cpool && cpool->initialised && cpool->idata) { + struct connectdata *conn; + SIGPIPE_VARIABLE(pipe_st); + + CURL_TRC_M(cpool->idata, "%s[CPOOL] destroy, %zu connections", + cpool->share ? "[SHARE] " : "", cpool->num_conn); + /* Move all connections to the shutdown list */ + sigpipe_init(&pipe_st); + CPOOL_LOCK(cpool, cpool->idata); + conn = cpool_get_first(cpool); + while(conn) { + cpool_remove_conn(cpool, conn); + sigpipe_apply(cpool->idata, &pipe_st); + connclose(conn, "kill all"); + cpool_discard_conn(cpool, cpool->idata, conn, FALSE); + conn = cpool_get_first(cpool); } + CPOOL_UNLOCK(cpool, cpool->idata); + sigpipe_restore(&pipe_st); Curl_hash_destroy(&cpool->dest2bundle); - cpool->multi = NULL; } } @@ -219,23 +251,14 @@ void Curl_cpool_xfer_init(struct Curl_easy *data) DEBUGASSERT(cpool); if(cpool) { - CPOOL_LOCK(cpool); + CPOOL_LOCK(cpool, data); /* 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); + CPOOL_UNLOCK(cpool, data); } else { /* We should not get here, but in a non-debug build, do something */ @@ -244,19 +267,12 @@ void Curl_cpool_xfer_init(struct Curl_easy *data) } } -static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool, - struct connectdata *conn) -{ - return Curl_hash_pick(&cpool->dest2bundle, - conn->destination, conn->destination_len); -} - static struct cpool_bundle * cpool_add_bundle(struct cpool *cpool, struct connectdata *conn) { struct cpool_bundle *bundle; - bundle = cpool_bundle_create(conn->destination, conn->destination_len); + bundle = cpool_bundle_create(conn->destination); if(!bundle) return NULL; @@ -268,17 +284,70 @@ cpool_add_bundle(struct cpool *cpool, struct connectdata *conn) return bundle; } -static void cpool_remove_bundle(struct cpool *cpool, - struct cpool_bundle *bundle) +static struct connectdata * +cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle) { - if(!cpool) - return; + struct Curl_llist_node *curr; + timediff_t highscore = -1; + timediff_t score; + struct curltime now; + struct connectdata *oldest_idle = NULL; + struct connectdata *conn; - Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len); + now = curlx_now(); + curr = Curl_llist_head(&bundle->conns); + while(curr) { + conn = Curl_node_elem(curr); + + if(!CONN_INUSE(conn)) { + /* Set higher score for the age passed since the connection was used */ + score = curlx_timediff(now, conn->lastused); + + if(score > highscore) { + highscore = score; + oldest_idle = conn; + } + } + curr = Curl_node_next(curr); + } + return oldest_idle; +} + +static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool) +{ + struct Curl_hash_iterator iter; + 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; + + now = curlx_now(); + Curl_hash_start_iterate(&cpool->dest2bundle, &iter); + + for(he = Curl_hash_next_element(&iter); he; + he = Curl_hash_next_element(&iter)) { + struct connectdata *conn; + bundle = he->ptr; + + 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 = curlx_timediff(now, conn->lastused); + if(score > highscore) { + highscore = score; + oldest_idle = conn; + } + } + } + return oldest_idle; } -static struct connectdata * -cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle); int Curl_cpool_check_limits(struct Curl_easy *data, struct connectdata *conn) @@ -293,39 +362,48 @@ int Curl_cpool_check_limits(struct Curl_easy *data, if(!cpool) return CPOOL_LIMIT_OK; - if(data && data->multi) { - dest_limit = data->multi->max_host_connections; - total_limit = data->multi->max_total_connections; + if(cpool->idata->multi) { + dest_limit = cpool->idata->multi->max_host_connections; + total_limit = cpool->idata->multi->max_total_connections; } if(!dest_limit && !total_limit) return CPOOL_LIMIT_OK; - CPOOL_LOCK(cpool); + CPOOL_LOCK(cpool, cpool->idata); if(dest_limit) { size_t live; bundle = cpool_find_bundle(cpool, conn); live = bundle ? Curl_llist_count(&bundle->conns) : 0; - shutdowns = cpool_shutdown_dest_count(cpool, conn->destination); - while(!shutdowns && bundle && live >= 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) + shutdowns = Curl_cshutdn_dest_count(data, conn->destination); + while((live + shutdowns) >= dest_limit) { + if(shutdowns) { + /* close one connection in shutdown right away, if we can */ + if(!Curl_cshutdn_close_oldest(data, conn->destination)) + break; + } + else if(!bundle) 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); + else { + 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 */ + CURL_TRC_M(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_conn_terminate(cpool->idata, oldest_idle, FALSE); - /* in case the bundle was destroyed in disconnect, look it up again */ - bundle = cpool_find_bundle(cpool, conn); - live = bundle ? Curl_llist_count(&bundle->conns) : 0; - shutdowns = cpool_shutdown_dest_count(cpool, conn->destination); + /* in case the bundle was destroyed in disconnect, look it up again */ + bundle = cpool_find_bundle(cpool, conn); + live = bundle ? Curl_llist_count(&bundle->conns) : 0; + } + shutdowns = Curl_cshutdn_dest_count(cpool->idata, conn->destination); } if((live + shutdowns) >= dest_limit) { result = CPOOL_LIMIT_DEST; @@ -334,18 +412,25 @@ int Curl_cpool_check_limits(struct Curl_easy *data, } if(total_limit) { - shutdowns = Curl_llist_count(&cpool->shutdowns); + shutdowns = Curl_cshutdn_count(cpool->idata); while((cpool->num_conn + shutdowns) >= 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 #%" + if(shutdowns) { + /* close one connection in shutdown right away, if we can */ + if(!Curl_cshutdn_close_oldest(data, NULL)) + break; + } + else { + struct connectdata *oldest_idle = cpool_get_oldest_idle(cpool); + if(!oldest_idle) + break; + /* disconnect the old conn and continue */ + CURL_TRC_M(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); - shutdowns = Curl_llist_count(&cpool->shutdowns); + oldest_idle->connection_id, cpool->num_conn, total_limit); + Curl_conn_terminate(cpool->idata, oldest_idle, FALSE); + } + shutdowns = Curl_cshutdn_count(cpool->idata); } if((cpool->num_conn + shutdowns) >= total_limit) { result = CPOOL_LIMIT_TOTAL; @@ -354,12 +439,12 @@ int Curl_cpool_check_limits(struct Curl_easy *data, } out: - CPOOL_UNLOCK(cpool); + CPOOL_UNLOCK(cpool, cpool->idata); return result; } -CURLcode Curl_cpool_add_conn(struct Curl_easy *data, - struct connectdata *conn) +CURLcode Curl_cpool_add(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result = CURLE_OK; struct cpool_bundle *bundle = NULL; @@ -370,7 +455,7 @@ CURLcode Curl_cpool_add_conn(struct Curl_easy *data, if(!cpool) return CURLE_FAILED_INIT; - CPOOL_LOCK(cpool); + CPOOL_LOCK(cpool, data); bundle = cpool_find_bundle(cpool, conn); if(!bundle) { bundle = cpool_add_bundle(cpool, conn); @@ -383,38 +468,15 @@ CURLcode Curl_cpool_add_conn(struct Curl_easy *data, 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, - cpool->num_conn + Curl_llist_count(&cpool->shutdowns))); + CURL_TRC_M(data, "[CPOOL] added connection %" FMT_OFF_T ". " + "The cache now contains %zu members", + conn->connection_id, cpool->num_conn); out: - CPOOL_UNLOCK(cpool); + CPOOL_UNLOCK(cpool, data); return result; } -static void cpool_remove_conn(struct cpool *cpool, - struct connectdata *conn) -{ - 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--; - } - else { - /* Not in a bundle, already in the shutdown list? */ - DEBUGASSERT(list == &cpool->shutdowns); - } - } -} - /* 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. @@ -462,25 +524,6 @@ static bool cpool_foreach(struct Curl_easy *data, return FALSE; } -/* 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 cpool_bundle *bundle; - struct Curl_llist_node *conn_node; - - Curl_hash_start_iterate(&cpool->dest2bundle, &iter); - for(he = Curl_hash_next_element(&iter); he; - he = Curl_hash_next_element(&iter)) { - bundle = he->ptr; - conn_node = Curl_llist_head(&bundle->conns); - if(conn_node) - return Curl_node_elem(conn_node); - } - return NULL; -} - /* * A connection (already in the pool) has become idle. Do any * cleanups in regard to the pool's limits. @@ -491,103 +534,36 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data, struct connectdata *conn) { unsigned int maxconnects = !data->multi->maxconnects ? - data->multi->num_easy * 4 : data->multi->maxconnects; + (Curl_multi_xfers_running(data->multi) * 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 */ + conn->lastused = curlx_now(); /* it was used up until now */ 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); + CPOOL_LOCK(cpool, data); if(cpool->num_conn > maxconnects) { - infof(data, "Connection pool is full, closing the oldest one"); + infof(data, "Connection pool is full, closing the oldest of %zu/%u", + cpool->num_conn, maxconnects); oldest_idle = cpool_get_oldest_idle(cpool); kept = (oldest_idle != conn); if(oldest_idle) { - Curl_cpool_disconnect(cpool->idata, oldest_idle, FALSE); + Curl_conn_terminate(data, oldest_idle, FALSE); } } if(do_lock) - CPOOL_UNLOCK(cpool); + CPOOL_UNLOCK(cpool, data); } return kept; } -/* - * This function finds the connection in the connection bundle that has been - * unused for the longest time. - */ -static struct connectdata * -cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle) -{ - struct Curl_llist_node *curr; - timediff_t highscore = -1; - timediff_t score; - struct curltime now; - struct connectdata *oldest_idle = NULL; - struct connectdata *conn; - - now = Curl_now(); - curr = Curl_llist_head(&bundle->conns); - while(curr) { - conn = Curl_node_elem(curr); - - if(!CONN_INUSE(conn)) { - /* 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 = Curl_node_next(curr); - } - return oldest_idle; -} - -static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool) -{ - struct Curl_hash_iterator iter; - 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; - - now = Curl_now(); - Curl_hash_start_iterate(&cpool->dest2bundle, &iter); - - for(he = Curl_hash_next_element(&iter); he; - he = Curl_hash_next_element(&iter)) { - struct connectdata *conn; - bundle = he->ptr; - - 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; - } - } - } - return oldest_idle; -} - bool Curl_cpool_find(struct Curl_easy *data, - const char *destination, size_t dest_len, + const char *destination, Curl_cpool_conn_match_cb *conn_cb, Curl_cpool_done_match_cb *done_cb, void *userdata) @@ -601,8 +577,10 @@ bool Curl_cpool_find(struct Curl_easy *data, if(!cpool) return FALSE; - CPOOL_LOCK(cpool); - bundle = Curl_hash_pick(&cpool->dest2bundle, (void *)destination, dest_len); + CPOOL_LOCK(cpool, data); + bundle = Curl_hash_pick(&cpool->dest2bundle, + CURL_UNCONST(destination), + strlen(destination) + 1); if(bundle) { struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns); while(curr) { @@ -620,104 +598,10 @@ bool Curl_cpool_find(struct Curl_easy *data, if(done_cb) { result = done_cb(result, userdata); } - CPOOL_UNLOCK(cpool); + CPOOL_UNLOCK(cpool, data); return result; } -/* How many connections to the given destination are in shutdown? */ -static size_t cpool_shutdown_dest_count(struct cpool *cpool, - const char *destination) -{ - size_t n = 0; - struct Curl_llist_node *e = Curl_llist_head(&cpool->shutdowns); - while(e) { - struct connectdata *conn = Curl_node_elem(e); - if(!strcmp(destination, conn->destination)) - ++n; - e = Curl_node_next(e); - } - return n; -} - -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); - } -} - -static void cpool_close_and_destroy_all(struct cpool *cpool) -{ - struct connectdata *conn; - int timeout_ms = 0; - SIGPIPE_VARIABLE(pipe_st); - - DEBUGASSERT(cpool); - /* Move all connections to the shutdown list */ - sigpipe_init(&pipe_st); - CPOOL_LOCK(cpool); - conn = cpool_get_live_conn(cpool); - while(conn) { - cpool_remove_conn(cpool, conn); - sigpipe_apply(cpool->idata, &pipe_st); - connclose(conn, "kill all"); - cpool_discard_conn(cpool, cpool->idata, conn, FALSE); - - 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); - - /* discard all connections in the shutdown list */ - cpool_shutdown_discard_all(cpool); - - 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, @@ -726,6 +610,7 @@ static void cpool_discard_conn(struct cpool *cpool, bool done = FALSE; DEBUGASSERT(data); + DEBUGASSERT(!data->conn); DEBUGASSERT(cpool); DEBUGASSERT(!conn->bits.in_cpool); @@ -734,9 +619,9 @@ static void cpool_discard_conn(struct cpool *cpool, * 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))); + CURL_TRC_M(data, "[CPOOL] not discarding #%" FMT_OFF_T + " still in use by %u transfers", conn->connection_id, + CONN_ATTACHED(conn)); return; } @@ -755,52 +640,18 @@ static void cpool_discard_conn(struct cpool *cpool, 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); + Curl_cshutdn_run_once(cpool->idata, conn, &done); } - 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_total_connections > 0 && - (data->multi->max_total_connections <= - (long)(cpool->num_conn + Curl_llist_count(&cpool->shutdowns)))) { - DEBUGF(infof(data, "[CCACHE] discarding oldest shutdown connection " - "due to connection limit of %ld", - data->multi->max_total_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 shutdowns, now %zu conns in shutdown", - conn->connection_id, Curl_llist_count(&cpool->shutdowns))); + if(done || !data->multi) + Curl_cshutdn_terminate(cpool->idata, conn, FALSE); + else + Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn); } -void Curl_cpool_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool aborted) +void Curl_conn_terminate(struct Curl_easy *data, + struct connectdata *conn, + bool aborted) { struct cpool *cpool = cpool_get_instance(data); bool do_lock; @@ -814,7 +665,7 @@ void Curl_cpool_disconnect(struct Curl_easy *data, * 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))); + DEBUGF(infof(data, "Curl_disconnect when inuse: %u", CONN_ATTACHED(conn))); return; } @@ -822,15 +673,17 @@ void Curl_cpool_disconnect(struct Curl_easy *data, * user callback in find. */ do_lock = !CPOOL_IS_LOCKED(cpool); if(do_lock) - CPOOL_LOCK(cpool); + CPOOL_LOCK(cpool, data); 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); + /* treat the connection as aborted in CONNECT_ONLY situations, + * so no graceful shutdown is attempted. */ + if(conn->connect_only) + aborted = TRUE; if(data->multi) { /* Add it to the multi's cpool for shutdown handling */ @@ -839,419 +692,15 @@ void Curl_cpool_disconnect(struct Curl_easy *data, cpool_discard_conn(&data->multi->cpool, data, conn, aborted); } else { - /* No multi available. Make a best-effort shutdown + close */ + /* No multi available, terminate */ infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id); - cpool_close_and_destroy(NULL, conn, data, !aborted); + Curl_cshutdn_terminate(cpool->idata, conn, !aborted); } if(do_lock) - CPOOL_UNLOCK(cpool); + CPOOL_UNLOCK(cpool, data); } -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; -} - -/* return information about the shutdown connections */ -unsigned int Curl_cpool_add_waitfds(struct cpool *cpool, - struct Curl_waitfds *cwfds) -{ - unsigned int need = 0; - - 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); - - need += Curl_waitfds_add_ps(cwfds, &ps); - } - } - CPOOL_UNLOCK(cpool); - return need; -} - -/* return fd_set info about the shutdown connections */ -void Curl_cpool_setfds(struct cpool *cpool, - fd_set *read_fd_set, fd_set *write_fd_set, - int *maxfd) -{ - CPOOL_LOCK(cpool); - if(Curl_llist_head(&cpool->shutdowns)) { - struct Curl_llist_node *e; - - for(e = Curl_llist_head(&cpool->shutdowns); e; - e = Curl_node_next(e)) { - struct easy_pollset ps; - unsigned int i; - struct connectdata *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); - - for(i = 0; i < ps.num; i++) { -#if defined(__DJGPP__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Warith-conversion" -#endif - if(ps.actions[i] & CURL_POLL_IN) - FD_SET(ps.sockets[i], read_fd_set); - if(ps.actions[i] & CURL_POLL_OUT) - FD_SET(ps.sockets[i], write_fd_set); -#if defined(__DJGPP__) -#pragma GCC diagnostic pop -#endif - if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) && - ((int)ps.sockets[i] > *maxfd)) - *maxfd = (int)ps.sockets[i]; - } - } - } - CPOOL_UNLOCK(cpool); -} - -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); - - if(cpool && cpool->multi) { - DEBUGF(infof(data, "[CCACHE] trigger multi connchanged")); - Curl_multi_connchanged(cpool->multi); - } -} - - -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; @@ -1263,7 +712,7 @@ static int cpool_reap_dead_cb(struct Curl_easy *data, 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); + Curl_conn_terminate(data, conn, FALSE); return 1; } return 0; /* continue iteration */ @@ -1285,16 +734,16 @@ void Curl_cpool_prune_dead(struct Curl_easy *data) if(!cpool) return; - rctx.now = Curl_now(); - CPOOL_LOCK(cpool); - elapsed = Curl_timediff(rctx.now, cpool->last_cleanup); + rctx.now = curlx_now(); + CPOOL_LOCK(cpool, data); + elapsed = curlx_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); + CPOOL_UNLOCK(cpool, data); } static int conn_upkeep(struct Curl_easy *data, @@ -1309,14 +758,14 @@ static int conn_upkeep(struct Curl_easy *data, CURLcode Curl_cpool_upkeep(void *data) { struct cpool *cpool = cpool_get_instance(data); - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); if(!cpool) return CURLE_OK; - CPOOL_LOCK(cpool); + CPOOL_LOCK(cpool, data); cpool_foreach(data, cpool, &now, conn_upkeep); - CPOOL_UNLOCK(cpool); + CPOOL_UNLOCK(cpool, data); return CURLE_OK; } @@ -1347,9 +796,9 @@ struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, return NULL; fctx.id = conn_id; fctx.conn = NULL; - CPOOL_LOCK(cpool); - cpool_foreach(cpool->idata, cpool, &fctx, cpool_find_conn); - CPOOL_UNLOCK(cpool); + CPOOL_LOCK(cpool, data); + cpool_foreach(data, cpool, &fctx, cpool_find_conn); + CPOOL_UNLOCK(cpool, data); return fctx.conn; } @@ -1382,9 +831,9 @@ void Curl_cpool_do_by_id(struct Curl_easy *data, curl_off_t conn_id, dctx.id = conn_id; dctx.cb = cb; dctx.cbdata = cbdata; - CPOOL_LOCK(cpool); + CPOOL_LOCK(cpool, data); cpool_foreach(data, cpool, &dctx, cpool_do_conn); - CPOOL_UNLOCK(cpool); + CPOOL_UNLOCK(cpool, data); } void Curl_cpool_do_locked(struct Curl_easy *data, @@ -1393,9 +842,9 @@ void Curl_cpool_do_locked(struct Curl_easy *data, { struct cpool *cpool = cpool_get_instance(data); if(cpool) { - CPOOL_LOCK(cpool); + CPOOL_LOCK(cpool, data); cb(conn, data, cbdata); - CPOOL_UNLOCK(cpool); + CPOOL_UNLOCK(cpool, data); } else cb(conn, data, cbdata); diff --git a/Utilities/cmcurl/lib/conncache.h b/Utilities/cmcurl/lib/conncache.h index 5f239bc0b0..1314b65c60 100644 --- a/Utilities/cmcurl/lib/conncache.h +++ b/Utilities/cmcurl/lib/conncache.h @@ -26,7 +26,7 @@ ***************************************************************************/ #include -#include "timeval.h" +#include "curlx/timeval.h" struct connectdata; struct Curl_easy; @@ -36,16 +36,17 @@ struct Curl_multi; struct Curl_share; /** - * 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); + * Terminate the connection, e.g. close and destroy. + * If the connection is in a cpool, remove it. + * If a `cshutdn` is available (e.g. data has a multi handle), + * pass the connection to that for controlled shutdown. + * Otherwise terminate it right away. + * Takes ownership of `conn`. + * `data` should not be attached to a connection. + */ +void Curl_conn_terminate(struct Curl_easy *data, + struct connectdata *conn, + bool aborted); struct cpool { /* the pooled connections, bundled per destination */ @@ -54,22 +55,19 @@ struct cpool { curl_off_t next_connection_id; curl_off_t next_easy_id; struct curltime last_cleanup; - 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; + struct Curl_easy *idata; /* internal handle for maintenance */ + struct Curl_share *share; /* != NULL if pool belongs to share */ BIT(locked); + BIT(initialised); }; /* Init the pool, pass multi only if pool is owned by it. - * returns 1 on error, 0 is fine. + * Cannot fail. */ -int Curl_cpool_init(struct cpool *cpool, - Curl_cpool_disconnect_cb *disconnect_cb, - struct Curl_multi *multi, - struct Curl_share *share, - size_t size); +void Curl_cpool_init(struct cpool *cpool, + struct Curl_easy *idata, + struct Curl_share *share, + size_t size); /* Destroy all connections and free all members */ void Curl_cpool_destroy(struct cpool *connc); @@ -78,14 +76,13 @@ void Curl_cpool_destroy(struct cpool *connc); * Assigns `data->id`. */ void Curl_cpool_xfer_init(struct Curl_easy *data); -/** - * Get the connection with the given id from the transfer's pool. - */ +/* Get the connection with the given id from `data`'s conn 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; +/* Add the connection to the pool. */ +CURLcode Curl_cpool_add(struct Curl_easy *data, + struct connectdata *conn) WARN_UNUSED_RESULT; /** * Return if the pool has reached its configured limits for adding @@ -110,14 +107,13 @@ typedef bool Curl_cpool_done_match_cb(bool result, void *userdata); * 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 * @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, + const char *destination, Curl_cpool_conn_match_cb *conn_cb, Curl_cpool_done_match_cb *done_cb, void *userdata); @@ -131,17 +127,6 @@ bool Curl_cpool_find(struct Curl_easy *data, 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. @@ -178,26 +163,4 @@ 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); -unsigned int Curl_cpool_add_waitfds(struct cpool *connc, - struct Curl_waitfds *cwfds); - -void Curl_cpool_setfds(struct cpool *cpool, - fd_set *read_fd_set, fd_set *write_fd_set, - int *maxfd); - -/** - * 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); - - #endif /* HEADER_CURL_CONNCACHE_H */ diff --git a/Utilities/cmcurl/lib/connect.c b/Utilities/cmcurl/lib/connect.c index 67cdb54d31..1dcdde3fc5 100644 --- a/Utilities/cmcurl/lib/connect.c +++ b/Utilities/cmcurl/lib/connect.c @@ -67,14 +67,14 @@ #include "multiif.h" #include "sockaddr.h" /* required for Curl_sockaddr_storage */ #include "inet_ntop.h" -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "vtls/vtls.h" /* for vtsl cfilters */ #include "progress.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "conncache.h" #include "multihandle.h" #include "share.h" -#include "version_win32.h" +#include "curlx/version_win32.h" #include "vquic/vquic.h" /* for quic cfilters */ #include "http_proxy.h" #include "socks.h" @@ -87,7 +87,7 @@ #if !defined(CURL_DISABLE_ALTSVC) || defined(USE_HTTPSRR) -enum alpnid Curl_alpn2alpnid(char *name, size_t len) +enum alpnid Curl_alpn2alpnid(const char *name, size_t len) { if(len == 2) { if(strncasecompare(name, "h1", 2)) @@ -112,7 +112,7 @@ enum alpnid Curl_alpn2alpnid(char *name, size_t len) * infinite time left). If the value is negative, the timeout time has already * elapsed. * @param data the transfer to check on - * @param nowp timestamp to use for calculation, NULL to use Curl_now() + * @param nowp timestamp to use for calculation, NULL to use curlx_now() * @param duringconnect TRUE iff connect timeout is also taken into account. * @unittest: 1303 */ @@ -133,13 +133,13 @@ timediff_t Curl_timeleft(struct Curl_easy *data, return 0; /* no timeout in place or checked, return "no limit" */ if(!nowp) { - now = Curl_now(); + now = curlx_now(); nowp = &now; } if(data->set.timeout > 0) { timeleft_ms = data->set.timeout - - Curl_timediff(*nowp, data->progress.t_startop); + curlx_timediff(*nowp, data->progress.t_startop); if(!timeleft_ms) timeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */ if(!duringconnect) @@ -150,7 +150,7 @@ timediff_t Curl_timeleft(struct Curl_easy *data, 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); + curlx_timediff(*nowp, data->progress.t_startsingle); if(!ctimeleft_ms) ctimeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */ if(!timeleft_ms) @@ -161,18 +161,24 @@ timediff_t Curl_timeleft(struct Curl_easy *data, } void Curl_shutdown_start(struct Curl_easy *data, int sockindex, - struct curltime *nowp) + int timeout_ms, struct curltime *nowp) { struct curltime now; DEBUGASSERT(data->conn); if(!nowp) { - now = Curl_now(); + now = curlx_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; + data->conn->shutdown.timeout_ms = (timeout_ms > 0) ? + (unsigned int)timeout_ms : + ((data->set.shutdowntimeout > 0) ? + data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS); + /* Set a timer, unless we operate on the admin handle */ + if(data->mid && data->conn->shutdown.timeout_ms) + Curl_expire_ex(data, nowp, data->conn->shutdown.timeout_ms, + EXPIRE_SHUTDOWN); } timediff_t Curl_shutdown_timeleft(struct connectdata *conn, int sockindex, @@ -185,11 +191,11 @@ timediff_t Curl_shutdown_timeleft(struct connectdata *conn, int sockindex, return 0; /* not started or no limits */ if(!nowp) { - now = Curl_now(); + now = curlx_now(); nowp = &now; } left_ms = conn->shutdown.timeout_ms - - Curl_timediff(*nowp, conn->shutdown.start[sockindex]); + curlx_timediff(*nowp, conn->shutdown.start[sockindex]); return left_ms ? left_ms : -1; } @@ -204,7 +210,7 @@ timediff_t Curl_conn_shutdown_timeleft(struct connectdata *conn, if(!conn->shutdown.start[i].tv_sec) continue; if(!nowp) { - now = Curl_now(); + now = curlx_now(); nowp = &now; } ms = Curl_shutdown_timeleft(conn, i, nowp); @@ -266,8 +272,7 @@ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, switch(sa->sa_family) { case AF_INET: si = (struct sockaddr_in *)(void *) sa; - if(Curl_inet_ntop(sa->sa_family, &si->sin_addr, - addr, MAX_IPADR_LEN)) { + if(Curl_inet_ntop(sa->sa_family, &si->sin_addr, addr, MAX_IPADR_LEN)) { unsigned short us_port = ntohs(si->sin_port); *port = us_port; return TRUE; @@ -276,8 +281,7 @@ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, #ifdef USE_IPV6 case AF_INET6: si6 = (struct sockaddr_in6 *)(void *) sa; - if(Curl_inet_ntop(sa->sa_family, &si6->sin6_addr, - addr, MAX_IPADR_LEN)) { + if(Curl_inet_ntop(sa->sa_family, &si6->sin6_addr, addr, MAX_IPADR_LEN)) { unsigned short us_port = ntohs(si6->sin6_port); *port = us_port; return TRUE; @@ -301,7 +305,7 @@ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, addr[0] = '\0'; *port = 0; - errno = EAFNOSUPPORT; + CURL_SETERRNO(SOCKEAFNOSUPPORT); return FALSE; } @@ -404,7 +408,6 @@ typedef enum { struct cf_he_ctx { int transport; cf_ip_connect_create *cf_create; - const struct Curl_dns_entry *remotehost; cf_connect_state state; struct eyeballer *baller[2]; struct eyeballer *winner; @@ -541,7 +544,7 @@ static CURLcode baller_start(struct Curl_cfilter *cf, baller->has_started = TRUE; while(baller->addr) { - baller->started = Curl_now(); + baller->started = curlx_now(); baller->timeoutms = addr_next_match(baller->addr, baller->ai_family) ? USETIME(timeoutms) : timeoutms; baller_initiate(cf, data, baller); @@ -593,18 +596,18 @@ static CURLcode baller_connect(struct Curl_cfilter *cf, *connected = baller->connected; if(!baller->result && !*connected) { /* evaluate again */ - baller->result = Curl_conn_cf_connect(baller->cf, data, 0, connected); + baller->result = Curl_conn_cf_connect(baller->cf, data, connected); if(!baller->result) { if(*connected) { baller->connected = TRUE; baller->is_done = TRUE; } - else if(Curl_timediff(*now, baller->started) >= baller->timeoutms) { + else if(curlx_timediff(*now, baller->started) >= baller->timeoutms) { infof(data, "%s connect timeout after %" FMT_TIMEDIFF_T "ms, move on!", baller->name, baller->timeoutms); -#if defined(ETIMEDOUT) - baller->error = ETIMEDOUT; +#ifdef SOCKETIMEDOUT + baller->error = SOCKETIMEDOUT; #endif baller->result = CURLE_OPERATION_TIMEDOUT; } @@ -638,7 +641,7 @@ static CURLcode is_connected(struct Curl_cfilter *cf, * cot ballers in a QUIC appropriate way. */ evaluate: *connected = FALSE; /* a negative world view is best */ - now = Curl_now(); + now = curlx_now(); ongoing = not_started = 0; for(i = 0; i < CURL_ARRAYSIZE(ctx->baller); i++) { struct eyeballer *baller = ctx->baller[i]; @@ -693,7 +696,7 @@ evaluate: * start new ballers or return ok. */ if((ongoing || not_started) && Curl_timeleft(data, &now, TRUE) < 0) { failf(data, "Connection timeout after %" FMT_OFF_T " ms", - Curl_timediff(now, data->progress.t_startsingle)); + curlx_timediff(now, data->progress.t_startsingle)); return CURLE_OPERATION_TIMEDOUT; } @@ -709,7 +712,7 @@ evaluate: /* We start its primary baller has failed to connect or if * its start delay_ms have expired */ if((baller->primary && baller->primary->is_done) || - Curl_timediff(now, ctx->started) >= baller->delay_ms) { + curlx_timediff(now, ctx->started) >= baller->delay_ms) { baller_start(cf, data, baller, Curl_timeleft(data, &now, TRUE)); if(baller->is_done) { CURL_TRC_CF(data, cf, "%s done", baller->name); @@ -762,14 +765,11 @@ evaluate: failf(data, "Failed to connect to %s port %u after " "%" FMT_TIMEDIFF_T " ms: %s", hostname, conn->primary.remote_port, - Curl_timediff(now, data->progress.t_startsingle), + curlx_timediff(now, data->progress.t_startsingle), curl_easy_strerror(result)); -#ifdef WSAETIMEDOUT - if(WSAETIMEDOUT == data->state.os_errno) - result = CURLE_OPERATION_TIMEDOUT; -#elif defined(ETIMEDOUT) - if(ETIMEDOUT == data->state.os_errno) +#ifdef SOCKETIMEDOUT + if(SOCKETIMEDOUT == data->state.os_errno) result = CURLE_OPERATION_TIMEDOUT; #endif @@ -781,8 +781,7 @@ evaluate: * There might be more than one IP address to try out. */ static CURLcode start_connect(struct Curl_cfilter *cf, - struct Curl_easy *data, - const struct Curl_dns_entry *remotehost) + struct Curl_easy *data) { struct cf_he_ctx *ctx = cf->ctx; struct connectdata *conn = cf->conn; @@ -790,6 +789,10 @@ static CURLcode start_connect(struct Curl_cfilter *cf, int ai_family0 = 0, ai_family1 = 0; timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); const struct Curl_addrinfo *addr0 = NULL, *addr1 = NULL; + struct Curl_dns_entry *dns = data->state.dns[cf->sockindex]; + + if(!dns) + return CURLE_FAILED_INIT; if(timeout_ms < 0) { /* a precaution, no need to continue if time already is up */ @@ -797,9 +800,9 @@ static CURLcode start_connect(struct Curl_cfilter *cf, return CURLE_OPERATION_TIMEDOUT; } - ctx->started = Curl_now(); + ctx->started = curlx_now(); - /* remotehost->addr is the list of addresses from the resolver, each + /* dns->addr is the list of addresses from the resolver, each * with an address family. The list has at least one entry, possibly * many more. * We try at most 2 at a time, until we either get a connection or @@ -811,27 +814,27 @@ static CURLcode start_connect(struct Curl_cfilter *cf, if(conn->ip_version == CURL_IPRESOLVE_V6) { #ifdef USE_IPV6 ai_family0 = AF_INET6; - addr0 = addr_first_match(remotehost->addr, ai_family0); + addr0 = addr_first_match(dns->addr, ai_family0); #endif } else if(conn->ip_version == CURL_IPRESOLVE_V4) { ai_family0 = AF_INET; - addr0 = addr_first_match(remotehost->addr, ai_family0); + addr0 = addr_first_match(dns->addr, ai_family0); } else { /* 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); + addr0 = addr_first_match(dns->addr, ai_family0); #endif /* next candidate is ipv4 */ ai_family1 = AF_INET; - addr1 = addr_first_match(remotehost->addr, ai_family1); + addr1 = addr_first_match(dns->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); + if(!addr1 && !addr0 && dns->addr) { + ai_family0 = dns->addr->ai_family; + addr0 = addr_first_match(dns->addr, ai_family0); } } @@ -948,7 +951,7 @@ static void cf_he_adjust_pollset(struct Curl_cfilter *cf, static CURLcode cf_he_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_he_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; @@ -958,7 +961,6 @@ static CURLcode cf_he_connect(struct Curl_cfilter *cf, return CURLE_OK; } - (void)blocking; DEBUGASSERT(ctx); *done = FALSE; @@ -966,7 +968,7 @@ static CURLcode cf_he_connect(struct Curl_cfilter *cf, case SCFST_INIT: DEBUGASSERT(CURL_SOCKET_BAD == Curl_conn_cf_get_socket(cf, data)); DEBUGASSERT(!cf->connected); - result = start_connect(cf, data, ctx->remotehost); + result = start_connect(cf, data); if(result) return result; ctx->state = SCFST_WAITING; @@ -1058,7 +1060,7 @@ static struct curltime get_max_baller_time(struct Curl_cfilter *cf, memset(&t, 0, sizeof(t)); if(baller && baller->cf && !baller->cf->cft->query(baller->cf, data, query, NULL, &t)) { - if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) + if((t.tv_sec || t.tv_usec) && curlx_timediff_us(t, tmax) > 0) tmax = t; } } @@ -1158,7 +1160,6 @@ cf_happy_eyeballs_create(struct Curl_cfilter **pcf, struct Curl_easy *data, struct connectdata *conn, cf_ip_connect_create *cf_create, - const struct Curl_dns_entry *remotehost, int transport) { struct cf_he_ctx *ctx = NULL; @@ -1174,14 +1175,13 @@ cf_happy_eyeballs_create(struct Curl_cfilter **pcf, } ctx->transport = transport; ctx->cf_create = cf_create; - ctx->remotehost = remotehost; result = Curl_cf_create(pcf, &Curl_cft_happy_eyeballs, ctx); out: if(result) { Curl_safefree(*pcf); - Curl_safefree(ctx); + free(ctx); } return result; } @@ -1220,7 +1220,6 @@ static cf_ip_connect_create *get_cf_create(int transport) static CURLcode cf_he_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - const struct Curl_dns_entry *remotehost, int transport) { cf_ip_connect_create *cf_create; @@ -1235,8 +1234,7 @@ static CURLcode cf_he_insert_after(struct Curl_cfilter *cf_at, return CURLE_UNSUPPORTED_PROTOCOL; } result = cf_happy_eyeballs_create(&cf, data, cf_at->conn, - cf_create, remotehost, - transport); + cf_create, transport); if(result) return result; @@ -1256,17 +1254,17 @@ typedef enum { struct cf_setup_ctx { cf_setup_state state; - const struct Curl_dns_entry *remotehost; int ssl_mode; int transport; }; static CURLcode cf_setup_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_setup_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; + struct Curl_dns_entry *dns = data->state.dns[cf->sockindex]; if(cf->connected) { *done = TRUE; @@ -1275,14 +1273,17 @@ static CURLcode cf_setup_connect(struct Curl_cfilter *cf, /* connect current sub-chain */ connect_sub_chain: + if(!dns) + return CURLE_FAILED_INIT; + if(cf->next && !cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, blocking, done); + result = Curl_conn_cf_connect(cf->next, data, done); if(result || !*done) return result; } if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) { - result = cf_he_insert_after(cf, data, ctx->remotehost, ctx->transport); + result = cf_he_insert_after(cf, data, ctx->transport); if(result) return result; ctx->state = CF_SETUP_CNNCT_EYEBALLS; @@ -1410,7 +1411,6 @@ struct Curl_cftype Curl_cft_setup = { static CURLcode cf_setup_create(struct Curl_cfilter **pcf, struct Curl_easy *data, - const struct Curl_dns_entry *remotehost, int transport, int ssl_mode) { @@ -1425,7 +1425,6 @@ static CURLcode cf_setup_create(struct Curl_cfilter **pcf, goto out; } ctx->state = CF_SETUP_INIT; - ctx->remotehost = remotehost; ctx->ssl_mode = ssl_mode; ctx->transport = transport; @@ -1436,14 +1435,15 @@ static CURLcode cf_setup_create(struct Curl_cfilter **pcf, out: *pcf = result ? NULL : cf; - free(ctx); + if(ctx) { + free(ctx); + } return result; } static CURLcode cf_setup_add(struct Curl_easy *data, struct connectdata *conn, int sockindex, - const struct Curl_dns_entry *remotehost, int transport, int ssl_mode) { @@ -1451,7 +1451,7 @@ static CURLcode cf_setup_add(struct Curl_easy *data, CURLcode result = CURLE_OK; DEBUGASSERT(data); - result = cf_setup_create(&cf, data, remotehost, transport, ssl_mode); + result = cf_setup_create(&cf, data, transport, ssl_mode); if(result) goto out; Curl_conn_cf_add(data, conn, sockindex, cf); @@ -1476,7 +1476,6 @@ void Curl_debug_set_transport_provider(int transport, CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - const struct Curl_dns_entry *remotehost, int transport, int ssl_mode) { @@ -1484,7 +1483,7 @@ CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, CURLcode result; DEBUGASSERT(data); - result = cf_setup_create(&cf, data, remotehost, transport, ssl_mode); + result = cf_setup_create(&cf, data, transport, ssl_mode); if(result) goto out; Curl_conn_cf_insert_after(cf_at, cf); @@ -1495,19 +1494,23 @@ out: CURLcode Curl_conn_setup(struct Curl_easy *data, struct connectdata *conn, int sockindex, - const struct Curl_dns_entry *remotehost, + struct Curl_dns_entry *dns, int ssl_mode) { CURLcode result = CURLE_OK; DEBUGASSERT(data); DEBUGASSERT(conn->handler); + DEBUGASSERT(dns); + + Curl_resolv_unlink(data, &data->state.dns[sockindex]); + data->state.dns[sockindex] = dns; #if !defined(CURL_DISABLE_HTTP) if(!conn->cfilter[sockindex] && conn->handler->protocol == CURLPROTO_HTTPS) { DEBUGASSERT(ssl_mode != CURL_CF_SSL_DISABLE); - result = Curl_cf_https_setup(data, conn, sockindex, remotehost); + result = Curl_cf_https_setup(data, conn, sockindex); if(result) goto out; } @@ -1515,13 +1518,14 @@ CURLcode Curl_conn_setup(struct Curl_easy *data, /* Still no cfilter set, apply default. */ if(!conn->cfilter[sockindex]) { - result = cf_setup_add(data, conn, sockindex, remotehost, - conn->transport, ssl_mode); + result = cf_setup_add(data, conn, sockindex, conn->transport, ssl_mode); if(result) goto out; } DEBUGASSERT(conn->cfilter[sockindex]); out: + if(result) + Curl_resolv_unlink(data, &data->state.dns[sockindex]); return result; } diff --git a/Utilities/cmcurl/lib/connect.h b/Utilities/cmcurl/lib/connect.h index b59c38d7ce..120338eb99 100644 --- a/Utilities/cmcurl/lib/connect.h +++ b/Utilities/cmcurl/lib/connect.h @@ -25,14 +25,14 @@ ***************************************************************************/ #include "curl_setup.h" -#include "nonblock.h" /* for curlx_nonblock(), formerly Curl_nonblock() */ +#include "curlx/nonblock.h" /* for curlx_nonblock() */ #include "sockaddr.h" -#include "timeval.h" +#include "curlx/timeval.h" struct Curl_dns_entry; struct ip_quadruple; -enum alpnid Curl_alpn2alpnid(char *name, size_t len); +enum alpnid Curl_alpn2alpnid(const char *name, size_t len); /* generic function that returns how much time there is left to run, according to the timeouts set */ @@ -45,7 +45,7 @@ timediff_t Curl_timeleft(struct Curl_easy *data, #define DEFAULT_SHUTDOWN_TIMEOUT_MS (2 * 1000) void Curl_shutdown_start(struct Curl_easy *data, int sockindex, - struct curltime *nowp); + int timeout_ms, 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. */ @@ -126,7 +126,6 @@ typedef CURLcode cf_ip_connect_create(struct Curl_cfilter **pcf, CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - const struct Curl_dns_entry *remotehost, int transport, int ssl_mode); @@ -138,7 +137,7 @@ CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, CURLcode Curl_conn_setup(struct Curl_easy *data, struct connectdata *conn, int sockindex, - const struct Curl_dns_entry *remotehost, + struct Curl_dns_entry *dns, int ssl_mode); extern struct Curl_cftype Curl_cft_happy_eyeballs; diff --git a/Utilities/cmcurl/lib/content_encoding.c b/Utilities/cmcurl/lib/content_encoding.c index e83a790ed8..ac80478f0a 100644 --- a/Utilities/cmcurl/lib/content_encoding.c +++ b/Utilities/cmcurl/lib/content_encoding.c @@ -65,12 +65,15 @@ /* allow no more than 5 "chained" compression steps */ #define MAX_ENCODE_STACK 5 + +#if defined(HAVE_LIBZ) || defined(HAVE_BROTLI) || defined(HAVE_ZSTD) #define DECOMPRESS_BUFFER_SIZE 16384 /* buffer size for decompressed data */ +#endif #ifdef HAVE_LIBZ -#if !defined(ZLIB_VERNUM) || (ZLIB_VERNUM < 0x1204) -#error "requires zlib 1.2.0.4 or newer" +#if !defined(ZLIB_VERNUM) || (ZLIB_VERNUM < 0x1252) +#error "requires zlib 1.2.5.2 or newer" #endif typedef enum { @@ -163,7 +166,7 @@ static CURLcode inflate_stream(struct Curl_easy *data, struct zlib_writer *zp = (struct zlib_writer *) writer; z_stream *z = &zp->z; /* zlib state structure */ uInt nread = z->avail_in; - Bytef *orig_in = z->next_in; + z_const Bytef *orig_in = z->next_in; bool done = FALSE; CURLcode result = CURLE_OK; /* Curl_client_write status */ @@ -183,13 +186,7 @@ static CURLcode inflate_stream(struct Curl_easy *data, z->next_out = (Bytef *) zp->buffer; z->avail_out = DECOMPRESS_BUFFER_SIZE; -#ifdef Z_BLOCK - /* Z_BLOCK is only available in zlib ver. >= 1.2.0.5 */ status = inflate(z, Z_BLOCK); -#else - /* fallback for zlib ver. < 1.2.0.5 */ - status = inflate(z, Z_SYNC_FLUSH); -#endif /* Flush output data if some. */ if(z->avail_out != DECOMPRESS_BUFFER_SIZE) { @@ -220,9 +217,7 @@ static CURLcode inflate_stream(struct Curl_easy *data, /* some servers seem to not generate zlib headers, so this is an attempt 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); /* do not care about the return code */ - if(inflateInit2(z, -MAX_WBITS) == Z_OK) { + if(inflateReset2(z, -MAX_WBITS) == Z_OK) { z->next_in = orig_in; z->avail_in = nread; zp->zlib_init = ZLIB_INFLATING; @@ -278,8 +273,8 @@ static CURLcode deflate_do_write(struct Curl_easy *data, 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; + z->next_in = (z_const Bytef *)buf; + z->avail_in = (uInt)nbytes; if(zp->zlib_init == ZLIB_EXTERNAL_TRAILER) return process_trailer(data, zp); @@ -337,8 +332,8 @@ static CURLcode gzip_do_write(struct Curl_easy *data, if(zp->zlib_init == ZLIB_INIT_GZIP) { /* Let zlib handle the gzip decompression entirely */ - z->next_in = (Bytef *) buf; - z->avail_in = (uInt) nbytes; + z->next_in = (z_const Bytef *)buf; + z->avail_in = (uInt)nbytes; /* Now uncompress the data */ return inflate_stream(data, writer, type, ZLIB_INIT_GZIP); } @@ -742,6 +737,7 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, Curl_cwriter_phase phase = is_transfer ? CURL_CW_TRANSFER_DECODE : CURL_CW_CONTENT_DECODE; CURLcode result; + bool has_chunked = FALSE; do { const char *name; @@ -755,7 +751,7 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, name = enclist; for(namelen = 0; *enclist && *enclist != ','; enclist++) - if(!ISSPACE(*enclist)) + if(*enclist > ' ') namelen = enclist - name + 1; if(namelen) { @@ -770,9 +766,21 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, * 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)) { + bool is_identity = strncasecompare(name, "identity", 8); /* not requested, ignore */ CURL_TRC_WRITE(data, "decoder not requested, ignored: %.*s", (int)namelen, name); + if(is_transfer && !data->set.http_te_skip) { + if(has_chunked) + failf(data, "A Transfer-Encoding (%.*s) was listed after chunked", + (int)namelen, name); + else if(is_identity) + continue; + else + failf(data, "Unsolicited Transfer-Encoding (%.*s) found", + (int)namelen, name); + return CURLE_BAD_CONTENT_ENCODING; + } return CURLE_OK; } @@ -823,6 +831,8 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, Curl_cwriter_free(data, writer); return result; } + if(is_chunked) + has_chunked = TRUE; } } while(*enclist); diff --git a/Utilities/cmcurl/lib/cookie.c b/Utilities/cmcurl/lib/cookie.c index 9819768454..1a8426ca27 100644 --- a/Utilities/cmcurl/lib/cookie.c +++ b/Utilities/cmcurl/lib/cookie.c @@ -76,11 +76,9 @@ Example set of cookies: #include "urldata.h" #include "cookie.h" #include "psl.h" -#include "strtok.h" #include "sendf.h" #include "slist.h" #include "share.h" -#include "strtoofft.h" #include "strcase.h" #include "curl_get_line.h" #include "curl_memrchr.h" @@ -89,6 +87,7 @@ Example set of cookies: #include "fopen.h" #include "strdup.h" #include "llist.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -160,12 +159,10 @@ static bool cookie_tailmatch(const char *cookie_domain, * 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) +static bool pathmatch(const char *cookie_path, const char *uri_path) { size_t cookie_path_len; size_t uri_path_len; - char *uri_path = NULL; - char *pos; bool ret = FALSE; /* cookie_path must not have last '/' separator. ex: /sample */ @@ -175,19 +172,9 @@ static bool pathmatch(const char *cookie_path, const char *request_uri) return TRUE; } - uri_path = strdup(request_uri); - if(!uri_path) - return FALSE; - pos = strchr(uri_path, '?'); - if(pos) - *pos = 0x0; - /* #-fragments are already cut off! */ - if(0 == strlen(uri_path) || uri_path[0] != '/') { - strstore(&uri_path, "/", 1); - if(!uri_path) - return FALSE; - } + if(0 == strlen(uri_path) || uri_path[0] != '/') + uri_path = "/"; /* * here, RFC6265 5.1.4 says @@ -201,16 +188,12 @@ static bool pathmatch(const char *cookie_path, const char *request_uri) uri_path_len = strlen(uri_path); - if(uri_path_len < cookie_path_len) { - ret = FALSE; + if(uri_path_len < cookie_path_len) goto pathmatched; - } /* not using checkprefix() because matching should be case-sensitive */ - if(strncmp(cookie_path, uri_path, cookie_path_len)) { - ret = FALSE; + if(strncmp(cookie_path, uri_path, cookie_path_len)) goto pathmatched; - } /* The cookie-path and the uri-path are identical. */ if(cookie_path_len == uri_path_len) { @@ -224,10 +207,7 @@ static bool pathmatch(const char *cookie_path, const char *request_uri) goto pathmatched; } - ret = FALSE; - pathmatched: - free(uri_path); return ret; } @@ -301,34 +281,27 @@ static size_t cookiehash(const char * const domain) */ static char *sanitize_cookie_path(const char *cookie_path) { - size_t len; - char *new_path = strdup(cookie_path); - if(!new_path) - return NULL; + size_t len = strlen(cookie_path); - /* some stupid site sends path attribute with '"'. */ - len = strlen(new_path); - if(new_path[0] == '\"') { - memmove(new_path, new_path + 1, len); + /* some sites send path attribute within '"'. */ + if(cookie_path[0] == '\"') { + cookie_path++; len--; } - if(len && (new_path[len - 1] == '\"')) { - new_path[--len] = 0x0; - } + if(len && (cookie_path[len - 1] == '\"')) + len--; /* RFC6265 5.2.4 The Path Attribute */ - if(new_path[0] != '/') { + if(cookie_path[0] != '/') /* Let cookie-path be the default-path. */ - strstore(&new_path, "/", 1); - return new_path; - } + return strdup("/"); + /* remove trailing slash */ /* convert /hoge/ to /hoge */ - if(len && new_path[len - 1] == '/') { - new_path[len - 1] = 0x0; - } + if(len && cookie_path[len - 1] == '/') + len--; - return new_path; + return Curl_memdup0(cookie_path, len); } /* @@ -370,9 +343,12 @@ void Curl_cookie_loadfiles(struct Curl_easy *data) */ static void strstore(char **str, const char *newstr, size_t len) { - DEBUGASSERT(newstr); DEBUGASSERT(str); free(*str); + if(!len) { + len++; + newstr = ""; + } *str = Curl_memdup0(newstr, len); } @@ -458,18 +434,16 @@ static bool bad_domain(const char *domain, size_t len) fine. The prime reason for filtering out control bytes is that some HTTP servers return 400 for requests that contain such. */ -static bool invalid_octets(const char *p) +static bool invalid_octets(const char *ptr) { + const unsigned char *p = (const unsigned char *)ptr; /* Reject all bytes \x01 - \x1f (*except* \x09, TAB) + \x7f */ - static const char badoctets[] = { - "\x01\x02\x03\x04\x05\x06\x07\x08\x0a" - "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14" - "\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f" - }; - size_t len; - /* scan for all the octets that are *not* in cookie-octet */ - len = strcspn(p, badoctets); - return p[len] != '\0'; + while(*p) { + if(((*p != 9) && (*p < 0x20)) || (*p == 0x7f)) + return TRUE; + p++; + } + return FALSE; } #define CERR_OK 0 @@ -486,7 +460,9 @@ static bool invalid_octets(const char *p) #define CERR_COMMENT 11 /* a commented line */ #define CERR_RANGE 12 /* expire range problem */ #define CERR_FIELDS 13 /* incomplete netscape line */ +#ifdef USE_LIBPSL #define CERR_PSL 14 /* a public suffix */ +#endif #define CERR_LIVE_WINS 15 /* The maximum length we accept a date string for the 'expire' keyword. The @@ -517,51 +493,29 @@ parse_cookie_header(struct Curl_easy *data, now = time(NULL); do { - size_t vlen; - size_t nlen; - - while(*ptr && ISBLANK(*ptr)) - ptr++; + struct Curl_str name; + struct Curl_str val; /* we have a = pair or a stand-alone word here */ - nlen = strcspn(ptr, ";\t\r\n="); - if(nlen) { + if(!curlx_str_cspn(&ptr, &name, ";\t\r\n=")) { bool done = FALSE; bool sep = FALSE; - const char *namep = ptr; - const char *valuep; + curlx_str_trimblanks(&name); - ptr += nlen; + if(!curlx_str_single(&ptr, '=')) { + sep = TRUE; /* a '=' was used */ + if(!curlx_str_cspn(&ptr, &val, ";\r\n")) { + curlx_str_trimblanks(&val); - /* 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--; - } - - /* Reject cookies with a TAB inside the value */ - if(memchr(valuep, '\t', vlen)) { - infof(data, "cookie contains TAB, dropping"); - return CERR_TAB; + /* Reject cookies with a TAB inside the value */ + if(memchr(curlx_str(&val), '\t', curlx_strlen(&val))) { + infof(data, "cookie contains TAB, dropping"); + return CERR_TAB; + } } } else { - valuep = NULL; - vlen = 0; + curlx_str_init(&val); } /* @@ -569,10 +523,11 @@ parse_cookie_header(struct Curl_easy *data, * 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)) { + if(curlx_strlen(&name) >= (MAX_NAME-1) || + curlx_strlen(&val) >= (MAX_NAME-1) || + ((curlx_strlen(&name) + curlx_strlen(&val)) > MAX_NAME)) { infof(data, "oversized cookie dropped, name/val %zu + %zu bytes", - nlen, vlen); + curlx_strlen(&name), curlx_strlen(&val)); return CERR_TOO_BIG; } @@ -582,12 +537,10 @@ parse_cookie_header(struct Curl_easy *data, * "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; - } + if(!strncmp("__Secure-", curlx_str(&name), 9)) + co->prefix_secure = TRUE; + else if(!strncmp("__Host-", curlx_str(&name), 7)) + co->prefix_host = TRUE; /* * Use strstore() below to properly deal with received cookie @@ -601,8 +554,8 @@ parse_cookie_header(struct Curl_easy *data, /* Bad name/value pair. */ return CERR_NO_SEP; - strstore(&co->name, namep, nlen); - strstore(&co->value, valuep, vlen); + strstore(&co->name, curlx_str(&name), curlx_strlen(&name)); + strstore(&co->value, curlx_str(&val), curlx_strlen(&val)); done = TRUE; if(!co->name || !co->value) return CERR_NO_NAME_VALUE; @@ -612,7 +565,7 @@ parse_cookie_header(struct Curl_easy *data, return CERR_INVALID_OCTET; } } - else if(!vlen) { + else if(!curlx_strlen(&val)) { /* * this was a "=" with no content, and we must allow * 'secure' and 'httponly' specified this weirdly @@ -623,7 +576,7 @@ parse_cookie_header(struct Curl_easy *data, * using a secure protocol, or when the cookie is being set by * reading from file */ - if((nlen == 6) && strncasecompare("secure", namep, 6)) { + if(curlx_str_casecompare(&name, "secure")) { if(secure || !ci->running) { co->secure = TRUE; } @@ -631,7 +584,7 @@ parse_cookie_header(struct Curl_easy *data, return CERR_BAD_SECURE; } } - else if((nlen == 8) && strncasecompare("httponly", namep, 8)) + else if(curlx_str_casecompare(&name, "httponly")) co->httponly = TRUE; else if(sep) /* there was a '=' so we are not done parsing this field */ @@ -639,8 +592,8 @@ parse_cookie_header(struct Curl_easy *data, } if(done) ; - else if((nlen == 4) && strncasecompare("path", namep, 4)) { - strstore(&co->path, valuep, vlen); + else if(curlx_str_casecompare(&name, "path")) { + strstore(&co->path, curlx_str(&val), curlx_strlen(&val)); if(!co->path) return CERR_OUT_OF_MEMORY; free(co->spath); /* if this is set again */ @@ -648,19 +601,16 @@ parse_cookie_header(struct Curl_easy *data, if(!co->spath) return CERR_OUT_OF_MEMORY; } - else if((nlen == 6) && - strncasecompare("domain", namep, 6) && vlen) { + else if(curlx_str_casecompare(&name, "domain") && curlx_strlen(&val)) { bool is_ip; - + const char *v = curlx_str(&val); /* * 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--; - } + if('.' == *v) + curlx_str_nudge(&val, 1); #ifndef USE_LIBPSL /* @@ -668,17 +618,19 @@ parse_cookie_header(struct Curl_easy *data, * TLD or otherwise "protected" suffix. To reduce risk, we require a * dot OR the exact hostname being "localhost". */ - if(bad_domain(valuep, vlen)) + if(bad_domain(curlx_str(&val), curlx_strlen(&val))) domain = ":"; #endif - is_ip = Curl_host_is_ipnum(domain ? domain : valuep); + is_ip = Curl_host_is_ipnum(domain ? domain : curlx_str(&val)); if(!domain - || (is_ip && !strncmp(valuep, domain, vlen) && - (vlen == strlen(domain))) - || (!is_ip && cookie_tailmatch(valuep, vlen, domain))) { - strstore(&co->domain, valuep, vlen); + || (is_ip && !strncmp(curlx_str(&val), domain, + curlx_strlen(&val)) && + (curlx_strlen(&val) == strlen(domain))) + || (!is_ip && cookie_tailmatch(curlx_str(&val), + curlx_strlen(&val), domain))) { + strstore(&co->domain, curlx_str(&val), curlx_strlen(&val)); if(!co->domain) return CERR_OUT_OF_MEMORY; @@ -692,14 +644,14 @@ parse_cookie_header(struct Curl_easy *data, * not a domain to which the current host belongs. Mark as bad. */ infof(data, "skipped cookie with bad tailmatch domain: %s", - valuep); + curlx_str(&val)); return CERR_NO_TAILMATCH; } } - else if((nlen == 7) && strncasecompare("version", namep, 7)) { + else if(curlx_str_casecompare(&name, "version")) { /* just ignore */ } - else if((nlen == 7) && strncasecompare("max-age", namep, 7)) { + else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) { /* * Defined in RFC2109: * @@ -709,21 +661,22 @@ parse_cookie_header(struct Curl_easy *data, * 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: + int rc; + const char *maxage = curlx_str(&val); + if(*maxage == '\"') + maxage++; + rc = curlx_str_number(&maxage, &co->expires, CURL_OFF_T_MAX); + + switch(rc) { + case STRE_OVERFLOW: /* overflow, used max value */ co->expires = CURL_OFF_T_MAX; break; - case CURL_OFFT_INVAL: + default: /* negative or otherwise bad, expire */ co->expires = 1; break; - case CURL_OFFT_OK: + case STRE_OK: if(!co->expires) /* already expired */ co->expires = 1; @@ -736,8 +689,8 @@ parse_cookie_header(struct Curl_easy *data, } cap_expires(now, co); } - else if((nlen == 7) && strncasecompare("expires", namep, 7)) { - if(!co->expires && (vlen < MAX_DATE_LENGTH)) { + else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val)) { + if(!co->expires && (curlx_strlen(&val) < MAX_DATE_LENGTH)) { /* * Let max-age have priority. * @@ -745,8 +698,8 @@ parse_cookie_header(struct Curl_easy *data, * will be treated as a session cookie */ char dbuf[MAX_DATE_LENGTH + 1]; - memcpy(dbuf, valuep, vlen); - dbuf[vlen] = 0; + memcpy(dbuf, curlx_str(&val), curlx_strlen(&val)); + dbuf[curlx_strlen(&val)] = 0; co->expires = Curl_getdate_capped(dbuf); /* @@ -766,15 +719,8 @@ parse_cookie_header(struct Curl_easy *data, * Else, this is the second (or more) name we do not know about! */ } - else { - /* this is an "illegal" = pair */ - } - while(*ptr && ISBLANK(*ptr)) - ptr++; - if(*ptr == ';') - ptr++; - else + if(curlx_str_single(&ptr, ';')) break; } while(1); @@ -787,23 +733,11 @@ parse_cookie_header(struct Curl_easy *data, if(!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. + * No path was given in the header line, set the default. */ - 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)); + const char *endslash = strrchr(path, '/'); if(endslash) { - size_t pathlen = (endslash-path + 1); /* include end slash */ + 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); @@ -916,16 +850,8 @@ parse_netscape(struct Cookie *co, } break; case 4: - { - char *endp; - const char *p; - /* make sure curlx_strtoofft won't read past the current field */ - for(p = ptr; p < &ptr[len] && ISDIGIT(*p); ++p) - ; - if(p == ptr || p != &ptr[len] || - curlx_strtoofft(ptr, &endp, 10, &co->expires) || endp != &ptr[len]) - return CERR_RANGE; - } + if(curlx_str_number(&ptr, &co->expires, CURL_OFF_T_MAX)) + return CERR_RANGE; break; case 5: co->name = Curl_memdup0(ptr, len); @@ -1284,21 +1210,20 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, ci->running = FALSE; /* this is not running, this is init */ if(fp) { struct dynbuf buf; - Curl_dyn_init(&buf, MAX_COOKIE_LINE); + curlx_dyn_init(&buf, MAX_COOKIE_LINE); while(Curl_get_line(&buf, fp)) { - char *lineptr = Curl_dyn_ptr(&buf); + const char *lineptr = curlx_dyn_ptr(&buf); bool headerline = FALSE; if(checkprefix("Set-Cookie:", lineptr)) { /* This is a cookie line, get it! */ lineptr += 11; headerline = TRUE; - while(*lineptr && ISBLANK(*lineptr)) - lineptr++; + curlx_str_passblanks(&lineptr); } Curl_cookie_add(data, ci, headerline, TRUE, lineptr, NULL, NULL, TRUE); } - Curl_dyn_free(&buf); /* free the line buffer */ + curlx_dyn_free(&buf); /* free the line buffer */ /* * Remove expired cookies from the hash. We must make sure to run this @@ -1326,8 +1251,8 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, */ static int cookie_sort(const void *p1, const void *p2) { - struct Cookie *c1 = *(struct Cookie **)p1; - struct Cookie *c2 = *(struct Cookie **)p2; + const struct Cookie *c1 = *(const struct Cookie * const *)p1; + const struct Cookie *c2 = *(const struct Cookie * const *)p2; size_t l1, l2; /* 1 - compare cookie path lengths */ @@ -1362,8 +1287,8 @@ static int cookie_sort(const void *p1, const void *p2) */ static int cookie_sort_ct(const void *p1, const void *p2) { - struct Cookie *c1 = *(struct Cookie **)p1; - struct Cookie *c2 = *(struct Cookie **)p2; + const struct Cookie *c1 = *(const struct Cookie * const *)p1; + const struct Cookie *c2 = *(const struct Cookie * const *)p2; return (c2->creationtime > c1->creationtime) ? 1 : -1; } diff --git a/Utilities/cmcurl/lib/cshutdn.c b/Utilities/cmcurl/lib/cshutdn.c new file mode 100644 index 0000000000..f05b87d277 --- /dev/null +++ b/Utilities/cmcurl/lib/cshutdn.c @@ -0,0 +1,581 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Linus Nielsen Feltzing, + * 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 "url.h" +#include "cfilters.h" +#include "progress.h" +#include "multiif.h" +#include "multi_ev.h" +#include "sendf.h" +#include "cshutdn.h" +#include "http_negotiate.h" +#include "http_ntlm.h" +#include "sigpipe.h" +#include "connect.h" +#include "select.h" +#include "strcase.h" +#include "curlx/strparse.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +static void cshutdn_run_conn_handler(struct Curl_easy *data, + struct connectdata *conn) +{ + if(!conn->bits.shutdown_handler) { + + /* 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) { + /* Some disconnect handlers do a blocking wait on server responses. + * FTP/IMAP/SMTP and SFTP are among them. When using the internal + * handle, set an overall short timeout so we do not hang for the + * default 120 seconds. */ + if(data->state.internal) { + data->set.timeout = DEFAULT_SHUTDOWN_TIMEOUT_MS; + (void)Curl_pgrsTime(data, TIMER_STARTOP); + } + + /* 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)); + /* There are protocol handlers that block on retrieving + * server responses here (FTP). Set a short timeout. */ + conn->handler->disconnect(data, conn, conn->bits.aborted); + } + + conn->bits.shutdown_handler = TRUE; + } +} + +static void cshutdn_run_once(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); + + cshutdn_run_conn_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; +} + +void Curl_cshutdn_run_once(struct Curl_easy *data, + struct connectdata *conn, + bool *done) +{ + DEBUGASSERT(!data->conn); + Curl_attach_connection(data, conn); + cshutdn_run_once(data, conn, done); + CURL_TRC_M(data, "[SHUTDOWN] shutdown, done=%d", *done); + Curl_detach_connection(data); +} + + +void Curl_cshutdn_terminate(struct Curl_easy *data, + struct connectdata *conn, + bool do_shutdown) +{ + struct Curl_easy *admin = data; + bool done; + + /* there must be a connection to close */ + DEBUGASSERT(conn); + /* it must be removed from the connection pool */ + DEBUGASSERT(!conn->bits.in_cpool); + /* the transfer must be detached from the connection */ + DEBUGASSERT(data && !data->conn); + + /* If we can obtain an internal admin handle, use that to attach + * and terminate the connection. Some protocol will try to mess with + * `data` during shutdown and we do not want that with a `data` from + * the application. */ + if(data->multi && data->multi->admin) + admin = data->multi->admin; + + Curl_attach_connection(admin, conn); + + cshutdn_run_conn_handler(admin, conn); + if(do_shutdown) { + /* Make a last attempt to shutdown handlers and filters, if + * not done so already. */ + cshutdn_run_once(admin, conn, &done); + } + CURL_TRC_M(admin, "[SHUTDOWN] %sclosing connection #%" FMT_OFF_T, + conn->bits.shutdown_filters ? "" : "force ", + conn->connection_id); + Curl_conn_close(admin, SECONDARYSOCKET); + Curl_conn_close(admin, FIRSTSOCKET); + Curl_detach_connection(admin); + + if(data->multi) + Curl_multi_ev_conn_done(data->multi, data, conn); + Curl_conn_free(admin, conn); + + if(data->multi) { + CURL_TRC_M(data, "[SHUTDOWN] trigger multi connchanged"); + Curl_multi_connchanged(data->multi); + } +} + +static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn, + struct Curl_easy *data, + const char *destination) +{ + struct Curl_llist_node *e; + struct connectdata *conn; + + e = Curl_llist_head(&cshutdn->list); + while(e) { + conn = Curl_node_elem(e); + if(!destination || !strcmp(destination, conn->destination)) + break; + e = Curl_node_next(e); + } + + if(e) { + SIGPIPE_VARIABLE(pipe_st); + conn = Curl_node_elem(e); + Curl_node_remove(e); + sigpipe_init(&pipe_st); + sigpipe_apply(data, &pipe_st); + Curl_cshutdn_terminate(data, conn, FALSE); + sigpipe_restore(&pipe_st); + return TRUE; + } + return FALSE; +} + +bool Curl_cshutdn_close_oldest(struct Curl_easy *data, + const char *destination) +{ + if(data && data->multi) { + struct cshutdn *csd = &data->multi->cshutdn; + return cshutdn_destroy_oldest(csd, data, destination); + } + return FALSE; +} + +#define NUM_POLLS_ON_STACK 10 + +static CURLcode cshutdn_wait(struct cshutdn *cshutdn, + struct Curl_easy *data, + 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 = Curl_cshutdn_add_pollfds(cshutdn, data, &cpfds); + if(result) + goto out; + + Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000)); + +out: + Curl_pollfds_cleanup(&cpfds); + return result; +} + + +static void cshutdn_perform(struct cshutdn *cshutdn, + struct Curl_easy *data) +{ + struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list); + struct Curl_llist_node *enext; + struct connectdata *conn; + struct curltime *nowp = NULL; + struct curltime now; + timediff_t next_expire_ms = 0, ms; + bool done; + + if(!e) + return; + + CURL_TRC_M(data, "[SHUTDOWN] perform on %zu connections", + Curl_llist_count(&cshutdn->list)); + while(e) { + enext = Curl_node_next(e); + conn = Curl_node_elem(e); + Curl_cshutdn_run_once(data, conn, &done); + if(done) { + Curl_node_remove(e); + Curl_cshutdn_terminate(data, conn, FALSE); + } + else { + /* idata has one timer list, but maybe more than one connection. + * Set EXPIRE_SHUTDOWN to the smallest time left for all. */ + if(!nowp) { + now = curlx_now(); + nowp = &now; + } + ms = Curl_conn_shutdown_timeleft(conn, nowp); + if(ms && ms < next_expire_ms) + next_expire_ms = ms; + } + e = enext; + } + + if(next_expire_ms) + Curl_expire_ex(data, nowp, next_expire_ms, EXPIRE_SHUTDOWN); +} + + +static void cshutdn_terminate_all(struct cshutdn *cshutdn, + struct Curl_easy *data, + int timeout_ms) +{ + struct curltime started = curlx_now(); + struct Curl_llist_node *e; + SIGPIPE_VARIABLE(pipe_st); + + DEBUGASSERT(cshutdn); + DEBUGASSERT(data); + + CURL_TRC_M(data, "[SHUTDOWN] shutdown all"); + sigpipe_init(&pipe_st); + sigpipe_apply(data, &pipe_st); + + while(Curl_llist_head(&cshutdn->list)) { + timediff_t timespent; + int remain_ms; + + cshutdn_perform(cshutdn, data); + + if(!Curl_llist_head(&cshutdn->list)) { + CURL_TRC_M(data, "[SHUTDOWN] shutdown finished cleanly"); + break; + } + + /* wait for activity, timeout or "nothing" */ + timespent = curlx_timediff(curlx_now(), started); + if(timespent >= (timediff_t)timeout_ms) { + CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, %s", + (timeout_ms > 0) ? "timeout" : "best effort done"); + break; + } + + remain_ms = timeout_ms - (int)timespent; + if(cshutdn_wait(cshutdn, data, remain_ms)) { + CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, aborted"); + break; + } + } + + /* Terminate any remaining. */ + e = Curl_llist_head(&cshutdn->list); + while(e) { + struct connectdata *conn = Curl_node_elem(e); + Curl_node_remove(e); + Curl_cshutdn_terminate(data, conn, FALSE); + e = Curl_llist_head(&cshutdn->list); + } + DEBUGASSERT(!Curl_llist_count(&cshutdn->list)); + + sigpipe_restore(&pipe_st); +} + + +int Curl_cshutdn_init(struct cshutdn *cshutdn, + struct Curl_multi *multi) +{ + DEBUGASSERT(multi); + cshutdn->multi = multi; + Curl_llist_init(&cshutdn->list, NULL); + cshutdn->initialised = TRUE; + return 0; /* good */ +} + + +void Curl_cshutdn_destroy(struct cshutdn *cshutdn, + struct Curl_easy *data) +{ + if(cshutdn->initialised && data) { + int timeout_ms = 0; + /* Just for testing, run graceful shutdown */ +#ifdef DEBUGBUILD + { + const char *p = getenv("CURL_GRACEFUL_SHUTDOWN"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, INT_MAX)) + timeout_ms = (int)l; + } + } +#endif + + CURL_TRC_M(data, "[SHUTDOWN] destroy, %zu connections, timeout=%dms", + Curl_llist_count(&cshutdn->list), timeout_ms); + cshutdn_terminate_all(cshutdn, data, timeout_ms); + } + cshutdn->multi = NULL; +} + +size_t Curl_cshutdn_count(struct Curl_easy *data) +{ + if(data && data->multi) { + struct cshutdn *csd = &data->multi->cshutdn; + return Curl_llist_count(&csd->list); + } + return 0; +} + +size_t Curl_cshutdn_dest_count(struct Curl_easy *data, + const char *destination) +{ + if(data && data->multi) { + struct cshutdn *csd = &data->multi->cshutdn; + size_t n = 0; + struct Curl_llist_node *e = Curl_llist_head(&csd->list); + while(e) { + struct connectdata *conn = Curl_node_elem(e); + if(!strcmp(destination, conn->destination)) + ++n; + e = Curl_node_next(e); + } + return n; + } + return 0; +} + + +static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn, + struct Curl_easy *data, + struct connectdata *conn) +{ + CURLMcode mresult; + + DEBUGASSERT(cshutdn); + DEBUGASSERT(cshutdn->multi->socket_cb); + + Curl_attach_connection(data, conn); + mresult = Curl_multi_ev_assess_conn(cshutdn->multi, data, conn); + Curl_detach_connection(data); + return mresult; +} + + +void Curl_cshutdn_add(struct cshutdn *cshutdn, + struct connectdata *conn, + size_t conns_in_pool) +{ + struct Curl_easy *data = cshutdn->multi->admin; + size_t max_total = (cshutdn->multi->max_total_connections > 0) ? + (size_t)cshutdn->multi->max_total_connections : 0; + + /* Add the connection to our shutdown list for non-blocking shutdown + * during multi processing. */ + if(max_total > 0 && (max_total <= + (conns_in_pool + Curl_llist_count(&cshutdn->list)))) { + CURL_TRC_M(data, "[SHUTDOWN] discarding oldest shutdown connection " + "due to connection limit of %zu", max_total); + cshutdn_destroy_oldest(cshutdn, data, NULL); + } + + if(cshutdn->multi->socket_cb) { + if(cshutdn_update_ev(cshutdn, data, conn)) { + CURL_TRC_M(data, "[SHUTDOWN] update events failed, discarding #%" + FMT_OFF_T, conn->connection_id); + Curl_cshutdn_terminate(data, conn, FALSE); + return; + } + } + + Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node); + CURL_TRC_M(data, "[SHUTDOWN] added #%" FMT_OFF_T + " to shutdowns, now %zu conns in shutdown", + conn->connection_id, Curl_llist_count(&cshutdn->list)); +} + + +static void cshutdn_multi_socket(struct cshutdn *cshutdn, + struct Curl_easy *data, + curl_socket_t s) +{ + struct Curl_llist_node *e; + struct connectdata *conn; + bool done; + + DEBUGASSERT(cshutdn->multi->socket_cb); + e = Curl_llist_head(&cshutdn->list); + while(e) { + conn = Curl_node_elem(e); + if(s == conn->sock[FIRSTSOCKET] || s == conn->sock[SECONDARYSOCKET]) { + Curl_cshutdn_run_once(data, conn, &done); + if(done || cshutdn_update_ev(cshutdn, data, conn)) { + Curl_node_remove(e); + Curl_cshutdn_terminate(data, conn, FALSE); + } + break; + } + e = Curl_node_next(e); + } +} + + +void Curl_cshutdn_perform(struct cshutdn *cshutdn, + struct Curl_easy *data, + curl_socket_t s) +{ + if((s == CURL_SOCKET_TIMEOUT) || (!cshutdn->multi->socket_cb)) + cshutdn_perform(cshutdn, data); + else + cshutdn_multi_socket(cshutdn, data, s); +} + +/* return fd_set info about the shutdown connections */ +void Curl_cshutdn_setfds(struct cshutdn *cshutdn, + struct Curl_easy *data, + fd_set *read_fd_set, fd_set *write_fd_set, + int *maxfd) +{ + if(Curl_llist_head(&cshutdn->list)) { + struct Curl_llist_node *e; + + for(e = Curl_llist_head(&cshutdn->list); e; + e = Curl_node_next(e)) { + struct easy_pollset ps; + unsigned int i; + struct connectdata *conn = Curl_node_elem(e); + memset(&ps, 0, sizeof(ps)); + Curl_attach_connection(data, conn); + Curl_conn_adjust_pollset(data, conn, &ps); + Curl_detach_connection(data); + + for(i = 0; i < ps.num; i++) { +#if defined(__DJGPP__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warith-conversion" +#endif + if(ps.actions[i] & CURL_POLL_IN) + FD_SET(ps.sockets[i], read_fd_set); + if(ps.actions[i] & CURL_POLL_OUT) + FD_SET(ps.sockets[i], write_fd_set); +#if defined(__DJGPP__) +#pragma GCC diagnostic pop +#endif + if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) && + ((int)ps.sockets[i] > *maxfd)) + *maxfd = (int)ps.sockets[i]; + } + } + } +} + +/* return information about the shutdown connections */ +unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, + struct Curl_easy *data, + struct Curl_waitfds *cwfds) +{ + unsigned int need = 0; + + if(Curl_llist_head(&cshutdn->list)) { + struct Curl_llist_node *e; + struct easy_pollset ps; + struct connectdata *conn; + + for(e = Curl_llist_head(&cshutdn->list); e; + e = Curl_node_next(e)) { + conn = Curl_node_elem(e); + memset(&ps, 0, sizeof(ps)); + Curl_attach_connection(data, conn); + Curl_conn_adjust_pollset(data, conn, &ps); + Curl_detach_connection(data); + + need += Curl_waitfds_add_ps(cwfds, &ps); + } + } + return need; +} + +CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, + struct Curl_easy *data, + struct curl_pollfds *cpfds) +{ + CURLcode result = CURLE_OK; + + if(Curl_llist_head(&cshutdn->list)) { + struct Curl_llist_node *e; + struct easy_pollset ps; + struct connectdata *conn; + + for(e = Curl_llist_head(&cshutdn->list); e; + e = Curl_node_next(e)) { + conn = Curl_node_elem(e); + memset(&ps, 0, sizeof(ps)); + Curl_attach_connection(data, conn); + Curl_conn_adjust_pollset(data, conn, &ps); + Curl_detach_connection(data); + + result = Curl_pollfds_add_ps(cpfds, &ps); + if(result) { + Curl_pollfds_cleanup(cpfds); + goto out; + } + } + } +out: + return result; +} diff --git a/Utilities/cmcurl/lib/cshutdn.h b/Utilities/cmcurl/lib/cshutdn.h new file mode 100644 index 0000000000..510d5bf506 --- /dev/null +++ b/Utilities/cmcurl/lib/cshutdn.h @@ -0,0 +1,110 @@ +#ifndef HEADER_CURL_CSHUTDN_H +#define HEADER_CURL_CSHUTDN_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Linus Nielsen Feltzing, + * + * 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 +#include "curlx/timeval.h" + +struct connectdata; +struct Curl_easy; +struct curl_pollfds; +struct Curl_waitfds; +struct Curl_multi; +struct Curl_share; + +/* Run the shutdown of the connection once. + * Will shortly attach/detach `data` to `conn` while doing so. + * `done` will be set TRUE if any error was encountered or if + * the connection was shut down completely. */ +void Curl_cshutdn_run_once(struct Curl_easy *data, + struct connectdata *conn, + bool *done); + +/* Terminates the connection, e.g. closes and destroys it. + * If `run_shutdown` is TRUE, the shutdown will be run once before + * terminating it. + * Takes ownership of `conn`. */ +void Curl_cshutdn_terminate(struct Curl_easy *data, + struct connectdata *conn, + bool run_shutdown); + +/* A `cshutdown` is always owned by a multi handle to maintain + * the connections to be shut down. It registers timers and + * sockets to monitor via the multi handle. */ +struct cshutdn { + struct Curl_llist list; /* connections being shut down */ + struct Curl_multi *multi; /* the multi owning this */ + BIT(initialised); +}; + +/* Init as part of the given multi handle. */ +int Curl_cshutdn_init(struct cshutdn *cshutdn, + struct Curl_multi *multi); + +/* Terminate all remaining connections and free resources. */ +void Curl_cshutdn_destroy(struct cshutdn *cshutdn, + struct Curl_easy *data); + +/* Number of connections being shut down. */ +size_t Curl_cshutdn_count(struct Curl_easy *data); + +/* Number of connections to the destination being shut down. */ +size_t Curl_cshutdn_dest_count(struct Curl_easy *data, + const char *destination); + +/* Close the oldest connection in shutdown to destination or, + * when destination is NULL for any destination. + * Return TRUE if a connection has been closed. */ +bool Curl_cshutdn_close_oldest(struct Curl_easy *data, + const char *destination); + +/* Add a connection to have it shut down. Will terminate the oldest + * connection when total connection limit of multi is being reached. */ +void Curl_cshutdn_add(struct cshutdn *cshutdn, + struct connectdata *conn, + size_t conns_in_pool); + +/* Add sockets and POLLIN/OUT flags for connections being shut down. */ +CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, + struct Curl_easy *data, + struct curl_pollfds *cpfds); + +unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, + struct Curl_easy *data, + struct Curl_waitfds *cwfds); + +void Curl_cshutdn_setfds(struct cshutdn *cshutdn, + struct Curl_easy *data, + fd_set *read_fd_set, fd_set *write_fd_set, + int *maxfd); + +/* Run shut down connections using socket. If socket is CURL_SOCKET_TIMEOUT, + * run maintenance on all connections. */ +void Curl_cshutdn_perform(struct cshutdn *cshutdn, + struct Curl_easy *data, + curl_socket_t s); + +#endif /* HEADER_CURL_CSHUTDN_H */ diff --git a/Utilities/cmcurl/lib/curl_addrinfo.c b/Utilities/cmcurl/lib/curl_addrinfo.c index 52f0f91255..b131c747b1 100644 --- a/Utilities/cmcurl/lib/curl_addrinfo.c +++ b/Utilities/cmcurl/lib/curl_addrinfo.c @@ -50,8 +50,9 @@ #include #include "curl_addrinfo.h" -#include "inet_pton.h" -#include "warnless.h" +#include "fake_addrinfo.h" +#include "curlx/inet_pton.h" +#include "curlx/warnless.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -118,7 +119,7 @@ Curl_getaddrinfo_ex(const char *nodename, *result = NULL; /* assume failure */ - error = getaddrinfo(nodename, servname, hints, &aihead); + error = CURL_GETADDRINFO(nodename, servname, hints, &aihead); if(error) return error; @@ -184,7 +185,7 @@ Curl_getaddrinfo_ex(const char *nodename, /* destroy the addrinfo list */ if(aihead) - freeaddrinfo(aihead); + CURL_FREEADDRINFO(aihead); /* if we failed, also destroy the Curl_addrinfo list */ if(error) { @@ -217,7 +218,7 @@ Curl_getaddrinfo_ex(const char *nodename, * * This function returns a pointer to the first element of a newly allocated * Curl_addrinfo struct linked list filled with the data of a given hostent. - * Curl_addrinfo is meant to work like the addrinfo struct does for a IPv6 + * Curl_addrinfo is meant to work like the addrinfo struct does for an IPv6 * stack, but usable also for IPv4, all hosts and environments. * * The memory allocated by this function *MUST* be free'd later on calling @@ -430,13 +431,13 @@ Curl_ip2addr(int af, const void *inaddr, const char *hostname, int port) struct Curl_addrinfo *Curl_str2addr(char *address, int port) { struct in_addr in; - if(Curl_inet_pton(AF_INET, address, &in) > 0) + if(curlx_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 USE_IPV6 { struct in6_addr in6; - if(Curl_inet_pton(AF_INET6, address, &in6) > 0) + if(curlx_inet_pton(AF_INET6, address, &in6) > 0) /* This is a dotted IPv6 address ::1-style */ return Curl_ip2addr(AF_INET6, &in6, address, port); } @@ -467,7 +468,7 @@ struct Curl_addrinfo *Curl_unix2addr(const char *path, bool *longpath, sa_un = (void *) ai->ai_addr; sa_un->sun_family = AF_UNIX; - /* sun_path must be able to store the NUL-terminated path */ + /* sun_path must be able to store the null-terminated path */ path_len = strlen(path) + 1; if(path_len > sizeof(sa_un->sun_path)) { free(ai); @@ -508,8 +509,16 @@ curl_dbg_freeaddrinfo(struct addrinfo *freethis, source, line, (void *)freethis); #ifdef USE_LWIPSOCK lwip_freeaddrinfo(freethis); +#elif defined(USE_FAKE_GETADDRINFO) + { + const char *env = getenv("CURL_DNS_SERVER"); + if(env) + r_freeaddrinfo(freethis); + else + freeaddrinfo(freethis); + } #else - (freeaddrinfo)(freethis); + freeaddrinfo(freethis); #endif } #endif /* defined(CURLDEBUG) && defined(HAVE_FREEADDRINFO) */ @@ -526,15 +535,22 @@ curl_dbg_freeaddrinfo(struct addrinfo *freethis, int curl_dbg_getaddrinfo(const char *hostname, - const char *service, - const struct addrinfo *hints, - struct addrinfo **result, - int line, const char *source) + const char *service, + const struct addrinfo *hints, + struct addrinfo **result, + int line, const char *source) { #ifdef USE_LWIPSOCK int res = lwip_getaddrinfo(hostname, service, hints, result); +#elif defined(USE_FAKE_GETADDRINFO) + int res; + const char *env = getenv("CURL_DNS_SERVER"); + if(env) + res = r_getaddrinfo(hostname, service, hints, result); + else + res = getaddrinfo(hostname, service, hints, result); #else - int res = (getaddrinfo)(hostname, service, hints, result); + int res = getaddrinfo(hostname, service, hints, result); #endif if(0 == res) /* success */ diff --git a/Utilities/cmcurl/lib/curl_config.h.cmake b/Utilities/cmcurl/lib/curl_config.h.cmake index 209106516b..5e942a8bcb 100644 --- a/Utilities/cmcurl/lib/curl_config.h.cmake +++ b/Utilities/cmcurl/lib/curl_config.h.cmake @@ -190,6 +190,9 @@ /* Define to 1 if you have _Atomic support. */ #cmakedefine HAVE_ATOMIC 1 +/* Define to 1 if you have the `accept4' function. */ +#cmakedefine HAVE_ACCEPT4 1 + /* Define to 1 if you have the `fnmatch' function. */ #cmakedefine HAVE_FNMATCH 1 @@ -317,10 +320,10 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_IFADDRS_H 1 -/* Define to 1 if you have a IPv6 capable working inet_ntop function. */ +/* Define to 1 if you have an IPv6 capable working inet_ntop function. */ #cmakedefine HAVE_INET_NTOP 1 -/* Define to 1 if you have a IPv6 capable working inet_pton function. */ +/* Define to 1 if you have an IPv6 capable working inet_pton function. */ #cmakedefine HAVE_INET_PTON 1 /* Define to 1 if symbol `sa_family_t' exists */ @@ -422,6 +425,9 @@ /* Define to 1 if you have the `pipe' function. */ #cmakedefine HAVE_PIPE 1 +/* Define to 1 if you have the `pipe2' function. */ +#cmakedefine HAVE_PIPE2 1 + /* Define to 1 if you have the `eventfd' function. */ #cmakedefine HAVE_EVENTFD 1 @@ -464,6 +470,9 @@ /* Define to 1 if you have the sendmmsg function. */ #cmakedefine HAVE_SENDMMSG 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDINT_H 1 + /* Define to 1 if you have the 'fsetxattr' function. */ #cmakedefine HAVE_FSETXATTR 1 @@ -542,12 +551,6 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STROPTS_H 1 -/* Define to 1 if you have the strtok_r function. */ -#cmakedefine HAVE_STRTOK_R 1 - -/* 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 @@ -709,14 +712,20 @@ ${SIZEOF_TIME_T_CODE} /* if wolfSSL is enabled */ #cmakedefine USE_WOLFSSL 1 +/* if wolfSSL has the wolfSSL_get_peer_certificate function. */ +#cmakedefine HAVE_WOLFSSL_GET_PEER_CERTIFICATE 1 + +/* if wolfSSL has the wolfSSL_UseALPN function. */ +#cmakedefine HAVE_WOLFSSL_USEALPN 1 + /* 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 +#cmakedefine HAVE_WOLFSSL_BIO_NEW 1 /* if wolfSSL has the wolfSSL_BIO_set_shutdown function. */ -#cmakedefine HAVE_WOLFSSL_FULL_BIO 1 +#cmakedefine HAVE_WOLFSSL_BIO_SET_SHUTDOWN 1 /* if libssh is in use */ #cmakedefine USE_LIBSSH 1 @@ -770,6 +779,9 @@ ${SIZEOF_TIME_T_CODE} /* to enable openssl + nghttp3 */ #cmakedefine USE_OPENSSL_QUIC 1 +/* to enable openssl + ngtcp2 + nghttp3 */ +#cmakedefine OPENSSL_QUIC_API2 1 + /* Define to 1 if you have the quiche_conn_set_qlog_fd function. */ #cmakedefine HAVE_QUICHE_CONN_SET_QLOG_FD 1 @@ -803,9 +815,6 @@ ${SIZEOF_TIME_T_CODE} /* Define to empty if `const' does not conform to ANSI C. */ #cmakedefine const ${const} -/* Type to use in place of in_addr_t when system does not provide it. */ -#cmakedefine in_addr_t ${in_addr_t} - /* Define to `unsigned int' if does not define. */ #cmakedefine size_t ${size_t} diff --git a/Utilities/cmcurl/lib/curl_ctype.h b/Utilities/cmcurl/lib/curl_ctype.h index b70acf3c5a..48c3c37c35 100644 --- a/Utilities/cmcurl/lib/curl_ctype.h +++ b/Utilities/cmcurl/lib/curl_ctype.h @@ -37,6 +37,7 @@ #define ISCNTRL(x) (ISLOWCNTRL(x) || IS7F(x)) #define ISALPHA(x) (ISLOWER(x) || ISUPPER(x)) #define ISXDIGIT(x) (ISDIGIT(x) || ISLOWHEXALHA(x) || ISUPHEXALHA(x)) +#define ISODIGIT(x) (((x) >= '0') && ((x) <= '7')) #define ISALNUM(x) (ISDIGIT(x) || ISLOWER(x) || ISUPPER(x)) #define ISUPPER(x) (((x) >= 'A') && ((x) <= 'Z')) #define ISLOWER(x) (((x) >= 'a') && ((x) <= 'z')) diff --git a/Utilities/cmcurl/lib/curl_fnmatch.c b/Utilities/cmcurl/lib/curl_fnmatch.c index ffac8048f6..21eca4ceda 100644 --- a/Utilities/cmcurl/lib/curl_fnmatch.c +++ b/Utilities/cmcurl/lib/curl_fnmatch.c @@ -71,13 +71,13 @@ typedef enum { #define SETCHARSET_OK 1 #define SETCHARSET_FAIL 0 -static int parsekeyword(unsigned char **pattern, unsigned char *charset) +static int parsekeyword(const unsigned char **pattern, unsigned char *charset) { parsekey_state state = CURLFNM_PKW_INIT; #define KEYLEN 10 char keyword[KEYLEN] = { 0 }; int i; - unsigned char *p = *pattern; + const unsigned char *p = *pattern; bool found = FALSE; for(i = 0; !found; i++) { char c = (char)*p++; @@ -140,9 +140,9 @@ static char_class charclass(unsigned char c) } /* Include a character or a range in set. */ -static void setcharorrange(unsigned char **pp, unsigned char *charset) +static void setcharorrange(const unsigned char **pp, unsigned char *charset) { - unsigned char *p = (*pp)++; + const unsigned char *p = (*pp)++; unsigned char c = *p++; charset[c] = 1; @@ -162,7 +162,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) */ -static int setcharset(unsigned char **p, unsigned char *charset) +static int setcharset(const unsigned char **p, unsigned char *charset) { setcharset_state state = CURLFNM_SCHS_DEFAULT; bool something_found = FALSE; @@ -185,7 +185,7 @@ static int setcharset(unsigned char **p, unsigned char *charset) (*p)++; } else if(c == '[') { - unsigned char *pp = *p + 1; + const unsigned char *pp = *p + 1; if(*pp++ == ':' && parsekeyword(&pp, charset)) *p = pp; @@ -257,12 +257,12 @@ fail: static int loop(const unsigned char *pattern, const unsigned char *string, int maxstars) { - unsigned char *p = (unsigned char *)pattern; - unsigned char *s = (unsigned char *)string; + const unsigned char *p = (const unsigned char *)pattern; + const unsigned char *s = (const unsigned char *)string; unsigned char charset[CURLFNM_CHSET_SIZE] = { 0 }; for(;;) { - unsigned char *pp; + const unsigned char *pp; switch(*p) { case '*': @@ -319,7 +319,7 @@ static int loop(const unsigned char *pattern, const unsigned char *string, else if(charset[CURLFNM_PRINT]) found = ISPRINT(*s); else if(charset[CURLFNM_SPACE]) - found = ISSPACE(*s); + found = ISBLANK(*s); else if(charset[CURLFNM_UPPER]) found = ISUPPER(*s); else if(charset[CURLFNM_LOWER]) @@ -359,7 +359,8 @@ int Curl_fnmatch(void *ptr, const char *pattern, const char *string) if(!pattern || !string) { return CURL_FNMATCH_FAIL; } - return loop((unsigned char *)pattern, (unsigned char *)string, 2); + return loop((const unsigned char *)pattern, + (const unsigned char *)string, 2); } #else #include diff --git a/Utilities/cmcurl/lib/curl_get_line.c b/Utilities/cmcurl/lib/curl_get_line.c index 7ee3b65af7..2bb57492b3 100644 --- a/Utilities/cmcurl/lib/curl_get_line.c +++ b/Utilities/cmcurl/lib/curl_get_line.c @@ -28,9 +28,7 @@ !defined(CURL_DISABLE_HSTS) || !defined(CURL_DISABLE_NETRC) #include "curl_get_line.h" -#ifdef BUILDING_LIBCURL #include "curl_memory.h" -#endif /* The last #include file should be: */ #include "memdebug.h" @@ -42,7 +40,7 @@ int Curl_get_line(struct dynbuf *buf, FILE *input) { CURLcode result; char buffer[128]; - Curl_dyn_reset(buf); + curlx_dyn_reset(buf); while(1) { char *b = fgets(buffer, sizeof(buffer), input); @@ -52,7 +50,7 @@ int Curl_get_line(struct dynbuf *buf, FILE *input) if(!rlen) break; - result = Curl_dyn_addn(buf, b, rlen); + result = curlx_dyn_addn(buf, b, rlen); if(result) /* too long line or out of memory */ return 0; /* error */ @@ -63,13 +61,15 @@ int Curl_get_line(struct dynbuf *buf, FILE *input) else if(feof(input)) { /* append a newline */ - result = Curl_dyn_addn(buf, "\n", 1); + result = curlx_dyn_addn(buf, "\n", 1); if(result) /* too long line or out of memory */ return 0; /* error */ return 1; /* all good */ } } + else if(curlx_dyn_len(buf)) + return 1; /* all good */ else break; } diff --git a/Utilities/cmcurl/lib/curl_get_line.h b/Utilities/cmcurl/lib/curl_get_line.h index 1e3b0f0357..d4877123f2 100644 --- a/Utilities/cmcurl/lib/curl_get_line.h +++ b/Utilities/cmcurl/lib/curl_get_line.h @@ -24,13 +24,7 @@ * ***************************************************************************/ -#include "dynbuf.h" - -#ifndef BUILDING_LIBCURL -/* this renames functions so that the tool code can use the same code - without getting symbol collisions */ -#define Curl_get_line(a,b) curlx_get_line(a,b) -#endif +#include "curlx/dynbuf.h" /* Curl_get_line() returns complete lines that end with a newline. */ int Curl_get_line(struct dynbuf *buf, FILE *input); diff --git a/Utilities/cmcurl/lib/curl_gssapi.c b/Utilities/cmcurl/lib/curl_gssapi.c index 0b383f5066..f83701ad64 100644 --- a/Utilities/cmcurl/lib/curl_gssapi.c +++ b/Utilities/cmcurl/lib/curl_gssapi.c @@ -46,10 +46,10 @@ #endif gss_OID_desc Curl_spnego_mech_oid CURL_ALIGN8 = { - 6, (char *)"\x2b\x06\x01\x05\x05\x02" + 6, CURL_UNCONST("\x2b\x06\x01\x05\x05\x02") }; gss_OID_desc Curl_krb5_mech_oid CURL_ALIGN8 = { - 9, (char *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" + 9, CURL_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02") }; OM_uint32 Curl_gss_init_sec_context( diff --git a/Utilities/cmcurl/lib/curl_krb5.h b/Utilities/cmcurl/lib/curl_krb5.h index ccf6b96a87..574340fd3c 100644 --- a/Utilities/cmcurl/lib/curl_krb5.h +++ b/Utilities/cmcurl/lib/curl_krb5.h @@ -39,14 +39,16 @@ struct Curl_sec_client_mech { #define AUTH_CONTINUE 1 #define AUTH_ERROR 2 -#ifdef HAVE_GSSAPI +#if defined(HAVE_GSSAPI) && !defined(CURL_DISABLE_FTP) +void Curl_sec_conn_init(struct connectdata *); +void Curl_sec_conn_destroy(struct connectdata *); int Curl_sec_read_msg(struct Curl_easy *data, struct connectdata *conn, char *, enum protection_level); -void Curl_sec_end(struct connectdata *); CURLcode Curl_sec_login(struct Curl_easy *, struct connectdata *); int Curl_sec_request_prot(struct connectdata *conn, const char *level); #else -#define Curl_sec_end(x) +#define Curl_sec_conn_init(x) Curl_nop_stmt +#define Curl_sec_conn_destroy(x) Curl_nop_stmt #endif #endif /* HEADER_CURL_KRB5_H */ diff --git a/Utilities/cmcurl/lib/curl_memory.h b/Utilities/cmcurl/lib/curl_memory.h index 7f110dab7d..bc3e944fea 100644 --- a/Utilities/cmcurl/lib/curl_memory.h +++ b/Utilities/cmcurl/lib/curl_memory.h @@ -84,20 +84,6 @@ #undef socketpair #endif -#ifndef CURL_NO_GETADDRINFO_OVERRIDE -#ifdef HAVE_GETADDRINFO -#if defined(getaddrinfo) && defined(__osf__) -#undef ogetaddrinfo -#else -#undef getaddrinfo -#endif -#endif /* HAVE_GETADDRINFO */ - -#ifdef HAVE_FREEADDRINFO -#undef freeaddrinfo -#endif /* HAVE_FREEADDRINFO */ -#endif /* !CURL_NO_GETADDRINFO_OVERRIDE */ - /* sclose is probably already defined, redefine it! */ #undef sclose #undef fopen diff --git a/Utilities/cmcurl/lib/curl_memrchr.c b/Utilities/cmcurl/lib/curl_memrchr.c index c6d55f1042..5b6a39c022 100644 --- a/Utilities/cmcurl/lib/curl_memrchr.c +++ b/Utilities/cmcurl/lib/curl_memrchr.c @@ -33,10 +33,6 @@ #include "memdebug.h" #ifndef HAVE_MEMRCHR -#if (!defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)) || \ - defined(USE_OPENSSL) || \ - defined(USE_SCHANNEL) - /* * Curl_memrchr() * @@ -57,12 +53,10 @@ Curl_memrchr(const void *s, int c, size_t n) while(p >= q) { if(*p == (unsigned char)c) - return (void *)p; + return CURL_UNCONST(p); p--; } } return NULL; } - -#endif #endif /* HAVE_MEMRCHR */ diff --git a/Utilities/cmcurl/lib/curl_memrchr.h b/Utilities/cmcurl/lib/curl_memrchr.h index 67a21ef361..3c7dda96ac 100644 --- a/Utilities/cmcurl/lib/curl_memrchr.h +++ b/Utilities/cmcurl/lib/curl_memrchr.h @@ -34,15 +34,9 @@ #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/Utilities/cmcurl/lib/curl_ntlm_core.c b/Utilities/cmcurl/lib/curl_ntlm_core.c index 54491fc0a8..d6cd44d963 100644 --- a/Utilities/cmcurl/lib/curl_ntlm_core.c +++ b/Utilities/cmcurl/lib/curl_ntlm_core.c @@ -71,16 +71,7 @@ # include # include # include -# if (defined(OPENSSL_VERSION_NUMBER) && \ - (OPENSSL_VERSION_NUMBER < 0x00907001L)) && !defined(USE_WOLFSSL) -# define DES_key_schedule des_key_schedule -# define DES_cblock des_cblock -# define DES_set_odd_parity des_set_odd_parity -# define DES_set_key des_set_key -# define DES_ecb_encrypt des_ecb_encrypt -# define DESKEY(x) x -# define DESKEYARG(x) x -# elif defined(OPENSSL_IS_AWSLC) +# if defined(OPENSSL_IS_AWSLC) # define DES_set_key_unchecked (void)DES_set_key # define DESKEYARG(x) *x # define DESKEY(x) &x @@ -135,7 +126,7 @@ #include "curl_ntlm_core.h" #include "curl_md5.h" #include "curl_hmac.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "curl_endian.h" #include "curl_des.h" #include "curl_md4.h" @@ -144,9 +135,6 @@ #include "curl_memory.h" #include "memdebug.h" -#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. @@ -333,16 +321,16 @@ void Curl_ntlm_core_lm_resp(const unsigned char *keys, DES_key_schedule ks; setup_des_key(keys, DESKEY(ks)); - DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) results, - DESKEY(ks), DES_ENCRYPT); + DES_ecb_encrypt((DES_cblock*)CURL_UNCONST(plaintext), + (DES_cblock*)results, DESKEY(ks), DES_ENCRYPT); setup_des_key(keys + 7, DESKEY(ks)); - DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) (results + 8), - DESKEY(ks), DES_ENCRYPT); + DES_ecb_encrypt((DES_cblock*)CURL_UNCONST(plaintext), + (DES_cblock*)(results + 8), DESKEY(ks), DES_ENCRYPT); setup_des_key(keys + 14, DESKEY(ks)); - DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) (results + 16), - DESKEY(ks), DES_ENCRYPT); + DES_ecb_encrypt((DES_cblock*)CURL_UNCONST(plaintext), + (DES_cblock*)(results + 16), DESKEY(ks), DES_ENCRYPT); #elif defined(USE_GNUTLS) struct des_ctx des; setup_des_key(keys, &des); @@ -387,12 +375,12 @@ CURLcode Curl_ntlm_core_mk_lm_hash(const char *password, DES_key_schedule ks; setup_des_key(pw, DESKEY(ks)); - DES_ecb_encrypt((DES_cblock *)magic, (DES_cblock *)lmbuffer, - DESKEY(ks), DES_ENCRYPT); + DES_ecb_encrypt((DES_cblock *)CURL_UNCONST(magic), + (DES_cblock *)lmbuffer, DESKEY(ks), DES_ENCRYPT); setup_des_key(pw + 7, DESKEY(ks)); - DES_ecb_encrypt((DES_cblock *)magic, (DES_cblock *)(lmbuffer + 8), - DESKEY(ks), DES_ENCRYPT); + DES_ecb_encrypt((DES_cblock *)CURL_UNCONST(magic), + (DES_cblock *)(lmbuffer + 8), DESKEY(ks), DES_ENCRYPT); #elif defined(USE_GNUTLS) struct des_ctx des; setup_des_key(pw, &des); @@ -465,6 +453,9 @@ CURLcode Curl_ntlm_core_mk_nt_hash(const char *password, #if !defined(USE_WINDOWS_SSPI) +#define NTLMv2_BLOB_SIGNATURE "\x01\x01\x00\x00" +#define NTLMv2_BLOB_LEN (44 -16 + ntlm->target_info_len + 4) + /* Timestamp in tenths of a microsecond since January 1, 1601 00:00:00 UTC. */ struct ms_filetime { unsigned int dwLowDateTime; @@ -626,7 +617,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, - NTLMv2_BLOB_LEN + 8, hmac_output); + NTLMv2_BLOB_LEN + 8, hmac_output); if(result) { free(ptr); return result; diff --git a/Utilities/cmcurl/lib/curl_printf.h b/Utilities/cmcurl/lib/curl_printf.h index e851b14a50..6e0fa1fa8d 100644 --- a/Utilities/cmcurl/lib/curl_printf.h +++ b/Utilities/cmcurl/lib/curl_printf.h @@ -24,21 +24,25 @@ * ***************************************************************************/ -/* - * This header should be included by ALL code in libcurl that uses any - * *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 +/* Lower-case digits. */ +extern const unsigned char Curl_ldigits[]; + +/* Upper-case digits. */ +extern const unsigned char Curl_udigits[]; + +#ifdef BUILDING_LIBCURL + +/* + * This header should be included by ALL code in libcurl that uses any + * *rintf() functions. + */ + # undef printf # undef fprintf # undef msnprintf @@ -55,4 +59,6 @@ # define mvsnprintf curl_mvsnprintf # define aprintf curl_maprintf # define vaprintf curl_mvaprintf + +#endif /* BUILDING_LIBCURL */ #endif /* HEADER_CURL_PRINTF_H */ diff --git a/Utilities/cmcurl/lib/curl_range.c b/Utilities/cmcurl/lib/curl_range.c index 49fb5f0778..e9620a29b5 100644 --- a/Utilities/cmcurl/lib/curl_range.c +++ b/Utilities/cmcurl/lib/curl_range.c @@ -26,7 +26,7 @@ #include #include "curl_range.h" #include "sendf.h" -#include "strtoofft.h" +#include "curlx/strparse.h" /* Only include this function if one or more of FTP, FILE are enabled. */ #if !defined(CURL_DISABLE_FTP) || !defined(CURL_DISABLE_FILE) @@ -37,28 +37,29 @@ */ CURLcode Curl_range(struct Curl_easy *data) { - curl_off_t from, to; - char *ptr; - char *ptr2; - if(data->state.use_range && data->state.range) { - CURLofft from_t; - CURLofft to_t; - from_t = curlx_strtoofft(data->state.range, &ptr, 10, &from); - if(from_t == CURL_OFFT_FLOW) + curl_off_t from, to; + bool first_num = TRUE; + const char *p = data->state.range; + if(curlx_str_number(&p, &from, CURL_OFF_T_MAX)) + first_num = FALSE; + + if(curlx_str_single(&p, '-')) + /* no leading dash or after the first number is an error */ 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) && !from_t) { + + if(curlx_str_number(&p, &to, CURL_OFF_T_MAX)) { + /* no second number */ /* X - */ data->state.resume_from = from; DEBUGF(infof(data, "RANGE %" FMT_OFF_T " to end of file", from)); } - else if((from_t == CURL_OFFT_INVAL) && !to_t) { + else if(!first_num) { /* -Y */ + if(!to) + /* "-0" is just wrong */ + return CURLE_RANGE_ERROR; + data->req.maxdownload = to; data->state.resume_from = -to; DEBUGF(infof(data, "RANGE the last %" FMT_OFF_T " bytes", to)); diff --git a/Utilities/cmcurl/lib/curl_rtmp.c b/Utilities/cmcurl/lib/curl_rtmp.c index caa92e7af3..62632c1e9c 100644 --- a/Utilities/cmcurl/lib/curl_rtmp.c +++ b/Utilities/cmcurl/lib/curl_rtmp.c @@ -29,10 +29,11 @@ #include "curl_rtmp.h" #include "urldata.h" -#include "nonblock.h" /* for curlx_nonblock */ +#include "url.h" +#include "curlx/nonblock.h" /* for curlx_nonblock */ #include "progress.h" /* for Curl_pgrsSetUploadSize */ #include "transfer.h" -#include "warnless.h" +#include "curlx/warnless.h" #include #include @@ -52,6 +53,10 @@ #define DEF_BUFTIME (2*60*60*1000) /* 2 hours */ +/* meta key for storing RTMP* at connection */ +#define CURL_META_RTMP_CONN "meta:proto:rtmp:conn" + + static CURLcode rtmp_setup_connection(struct Curl_easy *data, struct connectdata *conn); static CURLcode rtmp_do(struct Curl_easy *data, bool *done); @@ -217,11 +222,21 @@ const struct Curl_handler Curl_handler_rtmpts = { PROTOPT_NONE /* flags */ }; +static void rtmp_conn_dtor(void *key, size_t klen, void *entry) +{ + RTMP *r = entry; + (void)key; + (void)klen; + RTMP_Close(r); + RTMP_Free(r); +} + static CURLcode rtmp_setup_connection(struct Curl_easy *data, struct connectdata *conn) { RTMP *r = RTMP_Alloc(); - if(!r) + if(!r || + Curl_conn_meta_set(conn, CURL_META_RTMP_CONN, r, rtmp_conn_dtor)) return CURLE_OUT_OF_MEMORY; RTMP_Init(r); @@ -230,16 +245,18 @@ static CURLcode rtmp_setup_connection(struct Curl_easy *data, RTMP_Free(r); return CURLE_URL_MALFORMAT; } - conn->proto.rtmp = r; return CURLE_OK; } static CURLcode rtmp_connect(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - RTMP *r = conn->proto.rtmp; + RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); SET_RCVTIMEO(tv, 10); + if(!r) + return CURLE_FAILED_INIT; + r->m_sb.sb_socket = (int)conn->sock[FIRSTSOCKET]; /* We have to know if it is a write before we send the @@ -272,9 +289,9 @@ static CURLcode rtmp_connect(struct Curl_easy *data, bool *done) static CURLcode rtmp_do(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - RTMP *r = conn->proto.rtmp; + RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); - if(!RTMP_ConnectStream(r, 0)) + if(!r || !RTMP_ConnectStream(r, 0)) return CURLE_FAILED_INIT; if(data->state.upload) { @@ -301,14 +318,11 @@ static CURLcode rtmp_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { - RTMP *r = conn->proto.rtmp; + RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); (void)data; (void)dead_connection; - if(r) { - conn->proto.rtmp = NULL; - RTMP_Close(r); - RTMP_Free(r); - } + if(r) + Curl_conn_meta_remove(conn, CURL_META_RTMP_CONN); return CURLE_OK; } @@ -316,10 +330,14 @@ static ssize_t rtmp_recv(struct Curl_easy *data, int sockindex, char *buf, size_t len, CURLcode *err) { struct connectdata *conn = data->conn; - RTMP *r = conn->proto.rtmp; + RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); ssize_t nread; (void)sockindex; /* unused */ + if(!r) { + *err = CURLE_FAILED_INIT; + return -1; + } nread = RTMP_Read(r, buf, curlx_uztosi(len)); if(nread < 0) { @@ -338,13 +356,17 @@ static ssize_t rtmp_send(struct Curl_easy *data, int sockindex, const void *buf, size_t len, bool eos, CURLcode *err) { struct connectdata *conn = data->conn; - RTMP *r = conn->proto.rtmp; + RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN); ssize_t num; (void)sockindex; /* unused */ (void)eos; /* unused */ + if(!r) { + *err = CURLE_FAILED_INIT; + return -1; + } - num = RTMP_Write(r, (char *)buf, curlx_uztosi(len)); + num = RTMP_Write(r, (const char *)buf, curlx_uztosi(len)); if(num < 0) *err = CURLE_SEND_ERROR; diff --git a/Utilities/cmcurl/lib/curl_sasl.c b/Utilities/cmcurl/lib/curl_sasl.c index 24f8c8c53c..4fcbaac263 100644 --- a/Utilities/cmcurl/lib/curl_sasl.c +++ b/Utilities/cmcurl/lib/curl_sasl.c @@ -42,15 +42,14 @@ #include #include "urldata.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "curl_md5.h" #include "vauth/vauth.h" #include "cfilters.h" #include "vtls/vtls.h" #include "curl_hmac.h" #include "curl_sasl.h" -#include "warnless.h" -#include "strtok.h" +#include "curlx/warnless.h" #include "sendf.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -284,7 +283,7 @@ static CURLcode get_server_message(struct SASL *sasl, struct Curl_easy *data, if(!*serverdata || *serverdata == '=') Curl_bufref_set(out, NULL, 0, NULL); else { - result = Curl_base64_decode(serverdata, &msg, &msglen); + result = curlx_base64_decode(serverdata, &msg, &msglen); if(!result) Curl_bufref_set(out, msg, msglen, curl_free); } @@ -307,8 +306,8 @@ static CURLcode build_message(struct SASL *sasl, struct bufref *msg) char *base64; size_t base64len; - result = Curl_base64_encode((const char *) Curl_bufref_ptr(msg), - Curl_bufref_len(msg), &base64, &base64len); + result = curlx_base64_encode((const char *) Curl_bufref_ptr(msg), + Curl_bufref_len(msg), &base64, &base64len); if(!result) Curl_bufref_set(msg, base64, base64len, curl_free); } @@ -757,4 +756,101 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, return result; } + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void sasl_unchosen(struct Curl_easy *data, unsigned short mech, + unsigned short enabledmechs, + bool built_in, bool platform, + const char *param_missing) +{ + const char *mname = NULL; + size_t i; + + if(!(enabledmechs & mech)) + return; + + for(i = 0; mechtable[i].name; ++i) { + if(mechtable[i].bit == mech) { + mname = mechtable[i].name; + break; + } + } + if(!mname) /* should not happen */ + return; + if(!built_in) + infof(data, "SASL: %s not builtin", mname); + else if(!platform) + infof(data, "SASL: %s not supported by the platform/libraries", mname); + else { + if(param_missing) + infof(data, "SASL: %s is missing %s", mname, param_missing); + if(!data->state.aptr.user) + infof(data, "SASL: %s is missing username", mname); + } +} +#endif /* CURL_DISABLE_VERBOSE_STRINGS */ + +CURLcode Curl_sasl_is_blocked(struct SASL *sasl, struct Curl_easy *data) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS +#ifdef USE_KERBEROS5 +#define CURL_SASL_KERBEROS5 TRUE +#else +#define CURL_SASL_KERBEROS5 FALSE +#endif +#ifdef USE_GSASL +#define CURL_SASL_GASL TRUE +#else +#define CURL_SASL_GASL FALSE +#endif +#ifdef CURL_DISABLE_DIGEST_AUTH +#define CURL_SASL_DIGEST TRUE +#else +#define CURL_SASL_DIGEST FALSE +#endif +#ifndef USE_NTLM +#define CURL_SASL_NTLM TRUE +#else +#define CURL_SASL_NTLM FALSE +#endif + /* Failing SASL authentication is a pain. Give a helping hand if + * we were unable to select an AUTH mechanism. + * `sasl->authmechs` are mechanisms offered by the peer + * `sasl->prefmech` are mechanisms preferred by us */ + unsigned short enabledmechs = sasl->authmechs & sasl->prefmech; + + if(!sasl->authmechs) + infof(data, "SASL: no auth mechanism was offered or recognized"); + else if(!enabledmechs) + infof(data, "SASL: no overlap between offered and configured " + "auth mechanisms"); + else { + infof(data, "SASL: no auth mechanism offered could be selected"); + if((enabledmechs & SASL_MECH_EXTERNAL) && data->conn->passwd[0]) + infof(data, "SASL: auth EXTERNAL not chosen with password"); + sasl_unchosen(data, SASL_MECH_GSSAPI, enabledmechs, + CURL_SASL_KERBEROS5, Curl_auth_is_gssapi_supported(), NULL); + sasl_unchosen(data, SASL_MECH_SCRAM_SHA_256, enabledmechs, + CURL_SASL_GASL, FALSE, NULL); + sasl_unchosen(data, SASL_MECH_SCRAM_SHA_1, enabledmechs, + CURL_SASL_GASL, FALSE, NULL); + sasl_unchosen(data, SASL_MECH_DIGEST_MD5, enabledmechs, + CURL_SASL_DIGEST, Curl_auth_is_digest_supported(), NULL); + sasl_unchosen(data, SASL_MECH_CRAM_MD5, enabledmechs, + CURL_SASL_DIGEST, TRUE, NULL); + sasl_unchosen(data, SASL_MECH_NTLM, enabledmechs, + CURL_SASL_NTLM, Curl_auth_is_ntlm_supported(), NULL); + sasl_unchosen(data, SASL_MECH_OAUTHBEARER, enabledmechs, TRUE, TRUE, + data->set.str[STRING_BEARER] ? + NULL : "CURLOPT_XOAUTH2_BEARER"); + sasl_unchosen(data, SASL_MECH_XOAUTH2, enabledmechs, TRUE, TRUE, + data->set.str[STRING_BEARER] ? + NULL : "CURLOPT_XOAUTH2_BEARER"); + } +#endif /* CURL_DISABLE_VERBOSE_STRINGS */ + (void)sasl; + (void)data; + return CURLE_LOGIN_DENIED; +} + #endif /* protocols are enabled that use SASL */ diff --git a/Utilities/cmcurl/lib/curl_sasl.h b/Utilities/cmcurl/lib/curl_sasl.h index e94e6431a2..59983f7c66 100644 --- a/Utilities/cmcurl/lib/curl_sasl.h +++ b/Utilities/cmcurl/lib/curl_sasl.h @@ -162,4 +162,6 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct Curl_easy *data, CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, int code, saslprogress *progress); +CURLcode Curl_sasl_is_blocked(struct SASL *sasl, struct Curl_easy *data); + #endif /* HEADER_CURL_SASL_H */ diff --git a/Utilities/cmcurl/lib/curl_setup.h b/Utilities/cmcurl/lib/curl_setup.h index 89500a1c0a..2ffebf0600 100644 --- a/Utilities/cmcurl/lib/curl_setup.h +++ b/Utilities/cmcurl/lib/curl_setup.h @@ -28,9 +28,6 @@ #define CURL_NO_OLDIES #endif -/* Tell "curl/curl.h" not to include "curl/mprintf.h" */ -#define CURL_SKIP_INCLUDE_MPRINTF - /* Set default _WIN32_WINNT */ #ifdef __MINGW32__ #include <_mingw.h> @@ -56,7 +53,7 @@ # endif #endif -#if defined(__APPLE__) +#ifdef __APPLE__ #include #include /* Fixup faulty target macro initialization in macOS SDK since v14.4 (as of @@ -78,6 +75,12 @@ #endif #endif +/* Visual Studio 2008 is the minimum Visual Studio version we support. + Workarounds for older versions of Visual Studio have been removed. */ +#if defined(_MSC_VER) && (_MSC_VER < 1500) +#error "Ancient versions of Visual Studio are no longer supported due to bugs." +#endif + #ifdef _MSC_VER /* Disable Visual Studio warnings: 4127 "conditional expression is constant" */ #pragma warning(disable:4127) @@ -114,6 +117,14 @@ # endif #endif +/* Avoid bogus format check warnings with mingw32ce gcc 4.4.0 in + C99 (-std=gnu99) mode */ +#if defined(__MINGW32CE__) && !defined(CURL_NO_FMT_CHECKS) && \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) && \ + (defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ == 4)) +#define CURL_NO_FMT_CHECKS +#endif + /* Compatibility */ #ifdef ENABLE_IPV6 #define USE_IPV6 1 @@ -165,6 +176,12 @@ /* system header files in our config files, avoid this at any cost. */ /* ================================================================ */ +#ifdef HAVE_LIBZ +# ifndef ZLIB_CONST +# define ZLIB_CONST /* Use z_const. Supported by v1.2.5.2 and upper. */ +# endif +#endif + /* * AIX 4.3 and newer needs _THREAD_SAFE defined to build * proper reentrant code. Others may also need it. @@ -266,7 +283,7 @@ * When HTTP is disabled, disable HTTP-only features */ -#if defined(CURL_DISABLE_HTTP) +#ifdef CURL_DISABLE_HTTP # define CURL_DISABLE_ALTSVC 1 # define CURL_DISABLE_COOKIES 1 # define CURL_DISABLE_BASIC_AUTH 1 @@ -439,6 +456,11 @@ # define __NO_NET_API #endif +/* Whether to use eventfd() */ +#if defined(HAVE_EVENTFD) && defined(HAVE_SYS_EVENTFD_H) +#define USE_EVENTFD +#endif + #include #include @@ -452,12 +474,6 @@ #include #endif -#ifdef _WIN32 -#define Curl_getpid() GetCurrentProcessId() -#else -#define Curl_getpid() getpid() -#endif - /* Default Windows file API selection. */ #ifdef _WIN32 # if defined(_MSC_VER) && (_INTEGRAL_MAX_BITS >= 64) @@ -474,7 +490,9 @@ */ #ifdef USE_WIN32_LARGE_FILES +# ifdef HAVE_IO_H # include +# endif # include # include # undef lseek @@ -505,10 +523,12 @@ */ #if defined(_WIN32) && !defined(USE_WIN32_LARGE_FILES) +# ifdef HAVE_IO_H # include +# endif # include # include -# ifndef _WIN32_WCE +# ifndef UNDER_CE # undef lseek # define lseek(fdes,offset,whence) _lseek(fdes, (long)offset, whence) # define fstat(fdes,stp) _fstat(fdes, stp) @@ -683,7 +703,6 @@ # ifdef __minix /* Minix 3 versions up to at least 3.1.3 are missing these prototypes */ - extern char *strtok_r(char *s, const char *delim, char **last); extern struct tm *gmtime_r(const time_t * const timep, struct tm *tmp); # endif @@ -696,16 +715,6 @@ /* CURLRES_* defines to use in the host*.c sources */ /* ---------------------------------------------------------------- */ -/* - * MSVC threads support requires a multi-threaded runtime library. - * _beginthreadex() is not available in single-threaded ones. - * Single-threaded option was last available in VS2005: _MSC_VER <= 1400 - */ -#if defined(_MSC_VER) && !defined(_MT) /* available in _MSC_VER <= 1400 */ -# undef USE_THREADS_POSIX -# undef USE_THREADS_WIN32 -#endif - /* * Mutually exclusive CURLRES_* definitions. */ @@ -779,7 +788,7 @@ #endif /* Single point where USE_NTLM definition might be defined */ -#if !defined(CURL_DISABLE_NTLM) +#ifndef CURL_DISABLE_NTLM # if defined(USE_OPENSSL) || defined(USE_MBEDTLS) || \ defined(USE_GNUTLS) || defined(USE_SECTRANSP) || \ defined(USE_OS400CRYPTO) || defined(USE_WIN32_CRYPTO) || \ @@ -819,7 +828,7 @@ /* noreturn attribute */ -#if !defined(CURL_NORETURN) +#ifndef CURL_NORETURN #if (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__) || \ defined(__IAR_SYSTEMS_ICC__) # define CURL_NORETURN __attribute__((__noreturn__)) @@ -832,7 +841,7 @@ /* fallthrough attribute */ -#if !defined(FALLTHROUGH) +#ifndef FALLTHROUGH #if (defined(__GNUC__) && __GNUC__ >= 7) || \ (defined(__clang__) && __clang_major__ >= 10) # define FALLTHROUGH() __attribute__((fallthrough)) @@ -849,6 +858,27 @@ #include "curl_setup_once.h" #endif +#ifdef UNDER_CE +#define getenv curl_getenv /* Windows CE does not support getenv() */ +#define raise(s) ((void)(s)) +/* Terrible workarounds to make Windows CE compile */ +#define errno 0 +#define CURL_SETERRNO(x) ((void)(x)) +#define EINTR 4 +#define EAGAIN 11 +#define ENOMEM 12 +#define EACCES 13 +#define EEXIST 17 +#define EISDIR 21 +#define EINVAL 22 +#define ENOSPC 28 +#define strerror(x) "?" +#undef STDIN_FILENO +#define STDIN_FILENO 0 +#else +#define CURL_SETERRNO(x) (errno = (x)) +#endif + /* * Definition of our NOP statement Object-like macro */ @@ -893,6 +923,17 @@ #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif +/* For MSVC (all versions as of VS2022) */ +#ifndef STDIN_FILENO +#define STDIN_FILENO fileno(stdin) +#endif +#ifndef STDOUT_FILENO +#define STDOUT_FILENO fileno(stdout) +#endif +#ifndef STDERR_FILENO +#define STDERR_FILENO fileno(stderr) +#endif + /* Since O_BINARY is used in bitmasks, setting it to zero makes it usable in source code but yet it does not ruin anything */ #ifdef O_BINARY @@ -950,6 +991,16 @@ endings either CRLF or LF so 't' is appropriate. #define CURL_ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#ifdef CURLDEBUG +#define CURL_GETADDRINFO(host,serv,hint,res) \ + curl_dbg_getaddrinfo(host, serv, hint, res, __LINE__, __FILE__) +#define CURL_FREEADDRINFO(data) \ + curl_dbg_freeaddrinfo(data, __LINE__, __FILE__) +#else +#define CURL_GETADDRINFO getaddrinfo +#define CURL_FREEADDRINFO freeaddrinfo +#endif + /* Some versions of the Android NDK is missing the declaration */ #if defined(HAVE_GETPWUID_R) && \ defined(__ANDROID_API__) && (__ANDROID_API__ < 21) @@ -1012,7 +1063,7 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, # endif #endif -#if defined(CURL_INLINE) +#ifdef CURL_INLINE /* 'CURL_INLINE' defined, use as-is */ #elif defined(inline) # define CURL_INLINE inline /* 'inline' defined, assumed correct */ diff --git a/Utilities/cmcurl/lib/curl_setup_once.h b/Utilities/cmcurl/lib/curl_setup_once.h index 037bffb45c..c1051e0fae 100644 --- a/Utilities/cmcurl/lib/curl_setup_once.h +++ b/Utilities/cmcurl/lib/curl_setup_once.h @@ -33,7 +33,9 @@ #include #include #include +#ifndef UNDER_CE #include +#endif #ifdef HAVE_SYS_TYPES_H #include @@ -63,10 +65,20 @@ #include #endif -#ifdef USE_WOLFSSL +#if defined(HAVE_STDINT_H) || defined(USE_WOLFSSL) #include #endif +/* Macro to strip 'const' without triggering a compiler warning. + Use it for APIs that do not or cannot support the const qualifier. */ +#ifdef HAVE_STDINT_H +# define CURL_UNCONST(p) ((void *)(uintptr_t)(const void *)(p)) +#elif defined(_WIN32) /* for VS2008 */ +# define CURL_UNCONST(p) ((void *)(ULONG_PTR)(const void *)(p)) +#else +# define CURL_UNCONST(p) ((void *)(p)) /* Fall back to simple cast */ +#endif + #ifdef USE_SCHANNEL /* Must set this before is included directly or indirectly by another Windows header. */ @@ -123,7 +135,7 @@ struct timeval { #endif -#if defined(__minix) +#ifdef __minix /* Minix does not support recv on TCP sockets */ #define sread(x,y,z) (ssize_t)read((RECV_TYPE_ARG1)(x), \ (RECV_TYPE_ARG2)(y), \ @@ -163,15 +175,14 @@ struct timeval { #endif /* HAVE_RECV */ -#if defined(__minix) +#ifdef __minix /* 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)) - + (SEND_TYPE_ARG2)CURL_UNCONST(y), \ + (SEND_TYPE_ARG3)(z)) #elif defined(HAVE_SEND) #define swrite(x,y,z) (ssize_t)send((SEND_TYPE_ARG1)(x), \ - (SEND_QUAL_ARG2 SEND_TYPE_ARG2)(y), \ + (SEND_QUAL_ARG2 SEND_TYPE_ARG2)CURL_UNCONST(y), \ (SEND_TYPE_ARG3)(z), \ (SEND_TYPE_ARG4)(SEND_4TH_ARG)) #else /* HAVE_SEND */ @@ -185,7 +196,7 @@ struct timeval { * Function-like macro definition used to close a socket. */ -#if defined(HAVE_CLOSESOCKET) +#ifdef HAVE_CLOSESOCKET # define sclose(x) closesocket((x)) #elif defined(HAVE_CLOSESOCKET_CAMEL) # define sclose(x) CloseSocket((x)) @@ -200,7 +211,7 @@ struct timeval { /* * Stack-independent version of fcntl() on sockets: */ -#if defined(USE_LWIPSOCK) +#ifdef USE_LWIPSOCK # define sfcntl lwip_fcntl #else # define sfcntl fcntl @@ -284,7 +295,7 @@ typedef unsigned int bit; */ #undef DEBUGASSERT -#if defined(DEBUGBUILD) +#ifdef DEBUGBUILD #define DEBUGASSERT(x) assert(x) #else #define DEBUGASSERT(x) do { } while(0) @@ -310,78 +321,39 @@ typedef unsigned int bit; */ #ifdef USE_WINSOCK -#undef EBADF /* override definition in errno.h */ -#define EBADF WSAEBADF -#undef EINTR /* override definition in errno.h */ -#define EINTR WSAEINTR -#undef EINVAL /* override definition in errno.h */ -#define EINVAL WSAEINVAL -#undef EWOULDBLOCK /* override definition in errno.h */ -#define EWOULDBLOCK WSAEWOULDBLOCK -#undef EINPROGRESS /* override definition in errno.h */ -#define EINPROGRESS WSAEINPROGRESS -#undef EALREADY /* override definition in errno.h */ -#define EALREADY WSAEALREADY -#undef ENOTSOCK /* override definition in errno.h */ -#define ENOTSOCK WSAENOTSOCK -#undef EDESTADDRREQ /* override definition in errno.h */ -#define EDESTADDRREQ WSAEDESTADDRREQ -#undef EMSGSIZE /* override definition in errno.h */ -#define EMSGSIZE WSAEMSGSIZE -#undef EPROTOTYPE /* override definition in errno.h */ -#define EPROTOTYPE WSAEPROTOTYPE -#undef ENOPROTOOPT /* override definition in errno.h */ -#define ENOPROTOOPT WSAENOPROTOOPT -#undef EPROTONOSUPPORT /* override definition in errno.h */ -#define EPROTONOSUPPORT WSAEPROTONOSUPPORT -#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT -#undef EOPNOTSUPP /* override definition in errno.h */ -#define EOPNOTSUPP WSAEOPNOTSUPP -#define EPFNOSUPPORT WSAEPFNOSUPPORT -#undef EAFNOSUPPORT /* override definition in errno.h */ -#define EAFNOSUPPORT WSAEAFNOSUPPORT -#undef EADDRINUSE /* override definition in errno.h */ -#define EADDRINUSE WSAEADDRINUSE -#undef EADDRNOTAVAIL /* override definition in errno.h */ -#define EADDRNOTAVAIL WSAEADDRNOTAVAIL -#undef ENETDOWN /* override definition in errno.h */ -#define ENETDOWN WSAENETDOWN -#undef ENETUNREACH /* override definition in errno.h */ -#define ENETUNREACH WSAENETUNREACH -#undef ENETRESET /* override definition in errno.h */ -#define ENETRESET WSAENETRESET -#undef ECONNABORTED /* override definition in errno.h */ -#define ECONNABORTED WSAECONNABORTED -#undef ECONNRESET /* override definition in errno.h */ -#define ECONNRESET WSAECONNRESET -#undef ENOBUFS /* override definition in errno.h */ -#define ENOBUFS WSAENOBUFS -#undef EISCONN /* override definition in errno.h */ -#define EISCONN WSAEISCONN -#undef ENOTCONN /* override definition in errno.h */ -#define ENOTCONN WSAENOTCONN -#define ESHUTDOWN WSAESHUTDOWN -#define ETOOMANYREFS WSAETOOMANYREFS -#undef ETIMEDOUT /* override definition in errno.h */ -#define ETIMEDOUT WSAETIMEDOUT -#undef ECONNREFUSED /* override definition in errno.h */ -#define ECONNREFUSED WSAECONNREFUSED -#undef ELOOP /* override definition in errno.h */ -#define ELOOP WSAELOOP -#ifndef ENAMETOOLONG /* possible previous definition in errno.h */ -#define ENAMETOOLONG WSAENAMETOOLONG +#define SOCKEACCES WSAEACCES +#define SOCKEADDRINUSE WSAEADDRINUSE +#define SOCKEADDRNOTAVAIL WSAEADDRNOTAVAIL +#define SOCKEAFNOSUPPORT WSAEAFNOSUPPORT +#define SOCKEBADF WSAEBADF +#define SOCKECONNREFUSED WSAECONNREFUSED +#define SOCKECONNRESET WSAECONNRESET +#define SOCKEINPROGRESS WSAEINPROGRESS +#define SOCKEINTR WSAEINTR +#define SOCKEINVAL WSAEINVAL +#define SOCKEISCONN WSAEISCONN +#define SOCKEMSGSIZE WSAEMSGSIZE +#define SOCKENOMEM WSA_NOT_ENOUGH_MEMORY +#define SOCKETIMEDOUT WSAETIMEDOUT +#define SOCKEWOULDBLOCK WSAEWOULDBLOCK +#else +#define SOCKEACCES EACCES +#define SOCKEADDRINUSE EADDRINUSE +#define SOCKEADDRNOTAVAIL EADDRNOTAVAIL +#define SOCKEAFNOSUPPORT EAFNOSUPPORT +#define SOCKEBADF EBADF +#define SOCKECONNREFUSED ECONNREFUSED +#define SOCKECONNRESET ECONNRESET +#define SOCKEINPROGRESS EINPROGRESS +#define SOCKEINTR EINTR +#define SOCKEINVAL EINVAL +#define SOCKEISCONN EISCONN +#define SOCKEMSGSIZE EMSGSIZE +#define SOCKENOMEM ENOMEM +#ifdef ETIMEDOUT +#define SOCKETIMEDOUT ETIMEDOUT #endif -#define EHOSTDOWN WSAEHOSTDOWN -#undef EHOSTUNREACH /* override definition in errno.h */ -#define EHOSTUNREACH WSAEHOSTUNREACH -#ifndef ENOTEMPTY /* possible previous definition in errno.h */ -#define ENOTEMPTY WSAENOTEMPTY -#endif -#define EPROCLIM WSAEPROCLIM -#define EUSERS WSAEUSERS -#define EDQUOT WSAEDQUOT -#define ESTALE WSAESTALE -#define EREMOTE WSAEREMOTE +#define SOCKEWOULDBLOCK EWOULDBLOCK #endif /* @@ -390,7 +362,7 @@ typedef unsigned int bit; #ifdef __VMS #define argv_item_t __char_ptr32 -#elif defined(_UNICODE) +#elif defined(_UNICODE) && !defined(UNDER_CE) #define argv_item_t wchar_t * #else #define argv_item_t char * diff --git a/Utilities/cmcurl/lib/curl_sha512_256.c b/Utilities/cmcurl/lib/curl_sha512_256.c index 46c0bd5341..2d8a23f19a 100644 --- a/Utilities/cmcurl/lib/curl_sha512_256.c +++ b/Utilities/cmcurl/lib/curl_sha512_256.c @@ -27,7 +27,7 @@ #if !defined(CURL_DISABLE_DIGEST_AUTH) && !defined(CURL_DISABLE_SHA512_256) #include "curl_sha512_256.h" -#include "warnless.h" +#include "curlx/warnless.h" /* The recommended order of the TLS backends: * * OpenSSL @@ -82,7 +82,6 @@ # 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 */ @@ -269,9 +268,6 @@ Curl_sha512_256_finish(unsigned char *digest, #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 ** * diff --git a/Utilities/cmcurl/lib/curl_sspi.c b/Utilities/cmcurl/lib/curl_sspi.c index 680bb661b2..cd577e8a5e 100644 --- a/Utilities/cmcurl/lib/curl_sspi.c +++ b/Utilities/cmcurl/lib/curl_sspi.c @@ -28,10 +28,10 @@ #include #include "curl_sspi.h" -#include "curl_multibyte.h" +#include "curlx/multibyte.h" #include "system_win32.h" -#include "version_win32.h" -#include "warnless.h" +#include "curlx/version_win32.h" +#include "curlx/warnless.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -42,7 +42,7 @@ typedef PSecurityFunctionTable (APIENTRY *INITSECURITYINTERFACE_FN)(VOID); /* See definition of SECURITY_ENTRYPOINT in sspi.h */ #ifdef UNICODE -# ifdef _WIN32_WCE +# ifdef UNDER_CE # define SECURITYENTRYPOINT L"InitSecurityInterfaceW" # else # define SECURITYENTRYPOINT "InitSecurityInterfaceW" @@ -129,7 +129,7 @@ void Curl_sspi_global_cleanup(void) /* * Curl_create_sspi_identity() * - * This is used to populate a SSPI identity structure based on the supplied + * This is used to populate an SSPI identity structure based on the supplied * username and password. * * Parameters: @@ -154,7 +154,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, /* Initialize the identity */ memset(identity, 0, sizeof(*identity)); - useranddomain.tchar_ptr = curlx_convert_UTF8_to_tchar((char *)userp); + useranddomain.tchar_ptr = curlx_convert_UTF8_to_tchar(userp); if(!useranddomain.tchar_ptr) return CURLE_OUT_OF_MEMORY; @@ -198,7 +198,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, curlx_unicodefree(useranddomain.tchar_ptr); /* Setup the identity's password and length */ - passwd.tchar_ptr = curlx_convert_UTF8_to_tchar((char *)passwdp); + passwd.tchar_ptr = curlx_convert_UTF8_to_tchar(passwdp); if(!passwd.tchar_ptr) return CURLE_OUT_OF_MEMORY; dup_passwd.tchar_ptr = _tcsdup(passwd.tchar_ptr); @@ -221,7 +221,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, /* * Curl_sspi_free_identity() * - * This is used to free the contents of a SSPI identifier structure. + * This is used to free the contents of an SSPI identifier structure. * * Parameters: * diff --git a/Utilities/cmcurl/lib/curl_sspi.h b/Utilities/cmcurl/lib/curl_sspi.h index ac39afa253..8fdf8ef249 100644 --- a/Utilities/cmcurl/lib/curl_sspi.h +++ b/Utilities/cmcurl/lib/curl_sspi.h @@ -45,7 +45,7 @@ CURLcode Curl_sspi_global_init(void); void Curl_sspi_global_cleanup(void); -/* This is used to populate the domain in a SSPI identity structure */ +/* This is used to populate the domain in an SSPI identity structure */ CURLcode Curl_override_sspi_http_realm(const char *chlg, SEC_WINNT_AUTH_IDENTITY *identity); @@ -70,6 +70,225 @@ extern PSecurityFunctionTable Curl_pSecFn; #define ISC_REQ_USE_HTTP_STYLE 0x01000000 #endif +#ifdef __MINGW32CE__ +#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 +#endif /* __MINGW32CE__ */ #ifndef SEC_E_INVALID_PARAMETER # define SEC_E_INVALID_PARAMETER ((HRESULT)0x8009035DL) #endif @@ -80,6 +299,32 @@ extern PSecurityFunctionTable Curl_pSecFn; # define SEC_E_POLICY_NLTM_ONLY ((HRESULT)0x8009035FL) #endif +#ifdef __MINGW32CE__ +#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 +#endif /* __MINGW32CE__ */ #ifndef SEC_I_SIGNATURE_NEEDED #define SEC_I_SIGNATURE_NEEDED ((HRESULT)0x0009035CL) #endif diff --git a/Utilities/cmcurl/lib/curl_threads.c b/Utilities/cmcurl/lib/curl_threads.c index fbbbf9b2d3..eae7544016 100644 --- a/Utilities/cmcurl/lib/curl_threads.c +++ b/Utilities/cmcurl/lib/curl_threads.c @@ -35,9 +35,7 @@ #endif #include "curl_threads.h" -#ifdef BUILDING_LIBCURL #include "curl_memory.h" -#endif /* The last #include file should be: */ #include "memdebug.h" @@ -82,11 +80,12 @@ err: return curl_thread_t_null; } -void Curl_thread_destroy(curl_thread_t hnd) +void Curl_thread_destroy(curl_thread_t *hnd) { - if(hnd != curl_thread_t_null) { - pthread_detach(*hnd); - free(hnd); + if(*hnd != curl_thread_t_null) { + pthread_detach(**hnd); + free(*hnd); + *hnd = curl_thread_t_null; } } @@ -103,7 +102,7 @@ int Curl_thread_join(curl_thread_t *hnd) #elif defined(USE_THREADS_WIN32) curl_thread_t Curl_thread_create( -#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) DWORD #else unsigned int @@ -111,35 +110,39 @@ curl_thread_t Curl_thread_create( (CURL_STDCALL *func) (void *), void *arg) { -#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) typedef HANDLE 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; -#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) thread_handle = CreateThread(NULL, 0, func, arg, 0, NULL); #else thread_handle = _beginthreadex(NULL, 0, func, arg, 0, NULL); #endif t = (curl_thread_t)thread_handle; if((t == 0) || (t == LongToHandle(-1L))) { -#ifdef _WIN32_WCE +#ifdef UNDER_CE DWORD gle = GetLastError(); - errno = ((gle == ERROR_ACCESS_DENIED || - gle == ERROR_NOT_ENOUGH_MEMORY) ? - EACCES : EINVAL); + /* !checksrc! disable ERRNOVAR 1 */ + int err = (gle == ERROR_ACCESS_DENIED || + gle == ERROR_NOT_ENOUGH_MEMORY) ? + EACCES : EINVAL; + CURL_SETERRNO(err); #endif return curl_thread_t_null; } return t; } -void Curl_thread_destroy(curl_thread_t hnd) +void Curl_thread_destroy(curl_thread_t *hnd) { - if(hnd != curl_thread_t_null) - CloseHandle(hnd); + if(*hnd != curl_thread_t_null) { + CloseHandle(*hnd); + *hnd = curl_thread_t_null; + } } int Curl_thread_join(curl_thread_t *hnd) @@ -151,9 +154,7 @@ int Curl_thread_join(curl_thread_t *hnd) int ret = (WaitForSingleObjectEx(*hnd, INFINITE, FALSE) == WAIT_OBJECT_0); #endif - Curl_thread_destroy(*hnd); - - *hnd = curl_thread_t_null; + Curl_thread_destroy(hnd); return ret; } diff --git a/Utilities/cmcurl/lib/curl_threads.h b/Utilities/cmcurl/lib/curl_threads.h index c9f18a4e09..b060d4acd3 100644 --- a/Utilities/cmcurl/lib/curl_threads.h +++ b/Utilities/cmcurl/lib/curl_threads.h @@ -53,7 +53,7 @@ #if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) curl_thread_t Curl_thread_create( -#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) DWORD #else unsigned int @@ -61,7 +61,7 @@ curl_thread_t Curl_thread_create( (CURL_STDCALL *func) (void *), void *arg); -void Curl_thread_destroy(curl_thread_t hnd); +void Curl_thread_destroy(curl_thread_t *hnd); int Curl_thread_join(curl_thread_t *hnd); diff --git a/Utilities/cmcurl/lib/curl_trc.c b/Utilities/cmcurl/lib/curl_trc.c index 07137c1f24..566cdc533e 100644 --- a/Utilities/cmcurl/lib/curl_trc.c +++ b/Utilities/cmcurl/lib/curl_trc.c @@ -30,7 +30,6 @@ #include "urldata.h" #include "easyif.h" #include "cfilters.h" -#include "timeval.h" #include "multiif.h" #include "strcase.h" @@ -44,7 +43,7 @@ #include "cf-haproxy.h" #include "cf-https-connect.h" #include "socks.h" -#include "strtok.h" +#include "curlx/strparse.h" #include "vtls/vtls.h" #include "vquic/vquic.h" @@ -53,19 +52,20 @@ #include "curl_memory.h" #include "memdebug.h" -void Curl_debug(struct Curl_easy *data, curl_infotype type, - char *ptr, size_t size) +static void trc_write(struct Curl_easy *data, curl_infotype type, + const char *ptr, size_t size) { if(data->set.verbose) { - static const char s_infotype[CURLINFO_END][3] = { - "* ", "< ", "> ", "{ ", "} ", "{ ", "} " }; if(data->set.fdebug) { bool inCallback = Curl_is_in_callback(data); Curl_set_in_callback(data, TRUE); - (void)(*data->set.fdebug)(data, type, ptr, size, data->set.debugdata); + (void)(*data->set.fdebug)(data, type, CURL_UNCONST(ptr), size, + data->set.debugdata); Curl_set_in_callback(data, inCallback); } else { + static const char s_infotype[CURLINFO_END][3] = { + "* ", "< ", "> ", "{ ", "} ", "{ ", "} " }; switch(type) { case CURLINFO_TEXT: case CURLINFO_HEADER_OUT: @@ -80,6 +80,101 @@ void Curl_debug(struct Curl_easy *data, curl_infotype type, } } +/* max length we trace before ending in '...' */ +#define TRC_LINE_MAX 2048 + +#define CURL_TRC_FMT_IDSC "[x-%" CURL_FORMAT_CURL_OFF_T "] " +#define CURL_TRC_FMT_IDSD "[%" CURL_FORMAT_CURL_OFF_T "-x] " +#define CURL_TRC_FMT_IDSDC "[%" CURL_FORMAT_CURL_OFF_T "-%" \ + CURL_FORMAT_CURL_OFF_T "] " + +static struct curl_trc_feat Curl_trc_feat_ids = { + "LIB-IDS", + CURL_LOG_LVL_NONE, +}; +#define CURL_TRC_IDS(data) \ + (Curl_trc_is_verbose(data) && \ + Curl_trc_feat_ids.log_level >= CURL_LOG_LVL_INFO) + +static size_t trc_print_ids(struct Curl_easy *data, char *buf, size_t maxlen) +{ + curl_off_t cid = data->conn ? + data->conn->connection_id : data->state.recent_conn_id; + if(data->id >= 0) { + if(cid >= 0) + return msnprintf(buf, maxlen, CURL_TRC_FMT_IDSDC, data->id, cid); + else + return msnprintf(buf, maxlen, CURL_TRC_FMT_IDSD, data->id); + } + else if(cid >= 0) + return msnprintf(buf, maxlen, CURL_TRC_FMT_IDSC, cid); + else { + return msnprintf(buf, maxlen, "[x-x] "); + } +} + +static size_t trc_end_buf(char *buf, size_t len, size_t maxlen, bool addnl) +{ + /* make sure we end the trace line in `buf` properly. It needs + * to end with a terminating '\0' or '\n\0' */ + if(len >= (maxlen - (addnl ? 2 : 1))) { + len = maxlen - 5; + buf[len++] = '.'; + buf[len++] = '.'; + buf[len++] = '.'; + buf[len++] = '\n'; + } + else if(addnl) + buf[len++] = '\n'; + buf[len] = '\0'; + return len; +} + +void Curl_debug(struct Curl_easy *data, curl_infotype type, + const char *ptr, size_t size) +{ + if(data->set.verbose) { + static const char s_infotype[CURLINFO_END][3] = { + "* ", "< ", "> ", "{ ", "} ", "{ ", "} " }; + char buf[TRC_LINE_MAX]; + size_t len; + if(data->set.fdebug) { + bool inCallback = Curl_is_in_callback(data); + + if(CURL_TRC_IDS(data) && (size < TRC_LINE_MAX)) { + len = trc_print_ids(data, buf, TRC_LINE_MAX); + len += msnprintf(buf + len, TRC_LINE_MAX - len, "%.*s", + (int)size, ptr); + len = trc_end_buf(buf, len, TRC_LINE_MAX, FALSE); + Curl_set_in_callback(data, TRUE); + (void)(*data->set.fdebug)(data, type, buf, len, data->set.debugdata); + Curl_set_in_callback(data, inCallback); + } + else { + Curl_set_in_callback(data, TRUE); + (void)(*data->set.fdebug)(data, type, CURL_UNCONST(ptr), + size, data->set.debugdata); + Curl_set_in_callback(data, inCallback); + } + } + else { + switch(type) { + case CURLINFO_TEXT: + case CURLINFO_HEADER_OUT: + case CURLINFO_HEADER_IN: + if(CURL_TRC_IDS(data)) { + len = trc_print_ids(data, buf, TRC_LINE_MAX); + fwrite(buf, len, 1, data->set.err); + } + fwrite(s_infotype[type], 2, 1, data->set.err); + fwrite(ptr, size, 1, data->set.err); + break; + default: /* nada */ + break; + } + } + } +} /* Curl_failf() is for messages stating why we failed. * The message SHALL NOT include any LF or CR. @@ -89,7 +184,7 @@ void Curl_failf(struct Curl_easy *data, const char *fmt, ...) DEBUGASSERT(!strchr(fmt, '\n')); if(data->set.verbose || data->set.errorbuffer) { va_list ap; - int len; + size_t len; char error[CURL_ERROR_SIZE + 2]; va_start(ap, fmt); len = mvsnprintf(error, CURL_ERROR_SIZE, fmt, ap); @@ -100,36 +195,41 @@ void Curl_failf(struct Curl_easy *data, const char *fmt, ...) } error[len++] = '\n'; error[len] = '\0'; - Curl_debug(data, CURLINFO_TEXT, error, len); + trc_write(data, CURLINFO_TEXT, error, len); va_end(ap); } } #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 *opt_id, int opt_id_idx, + const char * const fmt, va_list ap) CURL_PRINTF(5, 0); -static void trc_infof(struct Curl_easy *data, struct curl_trc_feat *feat, +static void trc_infof(struct Curl_easy *data, + struct curl_trc_feat *feat, + const char *opt_id, int opt_id_idx, const char * const fmt, va_list ap) { - int len = 0; - char buffer[MAXINFO + 5]; + size_t len = 0; + char buf[TRC_LINE_MAX]; + + if(CURL_TRC_IDS(data)) + len += trc_print_ids(data, buf + len, TRC_LINE_MAX - len); 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++] = '.'; + len += msnprintf(buf + len, TRC_LINE_MAX - len, "[%s] ", feat->name); + if(opt_id) { + if(opt_id_idx > 0) + len += msnprintf(buf + len, TRC_LINE_MAX - len, "[%s-%d] ", + opt_id, opt_id_idx); + else + len += msnprintf(buf + len, TRC_LINE_MAX - len, "[%s] ", opt_id); } - buffer[len++] = '\n'; - buffer[len] = '\0'; - Curl_debug(data, CURLINFO_TEXT, buffer, len); + len += mvsnprintf(buf + len, TRC_LINE_MAX - len, fmt, ap); + len = trc_end_buf(buf, len, TRC_LINE_MAX, TRUE); + trc_write(data, CURLINFO_TEXT, buf, len); } void Curl_infof(struct Curl_easy *data, const char *fmt, ...) @@ -138,36 +238,27 @@ void Curl_infof(struct Curl_easy *data, const char *fmt, ...) if(Curl_trc_is_verbose(data)) { va_list ap; va_start(ap, fmt); - trc_infof(data, data->state.feat, fmt, ap); + trc_infof(data, data->state.feat, NULL, 0, fmt, ap); va_end(ap); } } -void Curl_trc_cf_infof(struct Curl_easy *data, struct Curl_cfilter *cf, +void Curl_trc_cf_infof(struct Curl_easy *data, const struct Curl_cfilter *cf, const char *fmt, ...) { DEBUGASSERT(cf); if(Curl_trc_cf_is_verbose(cf, data)) { va_list ap; - int len = 0; - char buffer[MAXINFO + 2]; - 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); + trc_infof(data, data->state.feat, cf->cft->name, cf->sockindex, fmt, ap); va_end(ap); - buffer[len++] = '\n'; - buffer[len] = '\0'; - Curl_debug(data, CURLINFO_TEXT, buffer, len); } } +struct curl_trc_feat Curl_trc_feat_multi = { + "MULTI", + CURL_LOG_LVL_NONE, +}; struct curl_trc_feat Curl_trc_feat_read = { "READ", CURL_LOG_LVL_NONE, @@ -176,6 +267,52 @@ struct curl_trc_feat Curl_trc_feat_write = { "WRITE", CURL_LOG_LVL_NONE, }; +struct curl_trc_feat Curl_trc_feat_dns = { + "DNS", + CURL_LOG_LVL_NONE, +}; + + +static const char * const Curl_trc_mstate_names[]={ + "INIT", + "PENDING", + "SETUP", + "CONNECT", + "RESOLVING", + "CONNECTING", + "TUNNELING", + "PROTOCONNECT", + "PROTOCONNECTING", + "DO", + "DOING", + "DOING_MORE", + "DID", + "PERFORMING", + "RATELIMITING", + "DONE", + "COMPLETED", + "MSGSENT", +}; + +const char *Curl_trc_mstate_name(int state) +{ + if((state >= 0) && ((size_t)state < CURL_ARRAYSIZE(Curl_trc_mstate_names))) + return Curl_trc_mstate_names[(size_t)state]; + return "?"; +} + +void Curl_trc_multi(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_multi)) { + const char *sname = (data->id >= 0) ? + Curl_trc_mstate_name(data->mstate) : NULL; + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_multi, sname, 0, fmt, ap); + va_end(ap); + } +} void Curl_trc_read(struct Curl_easy *data, const char *fmt, ...) { @@ -183,7 +320,7 @@ void Curl_trc_read(struct Curl_easy *data, const char *fmt, ...) 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); + trc_infof(data, &Curl_trc_feat_read, NULL, 0, fmt, ap); va_end(ap); } } @@ -194,7 +331,18 @@ void Curl_trc_write(struct Curl_easy *data, const char *fmt, ...) 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); + trc_infof(data, &Curl_trc_feat_write, NULL, 0, fmt, ap); + va_end(ap); + } +} + +void Curl_trc_dns(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_dns, NULL, 0, fmt, ap); va_end(ap); } } @@ -211,7 +359,7 @@ void Curl_trc_ftp(struct Curl_easy *data, const char *fmt, ...) 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); + trc_infof(data, &Curl_trc_feat_ftp, NULL, 0, fmt, ap); va_end(ap); } } @@ -229,7 +377,7 @@ void Curl_trc_smtp(struct Curl_easy *data, const char *fmt, ...) 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); + trc_infof(data, &Curl_trc_feat_smtp, NULL, 0, fmt, ap); va_end(ap); } } @@ -247,7 +395,7 @@ void Curl_trc_ssls(struct Curl_easy *data, const char *fmt, ...) if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ssls)) { va_list ap; va_start(ap, fmt); - trc_infof(data, &Curl_trc_feat_ssls, fmt, ap); + trc_infof(data, &Curl_trc_feat_ssls, NULL, 0, fmt, ap); va_end(ap); } } @@ -265,7 +413,7 @@ void Curl_trc_ws(struct Curl_easy *data, const char *fmt, ...) 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); + trc_infof(data, &Curl_trc_feat_ws, NULL, 0, fmt, ap); va_end(ap); } } @@ -275,6 +423,7 @@ void Curl_trc_ws(struct Curl_easy *data, const char *fmt, ...) #define TRC_CT_PROTOCOL (1<<(0)) #define TRC_CT_NETWORK (1<<(1)) #define TRC_CT_PROXY (1<<(2)) +#define TRC_CT_INTERNALS (1<<(3)) struct trc_feat_def { struct curl_trc_feat *feat; @@ -282,13 +431,15 @@ struct trc_feat_def { }; static struct trc_feat_def trc_feats[] = { + { &Curl_trc_feat_ids, TRC_CT_INTERNALS }, + { &Curl_trc_feat_multi, TRC_CT_NETWORK }, { &Curl_trc_feat_read, TRC_CT_NONE }, { &Curl_trc_feat_write, TRC_CT_NONE }, + { &Curl_trc_feat_dns, TRC_CT_NETWORK }, #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 }, @@ -341,18 +492,18 @@ static struct trc_cft_def trc_cfts[] = { #endif }; -static void trc_apply_level_by_name(const char * const token, int lvl) +static void trc_apply_level_by_name(struct Curl_str *token, int lvl) { size_t i; for(i = 0; i < CURL_ARRAYSIZE(trc_cfts); ++i) { - if(strcasecompare(token, trc_cfts[i].cft->name)) { + if(curlx_str_casecompare(token, trc_cfts[i].cft->name)) { trc_cfts[i].cft->log_level = lvl; break; } } for(i = 0; i < CURL_ARRAYSIZE(trc_feats); ++i) { - if(strcasecompare(token, trc_feats[i].feat->name)) { + if(curlx_str_casecompare(token, trc_feats[i].feat->name)) { trc_feats[i].feat->log_level = lvl; break; } @@ -375,42 +526,36 @@ static void trc_apply_level_by_category(int category, int lvl) static CURLcode trc_opt(const char *config) { - char *token, *tok_buf, *tmp; - int lvl; + struct Curl_str out; + while(!curlx_str_until(&config, &out, 32, ',')) { + int lvl = CURL_LOG_LVL_INFO; + const char *token = curlx_str(&out); - tmp = strdup(config); - if(!tmp) - return CURLE_OUT_OF_MEMORY; - - token = Curl_strtok_r(tmp, ", ", &tok_buf); - while(token) { - switch(*token) { - case '-': - lvl = CURL_LOG_LVL_NONE; - ++token; - break; - case '+': - lvl = CURL_LOG_LVL_INFO; - ++token; - break; - default: - lvl = CURL_LOG_LVL_INFO; - break; + if(*token == '-') { + lvl = CURL_LOG_LVL_NONE; + curlx_str_nudge(&out, 1); } - 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); + else if(*token == '+') + curlx_str_nudge(&out, 1); - token = Curl_strtok_r(NULL, ", ", &tok_buf); + if(curlx_str_casecompare(&out, "all")) + trc_apply_level_by_category(TRC_CT_NONE, lvl); + else if(curlx_str_casecompare(&out, "protocol")) + trc_apply_level_by_category(TRC_CT_PROTOCOL, lvl); + else if(curlx_str_casecompare(&out, "network")) + trc_apply_level_by_category(TRC_CT_NETWORK, lvl); + else if(curlx_str_casecompare(&out, "proxy")) + trc_apply_level_by_category(TRC_CT_PROXY, lvl); + else if(curlx_str_casecompare(&out, "doh")) { + struct Curl_str dns = { "dns", 3 }; + trc_apply_level_by_name(&dns, lvl); + } + else + trc_apply_level_by_name(&out, lvl); + + if(curlx_str_single(&config, ',')) + break; } - free(tmp); return CURLE_OK; } @@ -449,8 +594,7 @@ void Curl_infof(struct Curl_easy *data, const char *fmt, ...) (void)data; (void)fmt; } -void Curl_trc_cf_infof(struct Curl_easy *data, - struct Curl_cfilter *cf, +void Curl_trc_cf_infof(struct Curl_easy *data, const struct Curl_cfilter *cf, const char *fmt, ...) { (void)data; (void)cf; (void)fmt; @@ -458,11 +602,21 @@ void Curl_trc_cf_infof(struct Curl_easy *data, struct curl_trc_feat; +void Curl_trc_multi(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + void Curl_trc_write(struct Curl_easy *data, const char *fmt, ...) { (void)data; (void)fmt; } +void Curl_trc_dns(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + void Curl_trc_read(struct Curl_easy *data, const char *fmt, ...) { (void)data; (void)fmt; diff --git a/Utilities/cmcurl/lib/curl_trc.h b/Utilities/cmcurl/lib/curl_trc.h index 9b4e36eef9..ed7e5d89f0 100644 --- a/Utilities/cmcurl/lib/curl_trc.h +++ b/Utilities/cmcurl/lib/curl_trc.h @@ -52,7 +52,7 @@ CURLcode Curl_trc_opt(const char *config); /* the function used to output verbose information */ void Curl_debug(struct Curl_easy *data, curl_infotype type, - char *ptr, size_t size); + const char *ptr, size_t size); /** * Output a failure message on registered callbacks for transfer. @@ -80,12 +80,17 @@ void Curl_infof(struct Curl_easy *data, * 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, +void Curl_trc_cf_infof(struct Curl_easy *data, const struct Curl_cfilter *cf, const char *fmt, ...) CURL_PRINTF(3, 4); +void Curl_trc_multi(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +const char *Curl_trc_mstate_name(int state); 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); +void Curl_trc_dns(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); #ifndef CURL_DISABLE_FTP extern struct curl_trc_feat Curl_trc_feat_ftp; @@ -112,6 +117,9 @@ void Curl_trc_ws(struct Curl_easy *data, #define infof(data, ...) \ do { if(Curl_trc_is_verbose(data)) \ Curl_infof(data, __VA_ARGS__); } while(0) +#define CURL_TRC_M(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_multi)) \ + Curl_trc_multi(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) @@ -121,6 +129,9 @@ void Curl_trc_ws(struct Curl_easy *data, #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) +#define CURL_TRC_DNS(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) \ + Curl_trc_dns(data, __VA_ARGS__); } while(0) #ifndef CURL_DISABLE_FTP #define CURL_TRC_FTP(data, ...) \ @@ -146,9 +157,11 @@ void Curl_trc_ws(struct Curl_easy *data, #else /* CURL_HAVE_C99 */ #define infof Curl_infof +#define CURL_TRC_M Curl_trc_multi #define CURL_TRC_CF Curl_trc_cf_infof #define CURL_TRC_WRITE Curl_trc_write #define CURL_TRC_READ Curl_trc_read +#define CURL_TRC_DNS Curl_trc_dns #ifndef CURL_DISABLE_FTP #define CURL_TRC_FTP Curl_trc_ftp @@ -165,15 +178,18 @@ void Curl_trc_ws(struct Curl_easy *data, #endif /* !CURL_HAVE_C99 */ -#ifndef CURL_DISABLE_VERBOSE_STRINGS -/* informational messages enabled */ - struct curl_trc_feat { const char *name; int log_level; }; + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +/* informational messages enabled */ + +extern struct curl_trc_feat Curl_trc_feat_multi; extern struct curl_trc_feat Curl_trc_feat_read; extern struct curl_trc_feat Curl_trc_feat_write; +extern struct curl_trc_feat Curl_trc_feat_dns; #define Curl_trc_is_verbose(data) \ ((data) && (data)->set.verbose && \ @@ -185,6 +201,7 @@ extern struct curl_trc_feat Curl_trc_feat_write; #define Curl_trc_ft_is_verbose(data, ft) \ (Curl_trc_is_verbose(data) && \ (ft)->log_level >= CURL_LOG_LVL_INFO) +#define CURL_MSTATE_NAME(s) Curl_trc_mstate_name((int)(s)) #else /* defined(CURL_DISABLE_VERBOSE_STRINGS) */ /* All informational messages are not compiled in for size savings */ @@ -192,6 +209,7 @@ extern struct curl_trc_feat Curl_trc_feat_write; #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) +#define CURL_MSTATE_NAME(x) ((void)(x), "-") #endif /* !defined(CURL_DISABLE_VERBOSE_STRINGS) */ diff --git a/Utilities/cmcurl/lib/base64.c b/Utilities/cmcurl/lib/curlx/base64.c similarity index 81% rename from Utilities/cmcurl/lib/base64.c rename to Utilities/cmcurl/lib/curlx/base64.c index 8373115d20..92ebc57e1e 100644 --- a/Utilities/cmcurl/lib/base64.c +++ b/Utilities/cmcurl/lib/curlx/base64.c @@ -24,7 +24,7 @@ /* Base64 encoding/decoding */ -#include "curl_setup.h" +#include "../curl_setup.h" #if !defined(CURL_DISABLE_HTTP_AUTH) || defined(USE_SSH) || \ !defined(CURL_DISABLE_LDAP) || \ @@ -32,21 +32,20 @@ !defined(CURL_DISABLE_POP3) || \ !defined(CURL_DISABLE_IMAP) || \ !defined(CURL_DISABLE_DIGEST_AUTH) || \ - !defined(CURL_DISABLE_DOH) || defined(USE_SSL) || defined(BUILDING_CURL) -#include "curl/curl.h" + !defined(CURL_DISABLE_DOH) || defined(USE_SSL) || !defined(BUILDING_LIBCURL) +#include #include "warnless.h" -#include "curl_base64.h" +#include "base64.h" /* The last 2 #include files should be in this order */ #ifdef BUILDING_LIBCURL -#include "curl_memory.h" +#include "../curl_memory.h" #endif -#include "memdebug.h" +#include "../memdebug.h" /* ---- Base64 Encoding/Decoding Table --- */ -/* Padding character string starts at offset 64. */ -static const char base64encdec[]= - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; +const char Curl_base64encdec[]= + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /* The Base 64 encoding with a URL and filename safe alphabet, RFC 4648 section 5 */ @@ -60,11 +59,11 @@ static const unsigned char decodetable[] = 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; /* - * Curl_base64_decode() + * curlx_base64_decode() * - * Given a base64 NUL-terminated string at src, decode it and return a - * pointer in *outptr to a newly allocated memory area holding decoded - * data. Size of decoded data is returned in variable pointed by outlen. + * Given a base64 null-terminated string at src, decode it and return a + * pointer in *outptr to a newly allocated memory area holding decoded data. + * Size of decoded data is returned in variable pointed by outlen. * * Returns CURLE_OK on success, otherwise specific error code. Function * output shall not be considered valid unless CURLE_OK is returned. @@ -73,8 +72,8 @@ static const unsigned char decodetable[] = * * @unittest: 1302 */ -CURLcode Curl_base64_decode(const char *src, - unsigned char **outptr, size_t *outlen) +CURLcode curlx_base64_decode(const char *src, + unsigned char **outptr, size_t *outlen) { size_t srclen = 0; size_t padding = 0; @@ -119,14 +118,6 @@ CURLcode Curl_base64_decode(const char *src, memset(lookup, 0xff, sizeof(lookup)); memcpy(&lookup['+'], decodetable, sizeof(decodetable)); - /* replaces - { - unsigned char c; - const unsigned char *p = (const unsigned char *)base64encdec; - for(c = 0; *p; c++, p++) - lookup[*p] = c; - } - */ /* Decode the complete quantums first */ for(i = 0; i < fullQuantums; i++) { @@ -186,13 +177,13 @@ bad: } static CURLcode base64_encode(const char *table64, + unsigned char padbyte, const char *inputbuff, size_t insize, char **outptr, size_t *outlen) { char *output; char *base64data; - const unsigned char *in = (unsigned char *)inputbuff; - const char *padstr = &table64[64]; /* Point to padding string. */ + const unsigned char *in = (const unsigned char *)inputbuff; *outptr = NULL; *outlen = 0; @@ -222,17 +213,17 @@ static CURLcode base64_encode(const char *table64, *output++ = table64[ in[0] >> 2 ]; if(insize == 1) { *output++ = table64[ ((in[0] & 0x03) << 4) ]; - if(*padstr) { - *output++ = *padstr; - *output++ = *padstr; + if(padbyte) { + *output++ = padbyte; + *output++ = padbyte; } } else { /* insize == 2 */ *output++ = table64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xF0) >> 4) ]; *output++ = table64[ ((in[1] & 0x0F) << 2) ]; - if(*padstr) - *output++ = *padstr; + if(padbyte) + *output++ = padbyte; } } @@ -249,45 +240,46 @@ static CURLcode base64_encode(const char *table64, } /* - * Curl_base64_encode() + * curlx_base64_encode() * * Given a pointer to an input buffer and an input size, encode it and * return a pointer in *outptr to a newly allocated memory area holding * encoded data. Size of encoded data is returned in variable pointed by * outlen. * - * Input length of 0 indicates input buffer holds a NUL-terminated string. + * Input length of 0 indicates input buffer holds a null-terminated string. * * Returns CURLE_OK on success, otherwise specific error code. Function * output shall not be considered valid unless CURLE_OK is returned. * * @unittest: 1302 */ -CURLcode Curl_base64_encode(const char *inputbuff, size_t insize, - char **outptr, size_t *outlen) +CURLcode curlx_base64_encode(const char *inputbuff, size_t insize, + char **outptr, size_t *outlen) { - return base64_encode(base64encdec, inputbuff, insize, outptr, outlen); + return base64_encode(Curl_base64encdec, '=', + inputbuff, insize, outptr, outlen); } /* - * Curl_base64url_encode() + * curlx_base64url_encode() * * Given a pointer to an input buffer and an input size, encode it and * return a pointer in *outptr to a newly allocated memory area holding * encoded data. Size of encoded data is returned in variable pointed by * outlen. * - * Input length of 0 indicates input buffer holds a NUL-terminated string. + * Input length of 0 indicates input buffer holds a null-terminated string. * * Returns CURLE_OK on success, otherwise specific error code. Function * output shall not be considered valid unless CURLE_OK is returned. * * @unittest: 1302 */ -CURLcode Curl_base64url_encode(const char *inputbuff, size_t insize, - char **outptr, size_t *outlen) +CURLcode curlx_base64url_encode(const char *inputbuff, size_t insize, + char **outptr, size_t *outlen) { - return base64_encode(base64url, inputbuff, insize, outptr, outlen); + return base64_encode(base64url, 0, inputbuff, insize, outptr, outlen); } #endif /* no users so disabled */ diff --git a/Utilities/cmcurl/lib/curl_base64.h b/Utilities/cmcurl/lib/curlx/base64.h similarity index 61% rename from Utilities/cmcurl/lib/curl_base64.h rename to Utilities/cmcurl/lib/curlx/base64.h index 7f7cd1d98a..026f80e4d3 100644 --- a/Utilities/cmcurl/lib/curl_base64.h +++ b/Utilities/cmcurl/lib/curlx/base64.h @@ -24,18 +24,13 @@ * ***************************************************************************/ -#ifndef BUILDING_LIBCURL -/* this renames functions so that the tool code can use the same code - without getting symbol collisions */ -#define Curl_base64_encode(a,b,c,d) curlx_base64_encode(a,b,c,d) -#define Curl_base64url_encode(a,b,c,d) curlx_base64url_encode(a,b,c,d) -#define Curl_base64_decode(a,b,c) curlx_base64_decode(a,b,c) -#endif +CURLcode curlx_base64_encode(const char *inputbuff, size_t insize, + char **outptr, size_t *outlen); +CURLcode curlx_base64url_encode(const char *inputbuff, size_t insize, + char **outptr, size_t *outlen); +CURLcode curlx_base64_decode(const char *src, + unsigned char **outptr, size_t *outlen); + +extern const char Curl_base64encdec[]; -CURLcode Curl_base64_encode(const char *inputbuff, size_t insize, - char **outptr, size_t *outlen); -CURLcode Curl_base64url_encode(const char *inputbuff, size_t insize, - char **outptr, size_t *outlen); -CURLcode Curl_base64_decode(const char *src, - unsigned char **outptr, size_t *outlen); #endif /* HEADER_CURL_BASE64_H */ diff --git a/Utilities/cmcurl/lib/curlx.h b/Utilities/cmcurl/lib/curlx/curlx.h similarity index 79% rename from Utilities/cmcurl/lib/curlx.h rename to Utilities/cmcurl/lib/curlx/curlx.h index f0e4e6470b..983c7b5c75 100644 --- a/Utilities/cmcurl/lib/curlx.h +++ b/Utilities/cmcurl/lib/curlx/curlx.h @@ -31,17 +31,6 @@ * be. */ -/* map standard printf functions to curl implementations */ -#include "curl_printf.h" - -#include "strcase.h" -/* "strcase.h" provides the strcasecompare protos */ - -#include "strtoofft.h" -/* "strtoofft.h" provides this function: curlx_strtoofft(), returns a - curl_off_t number from a given string. -*/ - #include "nonblock.h" /* "nonblock.h" provides curlx_nonblock() */ @@ -53,8 +42,8 @@ curlx_uztosi() */ -#include "curl_multibyte.h" -/* "curl_multibyte.h" provides these functions and macros: +#include "multibyte.h" +/* "multibyte.h" provides these functions and macros: curlx_convert_UTF8_to_wchar() curlx_convert_wchar_to_UTF8() @@ -64,6 +53,22 @@ */ #include "version_win32.h" -/* "version_win32.h" provides curlx_verify_windows_version() */ +/* provides curlx_verify_windows_version() */ + +#include "strparse.h" +/* The curlx_str_* parsing functions */ + +#include "dynbuf.h" +/* The curlx_dyn_* functions */ + +#include "base64.h" +#include "timeval.h" +#include "timediff.h" + +#include "winapi.h" +/* for curlx_winapi_strerror */ + +#include "inet_pton.h" +/* for curlx_inet_pton */ #endif /* HEADER_CURL_CURLX_H */ diff --git a/Utilities/cmcurl/lib/dynbuf.c b/Utilities/cmcurl/lib/curlx/dynbuf.c similarity index 81% rename from Utilities/cmcurl/lib/dynbuf.c rename to Utilities/cmcurl/lib/curlx/dynbuf.c index 15164265f4..0b8dfe8e34 100644 --- a/Utilities/cmcurl/lib/dynbuf.c +++ b/Utilities/cmcurl/lib/curlx/dynbuf.c @@ -22,25 +22,28 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #include "dynbuf.h" -#include "curl_printf.h" +#include "../curl_printf.h" #ifdef BUILDING_LIBCURL -#include "curl_memory.h" +#include "../curl_memory.h" #endif -#include "memdebug.h" +#include "../memdebug.h" #define MIN_FIRST_ALLOC 32 +#ifdef DEBUGBUILD #define DYNINIT 0xbee51da /* random pattern */ +#endif /* * Init a dynbuf struct. */ -void Curl_dyn_init(struct dynbuf *s, size_t toobig) +void curlx_dyn_init(struct dynbuf *s, size_t toobig) { DEBUGASSERT(s); DEBUGASSERT(toobig); + DEBUGASSERT(toobig <= MAX_DYNBUF_SIZE); /* catch crazy mistakes */ s->bufr = NULL; s->leng = 0; s->allc = 0; @@ -54,9 +57,10 @@ void Curl_dyn_init(struct dynbuf *s, size_t toobig) * 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) +void curlx_dyn_free(struct dynbuf *s) { DEBUGASSERT(s); + DEBUGASSERT(s->init == DYNINIT); Curl_safefree(s->bufr); s->leng = s->allc = 0; } @@ -80,7 +84,7 @@ static CURLcode dyn_nappend(struct dynbuf *s, DEBUGASSERT(!len || mem); if(fit > s->toobig) { - Curl_dyn_free(s); + curlx_dyn_free(s); return CURLE_TOO_LARGE; } else if(!a) { @@ -106,7 +110,7 @@ static CURLcode dyn_nappend(struct dynbuf *s, include that as well when it uses this code */ void *p = realloc(s->bufr, a); if(!p) { - Curl_dyn_free(s); + curlx_dyn_free(s); return CURLE_OUT_OF_MEMORY; } s->bufr = p; @@ -124,7 +128,7 @@ static CURLcode dyn_nappend(struct dynbuf *s, * Clears the string, keeps the allocation. This can also be called on a * buffer that already was freed. */ -void Curl_dyn_reset(struct dynbuf *s) +void curlx_dyn_reset(struct dynbuf *s) { DEBUGASSERT(s); DEBUGASSERT(s->init == DYNINIT); @@ -138,7 +142,7 @@ void Curl_dyn_reset(struct dynbuf *s) * Specify the size of the tail to keep (number of bytes from the end of the * buffer). The rest will be dropped. */ -CURLcode Curl_dyn_tail(struct dynbuf *s, size_t trail) +CURLcode curlx_dyn_tail(struct dynbuf *s, size_t trail) { DEBUGASSERT(s); DEBUGASSERT(s->init == DYNINIT); @@ -148,7 +152,7 @@ CURLcode Curl_dyn_tail(struct dynbuf *s, size_t trail) else if(trail == s->leng) return CURLE_OK; else if(!trail) { - Curl_dyn_reset(s); + curlx_dyn_reset(s); } else { memmove(&s->bufr[0], &s->bufr[s->leng - trail], trail); @@ -162,7 +166,7 @@ CURLcode Curl_dyn_tail(struct dynbuf *s, size_t trail) /* * Appends a buffer with length. */ -CURLcode Curl_dyn_addn(struct dynbuf *s, const void *mem, size_t len) +CURLcode curlx_dyn_addn(struct dynbuf *s, const void *mem, size_t len) { DEBUGASSERT(s); DEBUGASSERT(s->init == DYNINIT); @@ -173,7 +177,7 @@ CURLcode Curl_dyn_addn(struct dynbuf *s, const void *mem, size_t len) /* * Append a null-terminated string at the end. */ -CURLcode Curl_dyn_add(struct dynbuf *s, const char *str) +CURLcode curlx_dyn_add(struct dynbuf *s, const char *str) { size_t n; DEBUGASSERT(str); @@ -181,13 +185,13 @@ CURLcode Curl_dyn_add(struct dynbuf *s, const char *str) DEBUGASSERT(s->init == DYNINIT); DEBUGASSERT(!s->leng || s->bufr); n = strlen(str); - return dyn_nappend(s, (unsigned char *)str, n); + return dyn_nappend(s, (const unsigned char *)str, n); } /* * Append a string vprintf()-style */ -CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) +CURLcode curlx_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) { #ifdef BUILDING_LIBCURL int rc; @@ -195,7 +199,7 @@ CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) DEBUGASSERT(s->init == DYNINIT); DEBUGASSERT(!s->leng || s->bufr); DEBUGASSERT(fmt); - rc = Curl_dyn_vprintf(s, fmt, ap); + rc = curlx_dyn_vprintf(s, fmt, ap); if(!rc) return CURLE_OK; @@ -204,15 +208,15 @@ CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) return CURLE_OUT_OF_MEMORY; #else char *str; - str = vaprintf(fmt, ap); /* this allocs a new string to append */ + str = curl_mvaprintf(fmt, ap); /* this allocs a new string to append */ if(str) { - CURLcode result = dyn_nappend(s, (unsigned char *)str, strlen(str)); + CURLcode result = dyn_nappend(s, (const unsigned char *)str, strlen(str)); free(str); return result; } /* If we failed, we cleanup the whole buffer and return error */ - Curl_dyn_free(s); + curlx_dyn_free(s); return CURLE_OUT_OF_MEMORY; #endif } @@ -220,7 +224,7 @@ CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) /* * Append a string printf()-style */ -CURLcode Curl_dyn_addf(struct dynbuf *s, const char *fmt, ...) +CURLcode curlx_dyn_addf(struct dynbuf *s, const char *fmt, ...) { CURLcode result; va_list ap; @@ -228,7 +232,7 @@ CURLcode Curl_dyn_addf(struct dynbuf *s, const char *fmt, ...) DEBUGASSERT(s->init == DYNINIT); DEBUGASSERT(!s->leng || s->bufr); va_start(ap, fmt); - result = Curl_dyn_vaddf(s, fmt, ap); + result = curlx_dyn_vaddf(s, fmt, ap); va_end(ap); return result; } @@ -236,7 +240,7 @@ CURLcode Curl_dyn_addf(struct dynbuf *s, const char *fmt, ...) /* * Returns a pointer to the buffer. */ -char *Curl_dyn_ptr(const struct dynbuf *s) +char *curlx_dyn_ptr(const struct dynbuf *s) { DEBUGASSERT(s); DEBUGASSERT(s->init == DYNINIT); @@ -244,7 +248,7 @@ char *Curl_dyn_ptr(const struct dynbuf *s) return s->bufr; } -char *Curl_dyn_take(struct dynbuf *s, size_t *plen) +char *curlx_dyn_take(struct dynbuf *s, size_t *plen) { char *ptr = s->bufr; DEBUGASSERT(s); @@ -259,7 +263,7 @@ char *Curl_dyn_take(struct dynbuf *s, size_t *plen) /* * Returns an unsigned pointer to the buffer. */ -unsigned char *Curl_dyn_uptr(const struct dynbuf *s) +unsigned char *curlx_dyn_uptr(const struct dynbuf *s) { DEBUGASSERT(s); DEBUGASSERT(s->init == DYNINIT); @@ -270,7 +274,7 @@ unsigned char *Curl_dyn_uptr(const struct dynbuf *s) /* * Returns the length of the buffer. */ -size_t Curl_dyn_len(const struct dynbuf *s) +size_t curlx_dyn_len(const struct dynbuf *s) { DEBUGASSERT(s); DEBUGASSERT(s->init == DYNINIT); @@ -281,7 +285,7 @@ size_t Curl_dyn_len(const struct dynbuf *s) /* * Set a new (smaller) length. */ -CURLcode Curl_dyn_setlen(struct dynbuf *s, size_t set) +CURLcode curlx_dyn_setlen(struct dynbuf *s, size_t set) { DEBUGASSERT(s); DEBUGASSERT(s->init == DYNINIT); diff --git a/Utilities/cmcurl/lib/dynbuf.h b/Utilities/cmcurl/lib/curlx/dynbuf.h similarity index 60% rename from Utilities/cmcurl/lib/dynbuf.h rename to Utilities/cmcurl/lib/curlx/dynbuf.h index 154b54cd9c..27335a6fbf 100644 --- a/Utilities/cmcurl/lib/dynbuf.h +++ b/Utilities/cmcurl/lib/curlx/dynbuf.h @@ -26,25 +26,6 @@ #include -#ifndef BUILDING_LIBCURL -/* this renames the functions so that the tool code can use the same code - without getting symbol collisions */ -#define Curl_dyn_init(a,b) curlx_dyn_init(a,b) -#define Curl_dyn_add(a,b) curlx_dyn_add(a,b) -#define Curl_dyn_addn(a,b,c) curlx_dyn_addn(a,b,c) -#define Curl_dyn_addf curlx_dyn_addf -#define Curl_dyn_vaddf curlx_dyn_vaddf -#define Curl_dyn_free(a) curlx_dyn_free(a) -#define Curl_dyn_ptr(a) curlx_dyn_ptr(a) -#define Curl_dyn_uptr(a) curlx_dyn_uptr(a) -#define Curl_dyn_len(a) curlx_dyn_len(a) -#define Curl_dyn_reset(a) curlx_dyn_reset(a) -#define Curl_dyn_take(a,b) curlx_dyn_take(a,b) -#define Curl_dyn_tail(a,b) curlx_dyn_tail(a,b) -#define Curl_dyn_setlen(a,b) curlx_dyn_setlen(a,b) -#define curlx_dynbuf dynbuf /* for the struct name */ -#endif - struct dynbuf { char *bufr; /* point to a null-terminated allocated buffer */ size_t leng; /* number of bytes *EXCLUDING* the null-terminator */ @@ -55,32 +36,34 @@ struct dynbuf { #endif }; -void Curl_dyn_init(struct dynbuf *s, size_t toobig); -void Curl_dyn_free(struct dynbuf *s); -CURLcode Curl_dyn_addn(struct dynbuf *s, const void *mem, size_t len) +void curlx_dyn_init(struct dynbuf *s, size_t toobig); +void curlx_dyn_free(struct dynbuf *s); +CURLcode curlx_dyn_addn(struct dynbuf *s, const void *mem, size_t len) WARN_UNUSED_RESULT; -CURLcode Curl_dyn_add(struct dynbuf *s, const char *str) +CURLcode curlx_dyn_add(struct dynbuf *s, const char *str) WARN_UNUSED_RESULT; -CURLcode Curl_dyn_addf(struct dynbuf *s, const char *fmt, ...) +CURLcode curlx_dyn_addf(struct dynbuf *s, const char *fmt, ...) WARN_UNUSED_RESULT CURL_PRINTF(2, 3); -CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) +CURLcode curlx_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) 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); -char *Curl_dyn_ptr(const struct dynbuf *s); -unsigned char *Curl_dyn_uptr(const struct dynbuf *s); -size_t Curl_dyn_len(const struct dynbuf *s); +void curlx_dyn_reset(struct dynbuf *s); +CURLcode curlx_dyn_tail(struct dynbuf *s, size_t trail); +CURLcode curlx_dyn_setlen(struct dynbuf *s, size_t set); +char *curlx_dyn_ptr(const struct dynbuf *s); +unsigned char *curlx_dyn_uptr(const struct dynbuf *s); +size_t curlx_dyn_len(const struct dynbuf *s); /* returns 0 on success, -1 on error */ /* The implementation of this function exists in mprintf.c */ -int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save); +int curlx_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save); /* Take the buffer out of the dynbuf. Caller has ownership and * dynbuf resets to initial state. */ -char *Curl_dyn_take(struct dynbuf *s, size_t *plen); +char *curlx_dyn_take(struct dynbuf *s, size_t *plen); /* Dynamic buffer max sizes */ +#define MAX_DYNBUF_SIZE (SIZE_T_MAX/2) + #define DYN_DOH_RESPONSE 3000 #define DYN_DOH_CNAME 256 #define DYN_PAUSE_BUFFER (64 * 1024 * 1024) @@ -95,4 +78,8 @@ char *Curl_dyn_take(struct dynbuf *s, size_t *plen); #define DYN_PINGPPONG_CMD (64*1024) #define DYN_IMAP_CMD (64*1024) #define DYN_MQTT_RECV (64*1024) +#define DYN_MQTT_SEND 0xFFFFFFF +#define DYN_CRLFILE_SIZE (400*1024*1024) /* 400mb */ +#define DYN_CERTFILE_SIZE (100*1024) /* 100KiB */ +#define DYN_KEYFILE_SIZE (100*1024) /* 100KiB */ #endif diff --git a/Utilities/cmcurl/lib/inet_pton.c b/Utilities/cmcurl/lib/curlx/inet_pton.c similarity index 90% rename from Utilities/cmcurl/lib/inet_pton.c rename to Utilities/cmcurl/lib/curlx/inet_pton.c index d0c04db1da..d2b39ae9f1 100644 --- a/Utilities/cmcurl/lib/inet_pton.c +++ b/Utilities/cmcurl/lib/curlx/inet_pton.c @@ -18,7 +18,9 @@ * SPDX-License-Identifier: ISC */ -#include "curl_setup.h" +#include "../curl_setup.h" +#include "../curl_ctype.h" +#include "strparse.h" #ifndef HAVE_INET_PTON @@ -72,7 +74,7 @@ static int inet_pton6(const char *src, unsigned char *dst); * Paul Vixie, 1996. */ int -Curl_inet_pton(int af, const char *src, void *dst) +curlx_inet_pton(int af, const char *src, void *dst) { switch(af) { case AF_INET: @@ -80,7 +82,7 @@ Curl_inet_pton(int af, const char *src, void *dst) case AF_INET6: return inet_pton6(src, (unsigned char *)dst); default: - errno = EAFNOSUPPORT; + CURL_SETERRNO(SOCKEAFNOSUPPORT); return -1; } /* NOTREACHED */ @@ -99,7 +101,6 @@ Curl_inet_pton(int af, const char *src, void *dst) static int inet_pton4(const char *src, unsigned char *dst) { - static const char digits[] = "0123456789"; int saw_digit, octets, ch; unsigned char tmp[INADDRSZ], *tp; @@ -108,12 +109,8 @@ inet_pton4(const char *src, unsigned char *dst) tp = tmp; *tp = 0; while((ch = *src++) != '\0') { - const char *pch; - - pch = strchr(digits, ch); - if(pch) { - unsigned int val = (unsigned int)(*tp * 10) + - (unsigned int)(pch - digits); + if(ISDIGIT(ch)) { + unsigned int val = (*tp * 10) + (ch - '0'); if(saw_digit && *tp == 0) return 0; @@ -157,8 +154,6 @@ inet_pton4(const char *src, unsigned char *dst) static int inet_pton6(const char *src, unsigned char *dst) { - static const char xdigits_l[] = "0123456789abcdef", - xdigits_u[] = "0123456789ABCDEF"; unsigned char tmp[IN6ADDRSZ], *tp, *endp, *colonp; const char *curtok; int ch, saw_xdigit; @@ -175,15 +170,9 @@ inet_pton6(const char *src, unsigned char *dst) saw_xdigit = 0; val = 0; while((ch = *src++) != '\0') { - const char *xdigits; - const char *pch; - - pch = strchr((xdigits = xdigits_l), ch); - if(!pch) - pch = strchr((xdigits = xdigits_u), ch); - if(pch) { + if(ISXDIGIT(ch)) { val <<= 4; - val |= (pch - xdigits); + val |= Curl_hexval(ch); if(++saw_xdigit > 4) return 0; continue; diff --git a/Utilities/cmcurl/lib/inet_pton.h b/Utilities/cmcurl/lib/curlx/inet_pton.h similarity index 86% rename from Utilities/cmcurl/lib/inet_pton.h rename to Utilities/cmcurl/lib/curlx/inet_pton.h index 915385fc25..a9dc43085f 100644 --- a/Utilities/cmcurl/lib/inet_pton.h +++ b/Utilities/cmcurl/lib/curlx/inet_pton.h @@ -24,9 +24,9 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" -int Curl_inet_pton(int, const char *, void *); +int curlx_inet_pton(int, const char *, void *); #ifdef HAVE_INET_PTON #ifdef HAVE_NETINET_IN_H @@ -38,10 +38,10 @@ int Curl_inet_pton(int, const char *, void *); #ifdef HAVE_ARPA_INET_H #include #endif -#if defined(__AMIGA__) -#define Curl_inet_pton(x,y,z) inet_pton(x,(unsigned char *)y,z) +#ifdef __AMIGA__ +#define curlx_inet_pton(x,y,z) inet_pton(x,(unsigned char *)CURL_UNCONST(y),z) #else -#define Curl_inet_pton(x,y,z) inet_pton(x,y,z) +#define curlx_inet_pton(x,y,z) inet_pton(x,y,z) #endif #endif diff --git a/Utilities/cmcurl/lib/curl_multibyte.c b/Utilities/cmcurl/lib/curlx/multibyte.c similarity index 88% rename from Utilities/cmcurl/lib/curl_multibyte.c rename to Utilities/cmcurl/lib/curlx/multibyte.c index 220b2fa3f6..30380275cc 100644 --- a/Utilities/cmcurl/lib/curl_multibyte.c +++ b/Utilities/cmcurl/lib/curlx/multibyte.c @@ -23,18 +23,18 @@ ***************************************************************************/ /* - * This file is 'mem-include-scan' clean, which means memdebug.h and - * curl_memory.h are purposely not included in this file. See test 1132. - * - * The functions in this file are curlx functions which are not tracked by the - * curl memory tracker memdebug. + * This file is 'mem-include-scan' clean, which means its memory allocations + * are not tracked by the curl memory tracker memdebug, so they must not use + * `CURLDEBUG` macro replacements in memdebug.h for free, malloc, etc. To avoid + * these macro replacements, wrap the names in parentheses to call the original + * versions: `ptr = (malloc)(123)`, `(free)(ptr)`, etc. */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef _WIN32 -#include "curl_multibyte.h" +#include "multibyte.h" /* * MultiByte conversions using Windows kernel32 library. @@ -48,11 +48,11 @@ wchar_t *curlx_convert_UTF8_to_wchar(const char *str_utf8) int str_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str_utf8, -1, NULL, 0); if(str_w_len > 0) { - str_w = malloc(str_w_len * sizeof(wchar_t)); + str_w = (malloc)(str_w_len * sizeof(wchar_t)); if(str_w) { if(MultiByteToWideChar(CP_UTF8, 0, str_utf8, -1, str_w, str_w_len) == 0) { - free(str_w); + (free)(str_w); return NULL; } } @@ -70,11 +70,11 @@ char *curlx_convert_wchar_to_UTF8(const wchar_t *str_w) int bytes = WideCharToMultiByte(CP_UTF8, 0, str_w, -1, NULL, 0, NULL, NULL); if(bytes > 0) { - str_utf8 = malloc(bytes); + str_utf8 = (malloc)(bytes); if(str_utf8) { if(WideCharToMultiByte(CP_UTF8, 0, str_w, -1, str_utf8, bytes, NULL, NULL) == 0) { - free(str_utf8); + (free)(str_utf8); return NULL; } } @@ -84,6 +84,8 @@ char *curlx_convert_wchar_to_UTF8(const wchar_t *str_w) return str_utf8; } +#ifndef UNDER_CE + /* declare GetFullPathNameW for mingw-w64 UWP builds targeting old windows */ #if defined(CURL_WINDOWS_UWP) && defined(__MINGW32__) && \ (_WIN32_WINNT < _WIN32_WINNT_WIN10) @@ -134,7 +136,7 @@ static bool fix_excessive_path(const TCHAR *in, TCHAR **out) if(needed == (size_t)-1 || needed >= max_path_len) goto cleanup; ++needed; /* for NUL */ - ibuf = malloc(needed * sizeof(wchar_t)); + ibuf = (malloc)(needed * sizeof(wchar_t)); if(!ibuf) goto cleanup; count = mbstowcs(ibuf, in, needed); @@ -154,7 +156,7 @@ static bool fix_excessive_path(const TCHAR *in, TCHAR **out) /* skip paths that are not excessive and do not need modification */ if(needed <= MAX_PATH) goto cleanup; - fbuf = malloc(needed * sizeof(wchar_t)); + fbuf = (malloc)(needed * sizeof(wchar_t)); if(!fbuf) goto cleanup; count = (size_t)GetFullPathNameW(in_w, (DWORD)needed, fbuf, NULL); @@ -187,7 +189,7 @@ static bool fix_excessive_path(const TCHAR *in, TCHAR **out) if(needed > max_path_len) goto cleanup; - temp = malloc(needed * sizeof(wchar_t)); + temp = (malloc)(needed * sizeof(wchar_t)); if(!temp) goto cleanup; @@ -200,7 +202,7 @@ static bool fix_excessive_path(const TCHAR *in, TCHAR **out) if(needed > max_path_len) goto cleanup; - temp = malloc(needed * sizeof(wchar_t)); + temp = (malloc)(needed * sizeof(wchar_t)); if(!temp) goto cleanup; @@ -208,7 +210,7 @@ static bool fix_excessive_path(const TCHAR *in, TCHAR **out) wcscpy(temp + 4, fbuf); } - free(fbuf); + (free)(fbuf); fbuf = temp; } @@ -218,7 +220,7 @@ static bool fix_excessive_path(const TCHAR *in, TCHAR **out) if(needed == (size_t)-1 || needed >= max_path_len) goto cleanup; ++needed; /* for NUL */ - obuf = malloc(needed); + obuf = (malloc)(needed); if(!obuf) goto cleanup; count = wcstombs(obuf, fbuf, needed); @@ -232,10 +234,10 @@ static bool fix_excessive_path(const TCHAR *in, TCHAR **out) #endif cleanup: - free(fbuf); + (free)(fbuf); #ifndef _UNICODE - free(ibuf); - free(obuf); + (free)(ibuf); + (free)(obuf); #endif return *out ? true : false; } @@ -267,16 +269,17 @@ int curlx_win32_open(const char *filename, int oflag, ...) curlx_unicodefree(filename_w); } else - errno = EINVAL; + /* !checksrc! disable ERRNOVAR 1 */ + CURL_SETERRNO(EINVAL); #else if(fix_excessive_path(filename, &fixed)) target = fixed; else target = filename; - result = (_open)(target, oflag, pmode); + result = _open(target, oflag, pmode); #endif - free(fixed); + (free)(fixed); return result; } @@ -297,7 +300,8 @@ FILE *curlx_win32_fopen(const char *filename, const char *mode) result = _wfopen(target, mode_w); } else - errno = EINVAL; + /* !checksrc! disable ERRNOVAR 1 */ + CURL_SETERRNO(EINVAL); curlx_unicodefree(filename_w); curlx_unicodefree(mode_w); #else @@ -308,7 +312,7 @@ FILE *curlx_win32_fopen(const char *filename, const char *mode) result = (fopen)(target, mode); #endif - free(fixed); + (free)(fixed); return result; } @@ -333,7 +337,8 @@ int curlx_win32_stat(const char *path, struct_stat *buffer) curlx_unicodefree(path_w); } else - errno = EINVAL; + /* !checksrc! disable ERRNOVAR 1 */ + CURL_SETERRNO(EINVAL); #else if(fix_excessive_path(path, &fixed)) target = fixed; @@ -346,8 +351,10 @@ int curlx_win32_stat(const char *path, struct_stat *buffer) #endif #endif - free(fixed); + (free)(fixed); return result; } +#endif /* UNDER_CE */ + #endif /* _WIN32 */ diff --git a/Utilities/cmcurl/lib/curl_multibyte.h b/Utilities/cmcurl/lib/curlx/multibyte.h similarity index 88% rename from Utilities/cmcurl/lib/curl_multibyte.h rename to Utilities/cmcurl/lib/curlx/multibyte.h index dec384e2fe..7835fdc3b5 100644 --- a/Utilities/cmcurl/lib/curl_multibyte.h +++ b/Utilities/cmcurl/lib/curlx/multibyte.h @@ -23,9 +23,9 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" -#if defined(_WIN32) +#ifdef _WIN32 /* * MultiByte conversions using Windows kernel32 library. @@ -81,12 +81,7 @@ typedef union { #endif /* UNICODE && _WIN32 */ -#define curlx_unicodefree(ptr) \ - do { \ - if(ptr) { \ - (free)(ptr); \ - (ptr) = NULL; \ - } \ - } while(0) +/* the purpose of this macro is to free() without being traced by memdebug */ +#define curlx_unicodefree(ptr) (free)(CURL_UNCONST(ptr)) #endif /* HEADER_CURL_MULTIBYTE_H */ diff --git a/Utilities/cmcurl/lib/nonblock.c b/Utilities/cmcurl/lib/curlx/nonblock.c similarity index 98% rename from Utilities/cmcurl/lib/nonblock.c rename to Utilities/cmcurl/lib/curlx/nonblock.c index a59cab8325..409b187f1f 100644 --- a/Utilities/cmcurl/lib/nonblock.c +++ b/Utilities/cmcurl/lib/curlx/nonblock.c @@ -22,7 +22,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef HAVE_SYS_IOCTL_H #include diff --git a/Utilities/cmcurl/lib/nonblock.h b/Utilities/cmcurl/lib/curlx/nonblock.h similarity index 100% rename from Utilities/cmcurl/lib/nonblock.h rename to Utilities/cmcurl/lib/curlx/nonblock.h diff --git a/Utilities/cmcurl/lib/curlx/strparse.c b/Utilities/cmcurl/lib/curlx/strparse.c new file mode 100644 index 0000000000..b8b2a14d3c --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/strparse.c @@ -0,0 +1,303 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "strparse.h" +#include "../strcase.h" + +void curlx_str_init(struct Curl_str *out) +{ + out->str = NULL; + out->len = 0; +} + +void curlx_str_assign(struct Curl_str *out, const char *str, size_t len) +{ + out->str = str; + out->len = len; +} + +/* Get a word until the first DELIM or end of string. At least one byte long. + return non-zero on error */ +int curlx_str_until(const char **linep, struct Curl_str *out, + const size_t max, char delim) +{ + const char *s = *linep; + size_t len = 0; + DEBUGASSERT(linep && *linep && out && max && delim); + + curlx_str_init(out); + while(*s && (*s != delim)) { + s++; + if(++len > max) { + return STRE_BIG; + } + } + if(!len) + return STRE_SHORT; + out->str = *linep; + out->len = len; + *linep = s; /* point to the first byte after the word */ + return STRE_OK; +} + +/* Get a word until the first space or end of string. At least one byte long. + return non-zero on error */ +int curlx_str_word(const char **linep, struct Curl_str *out, + const size_t max) +{ + return curlx_str_until(linep, out, max, ' '); +} + +/* Get a word until a newline byte or end of string. At least one byte long. + return non-zero on error */ +int curlx_str_untilnl(const char **linep, struct Curl_str *out, + const size_t max) +{ + const char *s = *linep; + size_t len = 0; + DEBUGASSERT(linep && *linep && out && max); + + curlx_str_init(out); + while(*s && !ISNEWLINE(*s)) { + s++; + if(++len > max) + return STRE_BIG; + } + if(!len) + return STRE_SHORT; + out->str = *linep; + out->len = len; + *linep = s; /* point to the first byte after the word */ + return STRE_OK; +} + + +/* Get a "quoted" word. No escaping possible. + return non-zero on error */ +int curlx_str_quotedword(const char **linep, struct Curl_str *out, + const size_t max) +{ + const char *s = *linep; + size_t len = 0; + DEBUGASSERT(linep && *linep && out && max); + + curlx_str_init(out); + if(*s != '\"') + return STRE_BEGQUOTE; + s++; + while(*s && (*s != '\"')) { + s++; + if(++len > max) + return STRE_BIG; + } + if(*s != '\"') + return STRE_ENDQUOTE; + out->str = (*linep) + 1; + out->len = len; + *linep = s + 1; + return STRE_OK; +} + +/* Advance over a single character. + return non-zero on error */ +int curlx_str_single(const char **linep, char byte) +{ + DEBUGASSERT(linep && *linep); + if(**linep != byte) + return STRE_BYTE; + (*linep)++; /* move over it */ + return STRE_OK; +} + +/* Advance over a single space. + return non-zero on error */ +int curlx_str_singlespace(const char **linep) +{ + return curlx_str_single(linep, ' '); +} + +/* given an ASCII character and max ascii, return TRUE if valid */ +#define valid_digit(x,m) \ + (((x) >= '0') && ((x) <= m) && Curl_hexasciitable[(x)-'0']) + +/* We use 16 for the zero index (and the necessary bitwise AND in the loop) + to be able to have a non-zero value there to make valid_digit() able to + use the info */ +const unsigned char Curl_hexasciitable[] = { + 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, /* 0x30: 0 - 9 */ + 0, 0, 0, 0, 0, 0, 0, + 10, 11, 12, 13, 14, 15, /* 0x41: A - F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 10, 11, 12, 13, 14, 15 /* 0x61: a - f */ +}; + +/* no support for 0x prefix nor leading spaces */ +static int str_num_base(const char **linep, curl_off_t *nump, curl_off_t max, + int base) /* 8, 10 or 16, nothing else */ +{ + curl_off_t num = 0; + const char *p; + int m = (base == 10) ? '9' : /* the largest digit possible */ + (base == 16) ? 'f' : '7'; + DEBUGASSERT(linep && *linep && nump); + DEBUGASSERT((base == 8) || (base == 10) || (base == 16)); + DEBUGASSERT(max >= 0); /* mostly to catch SIZE_T_MAX, which is too large */ + *nump = 0; + p = *linep; + if(!valid_digit(*p, m)) + return STRE_NO_NUM; + if(max < base) { + /* special-case low max scenario because check needs to be different */ + do { + int n = Curl_hexval(*p++); + num = num * base + n; + if(num > max) + return STRE_OVERFLOW; + } while(valid_digit(*p, m)); + } + else { + do { + int n = Curl_hexval(*p++); + if(num > ((max - n) / base)) + return STRE_OVERFLOW; + num = num * base + n; + } while(valid_digit(*p, m)); + } + *nump = num; + *linep = p; + return STRE_OK; +} + +/* Get an unsigned decimal number with no leading space or minus. Leading + zeroes are accepted. return non-zero on error */ +int curlx_str_number(const char **linep, curl_off_t *nump, curl_off_t max) +{ + return str_num_base(linep, nump, max, 10); +} + +/* Get an unsigned hexadecimal number with no leading space or minus and no + "0x" support. Leading zeroes are accepted. return non-zero on error */ +int curlx_str_hex(const char **linep, curl_off_t *nump, curl_off_t max) +{ + return str_num_base(linep, nump, max, 16); +} + +/* Get an unsigned octal number with no leading space or minus and no "0" + prefix support. Leading zeroes are accepted. return non-zero on error */ +int curlx_str_octal(const char **linep, curl_off_t *nump, curl_off_t max) +{ + return str_num_base(linep, nump, max, 8); +} + +/* + * Parse a positive number up to 63-bit number written in ASCII. Skip leading + * blanks. No support for prefixes. + */ +int curlx_str_numblanks(const char **str, curl_off_t *num) +{ + curlx_str_passblanks(str); + return curlx_str_number(str, num, CURL_OFF_T_MAX); +} + +/* CR or LF + return non-zero on error */ +int curlx_str_newline(const char **linep) +{ + DEBUGASSERT(linep && *linep); + if(ISNEWLINE(**linep)) { + (*linep)++; + return STRE_OK; /* yessir */ + } + return STRE_NEWLINE; +} + +#ifndef WITHOUT_LIBCURL +/* case insensitive compare that the parsed string matches the given string. + Returns non-zero on match. */ +int curlx_str_casecompare(struct Curl_str *str, const char *check) +{ + size_t clen = check ? strlen(check) : 0; + return ((str->len == clen) && strncasecompare(str->str, check, clen)); +} +#endif + +/* case sensitive string compare. Returns non-zero on match. */ +int curlx_str_cmp(struct Curl_str *str, const char *check) +{ + if(check) { + size_t clen = strlen(check); + return ((str->len == clen) && !strncmp(str->str, check, clen)); + } + return !!(str->len); +} + +/* Trim off 'num' number of bytes from the beginning (left side) of the + string. If 'num' is larger than the string, return error. */ +int curlx_str_nudge(struct Curl_str *str, size_t num) +{ + if(num <= str->len) { + str->str += num; + str->len -= num; + return STRE_OK; + } + return STRE_OVERFLOW; +} + +/* Get the following character sequence that consists only of bytes not + present in the 'reject' string. Like strcspn(). */ +int curlx_str_cspn(const char **linep, struct Curl_str *out, + const char *reject) +{ + const char *s = *linep; + size_t len; + DEBUGASSERT(linep && *linep); + + len = strcspn(s, reject); + if(len) { + out->str = s; + out->len = len; + *linep = &s[len]; + return STRE_OK; + } + curlx_str_init(out); + return STRE_SHORT; +} + +/* remove ISBLANK()s from both ends of the string */ +void curlx_str_trimblanks(struct Curl_str *out) +{ + while(out->len && ISBLANK(*out->str)) + curlx_str_nudge(out, 1); + + /* trim trailing spaces and tabs */ + while(out->len && ISBLANK(out->str[out->len - 1])) + out->len--; +} + +/* increase the pointer until it has moved over all blanks */ +void curlx_str_passblanks(const char **linep) +{ + while(ISBLANK(**linep)) + (*linep)++; /* move over it */ +} diff --git a/Utilities/cmcurl/lib/curlx/strparse.h b/Utilities/cmcurl/lib/curlx/strparse.h new file mode 100644 index 0000000000..17bfdb8071 --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/strparse.h @@ -0,0 +1,112 @@ +#ifndef HEADER_CURL_STRPARSE_H +#define HEADER_CURL_STRPARSE_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" + +#define STRE_OK 0 +#define STRE_BIG 1 +#define STRE_SHORT 2 +#define STRE_BEGQUOTE 3 +#define STRE_ENDQUOTE 4 +#define STRE_BYTE 5 +#define STRE_NEWLINE 6 +#define STRE_OVERFLOW 7 +#define STRE_NO_NUM 8 + +/* public struct, but all accesses should be done using the provided + functions */ +struct Curl_str { + const char *str; + size_t len; +}; + +void curlx_str_init(struct Curl_str *out); +void curlx_str_assign(struct Curl_str *out, const char *str, size_t len); + +#define curlx_str(x) ((x)->str) +#define curlx_strlen(x) ((x)->len) + +/* Get a word until the first space + return non-zero on error */ +int curlx_str_word(const char **linep, struct Curl_str *out, const size_t max); + +/* Get a word until the first DELIM or end of string + return non-zero on error */ +int curlx_str_until(const char **linep, struct Curl_str *out, const size_t max, + char delim); + +/* Get a word until a newline byte or end of string. At least one byte long. + return non-zero on error */ +int curlx_str_untilnl(const char **linep, struct Curl_str *out, + const size_t max); + +/* Get a "quoted" word. No escaping possible. + return non-zero on error */ +int curlx_str_quotedword(const char **linep, struct Curl_str *out, + const size_t max); + +/* Advance over a single character. + return non-zero on error */ +int curlx_str_single(const char **linep, char byte); + +/* Advance over a single space. + return non-zero on error */ +int curlx_str_singlespace(const char **linep); + +/* Get an unsigned decimal number. Return non-zero on error */ +int curlx_str_number(const char **linep, curl_off_t *nump, curl_off_t max); + +/* As above with CURL_OFF_T_MAX but also pass leading blanks */ +int curlx_str_numblanks(const char **str, curl_off_t *num); + +/* Get an unsigned hexadecimal number. Return non-zero on error */ +int curlx_str_hex(const char **linep, curl_off_t *nump, curl_off_t max); + +/* Get an unsigned octal number. Return non-zero on error */ +int curlx_str_octal(const char **linep, curl_off_t *nump, curl_off_t max); + +/* Check for CR or LF + return non-zero on error */ +int curlx_str_newline(const char **linep); + +/* case insensitive compare that the parsed string matches the + given string. */ +int curlx_str_casecompare(struct Curl_str *str, const char *check); +int curlx_str_cmp(struct Curl_str *str, const char *check); + +int curlx_str_nudge(struct Curl_str *str, size_t num); + +int curlx_str_cspn(const char **linep, struct Curl_str *out, const char *cspn); +void curlx_str_trimblanks(struct Curl_str *out); +void curlx_str_passblanks(const char **linep); + +/* given a hexadecimal letter, return the binary value. '0' returns 0, 'a' + returns 10. THIS ONLY WORKS ON VALID HEXADECIMAL LETTER INPUT. Verify + before calling this! +*/ +extern const unsigned char Curl_hexasciitable[]; +#define Curl_hexval(x) (unsigned char)(Curl_hexasciitable[(x) - '0'] & 0x0f) + +#endif /* HEADER_CURL_STRPARSE_H */ diff --git a/Utilities/cmcurl/lib/timediff.c b/Utilities/cmcurl/lib/curlx/timediff.c similarity index 94% rename from Utilities/cmcurl/lib/timediff.c rename to Utilities/cmcurl/lib/curlx/timediff.c index d0824d1448..a90da961ab 100644 --- a/Utilities/cmcurl/lib/timediff.c +++ b/Utilities/cmcurl/lib/curlx/timediff.c @@ -44,7 +44,7 @@ struct timeval *curlx_mstotv(struct timeval *tv, timediff_t ms) if(ms > 0) { timediff_t tv_sec = ms / 1000; - timediff_t tv_usec = (ms % 1000) * 1000; /* max=999999 */ + timediff_t tv_usec = (ms % 1000) * 1000; /* max=999000 */ #ifdef HAVE_SUSECONDS_T #if TIMEDIFF_T_MAX > TIME_T_MAX /* tv_sec overflow check in case time_t is signed */ @@ -84,5 +84,5 @@ struct timeval *curlx_mstotv(struct timeval *tv, timediff_t ms) */ timediff_t curlx_tvtoms(struct timeval *tv) { - return (tv->tv_sec*1000) + (timediff_t)(((double)tv->tv_usec)/1000.0); + return (tv->tv_sec*1000) + (timediff_t)(tv->tv_usec/1000); } diff --git a/Utilities/cmcurl/lib/timediff.h b/Utilities/cmcurl/lib/curlx/timediff.h similarity index 98% rename from Utilities/cmcurl/lib/timediff.h rename to Utilities/cmcurl/lib/curlx/timediff.h index 75f996c55c..aa224381dd 100644 --- a/Utilities/cmcurl/lib/timediff.h +++ b/Utilities/cmcurl/lib/curlx/timediff.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" /* Use a larger type even for 32-bit time_t systems so that we can keep microsecond accuracy in it */ diff --git a/Utilities/cmcurl/lib/timeval.c b/Utilities/cmcurl/lib/curlx/timeval.c similarity index 82% rename from Utilities/cmcurl/lib/timeval.c rename to Utilities/cmcurl/lib/curlx/timeval.c index bb29bfdfee..501bf9c3fd 100644 --- a/Utilities/cmcurl/lib/timeval.c +++ b/Utilities/cmcurl/lib/curlx/timeval.c @@ -24,30 +24,52 @@ #include "timeval.h" -#if defined(_WIN32) +#ifdef _WIN32 #include -#include "system_win32.h" +#include "version_win32.h" +#include "../system_win32.h" + +LARGE_INTEGER Curl_freq; +bool Curl_isVistaOrGreater; + +/* For tool or tests, we must initialize before calling curlx_now(). + Providing this function here is wrong. */ +void curlx_now_init(void) +{ + if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) + Curl_isVistaOrGreater = true; + else + Curl_isVistaOrGreater = false; + + QueryPerformanceFrequency(&Curl_freq); +} /* In case of bug fix this function has a counterpart in tool_util.c */ -struct curltime Curl_now(void) +struct curltime curlx_now(void) { struct curltime now; - if(Curl_isVistaOrGreater) { /* QPC timer might have issues pre-Vista */ + bool isVistaOrGreater; + isVistaOrGreater = Curl_isVistaOrGreater; + if(isVistaOrGreater) { /* QPC timer might have issues pre-Vista */ LARGE_INTEGER count; + LARGE_INTEGER freq; + freq = Curl_freq; + DEBUGASSERT(freq.QuadPart); QueryPerformanceCounter(&count); - now.tv_sec = (time_t)(count.QuadPart / Curl_freq.QuadPart); - now.tv_usec = (int)((count.QuadPart % Curl_freq.QuadPart) * 1000000 / - Curl_freq.QuadPart); + now.tv_sec = (time_t)(count.QuadPart / freq.QuadPart); + now.tv_usec = (int)((count.QuadPart % freq.QuadPart) * 1000000 / + freq.QuadPart); } else { /* Disable /analyze warning that GetTickCount64 is preferred */ -#if defined(_MSC_VER) +#ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:28159) #endif DWORD milliseconds = GetTickCount(); -#if defined(_MSC_VER) +#ifdef _MSC_VER #pragma warning(pop) #endif @@ -60,7 +82,7 @@ struct curltime Curl_now(void) #elif defined(HAVE_CLOCK_GETTIME_MONOTONIC) || \ defined(HAVE_CLOCK_GETTIME_MONOTONIC_RAW) -struct curltime Curl_now(void) +struct curltime curlx_now(void) { /* ** clock_gettime() is granted to be increased monotonically when the @@ -134,7 +156,7 @@ struct curltime Curl_now(void) #include #include -struct curltime Curl_now(void) +struct curltime curlx_now(void) { /* ** Monotonic timer on macOS is provided by mach_absolute_time(), which @@ -162,7 +184,7 @@ struct curltime Curl_now(void) #elif defined(HAVE_GETTIMEOFDAY) -struct curltime Curl_now(void) +struct curltime curlx_now(void) { /* ** gettimeofday() is not granted to be increased monotonically, due to @@ -179,7 +201,7 @@ struct curltime Curl_now(void) #else -struct curltime Curl_now(void) +struct curltime curlx_now(void) { /* ** time() returns the value of time in seconds since the Epoch. @@ -198,7 +220,7 @@ struct curltime Curl_now(void) * * @unittest: 1323 */ -timediff_t Curl_timediff(struct curltime newer, struct curltime older) +timediff_t curlx_timediff(struct curltime newer, struct curltime older) { timediff_t diff = (timediff_t)newer.tv_sec-older.tv_sec; if(diff >= (TIMEDIFF_T_MAX/1000)) @@ -212,7 +234,7 @@ timediff_t Curl_timediff(struct curltime newer, struct curltime older) * 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 curlx_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)) @@ -226,7 +248,7 @@ timediff_t Curl_timediff_ceil(struct curltime newer, struct curltime older) * Returns: time difference in number of microseconds. For too large diffs it * returns max value. */ -timediff_t Curl_timediff_us(struct curltime newer, struct curltime older) +timediff_t curlx_timediff_us(struct curltime newer, struct curltime older) { timediff_t diff = (timediff_t)newer.tv_sec-older.tv_sec; if(diff >= (TIMEDIFF_T_MAX/1000000)) diff --git a/Utilities/cmcurl/lib/timeval.h b/Utilities/cmcurl/lib/curlx/timeval.h similarity index 82% rename from Utilities/cmcurl/lib/timeval.h rename to Utilities/cmcurl/lib/curlx/timeval.h index 33dfb5b10d..1f8fe5e8ad 100644 --- a/Utilities/cmcurl/lib/timeval.h +++ b/Utilities/cmcurl/lib/curlx/timeval.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #include "timediff.h" @@ -33,7 +33,12 @@ struct curltime { int tv_usec; /* microseconds */ }; -struct curltime Curl_now(void); +#ifdef _WIN32 +/* For tool or tests, we must initialize before calling curlx_now() */ +void curlx_now_init(void); +#endif + +struct curltime curlx_now(void); /* * Make sure that the first argument (newer) is the more recent time and older @@ -41,7 +46,7 @@ struct curltime Curl_now(void); * * Returns: the time difference in number of milliseconds. */ -timediff_t Curl_timediff(struct curltime newer, struct curltime older); +timediff_t curlx_timediff(struct curltime newer, struct curltime older); /* * Make sure that the first argument (newer) is the more recent time and older @@ -49,7 +54,7 @@ timediff_t Curl_timediff(struct curltime newer, struct curltime older); * * Returns: the time difference in number of milliseconds, rounded up. */ -timediff_t Curl_timediff_ceil(struct curltime newer, struct curltime older); +timediff_t curlx_timediff_ceil(struct curltime newer, struct curltime older); /* * Make sure that the first argument (newer) is the more recent time and older @@ -57,6 +62,6 @@ timediff_t Curl_timediff_ceil(struct curltime newer, struct curltime older); * * Returns: the time difference in number of microseconds. */ -timediff_t Curl_timediff_us(struct curltime newer, struct curltime older); +timediff_t curlx_timediff_us(struct curltime newer, struct curltime older); #endif /* HEADER_CURL_TIMEVAL_H */ diff --git a/Utilities/cmcurl/lib/version_win32.c b/Utilities/cmcurl/lib/curlx/version_win32.c similarity index 73% rename from Utilities/cmcurl/lib/version_win32.c rename to Utilities/cmcurl/lib/curlx/version_win32.c index 21a122f2a6..8d0af68fcf 100644 --- a/Utilities/cmcurl/lib/version_win32.c +++ b/Utilities/cmcurl/lib/curlx/version_win32.c @@ -22,19 +22,17 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" -#if defined(_WIN32) +#ifdef _WIN32 #include #include "version_win32.h" #include "warnless.h" /* The last 2 #include files should be in this order */ -#ifdef BUILDING_LIBCURL -#include "curl_memory.h" -#endif -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* This Unicode version struct works for VerifyVersionInfoW (OSVERSIONINFOEXW) and RtlVerifyVersionInfo (RTLOSVERSIONINFOEXW) */ @@ -79,7 +77,7 @@ bool curlx_verify_windows_version(const unsigned int majorVersion, { bool matched = FALSE; -#if defined(CURL_WINDOWS_UWP) +#ifdef CURL_WINDOWS_UWP /* We have no way to determine the Windows version from Windows apps, so let's assume we are running on the target Windows version. */ const WORD fullVersion = MAKEWORD(minorVersion, majorVersion); @@ -113,88 +111,12 @@ bool curlx_verify_windows_version(const unsigned int majorVersion, /* we are always running on PLATFORM_WINNT */ matched = FALSE; } -#elif !defined(_WIN32_WINNT) || !defined(_WIN32_WINNT_WIN2K) || \ - (_WIN32_WINNT < _WIN32_WINNT_WIN2K) - OSVERSIONINFO osver; - - memset(&osver, 0, sizeof(osver)); - osver.dwOSVersionInfoSize = sizeof(osver); - - /* Find out Windows version */ - if(GetVersionEx(&osver)) { - /* Verify the Operating System version number */ - switch(condition) { - case VERSION_LESS_THAN: - if(osver.dwMajorVersion < majorVersion || - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion < minorVersion) || - (buildVersion != 0 && - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion == minorVersion && - osver.dwBuildNumber < buildVersion))) - matched = TRUE; - break; - - case VERSION_LESS_THAN_EQUAL: - if(osver.dwMajorVersion < majorVersion || - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion < minorVersion) || - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion == minorVersion && - (buildVersion == 0 || - osver.dwBuildNumber <= buildVersion))) - matched = TRUE; - break; - - case VERSION_EQUAL: - if(osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion == minorVersion && - (buildVersion == 0 || - osver.dwBuildNumber == buildVersion)) - matched = TRUE; - break; - - case VERSION_GREATER_THAN_EQUAL: - if(osver.dwMajorVersion > majorVersion || - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion > minorVersion) || - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion == minorVersion && - (buildVersion == 0 || - osver.dwBuildNumber >= buildVersion))) - matched = TRUE; - break; - - case VERSION_GREATER_THAN: - if(osver.dwMajorVersion > majorVersion || - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion > minorVersion) || - (buildVersion != 0 && - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion == minorVersion && - osver.dwBuildNumber > buildVersion))) - matched = TRUE; - break; - } - - /* Verify the platform identifier (if necessary) */ - if(matched) { - switch(platform) { - case PLATFORM_WINDOWS: - if(osver.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS) - matched = FALSE; - break; - - case PLATFORM_WINNT: - if(osver.dwPlatformId != VER_PLATFORM_WIN32_NT) - matched = FALSE; - break; - - default: /* like platform == PLATFORM_DONT_CARE */ - break; - } - } - } +#elif defined(UNDER_CE) + (void)majorVersion; + (void)minorVersion; + (void)buildVersion; + (void)platform; + (void)condition; #else ULONGLONG cm = 0; struct OUR_OSVERSIONINFOEXW osver; diff --git a/Utilities/cmcurl/lib/version_win32.h b/Utilities/cmcurl/lib/curlx/version_win32.h similarity index 97% rename from Utilities/cmcurl/lib/version_win32.h rename to Utilities/cmcurl/lib/curlx/version_win32.h index 95a9e7f215..471100a66f 100644 --- a/Utilities/cmcurl/lib/version_win32.h +++ b/Utilities/cmcurl/lib/curlx/version_win32.h @@ -24,9 +24,9 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" -#if defined(_WIN32) +#ifdef _WIN32 /* Version condition */ typedef enum { diff --git a/Utilities/cmcurl/lib/warnless.c b/Utilities/cmcurl/lib/curlx/warnless.c similarity index 90% rename from Utilities/cmcurl/lib/warnless.c rename to Utilities/cmcurl/lib/curlx/warnless.c index 41699600be..5ca92450d8 100644 --- a/Utilities/cmcurl/lib/warnless.c +++ b/Utilities/cmcurl/lib/curlx/warnless.c @@ -22,7 +22,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "warnless.h" #if defined(__INTEL_COMPILER) && defined(__unix__) @@ -35,8 +35,6 @@ #endif /* __INTEL_COMPILER && __unix__ */ -#include "warnless.h" - #ifdef _WIN32 #undef read #undef write @@ -45,42 +43,17 @@ #include #define CURL_MASK_UCHAR ((unsigned char)~0) -#define CURL_MASK_SCHAR (CURL_MASK_UCHAR >> 1) #define CURL_MASK_USHORT ((unsigned short)~0) -#define CURL_MASK_SSHORT (CURL_MASK_USHORT >> 1) #define CURL_MASK_UINT ((unsigned int)~0) #define CURL_MASK_SINT (CURL_MASK_UINT >> 1) #define CURL_MASK_ULONG ((unsigned long)~0) -#define CURL_MASK_SLONG (CURL_MASK_ULONG >> 1) - -#define CURL_MASK_UCOFFT ((unsigned CURL_TYPEOF_CURL_OFF_T)~0) -#define CURL_MASK_SCOFFT (CURL_MASK_UCOFFT >> 1) #define CURL_MASK_USIZE_T ((size_t)~0) #define CURL_MASK_SSIZE_T (CURL_MASK_USIZE_T >> 1) -/* -** unsigned long to unsigned short -*/ - -unsigned short curlx_ultous(unsigned long ulnum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - - DEBUGASSERT(ulnum <= (unsigned long) CURL_MASK_USHORT); - return (unsigned short)(ulnum & (unsigned long) CURL_MASK_USHORT); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - /* ** unsigned long to unsigned char */ @@ -323,7 +296,7 @@ size_t curlx_sitouz(int sinum) #endif } -#if defined(_WIN32) +#ifdef _WIN32 ssize_t curlx_read(int fd, void *buf, size_t count) { diff --git a/Utilities/cmcurl/lib/warnless.h b/Utilities/cmcurl/lib/curlx/warnless.h similarity index 95% rename from Utilities/cmcurl/lib/warnless.h rename to Utilities/cmcurl/lib/curlx/warnless.h index fe3453250b..c78b61169b 100644 --- a/Utilities/cmcurl/lib/warnless.h +++ b/Utilities/cmcurl/lib/curlx/warnless.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_WINSOCK #include /* for curl_socket_t */ @@ -33,8 +33,6 @@ #define CURLX_FUNCTION_CAST(target_type, func) \ (target_type)(void (*) (void))(func) -unsigned short curlx_ultous(unsigned long ulnum); - unsigned char curlx_ultouc(unsigned long ulnum); int curlx_uztosi(size_t uznum); @@ -59,7 +57,7 @@ unsigned short curlx_uitous(unsigned int uinum); size_t curlx_sitouz(int sinum); -#if defined(_WIN32) +#ifdef _WIN32 ssize_t curlx_read(int fd, void *buf, size_t count); @@ -72,7 +70,7 @@ 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) +#ifdef _WIN32 #undef read #define read(fd, buf, count) curlx_read(fd, buf, count) #undef write diff --git a/Utilities/cmcurl/lib/curlx/winapi.c b/Utilities/cmcurl/lib/curlx/winapi.c new file mode 100644 index 0000000000..6069424bec --- /dev/null +++ b/Utilities/cmcurl/lib/curlx/winapi.c @@ -0,0 +1,135 @@ +/*************************************************************************** + * _ _ ____ _ + * 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" + +/* + * curlx_winapi_strerror: + * Variant of Curl_strerror if the error code is definitely Windows API. + */ +#ifdef _WIN32 +#include "winapi.h" + +#ifdef BUILDING_LIBCURL +#include +#define SNPRINTF curl_msnprintf +#else +/* when built for the test servers */ + +/* adjust for old MSVC */ +#if defined(_MSC_VER) && (_MSC_VER < 1900) +# define SNPRINTF _snprintf +#else +#define SNPRINTF snprintf +#endif + +#endif /* !BUILDING_LIBCURL */ + +#ifdef _WIN32 +/* 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. + */ +const char *curlx_get_winapi_error(int err, char *buf, size_t buflen) +{ + char *p; + wchar_t wbuf[256]; + + if(!buflen) + return NULL; + + *buf = '\0'; + *wbuf = L'\0'; + + /* We return the local codepage version of the error string because if it is + output to the user's terminal it will likely be with functions which + 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, (DWORD)err, + LANG_NEUTRAL, wbuf, CURL_ARRAYSIZE(wbuf), NULL)) { + size_t written = wcstombs(buf, wbuf, buflen - 1); + if(written != (size_t)-1) + buf[written] = '\0'; + else + *buf = '\0'; + } + + /* Truncate multiple lines */ + p = strchr(buf, '\n'); + if(p) { + if(p > buf && *(p-1) == '\r') + *(p-1) = '\0'; + else + *p = '\0'; + } + + return *buf ? buf : NULL; +} +#endif /* _WIN32 */ + +const char *curlx_winapi_strerror(DWORD err, char *buf, size_t buflen) +{ +#ifdef _WIN32 + DWORD old_win_err = GetLastError(); +#endif + int old_errno = errno; + + if(!buflen) + return NULL; + + *buf = '\0'; + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(!curlx_get_winapi_error((int)err, buf, buflen)) { +#if defined(__GNUC__) && __GNUC__ >= 7 +#pragma GCC diagnostic push +#pragma GCC diagnostic warning "-Wformat-truncation=1" +#endif + /* some GCC compilers cause false positive warnings if we allow this + warning */ + SNPRINTF(buf, buflen, "Unknown error %lu (0x%08lX)", err, err); +#if defined(__GNUC__) && __GNUC__ >= 7 +#pragma GCC diagnostic pop +#endif + + } +#else + { + const char *txt = (err == ERROR_SUCCESS) ? "No error" : "Error"; + if(strlen(txt) < buflen) + strcpy(buf, txt); + } +#endif + + if(errno != old_errno) + CURL_SETERRNO(old_errno); + +#ifdef _WIN32 + if(old_win_err != GetLastError()) + SetLastError(old_win_err); +#endif + + return buf; +} +#endif /* _WIN32 */ diff --git a/Utilities/cmcurl/lib/strtok.h b/Utilities/cmcurl/lib/curlx/winapi.h similarity index 78% rename from Utilities/cmcurl/lib/strtok.h rename to Utilities/cmcurl/lib/curlx/winapi.h index 9b4d06275f..76ddcc53b8 100644 --- a/Utilities/cmcurl/lib/strtok.h +++ b/Utilities/cmcurl/lib/curlx/winapi.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_STRTOK_H -#define HEADER_CURL_STRTOK_H +#ifndef HEADER_CURLX_WINAPI_H +#define HEADER_CURLX_WINAPI_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -23,14 +23,11 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" -#include -#ifdef HAVE_STRTOK_R -#include -#define Curl_strtok_r strtok_r -#else -char *Curl_strtok_r(char *s, const char *delim, char **last); +#ifdef _WIN32 +#define WINAPI_ERROR_LEN 100 +const char *curlx_get_winapi_error(int err, char *buf, size_t buflen); +const char *curlx_winapi_strerror(DWORD err, char *buf, size_t buflen); #endif -#endif /* HEADER_CURL_STRTOK_H */ +#endif /* HEADER_CURLX_WINAPI_H */ diff --git a/Utilities/cmcurl/lib/cw-out.c b/Utilities/cmcurl/lib/cw-out.c index 4d3df0a650..097ef85e8b 100644 --- a/Utilities/cmcurl/lib/cw-out.c +++ b/Utilities/cmcurl/lib/cw-out.c @@ -32,6 +32,7 @@ #include "multiif.h" #include "sendf.h" #include "cw-out.h" +#include "cw-pause.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -86,7 +87,7 @@ 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); + curlx_dyn_init(&cwbuf->b, DYN_PAUSE_BUFFER); } return cwbuf; } @@ -94,7 +95,7 @@ static struct cw_out_buf *cw_out_buf_create(cw_out_type otype) static void cw_out_buf_free(struct cw_out_buf *cwbuf) { if(cwbuf) { - Curl_dyn_free(&cwbuf->b); + curlx_dyn_free(&cwbuf->b); free(cwbuf); } } @@ -113,7 +114,7 @@ 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 = { +const struct Curl_cwtype Curl_cwt_out = { "cw-out", NULL, cw_out_init, @@ -145,7 +146,7 @@ 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); + len += curlx_dyn_len(&cwbuf->b); cwbuf = cwbuf->next; } return len; @@ -198,7 +199,7 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, const char *buf, size_t blen, size_t *pconsumed) { - curl_write_callback wcb; + curl_write_callback wcb = NULL; void *wcb_data; size_t max_write, min_write; size_t wlen, nwritten; @@ -220,9 +221,9 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, break; wlen = max_write ? CURLMIN(blen, max_write) : blen; Curl_set_in_callback(data, TRUE); - nwritten = wcb((char *)buf, 1, wlen, wcb_data); + nwritten = wcb((char *)CURL_UNCONST(buf), 1, wlen, wcb_data); Curl_set_in_callback(data, FALSE); - CURL_TRC_WRITE(data, "cw_out, wrote %zu %s bytes -> %zu", + CURL_TRC_WRITE(data, "[OUT] wrote %zu %s bytes -> %zu", wlen, (otype == CW_OUT_BODY) ? "body" : "header", nwritten); if(CURL_WRITEFUNC_PAUSE == nwritten) { @@ -236,7 +237,7 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, /* 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"); + CURL_TRC_WRITE(data, "[OUT] PAUSE requested by client"); break; } else if(CURL_WRITEFUNC_ERROR == nwritten) { @@ -262,23 +263,24 @@ static CURLcode cw_out_buf_flush(struct cw_out_ctx *ctx, { CURLcode result = CURLE_OK; - if(Curl_dyn_len(&cwbuf->b)) { + if(curlx_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), + curlx_dyn_ptr(&cwbuf->b), + curlx_dyn_len(&cwbuf->b), &consumed); if(result) return result; if(consumed) { - if(consumed == Curl_dyn_len(&cwbuf->b)) { - Curl_dyn_free(&cwbuf->b); + if(consumed == curlx_dyn_len(&cwbuf->b)) { + curlx_dyn_free(&cwbuf->b); } else { - DEBUGASSERT(consumed < Curl_dyn_len(&cwbuf->b)); - result = Curl_dyn_tail(&cwbuf->b, Curl_dyn_len(&cwbuf->b) - consumed); + DEBUGASSERT(consumed < curlx_dyn_len(&cwbuf->b)); + result = curlx_dyn_tail(&cwbuf->b, + curlx_dyn_len(&cwbuf->b) - consumed); if(result) return result; } @@ -318,7 +320,7 @@ static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx, result = cw_out_buf_flush(ctx, data, cwbuf, flush_all); if(result) return result; - if(!Curl_dyn_len(&cwbuf->b)) { + if(!curlx_dyn_len(&cwbuf->b)) { cw_out_buf_free(cwbuf); *pcwbuf = NULL; } @@ -326,11 +328,16 @@ static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx, } static CURLcode cw_out_append(struct cw_out_ctx *ctx, + struct Curl_easy *data, cw_out_type otype, const char *buf, size_t blen) { - if(cw_out_bufs_len(ctx) + blen > DYN_PAUSE_BUFFER) + CURL_TRC_WRITE(data, "[OUT] paused, buffering %zu more bytes (%zu/%d)", + blen, cw_out_bufs_len(ctx), DYN_PAUSE_BUFFER); + if(cw_out_bufs_len(ctx) + blen > DYN_PAUSE_BUFFER) { + failf(data, "pause buffer not large enough -> CURLE_TOO_LARGE"); 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 @@ -343,7 +350,7 @@ static CURLcode cw_out_append(struct cw_out_ctx *ctx, ctx->buf = cwbuf; } DEBUGASSERT(ctx->buf && (ctx->buf->type == otype)); - return Curl_dyn_addn(&ctx->buf->b, buf, blen); + return curlx_dyn_addn(&ctx->buf->b, buf, blen); } static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, @@ -364,7 +371,7 @@ static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, if(ctx->buf) { /* still have buffered data, append and flush */ - result = cw_out_append(ctx, otype, buf, blen); + result = cw_out_append(ctx, data, otype, buf, blen); if(result) return result; result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all); @@ -380,7 +387,8 @@ static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, return result; if(consumed < blen) { /* did not write all, append the rest */ - result = cw_out_append(ctx, otype, buf + consumed, blen - consumed); + result = cw_out_append(ctx, data, otype, + buf + consumed, blen - consumed); if(result) goto out; } @@ -430,12 +438,31 @@ bool Curl_cw_out_is_paused(struct Curl_easy *data) 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, + bool flush_all) +{ + struct cw_out_ctx *ctx = (struct cw_out_ctx *)cw_out; + CURLcode result = CURLE_OK; + + if(ctx->errored) + return CURLE_WRITE_ERROR; + 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) { struct Curl_cwriter *cw_out; CURLcode result = CURLE_OK; @@ -443,31 +470,26 @@ static CURLcode cw_out_flush(struct Curl_easy *data, 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; - } + CURL_TRC_WRITE(data, "[OUT] unpause"); + ctx->paused = FALSE; + result = Curl_cw_pause_flush(data); + if(!result) + result = cw_out_flush(data, cw_out, FALSE); } 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); + struct Curl_cwriter *cw_out; + CURLcode result = CURLE_OK; + + cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out); + if(cw_out) { + CURL_TRC_WRITE(data, "[OUT] done"); + result = Curl_cw_pause_flush(data); + if(!result) + result = cw_out_flush(data, cw_out, TRUE); + } + return result; } diff --git a/Utilities/cmcurl/lib/cw-out.h b/Utilities/cmcurl/lib/cw-out.h index ca4c2e435d..89b9985bb5 100644 --- a/Utilities/cmcurl/lib/cw-out.h +++ b/Utilities/cmcurl/lib/cw-out.h @@ -33,7 +33,7 @@ * the client callbacks. Intended to be the last installed in the * client writer stack of a transfer. */ -extern struct Curl_cwtype Curl_cwt_out; +extern const struct Curl_cwtype Curl_cwt_out; /** * Return TRUE iff 'cw-out' client write has paused data. diff --git a/Utilities/cmcurl/lib/cw-pause.c b/Utilities/cmcurl/lib/cw-pause.c new file mode 100644 index 0000000000..9b9554c551 --- /dev/null +++ b/Utilities/cmcurl/lib/cw-pause.c @@ -0,0 +1,242 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "bufq.h" +#include "cfilters.h" +#include "headers.h" +#include "multiif.h" +#include "sendf.h" +#include "cw-pause.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +/* body dynbuf sizes */ +#define CW_PAUSE_BUF_CHUNK (16 * 1024) +/* when content decoding, write data in chunks */ +#define CW_PAUSE_DEC_WRITE_CHUNK (4096) + +struct cw_pause_buf { + struct cw_pause_buf *next; + struct bufq b; + int type; +}; + +static struct cw_pause_buf *cw_pause_buf_create(int type, size_t buflen) +{ + struct cw_pause_buf *cwbuf = calloc(1, sizeof(*cwbuf)); + if(cwbuf) { + cwbuf->type = type; + if(type & CLIENTWRITE_BODY) + Curl_bufq_init2(&cwbuf->b, CW_PAUSE_BUF_CHUNK, 1, + (BUFQ_OPT_SOFT_LIMIT|BUFQ_OPT_NO_SPARES)); + else + Curl_bufq_init(&cwbuf->b, buflen, 1); + } + return cwbuf; +} + +static void cw_pause_buf_free(struct cw_pause_buf *cwbuf) +{ + if(cwbuf) { + Curl_bufq_free(&cwbuf->b); + free(cwbuf); + } +} + +struct cw_pause_ctx { + struct Curl_cwriter super; + struct cw_pause_buf *buf; + size_t buf_total; +}; + +static CURLcode cw_pause_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes); +static void cw_pause_close(struct Curl_easy *data, + struct Curl_cwriter *writer); +static CURLcode cw_pause_init(struct Curl_easy *data, + struct Curl_cwriter *writer); + +const struct Curl_cwtype Curl_cwt_pause = { + "cw-pause", + NULL, + cw_pause_init, + cw_pause_write, + cw_pause_close, + sizeof(struct cw_pause_ctx) +}; + +static CURLcode cw_pause_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct cw_pause_ctx *ctx = writer->ctx; + (void)data; + ctx->buf = NULL; + return CURLE_OK; +} + +static void cw_pause_bufs_free(struct cw_pause_ctx *ctx) +{ + while(ctx->buf) { + struct cw_pause_buf *next = ctx->buf->next; + cw_pause_buf_free(ctx->buf); + ctx->buf = next; + } +} + +static void cw_pause_close(struct Curl_easy *data, struct Curl_cwriter *writer) +{ + struct cw_pause_ctx *ctx = writer->ctx; + + (void)data; + cw_pause_bufs_free(ctx); +} + +static CURLcode cw_pause_flush(struct Curl_easy *data, + struct Curl_cwriter *cw_pause) +{ + struct cw_pause_ctx *ctx = (struct cw_pause_ctx *)cw_pause; + bool decoding = Curl_cwriter_is_content_decoding(data); + CURLcode result = CURLE_OK; + + /* write the end of the chain until it blocks or gets empty */ + while(ctx->buf && !Curl_cwriter_is_paused(data)) { + struct cw_pause_buf **plast = &ctx->buf; + size_t blen, wlen = 0; + const unsigned char *buf = NULL; + + while((*plast)->next) /* got to last in list */ + plast = &(*plast)->next; + if(Curl_bufq_peek(&(*plast)->b, &buf, &blen)) { + wlen = (decoding && ((*plast)->type & CLIENTWRITE_BODY)) ? + CURLMIN(blen, CW_PAUSE_DEC_WRITE_CHUNK) : blen; + result = Curl_cwriter_write(data, cw_pause->next, (*plast)->type, + (const char *)buf, wlen); + CURL_TRC_WRITE(data, "[PAUSE] flushed %zu/%zu bytes, type=%x -> %d", + wlen, ctx->buf_total, (*plast)->type, result); + Curl_bufq_skip(&(*plast)->b, wlen); + DEBUGASSERT(ctx->buf_total >= wlen); + ctx->buf_total -= wlen; + if(result) + return result; + } + else if((*plast)->type & CLIENTWRITE_EOS) { + result = Curl_cwriter_write(data, cw_pause->next, (*plast)->type, + (const char *)buf, 0); + CURL_TRC_WRITE(data, "[PAUSE] flushed 0/%zu bytes, type=%x -> %d", + ctx->buf_total, (*plast)->type, result); + } + + if(Curl_bufq_is_empty(&(*plast)->b)) { + cw_pause_buf_free(*plast); + *plast = NULL; + } + } + return result; +} + +static CURLcode cw_pause_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) +{ + struct cw_pause_ctx *ctx = writer->ctx; + CURLcode result = CURLE_OK; + size_t wlen = 0; + bool decoding = Curl_cwriter_is_content_decoding(data); + + if(ctx->buf && !Curl_cwriter_is_paused(data)) { + result = cw_pause_flush(data, writer); + if(result) + return result; + } + + while(!ctx->buf && !Curl_cwriter_is_paused(data)) { + int wtype = type; + DEBUGASSERT(!ctx->buf); + /* content decoding might blow up size considerably, write smaller + * chunks to make pausing need buffer less. */ + wlen = (decoding && (type & CLIENTWRITE_BODY)) ? + CURLMIN(blen, CW_PAUSE_DEC_WRITE_CHUNK) : blen; + if(wlen < blen) + wtype &= ~CLIENTWRITE_EOS; + result = Curl_cwriter_write(data, writer->next, wtype, buf, wlen); + CURL_TRC_WRITE(data, "[PAUSE] writing %zu/%zu bytes of type %x -> %d", + wlen, blen, wtype, result); + if(result) + return result; + buf += wlen; + blen -= wlen; + if(!blen) + return result; + } + + do { + size_t nwritten = 0; + if(ctx->buf && (ctx->buf->type == type) && (type & CLIENTWRITE_BODY)) { + /* same type and body, append to current buffer which has a soft + * limit and should take everything up to OOM. */ + result = Curl_bufq_cwrite(&ctx->buf->b, buf, blen, &nwritten); + } + else { + /* Need a new buf, type changed */ + struct cw_pause_buf *cwbuf = cw_pause_buf_create(type, blen); + if(!cwbuf) + return CURLE_OUT_OF_MEMORY; + cwbuf->next = ctx->buf; + ctx->buf = cwbuf; + result = Curl_bufq_cwrite(&ctx->buf->b, buf, blen, &nwritten); + } + CURL_TRC_WRITE(data, "[PAUSE] buffer %zu more bytes of type %x, " + "total=%zu -> %d", nwritten, type, ctx->buf_total + wlen, + result); + if(result) + return result; + buf += nwritten; + blen -= nwritten; + ctx->buf_total += nwritten; + } while(blen); + + return result; +} + +CURLcode Curl_cw_pause_flush(struct Curl_easy *data) +{ + struct Curl_cwriter *cw_pause; + CURLcode result = CURLE_OK; + + cw_pause = Curl_cwriter_get_by_type(data, &Curl_cwt_pause); + if(cw_pause) + result = cw_pause_flush(data, cw_pause); + + return result; +} diff --git a/Utilities/cmcurl/lib/strtoofft.h b/Utilities/cmcurl/lib/cw-pause.h similarity index 50% rename from Utilities/cmcurl/lib/strtoofft.h rename to Utilities/cmcurl/lib/cw-pause.h index 71808b719c..2aa1a499cd 100644 --- a/Utilities/cmcurl/lib/strtoofft.h +++ b/Utilities/cmcurl/lib/cw-pause.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_STRTOOFFT_H -#define HEADER_CURL_STRTOOFFT_H +#ifndef HEADER_CURL_CW_PAUSE_H +#define HEADER_CURL_CW_PAUSE_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -26,29 +26,15 @@ #include "curl_setup.h" -/* - * 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 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. - * - * On systems where the size of curl_off_t is greater than the size of 'long' - * the conversion function to use is strtoll() if it is available, otherwise, - * we emulate its functionality with our own clone. - * - * On systems where the size of curl_off_t is smaller or equal than the size - * of 'long' the conversion function to use is strtol(). +#include "sendf.h" + +/** + * The client writer type "cw-pause" that buffers writes for + * paused transfer writes. */ +extern const struct Curl_cwtype Curl_cwt_pause; -typedef enum { - CURL_OFFT_OK, /* parsed fine */ - CURL_OFFT_FLOW, /* over or underflow */ - CURL_OFFT_INVAL /* nothing was parsed */ -} CURLofft; +CURLcode Curl_cw_pause_flush(struct Curl_easy *data); -CURLofft curlx_strtoofft(const char *str, char **endp, int base, - curl_off_t *num); -#endif /* HEADER_CURL_STRTOOFFT_H */ +#endif /* HEADER_CURL_CW_PAUSE_H */ diff --git a/Utilities/cmcurl/lib/dict.c b/Utilities/cmcurl/lib/dict.c index 143b76f519..637f349e70 100644 --- a/Utilities/cmcurl/lib/dict.c +++ b/Utilities/cmcurl/lib/dict.c @@ -65,6 +65,15 @@ /* The last #include file should be: */ #include "memdebug.h" + +#define DICT_MATCH "/MATCH:" +#define DICT_MATCH2 "/M:" +#define DICT_MATCH3 "/FIND:" +#define DICT_DEFINE "/DEFINE:" +#define DICT_DEFINE2 "/D:" +#define DICT_DEFINE3 "/LOOKUP:" + + /* * Forward declarations. */ @@ -106,7 +115,7 @@ static char *unescape_word(const char *input) struct dynbuf out; const char *ptr; CURLcode result = CURLE_OK; - Curl_dyn_init(&out, DYN_DICT_WORD); + curlx_dyn_init(&out, DYN_DICT_WORD); /* According to RFC2229 section 2.2, these letters need to be escaped with \[letter] */ @@ -114,13 +123,13 @@ static char *unescape_word(const char *input) char ch = *ptr; if((ch <= 32) || (ch == 127) || (ch == '\'') || (ch == '\"') || (ch == '\\')) - result = Curl_dyn_addn(&out, "\\", 1); + result = curlx_dyn_addn(&out, "\\", 1); if(!result) - result = Curl_dyn_addn(&out, ptr, 1); + result = curlx_dyn_addn(&out, ptr, 1); if(result) return NULL; } - return Curl_dyn_ptr(&out); + return curlx_dyn_ptr(&out); } /* sendf() sends formatted data to the server */ @@ -212,16 +221,8 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) if(!word || (*word == (char)0)) { infof(data, "lookup word is missing"); - word = (char *)"default"; } - if(!database || (*database == (char)0)) { - database = (char *)"!"; - } - if(!strategy || (*strategy == (char)0)) { - strategy = (char *)"."; - } - - eword = unescape_word(word); + eword = unescape_word((!word || (*word == (char)0)) ? "default" : word); if(!eword) { result = CURLE_OUT_OF_MEMORY; goto error; @@ -234,8 +235,8 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) "%s " /* strategy */ "%s\r\n" /* word */ "QUIT\r\n", - database, - strategy, + (!database || (*database == (char)0)) ? "!" : database, + (!strategy || (*strategy == (char)0)) ? "." : strategy, eword); if(result) { @@ -263,13 +264,8 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) if(!word || (*word == (char)0)) { infof(data, "lookup word is missing"); - word = (char *)"default"; } - if(!database || (*database == (char)0)) { - database = (char *)"!"; - } - - eword = unescape_word(word); + eword = unescape_word((!word || (*word == (char)0)) ? "default" : word); if(!eword) { result = CURLE_OUT_OF_MEMORY; goto error; @@ -281,7 +277,7 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) "%s " /* database */ "%s\r\n" /* word */ "QUIT\r\n", - database, + (!database || (*database == (char)0)) ? "!" : database, eword); if(result) { diff --git a/Utilities/cmcurl/lib/dllmain.c b/Utilities/cmcurl/lib/dllmain.c index 41e97b37eb..33076e0571 100644 --- a/Utilities/cmcurl/lib/dllmain.c +++ b/Utilities/cmcurl/lib/dllmain.c @@ -28,22 +28,13 @@ #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) +/* DllMain() must only be defined for Windows DLL builds. */ +#if defined(_WIN32) && !defined(CURL_STATICLIB) #if defined(USE_OPENSSL) && \ !defined(OPENSSL_IS_AWSLC) && \ diff --git a/Utilities/cmcurl/lib/doh.c b/Utilities/cmcurl/lib/doh.c index 617a761f97..9f408402a0 100644 --- a/Utilities/cmcurl/lib/doh.c +++ b/Utilities/cmcurl/lib/doh.c @@ -34,21 +34,20 @@ #include "multiif.h" #include "url.h" #include "share.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "connect.h" #include "strdup.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" +#include "escape.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" -#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[]={ "", @@ -74,10 +73,6 @@ static const char *doh_strerror(DOHcode code) return "bad error code"; } -struct curl_trc_feat Curl_doh_trc = { - "DoH", - CURL_LOG_LVL_NONE, -}; #endif /* !CURL_DISABLE_VERBOSE_STRINGS */ /* @unittest 1655 @@ -120,7 +115,7 @@ UNITTEST DOHcode doh_req_encode(const char *host, if(host[hostlen-1]!='.') expected_len++; - if(expected_len > (256 + 16)) /* RFCs 1034, 1035 */ + if(expected_len > DOH_MAX_DNSREQ_SIZE) return DOH_DNS_NAME_TOO_LONG; if(len < expected_len) @@ -180,18 +175,25 @@ UNITTEST DOHcode doh_req_encode(const char *host, } static size_t -doh_write_cb(char *contents, size_t size, size_t nmemb, void *userp) +doh_probe_write_cb(char *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; - struct dynbuf *mem = (struct dynbuf *)userp; + struct Curl_easy *data = userp; + struct doh_request *doh_req = Curl_meta_get(data, CURL_EZM_DOH_PROBE); + if(!doh_req) + return CURL_WRITEFUNC_ERROR; - if(Curl_dyn_addn(mem, contents, realsize)) + if(curlx_dyn_addn(&doh_req->resp_body, contents, realsize)) return 0; return realsize; } #if defined(USE_HTTPSRR) && defined(DEBUGBUILD) + +/* doh_print_buf truncates if the hex string will be more than this */ +#define LOCAL_PB_HEXMAX 400 + static void doh_print_buf(struct Curl_easy *data, const char *prefix, unsigned char *buf, size_t len) @@ -211,22 +213,45 @@ static void doh_print_buf(struct Curl_easy *data, } #endif -/* called from multi.c when this DoH transfer is complete */ -static int doh_done(struct Curl_easy *doh, CURLcode result) +/* called from multi when a sub transfer, e.g. doh probe, is done. + * This looks up the the probe response at its meta CURL_EZM_DOH_PROBE + * and copies the response body over to the struct at the master's + * meta at CURL_EZM_DOH_MASTER. */ +static void doh_probe_done(struct Curl_easy *data, + struct Curl_easy *doh, CURLcode result) { - struct Curl_easy *data; /* the transfer that asked for the DoH probe */ + struct doh_probes *dohp = data->state.async.doh; + DEBUGASSERT(dohp); + if(dohp) { + struct doh_request *doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE); + int i; + + for(i = 0; i < DOH_SLOT_COUNT; ++i) { + if(dohp->probe_resp[i].probe_mid == doh->mid) + break; + } + if(i >= DOH_SLOT_COUNT) { + failf(data, "unknown sub request done"); + return; + } - 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); + dohp->probe_resp[i].result = result; + /* We expect either the meta data still to exist or the sub request + * to have already failed. */ + DEBUGASSERT(doh_req || result); + if(doh_req) { + if(!result) { + dohp->probe_resp[i].dnstype = doh_req->dnstype; + result = curlx_dyn_addn(&dohp->probe_resp[i].body, + curlx_dyn_ptr(&doh_req->resp_body), + curlx_dyn_len(&doh_req->resp_body)); + curlx_dyn_free(&doh_req->resp_body); + } + Curl_meta_remove(doh, CURL_EZM_DOH_PROBE); + } + if(result) infof(doh, "DoH request %s", curl_easy_strerror(result)); @@ -235,7 +260,18 @@ static int doh_done(struct Curl_easy *doh, CURLcode result) Curl_expire(data, 0, EXPIRE_RUN_NOW); } } - return 0; +} + +static void doh_probe_dtor(void *key, size_t klen, void *e) +{ + (void)key; + (void)klen; + if(e) { + struct doh_request *doh_req = e; + curl_slist_free_all(doh_req->req_hds); + curlx_dyn_free(&doh_req->resp_body); + free(e); + } } #define ERROR_CHECK_SETOPT(x,y) \ @@ -247,30 +283,48 @@ static int doh_done(struct Curl_easy *doh, CURLcode result) goto error; \ } while(0) -static CURLcode doh_run_probe(struct Curl_easy *data, - struct doh_probe *p, DNStype dnstype, +static CURLcode doh_probe_run(struct Curl_easy *data, + DNStype dnstype, const char *host, const char *url, CURLM *multi, - struct curl_slist *headers) + unsigned int *pmid) { struct Curl_easy *doh = NULL; CURLcode result = CURLE_OK; timediff_t timeout_ms; - DOHcode d = doh_req_encode(host, dnstype, p->req_body, sizeof(p->req_body), - &p->req_body_len); + struct doh_request *doh_req; + DOHcode d; + + *pmid = UINT_MAX; + + doh_req = calloc(1, sizeof(*doh_req)); + if(!doh_req) + return CURLE_OUT_OF_MEMORY; + doh_req->dnstype = dnstype; + curlx_dyn_init(&doh_req->resp_body, DYN_DOH_RESPONSE); + + d = doh_req_encode(host, dnstype, doh_req->req_body, + sizeof(doh_req->req_body), + &doh_req->req_body_len); if(d) { failf(data, "Failed to encode DoH packet [%d]", d); - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto error; } - p->dnstype = dnstype; - Curl_dyn_init(&p->resp_body, DYN_DOH_RESPONSE); - timeout_ms = Curl_timeleft(data, NULL, TRUE); if(timeout_ms <= 0) { result = CURLE_OPERATION_TIMEDOUT; goto error; } + + doh_req->req_hds = + curl_slist_append(NULL, "Content-Type: application/dns-message"); + if(!doh_req->req_hds) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } + /* Curl_open() is the internal version of curl_easy_init() */ result = Curl_open(&doh); if(result) @@ -278,33 +332,32 @@ static CURLcode doh_run_probe(struct Curl_easy *data, /* 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; + doh->state.feat = &Curl_trc_feat_dns; #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); + ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_probe_write_cb); + ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, doh); + ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, doh_req->req_body); + ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)doh_req->req_body_len); + ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, doh_req->req_hds); #ifdef USE_HTTP2 ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); ERROR_CHECK_SETOPT(CURLOPT_PIPEWAIT, 1L); #endif #ifndef DEBUGBUILD /* enforce HTTPS if not debug */ - ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS); + ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, (long)CURLPROTO_HTTPS); #else /* in debug mode, also allow http */ - ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS); + ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, (long)CURLPROTO_HTTP|CURLPROTO_HTTPS); #endif 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)) + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L); if(data->set.no_signal) ERROR_CHECK_SETOPT(CURLOPT_NOSIGNAL, 1L); @@ -357,26 +410,16 @@ static CURLcode doh_run_probe(struct Curl_easy *data, 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, + (long)data->set.ssl.primary.ssl_options); - (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, mask); - } + doh->state.internal = TRUE; + doh->master_mid = data->mid; /* master transfer of this one */ - doh->set.fmultidone = doh_done; - doh->set.dohfor_mid = data->mid; /* for which transfer this is done */ + result = Curl_meta_set(doh, CURL_EZM_DOH_PROBE, doh_req, doh_probe_dtor); + doh_req = NULL; /* call took ownership */ + if(result) + goto error; /* 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 @@ -387,12 +430,13 @@ static CURLcode doh_run_probe(struct Curl_easy *data, if(curl_multi_add_handle(multi, doh)) goto error; - p->easy_mid = doh->mid; + *pmid = doh->mid; return CURLE_OK; error: Curl_close(&doh); - p->easy_mid = -1; + if(doh_req) + doh_probe_dtor(NULL, 0, doh_req); return result; } @@ -404,51 +448,59 @@ error: struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, const char *hostname, int port, + int ip_version, int *waitp) { CURLcode result = CURLE_OK; - struct doh_probes *dohp; + struct doh_probes *dohp = NULL; struct connectdata *conn = data->conn; size_t i; - *waitp = FALSE; - (void)hostname; - (void)port; - DEBUGASSERT(!data->req.doh); DEBUGASSERT(conn); + DEBUGASSERT(!data->state.async.doh); + if(data->state.async.doh) + Curl_doh_cleanup(data); + + data->state.async.done = FALSE; + data->state.async.port = port; + data->state.async.ip_version = ip_version; + data->state.async.hostname = strdup(hostname); + if(!data->state.async.hostname) + return NULL; /* start clean, consider allocating this struct on demand */ - dohp = data->req.doh = calloc(1, sizeof(struct doh_probes)); + data->state.async.doh = dohp = calloc(1, sizeof(struct doh_probes)); if(!dohp) return NULL; for(i = 0; i < DOH_SLOT_COUNT; ++i) { - dohp->probe[i].easy_mid = -1; + dohp->probe_resp[i].probe_mid = UINT_MAX; + curlx_dyn_init(&dohp->probe_resp[i].body, DYN_DOH_RESPONSE); } conn->bits.doh = TRUE; - dohp->host = hostname; - dohp->port = port; - dohp->req_hds = - curl_slist_append(NULL, - "Content-Type: application/dns-message"); - if(!dohp->req_hds) - goto error; + dohp->host = data->state.async.hostname; + dohp->port = data->state.async.port; + /* We are making sub easy handles and want to be called back when + * one is done. */ + data->sub_xfer_done = doh_probe_done; /* create IPv4 DoH request */ - result = doh_run_probe(data, &dohp->probe[DOH_SLOT_IPV4], - DNS_TYPE_A, hostname, data->set.str[STRING_DOH], - data->multi, dohp->req_hds); + result = doh_probe_run(data, DNS_TYPE_A, + hostname, data->set.str[STRING_DOH], + data->multi, + &dohp->probe_resp[DOH_SLOT_IPV4].probe_mid); if(result) goto error; dohp->pending++; #ifdef USE_IPV6 - if((conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { + if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { /* create IPv6 DoH request */ - result = doh_run_probe(data, &dohp->probe[DOH_SLOT_IPV6], - DNS_TYPE_AAAA, hostname, data->set.str[STRING_DOH], - data->multi, dohp->req_hds); + result = doh_probe_run(data, DNS_TYPE_AAAA, + hostname, data->set.str[STRING_DOH], + data->multi, + &dohp->probe_resp[DOH_SLOT_IPV6].probe_mid); if(result) goto error; dohp->pending++; @@ -464,10 +516,10 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, if(!qname) goto error; } - result = doh_run_probe(data, &dohp->probe[DOH_SLOT_HTTPS_RR], - DNS_TYPE_HTTPS, + result = doh_probe_run(data, DNS_TYPE_HTTPS, qname ? qname : hostname, data->set.str[STRING_DOH], - data->multi, dohp->req_hds); + data->multi, + &dohp->probe_resp[DOH_SLOT_HTTPS_RR].probe_mid); free(qname); if(result) goto error; @@ -598,14 +650,14 @@ static DOHcode doh_store_cname(const unsigned char *doh, size_t dohlen, index++; if(length) { - if(Curl_dyn_len(c)) { - if(Curl_dyn_addn(c, STRCONST("."))) + if(curlx_dyn_len(c)) { + if(curlx_dyn_addn(c, STRCONST("."))) return DOH_OUT_OF_MEM; } if((index + length) > dohlen) return DOH_DNS_BAD_LABEL; - if(Curl_dyn_addn(c, &doh[index], length)) + if(curlx_dyn_addn(c, &doh[index], length)) return DOH_OUT_OF_MEM; index += length; } @@ -669,7 +721,7 @@ UNITTEST void de_init(struct dohentry *de) memset(de, 0, sizeof(*de)); de->ttl = INT_MAX; for(i = 0; i < DOH_MAX_CNAME; i++) - Curl_dyn_init(&de->cname[i], DYN_DOH_CNAME); + curlx_dyn_init(&de->cname[i], DYN_DOH_CNAME); } @@ -856,7 +908,7 @@ static void doh_show(struct Curl_easy *data, } #endif for(i = 0; i < d->numcname; i++) { - infof(data, "CNAME: %s", Curl_dyn_ptr(&d->cname[i])); + infof(data, "CNAME: %s", curlx_dyn_ptr(&d->cname[i])); } } #else @@ -869,7 +921,7 @@ static void doh_show(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 - * a IPv6 stack, but usable also for IPv4, all hosts and environments. + * an 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 @@ -993,7 +1045,7 @@ UNITTEST void de_cleanup(struct dohentry *d) { int i = 0; for(i = 0; i < d->numcname; i++) { - Curl_dyn_free(&d->cname[i]); + curlx_dyn_free(&d->cname[i]); } #ifdef USE_HTTPSRR for(i = 0; i < d->numhttps_rrs; i++) @@ -1017,87 +1069,66 @@ UNITTEST void de_cleanup(struct dohentry *d) * 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) +static CURLcode doh_decode_rdata_name(const unsigned char **buf, + size_t *remaining, char **dnsname) { - unsigned char *cp = NULL; - int rem = 0; + const unsigned char *cp = NULL; + size_t rem = 0; unsigned char clen = 0; /* chunk len */ struct dynbuf thename; DEBUGASSERT(buf && remaining && dnsname); - if(!buf || !remaining || !dnsname) + if(!buf || !remaining || !dnsname || !*remaining) 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); + curlx_dyn_init(&thename, CURL_MAXLEN_host_name); + rem = *remaining; cp = *buf; clen = *cp++; if(clen == 0) { /* special case - return "." as name */ - if(Curl_dyn_addn(&thename, ".", 1)) + if(curlx_dyn_addn(&thename, ".", 1)) return CURLE_OUT_OF_MEMORY; } while(clen) { if(clen >= rem) { - Curl_dyn_free(&thename); + curlx_dyn_free(&thename); return CURLE_OUT_OF_MEMORY; } - if(Curl_dyn_addn(&thename, cp, clen) || - Curl_dyn_addn(&thename, ".", 1)) + if(curlx_dyn_addn(&thename, cp, clen) || + curlx_dyn_addn(&thename, ".", 1)) return CURLE_TOO_LARGE; cp += clen; rem -= (clen + 1); if(rem <= 0) { - Curl_dyn_free(&thename); + curlx_dyn_free(&thename); return CURLE_OUT_OF_MEMORY; } clen = *cp++; } *buf = cp; *remaining = rem - 1; - *dnsname = Curl_dyn_ptr(&thename); + *dnsname = curlx_dyn_ptr(&thename); return CURLE_OK; } -#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); - unsigned char aval[MAX_HTTPSRR_ALPNS] = { 0 }; - static const char expected[2] = { ALPN_h2, ALPN_none }; +UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, + const unsigned char *cp, size_t len, + struct Curl_https_rrinfo **hrr); - if(Curl_httpsrr_decode_alpn(example, example_len, aval) != CURLE_OK) - return CURLE_BAD_CONTENT_ENCODING; - if(memcmp(aval, expected, sizeof(expected))) - return CURLE_BAD_CONTENT_ENCODING; - return CURLE_OK; -} -#endif - -static CURLcode doh_resp_decode_httpsrr(unsigned char *cp, size_t len, - struct Curl_https_rrinfo **hrr) +/* @unittest 1658 */ +UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, + const unsigned char *cp, size_t len, + struct Curl_https_rrinfo **hrr) { uint16_t pcode = 0, plen = 0; + uint32_t expected_min_pcode = 0; struct Curl_https_rrinfo *lhrr = NULL; char *dnsname = NULL; + CURLcode result = CURLE_OUT_OF_MEMORY; + size_t olen; -#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 + *hrr = NULL; if(len <= 2) return CURLE_BAD_FUNCTION_ARGUMENT; lhrr = calloc(1, sizeof(struct Curl_https_rrinfo)); @@ -1109,68 +1140,43 @@ static CURLcode doh_resp_decode_httpsrr(unsigned char *cp, size_t len, if(doh_decode_rdata_name(&cp, &len, &dnsname) != CURLE_OK) goto err; lhrr->target = dnsname; + if(Curl_junkscan(dnsname, &olen, FALSE)) { + /* unacceptable hostname content */ + result = CURLE_WEIRD_SERVER_REPLY; + goto err; + } lhrr->port = -1; /* until set */ while(len >= 4) { pcode = doh_get16bit(cp, 0); plen = doh_get16bit(cp, 2); cp += 4; len -= 4; - switch(pcode) { - case HTTPS_RR_CODE_ALPN: - if(Curl_httpsrr_decode_alpn(cp, plen, lhrr->alpns) != CURLE_OK) - goto err; - break; - case HTTPS_RR_CODE_NO_DEF_ALPN: - lhrr->no_def_alpn = TRUE; - break; - case 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; - break; - case 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; - break; - case 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; - break; - case HTTPS_RR_CODE_PORT: - lhrr->port = doh_get16bit(cp, 0); - break; - default: - break; - } - if(plen > 0 && plen <= len) { - cp += plen; - len -= plen; + if(pcode < expected_min_pcode || plen > len) { + result = CURLE_WEIRD_SERVER_REPLY; + goto err; } + result = Curl_httpsrr_set(data, lhrr, pcode, cp, plen); + if(result) + goto err; + cp += plen; + len -= plen; + expected_min_pcode = pcode + 1; } DEBUGASSERT(!len); *hrr = lhrr; return CURLE_OK; err: - Curl_safefree(lhrr->target); - Curl_safefree(lhrr->echconfiglist); + Curl_httpsrr_cleanup(lhrr); Curl_safefree(lhrr); - return CURLE_OUT_OF_MEMORY; + return result; } -# ifdef DEBUGBUILD -static void doh_print_httpsrr(struct Curl_easy *data, - struct Curl_https_rrinfo *hrr) +#ifdef DEBUGBUILD +UNITTEST void doh_print_httpsrr(struct Curl_easy *data, + struct Curl_https_rrinfo *hrr); + +UNITTEST void doh_print_httpsrr(struct Curl_easy *data, + struct Curl_https_rrinfo *hrr) { DEBUGASSERT(hrr); infof(data, "HTTPS RR: priority %d, target: %s", @@ -1211,14 +1217,14 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, struct Curl_dns_entry **dnsp) { CURLcode result; - struct doh_probes *dohp = data->req.doh; + struct doh_probes *dohp = data->state.async.doh; *dnsp = NULL; /* defaults to no response */ if(!dohp) return CURLE_OUT_OF_MEMORY; - 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); + if(dohp->probe_resp[DOH_SLOT_IPV4].probe_mid == UINT_MAX && + dohp->probe_resp[DOH_SLOT_IPV6].probe_mid == UINT_MAX) { + failf(data, "Could not DoH-resolve: %s", dohp->host); return CONN_IS_PROXIED(data->conn) ? CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST; } @@ -1227,19 +1233,21 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, struct dohentry de; int slot; + /* Clear any result the might still be there */ + Curl_resolv_unlink(data, &data->state.async.dns); + memset(rc, 0, sizeof(rc)); /* remove DoH handles from multi handle and close them */ Curl_doh_close(data); /* parse the responses, create the struct and return it! */ de_init(&de); for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { - struct doh_probe *p = &dohp->probe[slot]; + struct doh_response *p = &dohp->probe_resp[slot]; if(!p->dnstype) continue; - rc[slot] = doh_resp_decode(Curl_dyn_uptr(&p->resp_body), - Curl_dyn_len(&p->resp_body), + rc[slot] = doh_resp_decode(curlx_dyn_uptr(&p->body), + curlx_dyn_len(&p->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]), @@ -1255,8 +1263,8 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, struct Curl_addrinfo *ai; - if(Curl_trc_ft_is_verbose(data, &Curl_doh_trc)) { - infof(data, "[DoH] hostname: %s", dohp->host); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) { + CURL_TRC_DNS(data, "hostname: %s", dohp->host); doh_show(data, &de); } @@ -1266,45 +1274,35 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, 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, FALSE); - - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); - - if(!dns) { - /* returned failure, bail out nicely */ - Curl_freeaddrinfo(ai); - } - else { + /* we got a response, create a dns entry. */ + dns = Curl_dnscache_mk_entry(data, ai, dohp->host, 0, dohp->port, FALSE); + if(dns) { + /* Now add and HTTPSRR information if we have */ +#ifdef USE_HTTPSRR + if(de.numhttps_rrs > 0 && result == CURLE_OK) { + struct Curl_https_rrinfo *hrr = NULL; + result = doh_resp_decode_httpsrr(data, 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 + dns->hinfo = hrr; + } +#endif + /* and add the entry to the cache */ data->state.async.dns = dns; - *dnsp = dns; - result = CURLE_OK; /* address resolution OK */ + result = Curl_dnscache_add(data, dns); + *dnsp = data->state.async.dns; } } /* 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 */ + data->state.async.done = TRUE; de_cleanup(&de); Curl_doh_cleanup(data); return result; @@ -1317,41 +1315,43 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, void Curl_doh_close(struct Curl_easy *data) { - struct doh_probes *doh = data->req.doh; + struct doh_probes *doh = data->state.async.doh; if(doh && data->multi) { struct Curl_easy *probe_data; - curl_off_t mid; + unsigned int mid; size_t slot; for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { - mid = doh->probe[slot].easy_mid; - if(mid < 0) + mid = doh->probe_resp[slot].probe_mid; + if(mid == UINT_MAX) continue; - doh->probe[slot].easy_mid = -1; + doh->probe_resp[slot].probe_mid = UINT_MAX; /* 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) : + probe_data = data->multi ? Curl_multi_get_easy(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)); + DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%u not found!", + doh->probe_resp[slot].probe_mid)); continue; } /* data->multi might already be reset at this time */ curl_multi_remove_handle(data->multi, probe_data); Curl_close(&probe_data); } + data->sub_xfer_done = NULL; } } void Curl_doh_cleanup(struct Curl_easy *data) { - struct doh_probes *doh = data->req.doh; - if(doh) { + struct doh_probes *dohp = data->state.async.doh; + if(dohp) { + int i; Curl_doh_close(data); - curl_slist_free_all(doh->req_hds); - data->req.doh->req_hds = NULL; - Curl_safefree(data->req.doh); + for(i = 0; i < DOH_SLOT_COUNT; ++i) { + curlx_dyn_free(&dohp->probe_resp[i].body); + } + Curl_safefree(data->state.async.doh); } } diff --git a/Utilities/cmcurl/lib/doh.h b/Utilities/cmcurl/lib/doh.h index 53644863e6..9146f53580 100644 --- a/Utilities/cmcurl/lib/doh.h +++ b/Utilities/cmcurl/lib/doh.h @@ -59,15 +59,6 @@ typedef enum { DNS_TYPE_HTTPS = 65 } DNStype; -/* one of these for each DoH request */ -struct doh_probe { - curl_off_t easy_mid; /* multi id of easy handle doing the lookup */ - DNStype dnstype; - unsigned char req_body[512]; - size_t req_body_len; - struct dynbuf resp_body; -}; - enum doh_slot_num { /* Explicit values for first two symbols so as to match hard-coded * constants in existing code @@ -89,9 +80,32 @@ enum doh_slot_num { DOH_SLOT_COUNT }; -struct doh_probes { +#define CURL_EZM_DOH_PROBE "ezm:doh-p" + +/* the largest one we can make, based on RFCs 1034, 1035 */ +#define DOH_MAX_DNSREQ_SIZE (256 + 16) + +/* each DoH probe request has this + * as easy meta for CURL_EZM_DOH_PROBE */ +struct doh_request { + unsigned char req_body[DOH_MAX_DNSREQ_SIZE]; struct curl_slist *req_hds; - struct doh_probe probe[DOH_SLOT_COUNT]; + struct dynbuf resp_body; + size_t req_body_len; + DNStype dnstype; +}; + +struct doh_response { + unsigned int probe_mid; + struct dynbuf body; + DNStype dnstype; + CURLcode result; +}; + +/* each transfer firing off DoH requests has this + * as easy meta for CURL_EZM_DOH_MASTER */ +struct doh_probes { + struct doh_response probe_resp[DOH_SLOT_COUNT]; unsigned int pending; /* still outstanding probes */ int port; const char *host; @@ -105,6 +119,7 @@ struct doh_probes { struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, const char *hostname, int port, + int ip_version, int *waitp); CURLcode Curl_doh_is_resolved(struct Curl_easy *data, @@ -167,10 +182,8 @@ 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(a,b,c,d,e) NULL #define Curl_doh_is_resolved(x,y) CURLE_COULDNT_RESOLVE_HOST #endif diff --git a/Utilities/cmcurl/lib/dynhds.c b/Utilities/cmcurl/lib/dynhds.c index 2c92ca63fe..5c52d7411a 100644 --- a/Utilities/cmcurl/lib/dynhds.c +++ b/Utilities/cmcurl/lib/dynhds.c @@ -359,9 +359,10 @@ CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf) return result; for(i = 0; i < dynhds->hds_len; ++i) { - result = Curl_dyn_addf(dbuf, "%.*s: %.*s\r\n", - (int)dynhds->hds[i]->namelen, dynhds->hds[i]->name, - (int)dynhds->hds[i]->valuelen, dynhds->hds[i]->value); + result = curlx_dyn_addf(dbuf, "%.*s: %.*s\r\n", + (int)dynhds->hds[i]->namelen, dynhds->hds[i]->name, + (int)dynhds->hds[i]->valuelen, + dynhds->hds[i]->value); if(result) break; } diff --git a/Utilities/cmcurl/lib/dynhds.h b/Utilities/cmcurl/lib/dynhds.h index fb162a30de..e533dcc369 100644 --- a/Utilities/cmcurl/lib/dynhds.h +++ b/Utilities/cmcurl/lib/dynhds.h @@ -26,13 +26,13 @@ #include "curl_setup.h" #include -#include "dynbuf.h" +#include "curlx/dynbuf.h" struct dynbuf; /** * A single header entry. - * `name` and `value` are non-NULL and always NUL terminated. + * `name` and `value` are non-NULL and always null-terminated. */ struct dynhds_entry { char *name; @@ -113,7 +113,7 @@ size_t Curl_dynhds_count_name(struct dynhds *dynhds, const char *name, size_t namelen); /** - * Return how often the given 0-terminated name appears in `dynhds`. + * Return how often the given null-terminated name appears in `dynhds`. * Names are case-insensitive. */ size_t Curl_dynhds_ccount_name(struct dynhds *dynhds, const char *name); @@ -156,14 +156,14 @@ CURLcode Curl_dynhds_cadd(struct dynhds *dynhds, /** * 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 + * may contain a delimiting CRLF or just LF. Any characters after * that will be ignored. */ CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *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 + * may contain a delimiting CRLF or just LF. Any characters after * that will be ignored. */ CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds, diff --git a/Utilities/cmcurl/lib/easy.c b/Utilities/cmcurl/lib/easy.c index 1573a9d929..3f86786252 100644 --- a/Utilities/cmcurl/lib/easy.c +++ b/Utilities/cmcurl/lib/easy.c @@ -49,6 +49,7 @@ #include "transfer.h" #include "vtls/vtls.h" #include "vtls/vtls_scache.h" +#include "vquic/vquic.h" #include "url.h" #include "getinfo.h" #include "hostip.h" @@ -65,14 +66,14 @@ #include "mime.h" #include "amigaos.h" #include "macos.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "sigpipe.h" #include "vssh/ssh.h" #include "setopt.h" #include "http_digest.h" #include "system_win32.h" #include "http2.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "altsvc.h" #include "hsts.h" @@ -105,7 +106,7 @@ static curl_simple_lock s_lock = CURL_SIMPLE_LOCK_INIT; * ways, but at this point it must be defined as the system-supplied strdup * so the callback pointer is initialized correctly. */ -#if defined(_WIN32_WCE) +#if defined(UNDER_CE) #define system_strdup _strdup #elif !defined(HAVE_STRDUP) #define system_strdup Curl_strdup @@ -170,6 +171,11 @@ static CURLcode global_init(long flags, bool memoryfuncs) goto fail; } + if(!Curl_vquic_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_vquic_init failed\n")); + goto fail; + } + if(Curl_win32_init(flags)) { DEBUGF(fprintf(stderr, "Error: win32_init failed\n")); goto fail; @@ -185,7 +191,7 @@ static CURLcode global_init(long flags, bool memoryfuncs) goto fail; } - if(Curl_resolver_global_init()) { + if(Curl_async_global_init()) { DEBUGF(fprintf(stderr, "Error: resolver_global_init failed\n")); goto fail; } @@ -288,7 +294,7 @@ void curl_global_cleanup(void) } Curl_ssl_cleanup(); - Curl_resolver_global_cleanup(); + Curl_async_global_cleanup(); #ifdef _WIN32 Curl_win32_cleanup(easy_init_flags); @@ -542,12 +548,34 @@ static void events_setup(struct Curl_multi *multi, struct events *ev) curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, ev); } +/* populate_fds() + * + * populate the fds[] array + */ +static unsigned int populate_fds(struct pollfd *fds, struct events *ev) +{ + unsigned int numfds = 0; + struct pollfd *f; + struct socketmonitor *m; + + f = &fds[0]; + for(m = ev->list; m; m = m->next) { + f->fd = m->socket.fd; + f->events = m->socket.events; + f->revents = 0; +#if DEBUG_EV_POLL + fprintf(stderr, "poll() %d check socket %d\n", numfds, f->fd); +#endif + f++; + numfds++; + } + return numfds; +} /* wait_or_timeout() * * waits for activity on any of the given sockets, or the timeout to trigger. */ - static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) { bool done = FALSE; @@ -556,37 +584,22 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) while(!done) { CURLMsg *msg; - struct socketmonitor *m; - struct pollfd *f; struct pollfd fds[4]; - int numfds = 0; int pollrc; - int i; struct curltime before; - - /* 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; -#if DEBUG_EV_POLL - fprintf(stderr, "poll() %d check socket %d\n", numfds, f->fd); -#endif - f++; - numfds++; - } + const unsigned int numfds = populate_fds(fds, ev); /* get the time stamp to use to figure out how long poll takes */ - before = Curl_now(); + before = curlx_now(); if(numfds) { /* wait for activity or timeout */ #if DEBUG_EV_POLL - fprintf(stderr, "poll(numfds=%d, timeout=%ldms)\n", numfds, ev->ms); + fprintf(stderr, "poll(numfds=%u, timeout=%ldms)\n", numfds, ev->ms); #endif - pollrc = Curl_poll(fds, (unsigned int)numfds, ev->ms); + pollrc = Curl_poll(fds, numfds, ev->ms); #if DEBUG_EV_POLL - fprintf(stderr, "poll(numfds=%d, timeout=%ldms) -> %d\n", + fprintf(stderr, "poll(numfds=%u, timeout=%ldms) -> %d\n", numfds, ev->ms, pollrc); #endif if(pollrc < 0) @@ -612,20 +625,15 @@ 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 */ + unsigned int i; for(i = 0; i < numfds; i++) { if(fds[i].revents) { /* socket activity, tell libcurl */ int act = poll2cselect(fds[i].revents); /* convert */ /* sending infof "randomly" to the first easy handle */ - infof(data, "call curl_multi_socket_action(socket " + infof(multi->admin, "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); @@ -637,7 +645,7 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) /* 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(Curl_now(), before); + timediff_t timediff = curlx_timediff(curlx_now(), before); if(timediff > 0) { #if DEBUG_EV_POLL fprintf(stderr, "poll timeout %ldms not updated, decrease by " @@ -770,7 +778,7 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) Curl_detach_connection(data); s = Curl_getconnectinfo(data, &c); if((s != CURL_SOCKET_BAD) && c) { - Curl_cpool_disconnect(data, c, TRUE); + Curl_conn_terminate(data, c, TRUE); } DEBUGASSERT(!data->conn); } @@ -780,7 +788,7 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) else { /* this multi handle will only ever have a single easy handle attached to it, so make it use minimal hash sizes */ - multi = Curl_multi_handle(1, 3, 7, 3); + multi = Curl_multi_handle(16, 1, 3, 7, 3); if(!multi) return CURLE_OUT_OF_MEMORY; } @@ -930,6 +938,15 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) return result; } +static void dupeasy_meta_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); +} + /* * curl_easy_duphandle() is an external interface to allow duplication of a * given input easy handle. The returned handle will be a new working handle @@ -949,19 +966,28 @@ CURL *curl_easy_duphandle(CURL *d) */ outcurl->set.buffer_size = data->set.buffer_size; - /* copy all userdefined values */ - if(dupset(outcurl, data)) - goto fail; - - Curl_dyn_init(&outcurl->state.headerb, CURL_MAX_HTTP_HEADER); + Curl_hash_init(&outcurl->meta_hash, 23, + Curl_hash_str, curlx_str_key_compare, dupeasy_meta_freeentry); + curlx_dyn_init(&outcurl->state.headerb, CURL_MAX_HTTP_HEADER); Curl_netrc_init(&outcurl->state.netrc); /* the connection pool is setup on demand */ outcurl->state.lastconnect_id = -1; outcurl->state.recent_conn_id = -1; outcurl->id = -1; + outcurl->mid = UINT_MAX; + outcurl->master_mid = UINT_MAX; - outcurl->progress.flags = data->progress.flags; +#ifndef CURL_DISABLE_HTTP + Curl_llist_init(&outcurl->state.httphdrs, NULL); +#endif + Curl_initinfo(outcurl); + + /* copy all userdefined values */ + if(dupset(outcurl, data)) + goto fail; + + outcurl->progress.hide = data->progress.hide; outcurl->progress.callback = data->progress.callback; #ifndef CURL_DISABLE_COOKIES @@ -1024,40 +1050,6 @@ CURL *curl_easy_duphandle(CURL *d) } #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 - { - CURLcode rc; - - rc = Curl_set_dns_servers(outcurl, data->set.str[STRING_DNS_SERVERS]); - if(rc && rc != CURLE_NOT_BUILT_IN) - goto fail; - - rc = Curl_set_dns_interface(outcurl, data->set.str[STRING_DNS_INTERFACE]); - if(rc && rc != CURLE_NOT_BUILT_IN) - goto fail; - - rc = Curl_set_dns_local_ip4(outcurl, data->set.str[STRING_DNS_LOCAL_IP4]); - if(rc && rc != CURLE_NOT_BUILT_IN) - goto fail; - - rc = Curl_set_dns_local_ip6(outcurl, data->set.str[STRING_DNS_LOCAL_IP6]); - if(rc && rc != CURLE_NOT_BUILT_IN) - 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; /* we reach this point and thus we are OK */ @@ -1070,7 +1062,7 @@ fail: #ifndef CURL_DISABLE_COOKIES free(outcurl->cookies); #endif - Curl_dyn_free(&outcurl->state.headerb); + curlx_dyn_free(&outcurl->state.headerb); Curl_altsvc_cleanup(&outcurl->asi); Curl_hsts_cleanup(&outcurl->hsts); Curl_freeset(outcurl); @@ -1088,7 +1080,14 @@ void curl_easy_reset(CURL *d) { struct Curl_easy *data = d; Curl_req_hard_reset(&data->req, data); + Curl_hash_clean(&data->meta_hash); + /* clear all meta data */ + Curl_meta_reset(data); + /* clear any resolve data */ + Curl_async_shutdown(data); + Curl_resolv_unlink(data, &data->state.dns[0]); + Curl_resolv_unlink(data, &data->state.dns[1]); /* zero out UserDefined data: */ Curl_freeset(data); memset(&data->set, 0, sizeof(struct UserDefined)); @@ -1100,7 +1099,7 @@ void curl_easy_reset(CURL *d) /* zero out PureInfo data: */ Curl_initinfo(data); - data->progress.flags |= PGRS_HIDE; + data->progress.hide = TRUE; data->state.current_speed = -1; /* init to negative == impossible */ data->state.retrycount = 0; /* reset the retry counter */ @@ -1111,6 +1110,7 @@ void curl_easy_reset(CURL *d) #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH) Curl_http_auth_cleanup_digest(data); #endif + data->master_mid = UINT_MAX; } /* @@ -1195,10 +1195,10 @@ CURLcode curl_easy_pause(CURL *d, int action) } 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(!result && !data->state.done && keep_changed && data->multi) + /* pause/unpausing may result in multi event changes */ + if(Curl_multi_ev_assess_xfer(data->multi, data)) + result = CURLE_ABORTED_BY_CALLBACK; if(recursive) /* this might have called a callback recursively which might have set this @@ -1388,3 +1388,30 @@ CURLcode curl_easy_ssls_export(CURL *d, return CURLE_NOT_BUILT_IN; #endif } + +CURLcode Curl_meta_set(struct Curl_easy *data, const char *key, + void *meta_data, Curl_meta_dtor *meta_dtor) +{ + DEBUGASSERT(meta_data); /* never set to NULL */ + if(!Curl_hash_add2(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1, + meta_data, meta_dtor)) { + meta_dtor(CURL_UNCONST(key), strlen(key) + 1, meta_data); + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; +} + +void Curl_meta_remove(struct Curl_easy *data, const char *key) +{ + Curl_hash_delete(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1); +} + +void *Curl_meta_get(struct Curl_easy *data, const char *key) +{ + return Curl_hash_pick(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1); +} + +void Curl_meta_reset(struct Curl_easy *data) +{ + Curl_hash_clean(&data->meta_hash); +} diff --git a/Utilities/cmcurl/lib/easygetopt.c b/Utilities/cmcurl/lib/easygetopt.c index 86833bf6b9..5d30d39a49 100644 --- a/Utilities/cmcurl/lib/easygetopt.c +++ b/Utilities/cmcurl/lib/easygetopt.c @@ -29,12 +29,12 @@ #ifndef CURL_DISABLE_GETOPTIONS /* Lookups easy options at runtime */ -static struct curl_easyoption *lookup(const char *name, CURLoption id) +static const struct curl_easyoption *lookup(const char *name, CURLoption id) { DEBUGASSERT(name || id); DEBUGASSERT(!Curl_easyopts_check()); if(name || id) { - struct curl_easyoption *o = &Curl_easyopts[0]; + const struct curl_easyoption *o = &Curl_easyopts[0]; do { if(name) { if(strcasecompare(o->name, name)) diff --git a/Utilities/cmcurl/lib/easyoptions.c b/Utilities/cmcurl/lib/easyoptions.c index f2ced2cb50..03d676df0e 100644 --- a/Utilities/cmcurl/lib/easyoptions.c +++ b/Utilities/cmcurl/lib/easyoptions.c @@ -28,7 +28,7 @@ #include "easyoptions.h" /* all easy setopt options listed in alphabetical order */ -struct curl_easyoption Curl_easyopts[] = { +const struct curl_easyoption Curl_easyopts[] = { {"ABSTRACT_UNIX_SOCKET", CURLOPT_ABSTRACT_UNIX_SOCKET, CURLOT_STRING, 0}, {"ACCEPTTIMEOUT_MS", CURLOPT_ACCEPTTIMEOUT_MS, CURLOT_LONG, 0}, {"ACCEPT_ENCODING", CURLOPT_ACCEPT_ENCODING, CURLOT_STRING, 0}, @@ -317,6 +317,8 @@ struct curl_easyoption Curl_easyopts[] = { {"SSL_FALSESTART", CURLOPT_SSL_FALSESTART, CURLOT_LONG, 0}, {"SSL_OPTIONS", CURLOPT_SSL_OPTIONS, CURLOT_VALUES, 0}, {"SSL_SESSIONID_CACHE", CURLOPT_SSL_SESSIONID_CACHE, CURLOT_LONG, 0}, + {"SSL_SIGNATURE_ALGORITHMS", CURLOPT_SSL_SIGNATURE_ALGORITHMS, + CURLOT_STRING, 0}, {"SSL_VERIFYHOST", CURLOPT_SSL_VERIFYHOST, CURLOT_LONG, 0}, {"SSL_VERIFYPEER", CURLOPT_SSL_VERIFYPEER, CURLOT_LONG, 0}, {"SSL_VERIFYSTATUS", CURLOPT_SSL_VERIFYSTATUS, CURLOT_LONG, 0}, @@ -353,6 +355,7 @@ struct curl_easyoption Curl_easyopts[] = { {"UPKEEP_INTERVAL_MS", CURLOPT_UPKEEP_INTERVAL_MS, CURLOT_LONG, 0}, {"UPLOAD", CURLOPT_UPLOAD, CURLOT_LONG, 0}, {"UPLOAD_BUFFERSIZE", CURLOPT_UPLOAD_BUFFERSIZE, CURLOT_LONG, 0}, + {"UPLOAD_FLAGS", CURLOPT_UPLOAD_FLAGS, CURLOT_LONG, 0}, {"URL", CURLOPT_URL, CURLOT_STRING, 0}, {"USERAGENT", CURLOPT_USERAGENT, CURLOT_STRING, 0}, {"USERNAME", CURLOPT_USERNAME, CURLOT_STRING, 0}, @@ -377,6 +380,6 @@ struct curl_easyoption Curl_easyopts[] = { */ int Curl_easyopts_check(void) { - return (CURLOPT_LASTENTRY % 10000) != (326 + 1); + return (CURLOPT_LASTENTRY % 10000) != (328 + 1); } #endif diff --git a/Utilities/cmcurl/lib/easyoptions.h b/Utilities/cmcurl/lib/easyoptions.h index 24b4cd93ed..44b6a8280a 100644 --- a/Utilities/cmcurl/lib/easyoptions.h +++ b/Utilities/cmcurl/lib/easyoptions.h @@ -29,7 +29,7 @@ #include /* generated table with all easy options */ -extern struct curl_easyoption Curl_easyopts[]; +extern const struct curl_easyoption Curl_easyopts[]; #ifdef DEBUGBUILD int Curl_easyopts_check(void); diff --git a/Utilities/cmcurl/lib/escape.c b/Utilities/cmcurl/lib/escape.c index eaad6d33ad..3cd906dc6c 100644 --- a/Utilities/cmcurl/lib/escape.c +++ b/Utilities/cmcurl/lib/escape.c @@ -32,9 +32,11 @@ struct Curl_easy; #include "urldata.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "escape.h" #include "strdup.h" +#include "curlx/strparse.h" + /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -69,7 +71,7 @@ char *curl_easy_escape(CURL *data, const char *string, if(!length) return strdup(""); - Curl_dyn_init(&d, length * 3 + 1); + curlx_dyn_init(&d, length * 3 + 1); while(length--) { /* treat the characters unsigned */ @@ -77,33 +79,21 @@ char *curl_easy_escape(CURL *data, const char *string, if(ISUNRESERVED(in)) { /* append this */ - if(Curl_dyn_addn(&d, &in, 1)) + if(curlx_dyn_addn(&d, &in, 1)) return NULL; } else { /* encode it */ - const char hex[] = "0123456789ABCDEF"; - char out[3]={'%'}; - out[1] = hex[in >> 4]; - out[2] = hex[in & 0xf]; - if(Curl_dyn_addn(&d, out, 3)) + unsigned char out[3]={'%'}; + Curl_hexbyte(&out[1], in, FALSE); + if(curlx_dyn_addn(&d, out, 3)) return NULL; } } - return Curl_dyn_ptr(&d); + return curlx_dyn_ptr(&d); } -static const unsigned char hextable[] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */ - 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */ - 0, 10, 11, 12, 13, 14, 15 /* 0x60 - 0x66 */ -}; - -/* the input is a single hex digit */ -#define onehex2dec(x) hextable[x - '0'] - /* * Curl_urldecode() URL decodes the given string. * @@ -144,8 +134,8 @@ CURLcode Curl_urldecode(const char *string, size_t length, if(('%' == in) && (alloc > 2) && ISXDIGIT(string[1]) && ISXDIGIT(string[2])) { /* this is two hexadecimal digits following a '%' */ - in = (unsigned char)(onehex2dec(string[1]) << 4) | onehex2dec(string[2]); - + in = (unsigned char)((Curl_hexval(string[1]) << 4) | + Curl_hexval(string[2])); string += 3; alloc -= 3; } @@ -219,15 +209,12 @@ void curl_free(void *p) 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]; + Curl_hexbyte(out, *src, TRUE); ++src; + out += 2; olen -= 2; } *out = 0; @@ -235,3 +222,17 @@ void Curl_hexencode(const unsigned char *src, size_t len, /* input length */ else if(olen) *out = 0; } + +/* Curl_hexbyte + * + * Output a single unsigned char as a two-digit hex number, lowercase or + * uppercase + */ +void Curl_hexbyte(unsigned char *dest, /* must fit two bytes */ + unsigned char val, + bool lowercase) +{ + const unsigned char *t = lowercase ? Curl_ldigits : Curl_udigits; + dest[0] = t[val >> 4]; + dest[1] = t[val & 0x0F]; +} diff --git a/Utilities/cmcurl/lib/escape.h b/Utilities/cmcurl/lib/escape.h index 690e417879..1f2bac8fac 100644 --- a/Utilities/cmcurl/lib/escape.h +++ b/Utilities/cmcurl/lib/escape.h @@ -41,4 +41,8 @@ CURLcode Curl_urldecode(const char *string, size_t length, void Curl_hexencode(const unsigned char *src, size_t len, /* input length */ unsigned char *out, size_t olen); /* output buffer size */ +void Curl_hexbyte(unsigned char *dest, /* must fit two bytes */ + unsigned char val, + bool lowercase); + #endif /* HEADER_CURL_ESCAPE_H */ diff --git a/Utilities/cmcurl/lib/fake_addrinfo.c b/Utilities/cmcurl/lib/fake_addrinfo.c new file mode 100644 index 0000000000..20d55ba2d1 --- /dev/null +++ b/Utilities/cmcurl/lib/fake_addrinfo.c @@ -0,0 +1,210 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "fake_addrinfo.h" + +#ifdef USE_FAKE_GETADDRINFO + +#include +#include +#include + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +void r_freeaddrinfo(struct addrinfo *cahead) +{ + struct addrinfo *canext; + struct addrinfo *ca; + + for(ca = cahead; ca; ca = canext) { + canext = ca->ai_next; + free(ca); + } +} + +struct context { + struct ares_addrinfo *result; +}; + +static void async_addrinfo_cb(void *userp, int status, int timeouts, + struct ares_addrinfo *result) +{ + struct context *ctx = (struct context *)userp; + (void)timeouts; + if(ARES_SUCCESS == status) { + ctx->result = result; + } +} + +/* convert the c-ares version into the "native" version */ +static struct addrinfo *mk_getaddrinfo(const struct ares_addrinfo *aihead) +{ + const struct ares_addrinfo_node *ai; + struct addrinfo *ca; + struct addrinfo *cafirst = NULL; + struct addrinfo *calast = NULL; + const char *name = aihead->name; + + /* traverse the addrinfo list */ + for(ai = aihead->nodes; ai != NULL; ai = ai->ai_next) { + size_t ss_size; + size_t namelen = name ? strlen(name) + 1 : 0; + /* ignore elements with unsupported address family, */ + /* settle family-specific sockaddr structure size. */ + if(ai->ai_family == AF_INET) + ss_size = sizeof(struct sockaddr_in); + else if(ai->ai_family == AF_INET6) + ss_size = sizeof(struct sockaddr_in6); + else + continue; + + /* ignore elements without required address info */ + if(!ai->ai_addr || !(ai->ai_addrlen > 0)) + continue; + + /* ignore elements with bogus address size */ + if((size_t)ai->ai_addrlen < ss_size) + continue; + + ca = malloc(sizeof(struct addrinfo) + ss_size + namelen); + if(!ca) { + r_freeaddrinfo(cafirst); + return NULL; + } + + /* copy each structure member individually, member ordering, */ + /* size, or padding might be different for each platform. */ + + ca->ai_flags = ai->ai_flags; + ca->ai_family = ai->ai_family; + ca->ai_socktype = ai->ai_socktype; + ca->ai_protocol = ai->ai_protocol; + ca->ai_addrlen = (curl_socklen_t)ss_size; + ca->ai_addr = NULL; + ca->ai_canonname = NULL; + ca->ai_next = NULL; + + ca->ai_addr = (void *)((char *)ca + sizeof(struct addrinfo)); + memcpy(ca->ai_addr, ai->ai_addr, ss_size); + + if(namelen) { + ca->ai_canonname = (void *)((char *)ca->ai_addr + ss_size); + memcpy(ca->ai_canonname, name, namelen); + + /* the name is only pointed to by the first entry in the "real" + addrinfo chain, so stop now */ + name = NULL; + } + + /* if the return list is empty, this becomes the first element */ + if(!cafirst) + cafirst = ca; + + /* add this element last in the return list */ + if(calast) + calast->ai_next = ca; + calast = ca; + } + + return cafirst; +} + +/* + RETURN VALUE + + getaddrinfo() returns 0 if it succeeds, or one of the following nonzero + error codes: + + ... +*/ +int r_getaddrinfo(const char *node, + const char *service, + const struct addrinfo *hints, + struct addrinfo **res) +{ + int status; + struct context ctx; + struct ares_options options; + int optmask = 0; + struct ares_addrinfo_hints ahints; + ares_channel channel; + int rc = 0; + + memset(&options, 0, sizeof(options)); + optmask |= ARES_OPT_EVENT_THREAD; + options.evsys = ARES_EVSYS_DEFAULT; + + memset(&ahints, 0, sizeof(ahints)); + memset(&ctx, 0, sizeof(ctx)); + + if(hints) { + ahints.ai_flags = hints->ai_flags; + ahints.ai_family = hints->ai_family; + ahints.ai_socktype = hints->ai_socktype; + ahints.ai_protocol = hints->ai_protocol; + } + + status = ares_init_options(&channel, &options, optmask); + if(status) + return EAI_MEMORY; /* major problem */ + + else { + const char *env = getenv("CURL_DNS_SERVER"); + if(env) { + rc = ares_set_servers_ports_csv(channel, env); + if(rc) { + fprintf(stderr, "ares_set_servers_ports_csv failed: %d", rc); + /* Cleanup */ + ares_destroy(channel); + return EAI_MEMORY; /* we can't run */ + } + } + } + + ares_getaddrinfo(channel, node, service, &ahints, + async_addrinfo_cb, &ctx); + + /* Wait until no more requests are left to be processed */ + ares_queue_wait_empty(channel, -1); + + if(ctx.result) { + /* convert the c-ares version */ + *res = mk_getaddrinfo(ctx.result); + /* free the old */ + ares_freeaddrinfo(ctx.result); + } + else + rc = EAI_NONAME; /* got nothing */ + + /* Cleanup */ + ares_destroy(channel); + + return rc; +} + +#endif /* USE_FAKE_GETADDRINFO */ diff --git a/Utilities/cmcurl/lib/fake_addrinfo.h b/Utilities/cmcurl/lib/fake_addrinfo.h new file mode 100644 index 0000000000..13b0d71dba --- /dev/null +++ b/Utilities/cmcurl/lib/fake_addrinfo.h @@ -0,0 +1,54 @@ +#ifndef HEADER_FAKE_ADDRINFO_H +#define HEADER_FAKE_ADDRINFO_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" + +#ifdef USE_ARES +#include +#endif + +#if defined(CURLDEBUG) && defined(USE_ARES) && defined(HAVE_GETADDRINFO) && \ + (ARES_VERSION >= 0x011a00) /* >= 1.26. 0 */ +#define USE_FAKE_GETADDRINFO 1 +#endif + +#ifdef USE_FAKE_GETADDRINFO + +#ifdef HAVE_NETDB_H +# include +#endif +#ifdef HAVE_ARPA_INET_H +# include +#endif + +void r_freeaddrinfo(struct addrinfo *res); +int r_getaddrinfo(const char *node, + const char *service, + const struct addrinfo *hints, + struct addrinfo **res); +#endif /* USE_FAKE_GETADDRINFO */ + +#endif /* HEADER_FAKE_ADDRINFO_H */ diff --git a/Utilities/cmcurl/lib/file.c b/Utilities/cmcurl/lib/file.c index 7751acf40f..b88f612305 100644 --- a/Utilities/cmcurl/lib/file.c +++ b/Utilities/cmcurl/lib/file.c @@ -58,7 +58,6 @@ #include #endif -#include "strtoofft.h" #include "urldata.h" #include #include "progress.h" @@ -71,7 +70,7 @@ #include "transfer.h" #include "url.h" #include "parsedate.h" /* for the week day and month names */ -#include "warnless.h" +#include "curlx/warnless.h" #include "curl_range.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -84,6 +83,16 @@ #define AMIGA_FILESYSTEM 1 #endif +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_FILE_EASY "meta:proto:file:easy" + +struct FILEPROTO { + char *path; /* the path we operate on */ + char *freepath; /* pointer to the allocated block we must free, this might + differ from the 'path' pointer */ + int fd; /* open file descriptor to read from! */ +}; + /* * Forward declarations. */ @@ -128,13 +137,34 @@ const struct Curl_handler Curl_handler_file = { }; +static void file_cleanup(struct FILEPROTO *file) +{ + Curl_safefree(file->freepath); + file->path = NULL; + if(file->fd != -1) { + close(file->fd); + file->fd = -1; + } +} + +static void file_easy_dtor(void *key, size_t klen, void *entry) +{ + struct FILEPROTO *file = entry; + (void)key; + (void)klen; + file_cleanup(file); + free(file); +} + static CURLcode file_setup_connection(struct Curl_easy *data, struct connectdata *conn) { + struct FILEPROTO *filep; (void)conn; /* allocate the FILE specific struct */ - data->req.p.file = calloc(1, sizeof(struct FILEPROTO)); - if(!data->req.p.file) + filep = calloc(1, sizeof(*filep)); + if(!filep || + Curl_meta_set(data, CURL_META_FILE_EASY, filep, file_easy_dtor)) return CURLE_OUT_OF_MEMORY; return CURLE_OK; @@ -148,7 +178,7 @@ static CURLcode file_setup_connection(struct Curl_easy *data, static CURLcode file_connect(struct Curl_easy *data, bool *done) { char *real_path; - struct FILEPROTO *file = data->req.p.file; + struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY); int fd; #ifdef DOS_FILESYSTEM size_t i; @@ -157,6 +187,9 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) size_t real_path_len; CURLcode result; + if(!file) + return CURLE_FAILED_INIT; + if(file->path) { /* already connected. * the handler->connect_it() is normally only called once, but @@ -241,7 +274,7 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) file->path = real_path; #endif #endif - Curl_safefree(file->freepath); + free(file->freepath); file->freepath = real_path; /* free this when done */ file->fd = fd; @@ -258,17 +291,12 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done) static CURLcode file_done(struct Curl_easy *data, CURLcode status, bool premature) { - struct FILEPROTO *file = data->req.p.file; + struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY); (void)status; /* not used */ (void)premature; /* not used */ - if(file) { - Curl_safefree(file->freepath); - file->path = NULL; - if(file->fd != -1) - close(file->fd); - file->fd = -1; - } + if(file) + file_cleanup(file); return CURLE_OK; } @@ -288,9 +316,9 @@ static CURLcode file_disconnect(struct Curl_easy *data, #define DIRSEP '/' #endif -static CURLcode file_upload(struct Curl_easy *data) +static CURLcode file_upload(struct Curl_easy *data, + struct FILEPROTO *file) { - struct FILEPROTO *file = data->req.p.file; const char *dir = strchr(file->path, DIRSEP); int fd; int mode; @@ -392,7 +420,7 @@ static CURLcode file_upload(struct Curl_easy *data) if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(data, Curl_now()); + result = Curl_speedcheck(data, curlx_now()); } if(!result && Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; @@ -419,6 +447,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) are supported. This means that files on remotely mounted directories (via NFS, Samba, NT sharing) can be accessed through a file:// URL */ + struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY); CURLcode result = CURLE_OK; struct_stat statbuf; /* struct_stat instead of struct stat just to allow the Windows version to have a different struct without @@ -427,16 +456,15 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) bool size_known; bool fstated = FALSE; int fd; - struct FILEPROTO *file; char *xfer_buf; size_t xfer_blen; *done = TRUE; /* unconditionally */ + if(!file) + return CURLE_FAILED_INIT; if(data->state.upload) - return file_upload(data); - - file = data->req.p.file; + return file_upload(data, file); /* get the fd from the connection phase */ fd = file->fd; @@ -548,7 +576,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) if(data->state.resume_from) { if(!S_ISDIR(statbuf.st_mode)) { -#ifdef __AMIGA__ +#if defined(__AMIGA__) || defined(__MINGW32CE__) if(data->state.resume_from != lseek(fd, (off_t)data->state.resume_from, SEEK_SET)) #else @@ -597,7 +625,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(data, Curl_now()); + result = Curl_speedcheck(data, curlx_now()); if(result) goto out; } diff --git a/Utilities/cmcurl/lib/file.h b/Utilities/cmcurl/lib/file.h index 4565525592..fea1eea57d 100644 --- a/Utilities/cmcurl/lib/file.h +++ b/Utilities/cmcurl/lib/file.h @@ -24,17 +24,6 @@ * ***************************************************************************/ - -/**************************************************************************** - * FILE unique setup - ***************************************************************************/ -struct FILEPROTO { - char *path; /* the path we operate on */ - char *freepath; /* pointer to the allocated block we must free, this might - differ from the 'path' pointer */ - int fd; /* open file descriptor to read from! */ -}; - #ifndef CURL_DISABLE_FILE extern const struct Curl_handler Curl_handler_file; #endif diff --git a/Utilities/cmcurl/lib/fileinfo.c b/Utilities/cmcurl/lib/fileinfo.c index 2be3b3239b..c3439af341 100644 --- a/Utilities/cmcurl/lib/fileinfo.c +++ b/Utilities/cmcurl/lib/fileinfo.c @@ -40,7 +40,7 @@ void Curl_fileinfo_cleanup(struct fileinfo *finfo) if(!finfo) return; - Curl_dyn_free(&finfo->buf); + curlx_dyn_free(&finfo->buf); free(finfo); } #endif diff --git a/Utilities/cmcurl/lib/fileinfo.h b/Utilities/cmcurl/lib/fileinfo.h index 0b3f56d9d4..6746ee2575 100644 --- a/Utilities/cmcurl/lib/fileinfo.h +++ b/Utilities/cmcurl/lib/fileinfo.h @@ -26,7 +26,7 @@ #include #include "llist.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" struct fileinfo { struct curl_fileinfo info; diff --git a/Utilities/cmcurl/lib/fopen.c b/Utilities/cmcurl/lib/fopen.c index 90dc0ae4d7..38b87f326e 100644 --- a/Utilities/cmcurl/lib/fopen.c +++ b/Utilities/cmcurl/lib/fopen.c @@ -66,7 +66,7 @@ static char *dirslash(const char *path) size_t n; struct dynbuf out; DEBUGASSERT(path); - Curl_dyn_init(&out, CURL_MAX_INPUT_LENGTH); + curlx_dyn_init(&out, CURL_MAX_INPUT_LENGTH); n = strlen(path); if(n) { /* find the rightmost path separator, if any */ @@ -76,12 +76,12 @@ static char *dirslash(const char *path) while(n && IS_SEP(path[n-1])) --n; } - if(Curl_dyn_addn(&out, path, n)) + if(curlx_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)) + if(n && curlx_dyn_addn(&out, PATHSEP, 1)) return NULL; - return Curl_dyn_ptr(&out); + return curlx_dyn_ptr(&out); } /* @@ -105,7 +105,13 @@ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, *fh = fopen(filename, FOPEN_WRITETEXT); if(!*fh) goto fail; - if(fstat(fileno(*fh), &sb) == -1 || !S_ISREG(sb.st_mode)) { + if( +#ifdef UNDER_CE + stat(filename, &sb) == -1 +#else + fstat(fileno(*fh), &sb) == -1 +#endif + || !S_ISREG(sb.st_mode)) { return CURLE_OK; } fclose(*fh); diff --git a/Utilities/cmcurl/lib/formdata.c b/Utilities/cmcurl/lib/formdata.c index 7ea7a8f396..2aa8eee94a 100644 --- a/Utilities/cmcurl/lib/formdata.c +++ b/Utilities/cmcurl/lib/formdata.c @@ -31,10 +31,6 @@ struct Curl_easy; #include "formdata.h" #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_FORM_API) -#if defined(HAVE_LIBGEN_H) && defined(HAVE_BASENAME) -#include -#endif - #include "urldata.h" /* for struct Curl_easy */ #include "mime.h" #include "vtls/vtls.h" @@ -42,7 +38,7 @@ struct Curl_easy; #include "sendf.h" #include "strdup.h" #include "rand.h" -#include "warnless.h" +#include "curlx/warnless.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -68,36 +64,31 @@ struct Curl_easy; * ***************************************************************************/ static struct curl_httppost * -AddHttpPost(char *name, size_t namelength, - char *value, curl_off_t contentslength, - char *buffer, size_t bufferlength, - char *contenttype, - long flags, - struct curl_slist *contentHeader, - char *showfilename, char *userp, +AddHttpPost(struct FormInfo *src, struct curl_httppost *parent_post, struct curl_httppost **httppost, struct curl_httppost **last_post) { struct curl_httppost *post; - if(!namelength && name) - namelength = strlen(name); - if((bufferlength > LONG_MAX) || (namelength > LONG_MAX)) + size_t namelength = src->namelength; + if(!namelength && src->name) + namelength = strlen(src->name); + if((src->bufferlength > LONG_MAX) || (namelength > LONG_MAX)) /* avoid overflow in typecasts below */ return NULL; post = calloc(1, sizeof(struct curl_httppost)); if(post) { - post->name = name; + post->name = src->name; post->namelength = (long)namelength; - post->contents = value; - post->contentlen = contentslength; - post->buffer = buffer; - post->bufferlength = (long)bufferlength; - post->contenttype = contenttype; - post->contentheader = contentHeader; - post->showfilename = showfilename; - post->userp = userp; - post->flags = flags | CURL_HTTPPOST_LARGE; + post->contents = src->value; + post->contentlen = src->contentslength; + post->buffer = src->buffer; + post->bufferlength = (long)src->bufferlength; + post->contenttype = src->contenttype; + post->flags = src->flags | CURL_HTTPPOST_LARGE; + post->contentheader = src->contentheader; + post->showfilename = src->showfilename; + post->userp = src->userp; } else return NULL; @@ -156,6 +147,28 @@ static struct FormInfo *AddFormInfo(char *value, return form_info; } +static void free_formlist(struct FormInfo *ptr) +{ + for(; ptr != NULL; ptr = ptr->more) { + if(ptr->name_alloc) { + Curl_safefree(ptr->name); + ptr->name_alloc = FALSE; + } + if(ptr->value_alloc) { + Curl_safefree(ptr->value); + ptr->value_alloc = FALSE; + } + if(ptr->contenttype_alloc) { + Curl_safefree(ptr->contenttype); + ptr->contenttype_alloc = FALSE; + } + if(ptr->showfilename_alloc) { + Curl_safefree(ptr->showfilename); + ptr->showfilename_alloc = FALSE; + } + } +} + /*************************************************************************** * * FormAdd() @@ -205,18 +218,123 @@ static struct FormInfo *AddFormInfo(char *value, * ***************************************************************************/ +static CURLFORMcode FormAddCheck(struct FormInfo *first_form, + struct curl_httppost **httppost, + struct curl_httppost **last_post) +{ + const char *prevtype = NULL; + struct FormInfo *form = NULL; + struct curl_httppost *post = NULL; + + /* go through the list, check for completeness and if everything is + * alright add the HttpPost item otherwise set retval accordingly */ + + for(form = first_form; + form != NULL; + form = form->more) { + if(((!form->name || !form->value) && !post) || + ( (form->contentslength) && + (form->flags & HTTPPOST_FILENAME) ) || + ( (form->flags & HTTPPOST_FILENAME) && + (form->flags & HTTPPOST_PTRCONTENTS) ) || + + ( (!form->buffer) && + (form->flags & HTTPPOST_BUFFER) && + (form->flags & HTTPPOST_PTRBUFFER) ) || + + ( (form->flags & HTTPPOST_READFILE) && + (form->flags & HTTPPOST_PTRCONTENTS) ) + ) { + return CURL_FORMADD_INCOMPLETE; + } + if(((form->flags & HTTPPOST_FILENAME) || + (form->flags & HTTPPOST_BUFFER)) && + !form->contenttype) { + char *f = (form->flags & HTTPPOST_BUFFER) ? + form->showfilename : form->value; + char const *type; + type = Curl_mime_contenttype(f); + if(!type) + type = prevtype; + if(!type) + type = FILE_CONTENTTYPE_DEFAULT; + + /* our contenttype is missing */ + form->contenttype = strdup(type); + if(!form->contenttype) + return CURL_FORMADD_MEMORY; + + form->contenttype_alloc = TRUE; + } + if(form->name && form->namelength) { + if(memchr(form->name, 0, form->namelength)) + return CURL_FORMADD_NULL; + } + if(!(form->flags & HTTPPOST_PTRNAME) && form->name) { + /* Note that there is small risk that form->name is NULL here if the app + passed in a bad combo, so we check for that. */ + + /* copy name (without strdup; possibly not null-terminated) */ + char *dupname = Curl_memdup0(form->name, form->namelength ? + form->namelength : strlen(form->name)); + if(!dupname) + return CURL_FORMADD_MEMORY; + + form->name = dupname; + form->name_alloc = TRUE; + } + if(!(form->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE | + HTTPPOST_PTRCONTENTS | HTTPPOST_PTRBUFFER | + HTTPPOST_CALLBACK)) && form->value) { + /* copy value (without strdup; possibly contains null characters) */ + size_t clen = (size_t) form->contentslength; + if(!clen) + clen = strlen(form->value) + 1; + + form->value = Curl_memdup(form->value, clen); + + if(!form->value) + return CURL_FORMADD_MEMORY; + + form->value_alloc = TRUE; + } + post = AddHttpPost(form, post, httppost, last_post); + + if(!post) + return CURL_FORMADD_MEMORY; + + if(form->contenttype) + prevtype = form->contenttype; + } + + return CURL_FORMADD_OK; +} + +/* Shallow cleanup. Remove the newly created chain, the structs only and not + the content they point to */ +static void free_chain(struct curl_httppost *c) +{ + while(c) { + struct curl_httppost *next = c->next; + if(c->more) + free_chain(c->more); + free(c); + c = next; + } +} + static CURLFORMcode FormAdd(struct curl_httppost **httppost, struct curl_httppost **last_post, va_list params) { - struct FormInfo *first_form, *current_form, *form = NULL; - CURLFORMcode return_value = CURL_FORMADD_OK; - const char *prevtype = NULL; - struct curl_httppost *post = NULL; + struct FormInfo *first_form, *curr, *form = NULL; + CURLFORMcode retval = CURL_FORMADD_OK; CURLformoption option; struct curl_forms *forms = NULL; - char *array_value = NULL; /* value read from an array */ + char *avalue = NULL; + struct curl_httppost *newchain = NULL; + struct curl_httppost *lastnode = NULL; /* 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 @@ -230,18 +348,18 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, if(!first_form) return CURL_FORMADD_MEMORY; - current_form = first_form; + curr = first_form; /* * Loop through all the options set. Break if we have an error to report. */ - while(return_value == CURL_FORMADD_OK) { + while(retval == CURL_FORMADD_OK) { /* first see if we have more parts of the array param */ if(array_state && forms) { /* get the upcoming option from the given array */ option = forms->option; - array_value = (char *)forms->value; + avalue = (char *)CURL_UNCONST(forms->value); forms++; /* advance this to next entry */ if(CURLFORM_END == option) { @@ -263,13 +381,13 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, case CURLFORM_ARRAY: if(array_state) /* we do not support an array from within an array */ - return_value = CURL_FORMADD_ILLEGAL_ARRAY; + retval = CURL_FORMADD_ILLEGAL_ARRAY; else { forms = va_arg(params, struct curl_forms *); if(forms) array_state = TRUE; else - return_value = CURL_FORMADD_NULL; + retval = CURL_FORMADD_NULL; } break; @@ -277,403 +395,253 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, * Set the Name property. */ case CURLFORM_PTRNAME: - current_form->flags |= HTTPPOST_PTRNAME; /* fall through */ + curr->flags |= HTTPPOST_PTRNAME; /* fall through */ FALLTHROUGH(); case CURLFORM_COPYNAME: - if(current_form->name) - return_value = CURL_FORMADD_OPTION_TWICE; + if(curr->name) + retval = CURL_FORMADD_OPTION_TWICE; else { - char *name = array_state ? - array_value : va_arg(params, char *); - if(name) - current_form->name = name; /* store for the moment */ + if(!array_state) + avalue = va_arg(params, char *); + if(avalue) + curr->name = avalue; /* store for the moment */ else - return_value = CURL_FORMADD_NULL; + retval = CURL_FORMADD_NULL; } break; case CURLFORM_NAMELENGTH: - if(current_form->namelength) - return_value = CURL_FORMADD_OPTION_TWICE; + if(curr->namelength) + retval = CURL_FORMADD_OPTION_TWICE; else - current_form->namelength = - array_state ? (size_t)array_value : (size_t)va_arg(params, long); + curr->namelength = + array_state ? (size_t)avalue : (size_t)va_arg(params, long); break; /* * Set the contents property. */ case CURLFORM_PTRCONTENTS: - current_form->flags |= HTTPPOST_PTRCONTENTS; + curr->flags |= HTTPPOST_PTRCONTENTS; FALLTHROUGH(); case CURLFORM_COPYCONTENTS: - if(current_form->value) - return_value = CURL_FORMADD_OPTION_TWICE; + if(curr->value) + retval = CURL_FORMADD_OPTION_TWICE; else { - char *value = - array_state ? array_value : va_arg(params, char *); - if(value) - current_form->value = value; /* store for the moment */ + if(!array_state) + avalue = va_arg(params, char *); + if(avalue) + curr->value = avalue; /* store for the moment */ else - return_value = CURL_FORMADD_NULL; + retval = CURL_FORMADD_NULL; } break; case CURLFORM_CONTENTSLENGTH: - current_form->contentslength = - array_state ? (size_t)array_value : (size_t)va_arg(params, long); + curr->contentslength = + array_state ? (size_t)avalue : (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 : + curr->flags |= CURL_HTTPPOST_LARGE; + curr->contentslength = + array_state ? (curl_off_t)(size_t)avalue : va_arg(params, curl_off_t); break; /* Get contents from a given filename */ case CURLFORM_FILECONTENT: - if(current_form->flags & (HTTPPOST_PTRCONTENTS|HTTPPOST_READFILE)) - return_value = CURL_FORMADD_OPTION_TWICE; + if(curr->flags & (HTTPPOST_PTRCONTENTS|HTTPPOST_READFILE)) + retval = CURL_FORMADD_OPTION_TWICE; else { - const char *filename = array_state ? - array_value : va_arg(params, char *); - if(filename) { - current_form->value = strdup(filename); - if(!current_form->value) - return_value = CURL_FORMADD_MEMORY; + if(!array_state) + avalue = va_arg(params, char *); + if(avalue) { + curr->value = strdup(avalue); + if(!curr->value) + retval = CURL_FORMADD_MEMORY; else { - current_form->flags |= HTTPPOST_READFILE; - current_form->value_alloc = TRUE; + curr->flags |= HTTPPOST_READFILE; + curr->value_alloc = TRUE; } } else - return_value = CURL_FORMADD_NULL; + retval = CURL_FORMADD_NULL; } break; /* We upload a file */ case CURLFORM_FILE: - { - const char *filename = array_state ? array_value : - va_arg(params, char *); + if(!array_state) + avalue = va_arg(params, char *); - if(current_form->value) { - if(current_form->flags & HTTPPOST_FILENAME) { - if(filename) { - char *fname = strdup(filename); - if(!fname) - return_value = CURL_FORMADD_MEMORY; + if(curr->value) { + if(curr->flags & HTTPPOST_FILENAME) { + if(avalue) { + char *fname = strdup(avalue); + if(!fname) + retval = CURL_FORMADD_MEMORY; + else { + form = AddFormInfo(fname, NULL, curr); + if(!form) { + free(fname); + retval = CURL_FORMADD_MEMORY; + } else { - form = AddFormInfo(fname, NULL, current_form); - if(!form) { - free(fname); - return_value = CURL_FORMADD_MEMORY; - } - else { - form->value_alloc = TRUE; - current_form = form; - form = NULL; - } + form->value_alloc = TRUE; + curr = form; + form = NULL; } } - else - return_value = CURL_FORMADD_NULL; } else - return_value = CURL_FORMADD_OPTION_TWICE; + retval = CURL_FORMADD_NULL; } - else { - if(filename) { - current_form->value = strdup(filename); - if(!current_form->value) - return_value = CURL_FORMADD_MEMORY; - else { - current_form->flags |= HTTPPOST_FILENAME; - current_form->value_alloc = TRUE; - } - } - else - return_value = CURL_FORMADD_NULL; - } - break; + else + retval = CURL_FORMADD_OPTION_TWICE; } + else { + if(avalue) { + curr->value = strdup(avalue); + if(!curr->value) + retval = CURL_FORMADD_MEMORY; + else { + curr->flags |= HTTPPOST_FILENAME; + curr->value_alloc = TRUE; + } + } + else + retval = CURL_FORMADD_NULL; + } + break; case CURLFORM_BUFFERPTR: - current_form->flags |= HTTPPOST_PTRBUFFER|HTTPPOST_BUFFER; - if(current_form->buffer) - return_value = CURL_FORMADD_OPTION_TWICE; + curr->flags |= HTTPPOST_PTRBUFFER|HTTPPOST_BUFFER; + if(curr->buffer) + retval = CURL_FORMADD_OPTION_TWICE; else { - char *buffer = - 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 + if(!array_state) + avalue = va_arg(params, char *); + if(avalue) { + curr->buffer = avalue; /* store for the moment */ + curr->value = avalue; /* make it non-NULL to be accepted as fine */ } else - return_value = CURL_FORMADD_NULL; + retval = CURL_FORMADD_NULL; } break; case CURLFORM_BUFFERLENGTH: - if(current_form->bufferlength) - return_value = CURL_FORMADD_OPTION_TWICE; + if(curr->bufferlength) + retval = CURL_FORMADD_OPTION_TWICE; else - current_form->bufferlength = - array_state ? (size_t)array_value : (size_t)va_arg(params, long); + curr->bufferlength = + array_state ? (size_t)avalue : (size_t)va_arg(params, long); break; case CURLFORM_STREAM: - current_form->flags |= HTTPPOST_CALLBACK; - if(current_form->userp) - return_value = CURL_FORMADD_OPTION_TWICE; + curr->flags |= HTTPPOST_CALLBACK; + if(curr->userp) + retval = CURL_FORMADD_OPTION_TWICE; else { - char *userp = - array_state ? array_value : va_arg(params, char *); - if(userp) { - current_form->userp = userp; - 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 */ + if(!array_state) + avalue = va_arg(params, char *); + if(avalue) { + curr->userp = avalue; + curr->value = avalue; /* 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 */ } else - return_value = CURL_FORMADD_NULL; + retval = CURL_FORMADD_NULL; } break; case CURLFORM_CONTENTTYPE: - { - const char *contenttype = - array_state ? array_value : va_arg(params, char *); - if(current_form->contenttype) { - if(current_form->flags & HTTPPOST_FILENAME) { - if(contenttype) { - char *type = strdup(contenttype); - if(!type) - return_value = CURL_FORMADD_MEMORY; + if(!array_state) + avalue = va_arg(params, char *); + if(curr->contenttype) { + if(curr->flags & HTTPPOST_FILENAME) { + if(avalue) { + char *type = strdup(avalue); + if(!type) + retval = CURL_FORMADD_MEMORY; + else { + form = AddFormInfo(NULL, type, curr); + if(!form) { + free(type); + retval = CURL_FORMADD_MEMORY; + } else { - form = AddFormInfo(NULL, type, current_form); - if(!form) { - free(type); - return_value = CURL_FORMADD_MEMORY; - } - else { - form->contenttype_alloc = TRUE; - current_form = form; - form = NULL; - } + form->contenttype_alloc = TRUE; + curr = form; + form = NULL; } } - else - return_value = CURL_FORMADD_NULL; } else - return_value = CURL_FORMADD_OPTION_TWICE; + retval = CURL_FORMADD_NULL; } - else { - if(contenttype) { - current_form->contenttype = strdup(contenttype); - if(!current_form->contenttype) - return_value = CURL_FORMADD_MEMORY; - else - current_form->contenttype_alloc = TRUE; - } - else - return_value = CURL_FORMADD_NULL; - } - break; + else + retval = CURL_FORMADD_OPTION_TWICE; } + else { + if(avalue) { + curr->contenttype = strdup(avalue); + if(!curr->contenttype) + retval = CURL_FORMADD_MEMORY; + else + curr->contenttype_alloc = TRUE; + } + else + retval = CURL_FORMADD_NULL; + } + break; + case CURLFORM_CONTENTHEADER: { /* 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 *)(void *)avalue : va_arg(params, struct curl_slist *); - if(current_form->contentheader) - return_value = CURL_FORMADD_OPTION_TWICE; + if(curr->contentheader) + retval = CURL_FORMADD_OPTION_TWICE; else - current_form->contentheader = list; + curr->contentheader = list; break; } case CURLFORM_FILENAME: case CURLFORM_BUFFER: - { - const char *filename = array_state ? array_value : - va_arg(params, char *); - if(current_form->showfilename) - return_value = CURL_FORMADD_OPTION_TWICE; - else { - current_form->showfilename = strdup(filename); - if(!current_form->showfilename) - return_value = CURL_FORMADD_MEMORY; - else - current_form->showfilename_alloc = TRUE; - } - break; + if(!array_state) + avalue = va_arg(params, char *); + if(curr->showfilename) + retval = CURL_FORMADD_OPTION_TWICE; + else { + curr->showfilename = strdup(avalue); + if(!curr->showfilename) + retval = CURL_FORMADD_MEMORY; + else + curr->showfilename_alloc = TRUE; } + break; + default: - return_value = CURL_FORMADD_UNKNOWN_OPTION; + retval = CURL_FORMADD_UNKNOWN_OPTION; break; } } - if(CURL_FORMADD_OK != return_value) { + if(!retval) + retval = FormAddCheck(first_form, &newchain, &lastnode); + + if(retval) /* On error, free allocated fields for all nodes of the FormInfo linked list without deallocating nodes. List nodes are deallocated later on */ - struct FormInfo *ptr; - for(ptr = first_form; ptr != NULL; ptr = ptr->more) { - if(ptr->name_alloc) { - Curl_safefree(ptr->name); - ptr->name_alloc = FALSE; - } - if(ptr->value_alloc) { - Curl_safefree(ptr->value); - ptr->value_alloc = FALSE; - } - if(ptr->contenttype_alloc) { - Curl_safefree(ptr->contenttype); - ptr->contenttype_alloc = FALSE; - } - if(ptr->showfilename_alloc) { - Curl_safefree(ptr->showfilename); - ptr->showfilename_alloc = FALSE; - } - } - } - - if(CURL_FORMADD_OK == return_value) { - /* go through the list, check for completeness and if everything is - * alright add the HttpPost item otherwise set return_value accordingly */ - - post = NULL; - for(form = first_form; - form != NULL; - form = form->more) { - if(((!form->name || !form->value) && !post) || - ( (form->contentslength) && - (form->flags & HTTPPOST_FILENAME) ) || - ( (form->flags & HTTPPOST_FILENAME) && - (form->flags & HTTPPOST_PTRCONTENTS) ) || - - ( (!form->buffer) && - (form->flags & HTTPPOST_BUFFER) && - (form->flags & HTTPPOST_PTRBUFFER) ) || - - ( (form->flags & HTTPPOST_READFILE) && - (form->flags & HTTPPOST_PTRCONTENTS) ) - ) { - return_value = CURL_FORMADD_INCOMPLETE; - break; - } - if(((form->flags & HTTPPOST_FILENAME) || - (form->flags & HTTPPOST_BUFFER)) && - !form->contenttype) { - char *f = (form->flags & HTTPPOST_BUFFER) ? - form->showfilename : form->value; - char const *type; - type = Curl_mime_contenttype(f); - if(!type) - type = prevtype; - if(!type) - type = FILE_CONTENTTYPE_DEFAULT; - - /* our contenttype is missing */ - form->contenttype = strdup(type); - if(!form->contenttype) { - return_value = CURL_FORMADD_MEMORY; - break; - } - form->contenttype_alloc = TRUE; - } - if(form->name && form->namelength) { - /* Name should not contain nul bytes. */ - size_t i; - for(i = 0; i < form->namelength; i++) - if(!form->name[i]) { - return_value = CURL_FORMADD_NULL; - break; - } - if(return_value != CURL_FORMADD_OK) - break; - } - if(!(form->flags & HTTPPOST_PTRNAME) && - (form == first_form) ) { - /* 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_memdup0(form->name, form->namelength ? - form->namelength : - strlen(form->name)); - } - if(!form->name) { - return_value = CURL_FORMADD_MEMORY; - break; - } - form->name_alloc = TRUE; - } - if(!(form->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE | - HTTPPOST_PTRCONTENTS | HTTPPOST_PTRBUFFER | - HTTPPOST_CALLBACK)) && form->value) { - /* copy value (without strdup; possibly contains null characters) */ - size_t clen = (size_t) form->contentslength; - if(!clen) - clen = strlen(form->value) + 1; - - form->value = Curl_memdup(form->value, clen); - - if(!form->value) { - return_value = CURL_FORMADD_MEMORY; - break; - } - form->value_alloc = TRUE; - } - post = AddHttpPost(form->name, form->namelength, - form->value, form->contentslength, - form->buffer, form->bufferlength, - form->contenttype, form->flags, - form->contentheader, form->showfilename, - form->userp, - post, httppost, - last_post); - - if(!post) { - return_value = CURL_FORMADD_MEMORY; - break; - } - - if(form->contenttype) - prevtype = form->contenttype; - } - if(CURL_FORMADD_OK != return_value) { - /* On error, free allocated fields for nodes of the FormInfo linked - list which are not already owned by the httppost linked list - without deallocating nodes. List nodes are deallocated later on */ - struct FormInfo *ptr; - for(ptr = form; ptr != NULL; ptr = ptr->more) { - if(ptr->name_alloc) { - Curl_safefree(ptr->name); - ptr->name_alloc = FALSE; - } - if(ptr->value_alloc) { - Curl_safefree(ptr->value); - ptr->value_alloc = FALSE; - } - if(ptr->contenttype_alloc) { - Curl_safefree(ptr->contenttype); - ptr->contenttype_alloc = FALSE; - } - if(ptr->showfilename_alloc) { - Curl_safefree(ptr->showfilename); - ptr->showfilename_alloc = FALSE; - } - } - } - } + free_formlist(first_form); /* Always deallocate FormInfo linked list nodes without touching node fields given that these have either been deallocated or are owned @@ -684,7 +652,19 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, first_form = ptr; } - return return_value; + if(!retval) { + /* Only if all is fine, link the new chain into the provided list */ + if(*last_post) + (*last_post)->next = newchain; + else + (*httppost) = newchain; + + (*last_post) = lastnode; + } + else + free_chain(newchain); + + return retval; } /* diff --git a/Utilities/cmcurl/lib/formdata.h b/Utilities/cmcurl/lib/formdata.h index 0e35e1892b..74f00bf4fc 100644 --- a/Utilities/cmcurl/lib/formdata.h +++ b/Utilities/cmcurl/lib/formdata.h @@ -35,7 +35,6 @@ struct FormInfo { char *value; curl_off_t contentslength; char *contenttype; - long flags; char *buffer; /* pointer to existing buffer used for file upload */ size_t bufferlength; char *showfilename; /* The filename to show. If not set, the actual @@ -43,10 +42,11 @@ struct FormInfo { char *userp; /* pointer for the read callback */ struct curl_slist *contentheader; struct FormInfo *more; - bool name_alloc; - bool value_alloc; - bool contenttype_alloc; - bool showfilename_alloc; + unsigned char flags; + BIT(name_alloc); + BIT(value_alloc); + BIT(contenttype_alloc); + BIT(showfilename_alloc); }; CURLcode Curl_getformdata(CURL *data, diff --git a/Utilities/cmcurl/lib/ftp.c b/Utilities/cmcurl/lib/ftp.c index 5733d696dd..2db4a7d567 100644 --- a/Utilities/cmcurl/lib/ftp.c +++ b/Utilities/cmcurl/lib/ftp.c @@ -54,7 +54,6 @@ #include "ftplistparser.h" #include "curl_range.h" #include "curl_krb5.h" -#include "strtoofft.h" #include "strcase.h" #include "vtls/vtls.h" #include "cfilters.h" @@ -62,17 +61,18 @@ #include "connect.h" #include "strerror.h" #include "inet_ntop.h" -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "select.h" #include "parsedate.h" /* for the week day and month names */ #include "sockaddr.h" /* required for Curl_sockaddr_storage */ #include "multiif.h" #include "url.h" #include "speedcheck.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "http_proxy.h" #include "socks.h" #include "strdup.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -95,8 +95,7 @@ #ifdef CURL_DISABLE_VERBOSE_STRINGS #define ftp_pasv_verbose(a,b,c,d) Curl_nop_stmt -#define FTP_CSTATE(c) "" -#define FTP_DSTATE(d) "" +#define FTP_CSTATE(c) ((void)(c), "") #else /* CURL_DISABLE_VERBOSE_STRINGS */ /* for tracing purposes */ static const char * const ftp_state_names[]={ @@ -136,23 +135,19 @@ static const char * const ftp_state_names[]={ "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] : "???") +#define FTP_CSTATE(ftpc) ((ftpc)? ftp_state_names[(ftpc)->state] : "???") #endif /* !CURL_DISABLE_VERBOSE_STRINGS */ /* This is the ONLY way to change FTP state! */ static void _ftp_state(struct Curl_easy *data, + struct ftp_conn *ftpc, 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; @@ -160,10 +155,10 @@ static void _ftp_state(struct Curl_easy *data, #else /* CURL_DISABLE_VERBOSE_STRINGS */ if(ftpc->state != newstate) #ifdef DEBUGBUILD - CURL_TRC_FTP(data, "[%s] -> [%s] (line %d)", FTP_DSTATE(data), + CURL_TRC_FTP(data, "[%s] -> [%s] (line %d)", FTP_CSTATE(ftpc), ftp_state_names[newstate], lineno); #else - CURL_TRC_FTP(data, "[%s] -> [%s]", FTP_DSTATE(data), + CURL_TRC_FTP(data, "[%s] -> [%s]", FTP_CSTATE(ftpc), ftp_state_names[newstate]); #endif #endif /* !CURL_DISABLE_VERBOSE_STRINGS */ @@ -174,32 +169,40 @@ static void _ftp_state(struct Curl_easy *data, /* Local API functions */ #ifndef DEBUGBUILD -#define ftp_state(x,y) _ftp_state(x,y) +#define ftp_state(x,y,z) _ftp_state(x,y,z) #else /* !DEBUGBUILD */ -#define ftp_state(x,y) _ftp_state(x,y,__LINE__) +#define ftp_state(x,y,z) _ftp_state(x,y,z,__LINE__) #endif /* DEBUGBUILD */ static CURLcode ftp_sendquote(struct Curl_easy *data, - struct connectdata *conn, + struct ftp_conn *ftpc, struct curl_slist *quote); -static CURLcode ftp_quit(struct Curl_easy *data, struct connectdata *conn); -static CURLcode ftp_parse_url_path(struct Curl_easy *data); -static CURLcode ftp_regular_transfer(struct Curl_easy *data, bool *done); +static CURLcode ftp_quit(struct Curl_easy *data, struct ftp_conn *ftpc); +static CURLcode ftp_parse_url_path(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp); +static CURLcode ftp_regular_transfer(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, + bool *done); #ifndef CURL_DISABLE_VERBOSE_STRINGS static void ftp_pasv_verbose(struct Curl_easy *data, struct Curl_addrinfo *ai, char *newhost, /* ASCII version */ int port); #endif -static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data); -static CURLcode ftp_state_mdtm(struct Curl_easy *data); +static CURLcode ftp_state_mdtm(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp); static CURLcode ftp_state_quote(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, bool init, ftpstate instate); static CURLcode ftp_nb_type(struct Curl_easy *data, - struct connectdata *conn, + struct ftp_conn *ftpc, + struct FTP *ftp, bool ascii, ftpstate newstate); -static int ftp_need_type(struct connectdata *conn, - bool ascii); +static int ftp_need_type(struct ftp_conn *ftpc, bool ascii); static CURLcode ftp_do(struct Curl_easy *data, bool *done); static CURLcode ftp_done(struct Curl_easy *data, CURLcode, bool premature); @@ -216,16 +219,26 @@ static CURLcode ftp_doing(struct Curl_easy *data, bool *dophase_done); static CURLcode ftp_setup_connection(struct Curl_easy *data, struct connectdata *conn); -static CURLcode init_wc_data(struct Curl_easy *data); -static CURLcode wc_statemach(struct Curl_easy *data); +static CURLcode init_wc_data(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp); +static CURLcode wc_statemach(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp); static void wc_data_dtor(void *ptr); -static CURLcode ftp_state_retr(struct Curl_easy *data, curl_off_t filesize); +static CURLcode ftp_state_retr(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, + curl_off_t filesize); static CURLcode ftp_readresp(struct Curl_easy *data, + struct ftp_conn *ftpc, int sockindex, struct pingpong *pp, int *ftpcode, size_t *size); static CURLcode ftp_dophase_done(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, bool connected); /* @@ -292,9 +305,11 @@ const struct Curl_handler Curl_handler_ftps = { }; #endif -static void close_secondarysocket(struct Curl_easy *data) +static void close_secondarysocket(struct Curl_easy *data, + struct ftp_conn *ftpc) { - CURL_TRC_FTP(data, "[%s] closing DATA connection", FTP_DSTATE(data)); + (void)ftpc; + CURL_TRC_FTP(data, "[%s] closing DATA connection", FTP_CSTATE(ftpc)); Curl_conn_close(data, SECONDARYSOCKET); Curl_conn_cf_discard_all(data, data->conn, SECONDARYSOCKET); } @@ -330,8 +345,7 @@ static void freedirs(struct ftp_conn *ftpc) } #ifdef CURL_PREFER_LF_LINEENDS -/*********************************************************************** - * +/* * Lineend Conversions * On ASCII transfers, e.g. directory listings, we might get lines * ending in '\r\n' and we prefer just '\n'. @@ -348,9 +362,12 @@ static CURLcode ftp_cw_lc_write(struct Curl_easy *data, { static const char nl = '\n'; struct ftp_cw_lc_ctx *ctx = writer->ctx; + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); - if(!(type & CLIENTWRITE_BODY) || - data->conn->proto.ftpc.transfertype != 'A') + if(!ftpc) + return CURLE_FAILED_INIT; + + if(!(type & CLIENTWRITE_BODY) || ftpc->transfertype != 'A') return Curl_cwriter_write(data, writer->next, type, buf, blen); /* ASCII mode BODY data, convert lineends */ @@ -424,18 +441,18 @@ static const struct Curl_cwtype ftp_cw_lc = { * ftp_check_ctrl_on_data_wait() * */ -static CURLcode ftp_check_ctrl_on_data_wait(struct Curl_easy *data) +static CURLcode ftp_check_ctrl_on_data_wait(struct Curl_easy *data, + struct ftp_conn *ftpc) { struct connectdata *conn = data->conn; curl_socket_t ctrl_sock = conn->sock[FIRSTSOCKET]; - struct ftp_conn *ftpc = &conn->proto.ftpc; struct pingpong *pp = &ftpc->pp; ssize_t nread; int ftpcode; bool response = FALSE; /* First check whether there is a cached response from server */ - if(Curl_dyn_len(&pp->recvbuf) && (*Curl_dyn_ptr(&pp->recvbuf) > '3')) { + if(curlx_dyn_len(&pp->recvbuf) && (*curlx_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); @@ -464,16 +481,16 @@ static CURLcode ftp_check_ctrl_on_data_wait(struct Curl_easy *data) if(response) { infof(data, "Ctrl conn has data while waiting for data conn"); if(pp->overflow > 3) { - char *r = Curl_dyn_ptr(&pp->recvbuf); + const char *r = curlx_dyn_ptr(&pp->recvbuf); DEBUGASSERT((pp->overflow + pp->nfinal) <= - Curl_dyn_len(&pp->recvbuf)); + curlx_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) { + curl_off_t status; + if(!curlx_str_number(&r, &status, 999) && (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 @@ -499,30 +516,30 @@ static CURLcode ftp_check_ctrl_on_data_wait(struct Curl_easy *data) /*********************************************************************** * - * InitiateTransfer() + * ftp_initiate_transfer() * * After connection from server is accepted this function is called to * setup transfer parameters and initiate the data transfer. * */ -static CURLcode InitiateTransfer(struct Curl_easy *data) +static CURLcode ftp_initiate_transfer(struct Curl_easy *data, + struct ftp_conn *ftpc) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; bool connected; - CURL_TRC_FTP(data, "InitiateTransfer()"); + CURL_TRC_FTP(data, "ftp_initiate_transfer()"); result = Curl_conn_connect(data, SECONDARYSOCKET, TRUE, &connected); if(result || !connected) return result; - if(conn->proto.ftpc.state_saved == FTP_STOR) { + if(ftpc->state_saved == FTP_STOR) { /* 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_sndbuf_init(conn->sock[SECONDARYSOCKET]); + Curl_sndbuf_init(data->conn->sock[SECONDARYSOCKET]); /* FTP upload, shutdown DATA, ignore shutdown errors, as we rely * on the server response on the CONTROL connection. */ @@ -531,23 +548,24 @@ static CURLcode InitiateTransfer(struct Curl_easy *data) else { /* FTP download, shutdown, do not ignore errors */ Curl_xfer_setup2(data, CURL_XFER_RECV, - conn->proto.ftpc.retr_size_saved, TRUE, FALSE); + ftpc->retr_size_saved, TRUE, FALSE); } - conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */ - ftp_state(data, FTP_STOP); + ftpc->pp.pending_resp = TRUE; /* expect server response */ + ftp_state(data, ftpc, FTP_STOP); return CURLE_OK; } static bool ftp_endofresp(struct Curl_easy *data, struct connectdata *conn, - char *line, size_t len, int *code) + const char *line, size_t len, int *code) { + curl_off_t status; (void)data; (void)conn; - if((len > 3) && LASTLINE(line)) { - *code = curlx_sltosi(strtol(line, NULL, 10)); + if((len > 3) && LASTLINE(line) && !curlx_str_number(&line, &status, 999)) { + *code = (int)status; return TRUE; } @@ -555,6 +573,7 @@ static bool ftp_endofresp(struct Curl_easy *data, struct connectdata *conn, } static CURLcode ftp_readresp(struct Curl_easy *data, + struct ftp_conn *ftpc, int sockindex, struct pingpong *pp, int *ftpcode, /* return the ftp-code if done */ @@ -566,7 +585,7 @@ static CURLcode ftp_readresp(struct Curl_easy *data, #ifdef HAVE_GSSAPI { struct connectdata *conn = data->conn; - char * const buf = Curl_dyn_ptr(&data->conn->proto.ftpc.pp.recvbuf); + char * const buf = curlx_dyn_ptr(&ftpc->pp.recvbuf); /* handle the security-oriented responses 6xx ***/ switch(code) { @@ -586,8 +605,9 @@ static CURLcode ftp_readresp(struct Curl_easy *data, } #endif - /* store the latest code for later retrieval */ - data->info.httpcode = code; + /* store the latest code for later retrieval, except during shutdown */ + if(!ftpc->shutdown) + data->info.httpcode = code; if(ftpcode) *ftpcode = code; @@ -601,7 +621,7 @@ static CURLcode ftp_readresp(struct Curl_easy *data, * generically is a good idea. */ infof(data, "We got a 421 - timeout"); - ftp_state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); return CURLE_OPERATION_TIMEDOUT; } @@ -630,21 +650,22 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, struct connectdata *conn = data->conn; curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); struct pingpong *pp = &ftpc->pp; size_t nread; int cache_skip = 0; int value_to_be_ignored = 0; CURL_TRC_FTP(data, "getFTPResponse start"); - + *nreadp = 0; if(ftpcode) *ftpcode = 0; /* 0 for errors */ else /* make the pointer point to something for the rest of this function */ ftpcode = &value_to_be_ignored; - *nreadp = 0; + if(!ftpc) + return CURLE_FAILED_INIT; while(!*ftpcode && !result) { /* check and reset timeout value every lap */ @@ -674,7 +695,7 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, * */ - if(Curl_dyn_len(&pp->recvbuf) && (cache_skip < 2)) { + if(curlx_dyn_len(&pp->recvbuf) && (cache_skip < 2)) { /* * 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 @@ -704,11 +725,11 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, break; } - result = ftp_readresp(data, FIRSTSOCKET, pp, ftpcode, &nread); + result = ftp_readresp(data, ftpc, FIRSTSOCKET, pp, ftpcode, &nread); if(result) break; - if(!nread && Curl_dyn_len(&pp->recvbuf)) + if(!nread && curlx_dyn_len(&pp->recvbuf)) /* bump cache skip counter as on repeated skips we must wait for more data */ cache_skip++; @@ -729,25 +750,24 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, } static CURLcode ftp_state_user(struct Curl_easy *data, + struct ftp_conn *ftpc, struct connectdata *conn) { - CURLcode result = Curl_pp_sendf(data, - &conn->proto.ftpc.pp, "USER %s", + CURLcode result = Curl_pp_sendf(data, &ftpc->pp, "USER %s", conn->user ? conn->user : ""); if(!result) { - struct ftp_conn *ftpc = &conn->proto.ftpc; ftpc->ftp_trying_alternative = FALSE; - ftp_state(data, FTP_USER); + ftp_state(data, ftpc, FTP_USER); } return result; } static CURLcode ftp_state_pwd(struct Curl_easy *data, - struct connectdata *conn) + struct ftp_conn *ftpc) { - CURLcode result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "PWD"); + CURLcode result = Curl_pp_sendf(data, &ftpc->pp, "%s", "PWD"); if(!result) - ftp_state(data, FTP_PWD); + ftp_state(data, ftpc, FTP_PWD); return result; } @@ -757,21 +777,25 @@ static int ftp_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks) { - return Curl_pp_getsock(data, &conn->proto.ftpc.pp, socks); + struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); + return ftpc ? Curl_pp_getsock(data, &ftpc->pp, socks) : GETSOCK_BLANK; } /* For the FTP "DO_MORE" phase only */ static int ftp_domore_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks) { - struct ftp_conn *ftpc = &conn->proto.ftpc; + struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); (void)data; + if(!ftpc) + return GETSOCK_BLANK; + /* When in DO_MORE state, we could be either waiting for us to connect to a * remote site, or we could wait for that site to connect to us. Or just * handle ordinary commands. */ - CURL_TRC_FTP(data, "[%s] ftp_domore_getsock()", FTP_DSTATE(data)); + CURL_TRC_FTP(data, "[%s] ftp_domore_getsock()", FTP_CSTATE(ftpc)); if(FTP_STOP == ftpc->state) { /* if stopped and still in this state, then we are also waiting for a @@ -784,7 +808,7 @@ static int ftp_domore_getsock(struct Curl_easy *data, * via its adjust_pollset() */ return GETSOCK_READSOCK(0); } - return Curl_pp_getsock(data, &conn->proto.ftpc.pp, socks); + return Curl_pp_getsock(data, &ftpc->pp, socks); } /* This is called after the FTP_QUOTE state is passed. @@ -794,14 +818,14 @@ static int ftp_domore_getsock(struct Curl_easy *data, missing ones, if that option is enabled. */ static CURLcode ftp_state_cwd(struct Curl_easy *data, - struct connectdata *conn) + struct ftp_conn *ftpc, + struct FTP *ftp) { CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; if(ftpc->cwddone) /* already done and fine */ - result = ftp_state_mdtm(data); + result = ftp_state_mdtm(data, ftpc, ftp); else { /* FTPFILE_NOCWD with full path: expect ftpc->cwddone! */ DEBUGASSERT((data->set.ftp_filemethod != FTPFILE_NOCWD) || @@ -809,7 +833,7 @@ static CURLcode ftp_state_cwd(struct Curl_easy *data, ftpc->count2 = 0; /* count2 counts failed CWDs */ - if(conn->bits.reuse && ftpc->entrypath && + if(data->conn->bits.reuse && ftpc->entrypath && /* no need to go to entrypath when we have an absolute path */ !(ftpc->dirdepth && ftpc->dirs[0][0] == '/')) { /* This is a reused connection. Since we change directory to where the @@ -819,7 +843,7 @@ static CURLcode ftp_state_cwd(struct Curl_easy *data, for all upcoming ones in the ftp->dirs[] array */ result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", ftpc->entrypath); if(!result) - ftp_state(data, FTP_CWD); + ftp_state(data, ftpc, FTP_CWD); } else { if(ftpc->dirdepth) { @@ -829,11 +853,11 @@ static CURLcode ftp_state_cwd(struct Curl_easy *data, result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", ftpc->dirs[ftpc->cwdcount -1]); if(!result) - ftp_state(data, FTP_CWD); + ftp_state(data, ftpc, FTP_CWD); } else { /* No CWD necessary */ - result = ftp_state_mdtm(data); + result = ftp_state_mdtm(data, ftpc, ftp); } } } @@ -847,11 +871,11 @@ typedef enum { } ftpport; static CURLcode ftp_state_use_port(struct Curl_easy *data, + struct ftp_conn *ftpc, ftpport fcmd) /* start with this */ { CURLcode result = CURLE_FTP_PORT_FAILED; struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; curl_socket_t portsock = CURL_SOCKET_BAD; char myhost[MAX_IPADR_LEN + 1] = ""; @@ -865,7 +889,6 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, struct sockaddr_in6 * const sa6 = (void *)sa; #endif static const char mode[][5] = { "EPRT", "PORT" }; - enum resolve_t rc; int error; char *host = NULL; char *string_ftpport = data->set.str[STRING_FTPPORT]; @@ -911,7 +934,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, /* either ipv6 or (ipv4|domain|interface):port(-range) */ addrlen = ip_end - string_ftpport; #ifdef USE_IPV6 - if(Curl_inet_pton(AF_INET6, string_ftpport, &sa6->sin6_addr) == 1) { + if(curlx_inet_pton(AF_INET6, string_ftpport, &sa6->sin6_addr) == 1) { /* ipv6 */ port_min = port_max = 0; ip_end = NULL; /* this got no port ! */ @@ -925,13 +948,20 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, /* parse the port */ if(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, '-'); - if(port_sep) { - port_max = curlx_ultous(strtoul(port_sep + 1, NULL, 10)); + const char *portp = strchr(ip_end, ':'); + if(portp) { + curl_off_t start; + curl_off_t end; + portp++; + if(!curlx_str_number(&portp, &start, 0xffff)) { + /* got the first number */ + port_min = (unsigned short)start; + if(!curlx_str_single(&portp, '-')) { + /* got the dash */ + if(!curlx_str_number(&portp, &end, 0xffff)) + /* got the second number */ + port_max = (unsigned short)end; + } } else port_max = port_min; @@ -1005,14 +1035,12 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, } /* resolv ip/host to ip */ - rc = Curl_resolv(data, host, 0, FALSE, &dns_entry); - if(rc == CURLRESOLV_PENDING) - (void)Curl_resolver_wait_resolv(data, &dns_entry); - if(dns_entry) { + res = NULL; + result = Curl_resolv_blocking(data, host, 0, conn->ip_version, &dns_entry); + if(!result) { + DEBUGASSERT(dns_entry); res = dns_entry->addr; } - else - res = NULL; /* failure! */ if(!res) { failf(data, "failed to resolve the address provided to PORT: %s", host); @@ -1036,7 +1064,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, goto out; } CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), opened socket", - FTP_DSTATE(data)); + FTP_CSTATE(ftpc)); /* step 3, bind to a suitable local address */ @@ -1054,7 +1082,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, if(bind(portsock, sa, sslen) ) { /* It failed. */ error = SOCKERRNO; - if(possibly_non_local && (error == EADDRNOTAVAIL)) { + if(possibly_non_local && (error == SOCKEADDRNOTAVAIL)) { /* The requested bind address is not local. Use the address used for * the control connection instead and restart the port loop */ @@ -1071,7 +1099,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, possibly_non_local = FALSE; /* do not try this again */ continue; } - if(error != EADDRINUSE && error != EACCES) { + if(error != SOCKEADDRINUSE && error != SOCKEACCES) { failf(data, "bind(port=%hu) failed: %s", port, Curl_strerror(error, buffer, sizeof(buffer))); goto out; @@ -1098,7 +1126,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, goto out; } CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), socket bound to port %d", - FTP_DSTATE(data), port); + FTP_CSTATE(ftpc), port); /* step 4, listen on the socket */ @@ -1108,7 +1136,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, goto out; } CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), listening on %d", - FTP_DSTATE(data), port); + FTP_CSTATE(ftpc), port); /* step 5, send the proper FTP command */ @@ -1118,7 +1146,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, #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 + /* EPRT is disabled but we are connected to an IPv6 host, so we ignore the request and enable EPRT again! */ conn->bits.ftp_use_eprt = TRUE; #endif @@ -1195,7 +1223,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, /* store which command was sent */ ftpc->count1 = fcmd; - ftp_state(data, FTP_PORT); + ftp_state(data, ftpc, FTP_PORT); /* Replace any filter on SECONDARY with one listening on this socket */ result = Curl_conn_tcp_listen_set(data, conn, SECONDARYSOCKET, &portsock); @@ -1207,7 +1235,7 @@ out: if(dns_entry) Curl_resolv_unlink(data, &dns_entry); if(result) { - ftp_state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); } else { /* successfully setup the list socket filter. Do we need more? */ @@ -1227,9 +1255,9 @@ out: } static CURLcode ftp_state_use_pasv(struct Curl_easy *data, + struct ftp_conn *ftpc, struct connectdata *conn) { - struct ftp_conn *ftpc = &conn->proto.ftpc; CURLcode result = CURLE_OK; /* Here's the executive summary on what to do: @@ -1250,7 +1278,7 @@ static CURLcode ftp_state_use_pasv(struct Curl_easy *data, #ifdef PF_INET6 if(!conn->bits.ftp_use_epsv && conn->bits.ipv6) - /* EPSV is disabled but we are connected to a IPv6 host, so we ignore the + /* EPSV is disabled but we are connected to an IPv6 host, so we ignore the request and enable EPSV again! */ conn->bits.ftp_use_epsv = TRUE; #endif @@ -1260,7 +1288,7 @@ static CURLcode ftp_state_use_pasv(struct Curl_easy *data, result = Curl_pp_sendf(data, &ftpc->pp, "%s", mode[modeoff]); if(!result) { ftpc->count1 = modeoff; - ftp_state(data, FTP_PASV); + ftp_state(data, ftpc, FTP_PASV); infof(data, "Connect data stream passively"); } return result; @@ -1273,55 +1301,52 @@ static CURLcode ftp_state_use_pasv(struct Curl_easy *data, * request is made. Thus, if an actual transfer is to be made this is where we * take off for real. */ -static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data) +static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; struct connectdata *conn = data->conn; if(ftp->transfer != PPTRANSFER_BODY) { /* does not transfer any data */ /* still possibly do PRE QUOTE jobs */ - ftp_state(data, FTP_RETR_PREQUOTE); - result = ftp_state_quote(data, TRUE, FTP_RETR_PREQUOTE); + ftp_state(data, ftpc, FTP_RETR_PREQUOTE); + result = ftp_state_quote(data, ftpc, ftp, TRUE, FTP_RETR_PREQUOTE); } else if(data->set.ftp_use_port) { /* We have chosen to use the PORT (or similar) command */ - result = ftp_state_use_port(data, EPRT); + result = ftp_state_use_port(data, ftpc, EPRT); } else { /* We have chosen (this is default) to use the PASV (or similar) command */ if(data->set.ftp_use_pret) { /* The user has requested that we send a PRET command to prepare the server for the upcoming PASV */ - struct ftp_conn *ftpc = &conn->proto.ftpc; - if(!conn->proto.ftpc.file) + if(!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")); else if(data->state.upload) - result = Curl_pp_sendf(data, &ftpc->pp, "PRET STOR %s", - conn->proto.ftpc.file); + result = Curl_pp_sendf(data, &ftpc->pp, "PRET STOR %s", ftpc->file); else - result = Curl_pp_sendf(data, &ftpc->pp, "PRET RETR %s", - conn->proto.ftpc.file); + result = Curl_pp_sendf(data, &ftpc->pp, "PRET RETR %s", ftpc->file); if(!result) - ftp_state(data, FTP_PRET); + ftp_state(data, ftpc, FTP_PRET); } else - result = ftp_state_use_pasv(data, conn); + result = ftp_state_use_pasv(data, ftpc, conn); } return result; } static CURLcode ftp_state_rest(struct Curl_easy *data, - struct connectdata *conn) + struct ftp_conn *ftpc, + struct FTP *ftp) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; if((ftp->transfer != PPTRANSFER_BODY) && ftpc->file) { /* if a "head"-like request is being made (on a file) */ @@ -1330,20 +1355,19 @@ static CURLcode ftp_state_rest(struct Curl_easy *data, whether it supports range */ result = Curl_pp_sendf(data, &ftpc->pp, "REST %d", 0); if(!result) - ftp_state(data, FTP_REST); + ftp_state(data, ftpc, FTP_REST); } else - result = ftp_state_prepare_transfer(data); + result = ftp_state_prepare_transfer(data, ftpc, ftp); return result; } static CURLcode ftp_state_size(struct Curl_easy *data, - struct connectdata *conn) + struct ftp_conn *ftpc, + struct FTP *ftp) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; if((ftp->transfer == PPTRANSFER_INFO) && ftpc->file) { /* if a "head"-like request is being made (on a file) */ @@ -1351,19 +1375,19 @@ static CURLcode ftp_state_size(struct Curl_easy *data, /* 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); + ftp_state(data, ftpc, FTP_SIZE); } else - result = ftp_state_rest(data, conn); + result = ftp_state_rest(data, ftpc, ftp); return result; } -static CURLcode ftp_state_list(struct Curl_easy *data) +static CURLcode ftp_state_list(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; /* If this output is to be machine-parsed, the NLST command might be better to use, since the LIST command output is not specified or standard in any @@ -1415,39 +1439,42 @@ static CURLcode ftp_state_list(struct Curl_easy *data) if(!cmd) return CURLE_OUT_OF_MEMORY; - result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", cmd); + result = Curl_pp_sendf(data, &ftpc->pp, "%s", cmd); free(cmd); if(!result) - ftp_state(data, FTP_LIST); + ftp_state(data, ftpc, FTP_LIST); return result; } -static CURLcode ftp_state_retr_prequote(struct Curl_easy *data) +static CURLcode ftp_state_retr_prequote(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { /* We have sent the TYPE, now we must send the list of prequote strings */ - return ftp_state_quote(data, TRUE, FTP_RETR_PREQUOTE); + return ftp_state_quote(data, ftpc, ftp, TRUE, FTP_RETR_PREQUOTE); } -static CURLcode ftp_state_stor_prequote(struct Curl_easy *data) +static CURLcode ftp_state_stor_prequote(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { /* We have sent the TYPE, now we must send the list of prequote strings */ - return ftp_state_quote(data, TRUE, FTP_STOR_PREQUOTE); + return ftp_state_quote(data, ftpc, ftp, TRUE, FTP_STOR_PREQUOTE); } -static CURLcode ftp_state_type(struct Curl_easy *data) +static CURLcode ftp_state_type(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - 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 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)) { + ftp_need_type(ftpc, data->state.prefer_ascii)) { /* The SIZE command is _not_ RFC 959 specified, and therefore many servers may not support it! It is however the only way we have to get a file's size! */ @@ -1457,23 +1484,23 @@ static CURLcode ftp_state_type(struct Curl_easy *data) /* Some servers return different sizes for different modes, and thus we must set the proper type before we check the size */ - result = ftp_nb_type(data, conn, data->state.prefer_ascii, FTP_TYPE); + result = ftp_nb_type(data, ftpc, ftp, data->state.prefer_ascii, FTP_TYPE); if(result) return result; } else - result = ftp_state_size(data, conn); + result = ftp_state_size(data, ftpc, ftp); return result; } /* This is called after the CWD commands have been done in the beginning of the DO phase */ -static CURLcode ftp_state_mdtm(struct Curl_easy *data) +static CURLcode ftp_state_mdtm(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; /* Requested time of file or time-depended transfer? */ if((data->set.get_filetime || data->set.timecondition) && ftpc->file) { @@ -1483,10 +1510,10 @@ static CURLcode ftp_state_mdtm(struct Curl_easy *data) result = Curl_pp_sendf(data, &ftpc->pp, "MDTM %s", ftpc->file); if(!result) - ftp_state(data, FTP_MDTM); + ftp_state(data, ftpc, FTP_MDTM); } else - result = ftp_state_type(data); + result = ftp_state_type(data, ftpc, ftp); return result; } @@ -1494,12 +1521,11 @@ static CURLcode ftp_state_mdtm(struct Curl_easy *data) /* This is called after the TYPE and possible quote commands have been sent */ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, bool sizechecked) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; bool append = data->set.remote_append; if((data->state.resume_from && !sizechecked) || @@ -1522,7 +1548,7 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, /* Got no given size to start from, figure it out */ result = Curl_pp_sendf(data, &ftpc->pp, "SIZE %s", ftpc->file); if(!result) - ftp_state(data, FTP_STOR_SIZE); + ftp_state(data, ftpc, FTP_STOR_SIZE); return result; } @@ -1578,7 +1604,7 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, * ftp_done() because we did not transfer anything! */ ftp->transfer = PPTRANSFER_NONE; - ftp_state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); return CURLE_OK; } } @@ -1588,19 +1614,18 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, result = Curl_pp_sendf(data, &ftpc->pp, append ? "APPE %s" : "STOR %s", ftpc->file); if(!result) - ftp_state(data, FTP_STOR); + ftp_state(data, ftpc, FTP_STOR); return result; } static CURLcode ftp_state_quote(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, bool init, ftpstate instate) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; bool quote = FALSE; struct curl_slist *item; @@ -1649,7 +1674,7 @@ static CURLcode ftp_state_quote(struct Curl_easy *data, result = Curl_pp_sendf(data, &ftpc->pp, "%s", cmd); if(result) return result; - ftp_state(data, instate); + ftp_state(data, ftpc, instate); quote = TRUE; } } @@ -1659,15 +1684,15 @@ static CURLcode ftp_state_quote(struct Curl_easy *data, switch(instate) { case FTP_QUOTE: default: - result = ftp_state_cwd(data, conn); + result = ftp_state_cwd(data, ftpc, ftp); break; case FTP_RETR_PREQUOTE: if(ftp->transfer != PPTRANSFER_BODY) - ftp_state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); else { if(ftpc->known_filesize != -1) { Curl_pgrsSetDownloadSize(data, ftpc->known_filesize); - result = ftp_state_retr(data, ftpc->known_filesize); + result = ftp_state_retr(data, ftpc, ftp, ftpc->known_filesize); } else { if(data->set.ignorecl || data->state.prefer_ascii) { @@ -1685,18 +1710,18 @@ static CURLcode ftp_state_quote(struct Curl_easy *data, */ result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); if(!result) - ftp_state(data, FTP_RETR); + ftp_state(data, ftpc, FTP_RETR); } else { result = Curl_pp_sendf(data, &ftpc->pp, "SIZE %s", ftpc->file); if(!result) - ftp_state(data, FTP_RETR_SIZE); + ftp_state(data, ftpc, FTP_RETR_SIZE); } } } break; case FTP_STOR_PREQUOTE: - result = ftp_state_ul_setup(data, FALSE); + result = ftp_state_ul_setup(data, ftpc, ftp, FALSE); break; case FTP_POSTQUOTE: break; @@ -1709,6 +1734,7 @@ static CURLcode ftp_state_quote(struct Curl_easy *data, /* called from ftp_state_pasv_resp to switch to PASV in case of EPSV problems */ static CURLcode ftp_epsv_disable(struct Curl_easy *data, + struct ftp_conn *ftpc, struct connectdata *conn) { CURLcode result = CURLE_OK; @@ -1726,15 +1752,14 @@ static CURLcode ftp_epsv_disable(struct Curl_easy *data, infof(data, "Failed EPSV attempt. Disabling EPSV"); /* disable it for next transfer */ conn->bits.ftp_use_epsv = FALSE; - Curl_conn_close(data, SECONDARYSOCKET); - Curl_conn_cf_discard_all(data, conn, SECONDARYSOCKET); + close_secondarysocket(data, ftpc); data->state.errorbuf = FALSE; /* allow error message to get rewritten */ - result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "PASV"); + result = Curl_pp_sendf(data, &ftpc->pp, "%s", "PASV"); if(!result) { - conn->proto.ftpc.count1++; + ftpc->count1++; /* remain in/go to the FTP_PASV state */ - ftp_state(data, FTP_PASV); + ftp_state(data, ftpc, FTP_PASV); } return result; } @@ -1758,36 +1783,30 @@ static bool match_pasv_6nums(const char *p, { int i; for(i = 0; i < 6; i++) { - unsigned long num; - char *endp; + curl_off_t num; if(i) { if(*p != ',') return FALSE; p++; } - if(!ISDIGIT(*p)) - return FALSE; - num = strtoul(p, &endp, 10); - if(num > 255) + if(curlx_str_number(&p, &num, 0xff)) return FALSE; array[i] = (unsigned int)num; - p = endp; } return TRUE; } static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, int ftpcode) { struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; CURLcode result; - struct Curl_dns_entry *addr = NULL; - enum resolve_t rc; + struct Curl_dns_entry *dns = NULL; unsigned short connectport; /* the local port connect() should use! */ struct pingpong *pp = &ftpc->pp; char *str = - Curl_dyn_ptr(&pp->recvbuf) + 4; /* start on the first letter */ + curlx_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); @@ -1801,23 +1820,17 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, ptr++; /* |||12345| */ sep = ptr[0]; - /* the ISDIGIT() check here is because strtoul() accepts leading minus - etc */ if((ptr[1] == sep) && (ptr[2] == sep) && ISDIGIT(ptr[3])) { - char *endp; - unsigned long num = strtoul(&ptr[3], &endp, 10); - if(*endp != sep) - ptr = NULL; - else if(num > 0xffff) { + const char *p = &ptr[3]; + curl_off_t num; + if(curlx_str_number(&p, &num, 0xffff) || (*p != sep)) { failf(data, "Illegal port number in EPSV reply"); return CURLE_FTP_WEIRD_PASV_REPLY; } - if(ptr) { - ftpc->newport = (unsigned short)(num & 0xffff); - ftpc->newhost = strdup(control_address(conn)); - if(!ftpc->newhost) - return CURLE_OUT_OF_MEMORY; - } + ftpc->newport = (unsigned short)num; + ftpc->newhost = strdup(control_address(conn)); + if(!ftpc->newhost) + return CURLE_OUT_OF_MEMORY; } else ptr = NULL; @@ -1871,7 +1884,7 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, } else if(ftpc->count1 == 0) { /* EPSV failed, move on to PASV */ - return ftp_epsv_disable(data, conn); + return ftp_epsv_disable(data, ftpc, conn); } else { failf(data, "Bad PASV/EPSV response: %03d", ftpcode); @@ -1887,16 +1900,12 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, */ 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->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); - + (void)Curl_resolv_blocking(data, host_name, conn->primary.remote_port, + conn->ip_version, &dns); /* we connect to the proxy's port */ connectport = (unsigned short)conn->primary.remote_port; - if(!addr) { + if(!dns) { failf(data, "cannot resolve proxy host %s:%hu", host_name, connectport); return CURLE_COULDNT_RESOLVE_PROXY; } @@ -1909,34 +1918,30 @@ 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_safefree(ftpc->newhost); + free(ftpc->newhost); ftpc->newhost = strdup(control_address(conn)); if(!ftpc->newhost) return CURLE_OUT_OF_MEMORY; } - rc = Curl_resolv(data, ftpc->newhost, ftpc->newport, FALSE, &addr); - if(rc == CURLRESOLV_PENDING) - /* BLOCKING */ - (void)Curl_resolver_wait_resolv(data, &addr); - + (void)Curl_resolv_blocking(data, ftpc->newhost, ftpc->newport, + conn->ip_version, &dns); connectport = ftpc->newport; /* we connect to the remote port */ - if(!addr) { + if(!dns) { 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, + result = Curl_conn_setup(data, conn, SECONDARYSOCKET, dns, conn->bits.ftp_use_data_ssl ? CURL_CF_SSL_ENABLE : CURL_CF_SSL_DISABLE); if(result) { - Curl_resolv_unlink(data, &addr); /* we are done using this address */ if(ftpc->count1 == 0 && ftpcode == 229) - return ftp_epsv_disable(data, conn); + return ftp_epsv_disable(data, ftpc, conn); return result; } @@ -1950,27 +1955,26 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, if(data->set.verbose) /* this just dumps information about this second connection */ - ftp_pasv_verbose(data, addr->addr, ftpc->newhost, connectport); + ftp_pasv_verbose(data, dns->addr, ftpc->newhost, connectport); - Curl_resolv_unlink(data, &addr); /* we are done using this address */ - - Curl_safefree(conn->secondaryhostname); + free(conn->secondaryhostname); conn->secondary_port = ftpc->newport; conn->secondaryhostname = strdup(ftpc->newhost); if(!conn->secondaryhostname) return CURLE_OUT_OF_MEMORY; conn->bits.do_more = TRUE; - ftp_state(data, FTP_STOP); /* this phase is completed */ + ftp_state(data, ftpc, FTP_STOP); /* this phase is completed */ return result; } static CURLcode ftp_state_port_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, int ftpcode) { struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; ftpport fcmd = (ftpport)ftpc->count1; CURLcode result = CURLE_OK; @@ -1991,12 +1995,12 @@ static CURLcode ftp_state_port_resp(struct Curl_easy *data, } else /* try next */ - result = ftp_state_use_port(data, fcmd); + result = ftp_state_use_port(data, ftpc, fcmd); } else { infof(data, "Connect data stream actively"); - ftp_state(data, FTP_STOP); /* end of DO phase */ - result = ftp_dophase_done(data, FALSE); + ftp_state(data, ftpc, FTP_STOP); /* end of DO phase */ + result = ftp_dophase_done(data, ftpc, ftp, FALSE); } return result; @@ -2052,12 +2056,11 @@ static CURLcode client_write_header(struct Curl_easy *data, } static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, int ftpcode) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; switch(ftpcode) { case 213: @@ -2066,7 +2069,7 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, last .sss part is optional and means fractions of a second */ int year, month, day, hour, minute, second; struct pingpong *pp = &ftpc->pp; - char *resp = Curl_dyn_ptr(&pp->recvbuf) + 4; + char *resp = curlx_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]; @@ -2142,7 +2145,7 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, infof(data, "The requested document is not new enough"); ftp->transfer = PPTRANSFER_NONE; /* mark to not transfer data */ data->info.timecond = TRUE; - ftp_state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); return CURLE_OK; } break; @@ -2151,7 +2154,7 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, infof(data, "The requested document is not old enough"); ftp->transfer = PPTRANSFER_NONE; /* mark to not transfer data */ data->info.timecond = TRUE; - ftp_state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); return CURLE_OK; } break; @@ -2163,17 +2166,18 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, } if(!result) - result = ftp_state_type(data); + result = ftp_state_type(data, ftpc, ftp); return result; } static CURLcode ftp_state_type_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, int ftpcode, ftpstate instate) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; if(ftpcode/100 != 2) { /* "sasserftpd" and "(u)r(x)bot ftpd" both responds with 226 after a @@ -2187,26 +2191,25 @@ static CURLcode ftp_state_type_resp(struct Curl_easy *data, ftpcode); if(instate == FTP_TYPE) - result = ftp_state_size(data, conn); + result = ftp_state_size(data, ftpc, ftp); else if(instate == FTP_LIST_TYPE) - result = ftp_state_list(data); + result = ftp_state_list(data, ftpc, ftp); else if(instate == FTP_RETR_TYPE) - result = ftp_state_retr_prequote(data); + result = ftp_state_retr_prequote(data, ftpc, ftp); else if(instate == FTP_STOR_TYPE) - result = ftp_state_stor_prequote(data); + result = ftp_state_stor_prequote(data, ftpc, ftp); return result; } static CURLcode ftp_state_retr(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, curl_off_t filesize) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - CURL_TRC_FTP(data, "[%s] ftp_state_retr()", FTP_DSTATE(data)); + CURL_TRC_FTP(data, "[%s] ftp_state_retr()", FTP_CSTATE(ftpc)); if(data->set.max_filesize && (filesize > data->set.max_filesize)) { failf(data, "Maximum file size exceeded"); return CURLE_FILESIZE_EXCEEDED; @@ -2259,7 +2262,7 @@ static CURLcode ftp_state_retr(struct Curl_easy *data, /* 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); + ftp_state(data, ftpc, FTP_STOP); return CURLE_OK; } @@ -2270,26 +2273,28 @@ static CURLcode ftp_state_retr(struct Curl_easy *data, result = Curl_pp_sendf(data, &ftpc->pp, "REST %" FMT_OFF_T, data->state.resume_from); if(!result) - ftp_state(data, FTP_RETR_REST); + ftp_state(data, ftpc, FTP_RETR_REST); } else { /* no resume */ result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); if(!result) - ftp_state(data, FTP_RETR); + ftp_state(data, ftpc, FTP_RETR); } return result; } static CURLcode ftp_state_size_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, int ftpcode, ftpstate instate) { CURLcode result = CURLE_OK; curl_off_t filesize = -1; - char *buf = Curl_dyn_ptr(&data->conn->proto.ftpc.pp.recvbuf); - size_t len = data->conn->proto.ftpc.pp.nfinal; + char *buf = curlx_dyn_ptr(&ftpc->pp.recvbuf); + size_t len = ftpc->pp.nfinal; /* get the size from the ascii string: */ if(ftpcode == 213) { @@ -2297,7 +2302,7 @@ 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 = memchr(start, '\r', len); + const char *fdigit = memchr(start, '\r', len); if(fdigit) { fdigit--; if(*fdigit == '\n') @@ -2307,9 +2312,8 @@ static CURLcode ftp_state_size_resp(struct Curl_easy *data, } else fdigit = start; - /* ignores parsing errors, which will make the size remain unknown */ - (void)curlx_strtoofft(fdigit, NULL, 10, &filesize); - + if(curlx_str_number(&fdigit, &filesize, CURL_OFF_T_MAX)) + filesize = -1; /* size remain unknown */ } else if(ftpcode == 550) { /* "No such file or directory" */ /* allow a SIZE failure for (resumed) uploads, when probing what command @@ -2332,27 +2336,27 @@ static CURLcode ftp_state_size_resp(struct Curl_easy *data, } #endif Curl_pgrsSetDownloadSize(data, filesize); - result = ftp_state_rest(data, data->conn); + result = ftp_state_rest(data, ftpc, ftp); } else if(instate == FTP_RETR_SIZE) { Curl_pgrsSetDownloadSize(data, filesize); - result = ftp_state_retr(data, filesize); + result = ftp_state_retr(data, ftpc, ftp, filesize); } else if(instate == FTP_STOR_SIZE) { data->state.resume_from = filesize; - result = ftp_state_ul_setup(data, TRUE); + result = ftp_state_ul_setup(data, ftpc, ftp, TRUE); } return result; } static CURLcode ftp_state_rest_resp(struct Curl_easy *data, - struct connectdata *conn, + struct ftp_conn *ftpc, + struct FTP *ftp, int ftpcode, ftpstate instate) { CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; switch(instate) { case FTP_REST: @@ -2365,7 +2369,7 @@ static CURLcode ftp_state_rest_resp(struct Curl_easy *data, return result; } #endif - result = ftp_state_prepare_transfer(data); + result = ftp_state_prepare_transfer(data, ftpc, ftp); break; case FTP_RETR_REST: @@ -2376,7 +2380,7 @@ static CURLcode ftp_state_rest_resp(struct Curl_easy *data, else { result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); if(!result) - ftp_state(data, FTP_RETR); + ftp_state(data, ftpc, FTP_RETR); } break; } @@ -2385,26 +2389,25 @@ static CURLcode ftp_state_rest_resp(struct Curl_easy *data, } static CURLcode ftp_state_stor_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, int ftpcode, ftpstate instate) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; if(ftpcode >= 400) { failf(data, "Failed FTP upload: %0d", ftpcode); - ftp_state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); /* oops, we never close the sockets! */ return CURLE_UPLOAD_FAILED; } - conn->proto.ftpc.state_saved = instate; + ftpc->state_saved = instate; /* 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 */ + ftp_state(data, ftpc, FTP_STOP); /* no longer in STOR state */ result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &connected); if(result) @@ -2413,21 +2416,21 @@ static CURLcode ftp_state_stor_resp(struct Curl_easy *data, if(!connected) { infof(data, "Data conn was not available immediately"); ftpc->wait_data_conn = TRUE; - return ftp_check_ctrl_on_data_wait(data); + return ftp_check_ctrl_on_data_wait(data, ftpc); } ftpc->wait_data_conn = FALSE; } - return InitiateTransfer(data); + return ftp_initiate_transfer(data, ftpc); } /* for LIST and RETR responses */ static CURLcode ftp_state_get_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, int ftpcode, ftpstate instate) { CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; if((ftpcode == 150) || (ftpcode == 125)) { @@ -2471,8 +2474,8 @@ static CURLcode ftp_state_get_resp(struct Curl_easy *data, * those cases only confuses us. * * Example D above makes this parsing a little tricky */ - char *bytes; - char *buf = Curl_dyn_ptr(&conn->proto.ftpc.pp.recvbuf); + const char *bytes; + char *buf = curlx_dyn_ptr(&ftpc->pp.recvbuf); bytes = strstr(buf, " bytes"); if(bytes) { long in = (long)(--bytes-buf); @@ -2493,7 +2496,8 @@ static CURLcode ftp_state_get_resp(struct Curl_easy *data, if(bytes) { ++bytes; /* get the number! */ - (void)curlx_strtoofft(bytes, NULL, 10, &size); + if(curlx_str_number(&bytes, &size, CURL_OFF_T_MAX)) + size = 1; } } } @@ -2511,11 +2515,10 @@ static CURLcode ftp_state_get_resp(struct Curl_easy *data, 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; + ftpc->state_saved = instate; + ftpc->retr_size_saved = size; if(data->set.ftp_use_port) { - struct ftp_conn *ftpc = &conn->proto.ftpc; bool connected; result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &connected); @@ -2524,19 +2527,19 @@ static CURLcode ftp_state_get_resp(struct Curl_easy *data, if(!connected) { infof(data, "Data conn was not available immediately"); - ftp_state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); ftpc->wait_data_conn = TRUE; - return ftp_check_ctrl_on_data_wait(data); + return ftp_check_ctrl_on_data_wait(data, ftpc); } ftpc->wait_data_conn = FALSE; } - return InitiateTransfer(data); + return ftp_initiate_transfer(data, ftpc); } else { if((instate == FTP_LIST) && (ftpcode == 450)) { /* simply no matching files in the dir listing */ ftp->transfer = PPTRANSFER_NONE; /* do not download anything */ - ftp_state(data, FTP_STOP); /* this phase is over */ + ftp_state(data, ftpc, FTP_STOP); /* this phase is over */ } else { failf(data, "RETR response: %03d", ftpcode); @@ -2550,12 +2553,12 @@ static CURLcode ftp_state_get_resp(struct Curl_easy *data, } /* after USER, PASS and ACCT */ -static CURLcode ftp_state_loggedin(struct Curl_easy *data) +static CURLcode ftp_state_loggedin(struct Curl_easy *data, + struct ftp_conn *ftpc) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - if(conn->bits.ftp_use_control_ssl) { + if(data->conn->bits.ftp_use_control_ssl) { /* PBSZ = PROTECTION BUFFER SIZE. The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says: @@ -2570,44 +2573,43 @@ static CURLcode ftp_state_loggedin(struct Curl_easy *data) parameter of '0' to indicate that no buffering is taking place and the data connection should not be encapsulated. */ - result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "PBSZ %d", 0); + result = Curl_pp_sendf(data, &ftpc->pp, "PBSZ %d", 0); if(!result) - ftp_state(data, FTP_PBSZ); + ftp_state(data, ftpc, FTP_PBSZ); } else { - result = ftp_state_pwd(data, conn); + result = ftp_state_pwd(data, ftpc); } return result; } /* for USER and PASS responses */ static CURLcode ftp_state_user_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, int ftpcode) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; /* some need password anyway, and others just return 2xx ignored */ if((ftpcode == 331) && (ftpc->state == FTP_USER)) { /* 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 : ""); + data->conn->passwd ? data->conn->passwd : ""); if(!result) - ftp_state(data, FTP_PASS); + ftp_state(data, ftpc, FTP_PASS); } else if(ftpcode/100 == 2) { /* 230 User ... logged in. (the user logged in with or without password) */ - result = ftp_state_loggedin(data); + result = ftp_state_loggedin(data, ftpc); } else if(ftpcode == 332) { if(data->set.str[STRING_FTP_ACCOUNT]) { result = Curl_pp_sendf(data, &ftpc->pp, "ACCT %s", data->set.str[STRING_FTP_ACCOUNT]); if(!result) - ftp_state(data, FTP_ACCT); + ftp_state(data, ftpc, FTP_ACCT); } else { failf(data, "ACCT requested but none available"); @@ -2628,7 +2630,7 @@ static CURLcode ftp_state_user_resp(struct Curl_easy *data, data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]); if(!result) { ftpc->ftp_trying_alternative = TRUE; - ftp_state(data, FTP_USER); + ftp_state(data, ftpc, FTP_USER); } } else { @@ -2641,6 +2643,7 @@ static CURLcode ftp_state_user_resp(struct Curl_easy *data, /* for ACCT response */ static CURLcode ftp_state_acct_resp(struct Curl_easy *data, + struct ftp_conn *ftpc, int ftpcode) { CURLcode result = CURLE_OK; @@ -2649,26 +2652,30 @@ static CURLcode ftp_state_acct_resp(struct Curl_easy *data, result = CURLE_FTP_WEIRD_PASS_REPLY; /* FIX */ } else - result = ftp_state_loggedin(data); + result = ftp_state_loggedin(data, ftpc); return result; } -static CURLcode ftp_statemachine(struct Curl_easy *data, - struct connectdata *conn) +static CURLcode ftp_pp_statemachine(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result; int ftpcode; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; + struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); + struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); + struct pingpong *pp; static const char * const ftpauth[] = { "SSL", "TLS" }; size_t nread = 0; + if(!ftpc || !ftp) + return CURLE_FAILED_INIT; + pp = &ftpc->pp; if(pp->sendleft) return Curl_pp_flushsend(data, pp); - result = ftp_readresp(data, FIRSTSOCKET, pp, &ftpcode, &nread); + result = ftp_readresp(data, ftpc, FIRSTSOCKET, pp, &ftpcode, &nread); if(result) return result; @@ -2680,7 +2687,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, /* 230 User logged in - already! Take as 220 if TLS required. */ if(data->set.use_ssl <= CURLUSESSL_TRY || conn->bits.ftp_use_control_ssl) - return ftp_state_user_resp(data, ftpcode); + return ftp_state_user_resp(data, ftpc, ftpcode); } else if(ftpcode != 220) { failf(data, "Got a %03d ftp-server response when 220 was expected", @@ -2708,8 +2715,8 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, #endif if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) { - /* We do not have a SSL/TLS control connection yet, but FTPS is - requested. Try a FTPS connection now */ + /* We do not have an SSL/TLS control connection yet, but FTPS is + requested. Try an FTPS connection now */ ftpc->count3 = 0; switch(data->set.ftpsslauth) { @@ -2730,10 +2737,10 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s", ftpauth[ftpc->count1]); if(!result) - ftp_state(data, FTP_AUTH); + ftp_state(data, ftpc, FTP_AUTH); } else - result = ftp_state_user(data, conn); + result = ftp_state_user(data, ftpc, conn); break; case FTP_AUTH: @@ -2763,7 +2770,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, if(!result) { conn->bits.ftp_use_data_ssl = FALSE; /* clear-text data */ conn->bits.ftp_use_control_ssl = TRUE; /* SSL on control */ - result = ftp_state_user(data, conn); + result = ftp_state_user(data, ftpc, conn); } } else if(ftpc->count3 < 1) { @@ -2779,17 +2786,17 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, result = CURLE_USE_SSL_FAILED; else /* ignore the failure and continue */ - result = ftp_state_user(data, conn); + result = ftp_state_user(data, ftpc, conn); } break; case FTP_USER: case FTP_PASS: - result = ftp_state_user_resp(data, ftpcode); + result = ftp_state_user_resp(data, ftpc, ftpcode); break; case FTP_ACCT: - result = ftp_state_acct_resp(data, ftpcode); + result = ftp_state_acct_resp(data, ftpc, ftpcode); break; case FTP_PBSZ: @@ -2797,7 +2804,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, Curl_pp_sendf(data, &ftpc->pp, "PROT %c", data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P'); if(!result) - ftp_state(data, FTP_PROT); + ftp_state(data, ftpc, FTP_PROT); break; case FTP_PROT: @@ -2816,10 +2823,10 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, */ result = Curl_pp_sendf(data, &ftpc->pp, "%s", "CCC"); if(!result) - ftp_state(data, FTP_CCC); + ftp_state(data, ftpc, FTP_CCC); } else - result = ftp_state_pwd(data, conn); + result = ftp_state_pwd(data, ftpc); break; case FTP_CCC: @@ -2838,16 +2845,16 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, } if(!result) /* Then continue as normal */ - result = ftp_state_pwd(data, conn); + result = ftp_state_pwd(data, ftpc); break; case FTP_PWD: if(ftpcode == 257) { - char *ptr = Curl_dyn_ptr(&pp->recvbuf) + 4; /* start on the first - letter */ + char *ptr = curlx_dyn_ptr(&pp->recvbuf) + 4; /* start on the first + letter */ bool entry_extracted = FALSE; struct dynbuf out; - Curl_dyn_init(&out, 1000); + curlx_dyn_init(&out, 1000); /* Reply format is like 257[rubbish]"" and the @@ -2868,18 +2875,18 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, if('\"' == *ptr) { if('\"' == ptr[1]) { /* "quote-doubling" */ - result = Curl_dyn_addn(&out, &ptr[1], 1); + result = curlx_dyn_addn(&out, &ptr[1], 1); ptr++; } else { /* end of path */ - if(Curl_dyn_len(&out)) + if(curlx_dyn_len(&out)) entry_extracted = TRUE; break; /* get out of this loop */ } } else - result = Curl_dyn_addn(&out, ptr, 1); + result = curlx_dyn_addn(&out, ptr, 1); if(result) return result; } @@ -2896,7 +2903,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); + char *dir = curlx_dyn_ptr(&out); if(!ftpc->server_os && dir[0] != '/') { result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST"); @@ -2904,34 +2911,40 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, free(dir); return result; } - Curl_safefree(ftpc->entrypath); + free(ftpc->entrypath); ftpc->entrypath = dir; /* remember this */ infof(data, "Entry path is '%s'", ftpc->entrypath); /* also save it where getinfo can access it: */ - data->state.most_recent_ftp_entrypath = ftpc->entrypath; - ftp_state(data, FTP_SYST); + free(data->state.most_recent_ftp_entrypath); + data->state.most_recent_ftp_entrypath = strdup(ftpc->entrypath); + if(!data->state.most_recent_ftp_entrypath) + return CURLE_OUT_OF_MEMORY; + ftp_state(data, ftpc, FTP_SYST); break; } - Curl_safefree(ftpc->entrypath); + free(ftpc->entrypath); ftpc->entrypath = dir; /* remember this */ infof(data, "Entry path is '%s'", ftpc->entrypath); /* also save it where getinfo can access it: */ - data->state.most_recent_ftp_entrypath = ftpc->entrypath; + free(data->state.most_recent_ftp_entrypath); + data->state.most_recent_ftp_entrypath = strdup(ftpc->entrypath); + if(!data->state.most_recent_ftp_entrypath) + return CURLE_OUT_OF_MEMORY; } else { /* could not get the path */ - Curl_dyn_free(&out); + curlx_dyn_free(&out); infof(data, "Failed to figure out path"); } } - ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_DSTATE(data)); + ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */ + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc)); break; case FTP_SYST: if(ftpcode == 215) { - char *ptr = Curl_dyn_ptr(&pp->recvbuf) + 4; /* start on the first + char *ptr = curlx_dyn_ptr(&pp->recvbuf) + 4; /* start on the first letter */ char *os; char *start; @@ -2956,33 +2969,33 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, return result; } /* remember target server OS */ - Curl_safefree(ftpc->server_os); + free(ftpc->server_os); ftpc->server_os = os; - ftp_state(data, FTP_NAMEFMT); + ftp_state(data, ftpc, FTP_NAMEFMT); break; } /* Nothing special for the target server. */ /* remember target server OS */ - Curl_safefree(ftpc->server_os); + free(ftpc->server_os); ftpc->server_os = os; } else { /* Cannot identify server OS. Continue anyway and cross fingers. */ } - ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_DSTATE(data)); + ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */ + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc)); break; case FTP_NAMEFMT: if(ftpcode == 250) { /* Name format change successful: reload initial path. */ - ftp_state_pwd(data, conn); + ftp_state_pwd(data, ftpc); break; } - ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_DSTATE(data)); + ftp_state(data, ftpc, FTP_STOP); /* we are done with CONNECT phase! */ + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_CSTATE(ftpc)); break; case FTP_QUOTE: @@ -2995,7 +3008,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, result = CURLE_QUOTE_ERROR; } else - result = ftp_state_quote(data, FALSE, ftpc->state); + result = ftp_state_quote(data, ftpc, ftp, FALSE, ftpc->state); break; case FTP_CWD: @@ -3014,7 +3027,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, result = Curl_pp_sendf(data, &ftpc->pp, "MKD %s", ftpc->dirs[ftpc->cwdcount - 1]); if(!result) - ftp_state(data, FTP_MKD); + ftp_state(data, ftpc, FTP_MKD); } else { /* return failure */ @@ -3032,7 +3045,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", ftpc->dirs[ftpc->cwdcount - 1]); else - result = ftp_state_mdtm(data); + result = ftp_state_mdtm(data, ftpc, ftp); } break; @@ -3043,7 +3056,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, result = CURLE_REMOTE_ACCESS_DENIED; } else { - ftp_state(data, FTP_CWD); + ftp_state(data, ftpc, FTP_CWD); /* send CWD */ result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", ftpc->dirs[ftpc->cwdcount - 1]); @@ -3051,25 +3064,25 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, break; case FTP_MDTM: - result = ftp_state_mdtm_resp(data, ftpcode); + result = ftp_state_mdtm_resp(data, ftpc, ftp, ftpcode); break; case FTP_TYPE: case FTP_LIST_TYPE: case FTP_RETR_TYPE: case FTP_STOR_TYPE: - result = ftp_state_type_resp(data, ftpcode, ftpc->state); + result = ftp_state_type_resp(data, ftpc, ftp, ftpcode, ftpc->state); break; case FTP_SIZE: case FTP_RETR_SIZE: case FTP_STOR_SIZE: - result = ftp_state_size_resp(data, ftpcode, ftpc->state); + result = ftp_state_size_resp(data, ftpc, ftp, ftpcode, ftpc->state); break; case FTP_REST: case FTP_RETR_REST: - result = ftp_state_rest_resp(data, conn, ftpcode, ftpc->state); + result = ftp_state_rest_resp(data, ftpc, ftp, ftpcode, ftpc->state); break; case FTP_PRET: @@ -3078,30 +3091,30 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, failf(data, "PRET command not accepted: %03d", ftpcode); return CURLE_FTP_PRET_FAILED; } - result = ftp_state_use_pasv(data, conn); + result = ftp_state_use_pasv(data, ftpc, conn); break; case FTP_PASV: - result = ftp_state_pasv_resp(data, ftpcode); + result = ftp_state_pasv_resp(data, ftpc, ftpcode); break; case FTP_PORT: - result = ftp_state_port_resp(data, ftpcode); + result = ftp_state_port_resp(data, ftpc, ftp, ftpcode); break; case FTP_LIST: case FTP_RETR: - result = ftp_state_get_resp(data, ftpcode, ftpc->state); + result = ftp_state_get_resp(data, ftpc, ftp, ftpcode, ftpc->state); break; case FTP_STOR: - result = ftp_state_stor_resp(data, ftpcode, ftpc->state); + result = ftp_state_stor_resp(data, ftpc, ftpcode, ftpc->state); break; case FTP_QUIT: default: /* internal error */ - ftp_state(data, FTP_STOP); + ftp_state(data, ftpc, FTP_STOP); break; } } /* if(ftpcode) */ @@ -3111,11 +3124,10 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, /* called repeatedly until done from multi.c */ -static CURLcode ftp_multi_statemach(struct Curl_easy *data, - bool *done) +static CURLcode ftp_statemach(struct Curl_easy *data, + struct ftp_conn *ftpc, + bool *done) { - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; CURLcode result = Curl_pp_statemach(data, &ftpc->pp, FALSE, FALSE); /* Check for the state outside of the Curl_socket_check() return code checks @@ -3126,14 +3138,23 @@ static CURLcode ftp_multi_statemach(struct Curl_easy *data, return result; } +/* called repeatedly until done from multi.c */ +static CURLcode ftp_multi_statemach(struct Curl_easy *data, + bool *done) +{ + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + return ftpc ? ftp_statemach(data, ftpc, done) : CURLE_FAILED_INIT; +} + static CURLcode ftp_block_statemach(struct Curl_easy *data, - struct connectdata *conn) + struct ftp_conn *ftpc) { - struct ftp_conn *ftpc = &conn->proto.ftpc; struct pingpong *pp = &ftpc->pp; CURLcode result = CURLE_OK; while(ftpc->state != FTP_STOP) { + if(ftpc->shutdown) + CURL_TRC_FTP(data, "in shutdown, waiting for server response"); result = Curl_pp_statemach(data, pp, TRUE, TRUE /* disconnecting */); if(result) break; @@ -3155,15 +3176,17 @@ static CURLcode ftp_connect(struct Curl_easy *data, { CURLcode result; struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + struct pingpong *pp; *done = FALSE; /* default to not done yet */ - + if(!ftpc) + return CURLE_FAILED_INIT; + pp = &ftpc->pp; /* We always support persistent connections on ftp */ connkeep(conn, "FTP default"); - PINGPONG_SETUP(pp, ftp_statemachine, ftp_endofresp); + PINGPONG_SETUP(pp, ftp_pp_statemachine, ftp_endofresp); if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) { /* BLOCKING */ @@ -3177,9 +3200,9 @@ static CURLcode ftp_connect(struct Curl_easy *data, /* When we connect, we start in the state where we await the 220 response */ - ftp_state(data, FTP_WAIT220); + ftp_state(data, ftpc, FTP_WAIT220); - result = ftp_multi_statemach(data, done); + result = ftp_statemach(data, ftpc, done); return result; } @@ -3197,18 +3220,19 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, bool premature) { struct connectdata *conn = data->conn; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; + struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + struct pingpong *pp; ssize_t nread; int ftpcode; CURLcode result = CURLE_OK; char *rawPath = NULL; size_t pathLen = 0; - if(!ftp) + if(!ftp || !ftpc) return CURLE_OK; + pp = &ftpc->pp; switch(status) { case CURLE_BAD_DOWNLOAD_RESUME: case CURLE_FTP_WEIRD_PASV_REPLY: @@ -3293,11 +3317,11 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, /* shut down the socket to inform the server we are done */ -#ifdef _WIN32_WCE +#ifdef UNDER_CE shutdown(conn->sock[SECONDARYSOCKET], 2); /* SD_BOTH */ #endif - if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) { + if(Curl_conn_is_setup(conn, SECONDARYSOCKET)) { if(!result && ftpc->dont_check && data->req.maxdownload > 0) { /* partial download completed */ result = Curl_pp_sendf(data, pp, "%s", "ABOR"); @@ -3309,7 +3333,7 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, } } - close_secondarysocket(data); + close_secondarysocket(data, ftpc); } if(!result && (ftp->transfer == PPTRANSFER_BODY) && ftpc->ctl_valid && @@ -3323,7 +3347,7 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, timediff_t old_time = pp->response_time; pp->response_time = 60*1000; /* give it only a minute for now */ - pp->response = Curl_now(); /* timeout relative now */ + pp->response = curlx_now(); /* timeout relative now */ result = Curl_GetFTPResponse(data, &nread, &ftpcode); @@ -3335,10 +3359,8 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, connclose(conn, "Timeout or similar in FTP DONE operation"); /* close */ } - if(result) { - Curl_safefree(ftp->pathalloc); + if(result) return result; - } if(ftpc->dont_check && data->req.maxdownload > 0) { /* we have just sent ABOR and there is no reliable way to check if it was @@ -3371,10 +3393,13 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, use checking further */ ; else if(data->state.upload) { - if((-1 != data->state.infilesize) && - (data->state.infilesize != data->req.writebytecount) && - !data->set.crlf && - (ftp->transfer == PPTRANSFER_BODY)) { + if((ftp->transfer == PPTRANSFER_BODY) && + (data->state.infilesize != -1) && /* upload with known size */ + ((!data->set.crlf && !data->state.prefer_ascii && /* no conversion */ + (data->state.infilesize != data->req.writebytecount)) || + ((data->set.crlf || data->state.prefer_ascii) && /* maybe crlf conv */ + (data->state.infilesize > data->req.writebytecount)) + )) { failf(data, "Uploaded unaligned file size (%" FMT_OFF_T " out of %" FMT_OFF_T " bytes)", data->req.writebytecount, data->state.infilesize); @@ -3403,9 +3428,8 @@ 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); + result = ftp_sendquote(data, ftpc, data->set.postquote); + CURL_TRC_FTP(data, "[%s] done, result=%d", FTP_CSTATE(ftpc), result); return result; } @@ -3421,10 +3445,10 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, static CURLcode ftp_sendquote(struct Curl_easy *data, - struct connectdata *conn, struct curl_slist *quote) + struct ftp_conn *ftpc, + struct curl_slist *quote) { struct curl_slist *item; - struct ftp_conn *ftpc = &conn->proto.ftpc; struct pingpong *pp = &ftpc->pp; item = quote; @@ -3448,7 +3472,7 @@ CURLcode ftp_sendquote(struct Curl_easy *data, result = Curl_pp_sendf(data, &ftpc->pp, "%s", cmd); if(!result) { - pp->response = Curl_now(); /* timeout relative now */ + pp->response = curlx_now(); /* timeout relative now */ result = Curl_GetFTPResponse(data, &nread, &ftpcode); } if(result) @@ -3472,10 +3496,10 @@ CURLcode ftp_sendquote(struct Curl_easy *data, * * Returns TRUE if we in the current situation should send TYPE */ -static int ftp_need_type(struct connectdata *conn, +static int ftp_need_type(struct ftp_conn *ftpc, bool ascii_wanted) { - return conn->proto.ftpc.transfertype != (ascii_wanted ? 'A' : 'I'); + return ftpc->transfertype != (ascii_wanted ? 'A' : 'I'); } /*********************************************************************** @@ -3487,21 +3511,21 @@ static int ftp_need_type(struct connectdata *conn, * If the transfer type is not sent, simulate on OK response in newstate */ static CURLcode ftp_nb_type(struct Curl_easy *data, - struct connectdata *conn, + struct ftp_conn *ftpc, + struct FTP *ftp, bool ascii, ftpstate newstate) { - struct ftp_conn *ftpc = &conn->proto.ftpc; CURLcode result; char want = (char)(ascii ? 'A' : 'I'); if(ftpc->transfertype == want) { - ftp_state(data, newstate); - return ftp_state_type_resp(data, 200, newstate); + ftp_state(data, ftpc, newstate); + return ftp_state_type_resp(data, ftpc, ftp, 200, newstate); } result = Curl_pp_sendf(data, &ftpc->pp, "TYPE %c", want); if(!result) { - ftp_state(data, newstate); + ftp_state(data, ftpc, newstate); /* keep track of our current transfer type */ ftpc->transfertype = want; @@ -3545,16 +3569,17 @@ ftp_pasv_verbose(struct Curl_easy *data, static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) { struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); CURLcode result = CURLE_OK; bool connected = FALSE; bool complete = FALSE; - /* the ftp struct is inited in ftp_connect(). If we are connecting to an HTTP * proxy then the state will not be valid until after that connection is * complete */ - struct FTP *ftp = NULL; + if(!ftpc || !ftp) + return CURLE_FAILED_INIT; /* 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: @@ -3571,20 +3596,17 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) 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); + return ftp_epsv_disable(data, ftpc, conn); } *completep = (int)complete; return result; } } - /* Curl_proxy_connect might have moved the protocol state */ - ftp = data->req.p.ftp; - if(ftpc->state) { /* already in a state so skip the initial commands. They are only done to kickstart the do_more state */ - result = ftp_multi_statemach(data, &complete); + result = ftp_statemach(data, ftpc, &complete); *completep = (int)complete; @@ -3606,14 +3628,14 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) if(ftpc->wait_data_conn) { bool serv_conned; - result = Curl_conn_connect(data, SECONDARYSOCKET, TRUE, &serv_conned); + result = Curl_conn_connect(data, SECONDARYSOCKET, FALSE, &serv_conned); if(result) return result; /* Failed to accept data connection */ if(serv_conned) { /* It looks data connection is established */ ftpc->wait_data_conn = FALSE; - result = InitiateTransfer(data); + result = ftp_initiate_transfer(data, ftpc); if(result) return result; @@ -3622,19 +3644,23 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) connected back to us */ } else { - result = ftp_check_ctrl_on_data_wait(data); + result = ftp_check_ctrl_on_data_wait(data, ftpc); if(result) return result; } } else if(data->state.upload) { - result = ftp_nb_type(data, conn, data->state.prefer_ascii, + result = ftp_nb_type(data, ftpc, ftp, data->state.prefer_ascii, FTP_STOR_TYPE); if(result) return result; - result = ftp_multi_statemach(data, &complete); - *completep = (int)complete; + result = ftp_statemach(data, ftpc, &complete); + /* ftp_nb_type() might have skipped sending `TYPE A|I` when not + * deemed necessary and directly sent `STORE name`. If this was + * then complete, but we are still waiting on the data connection, + * the transfer has not been initiated yet. */ + *completep = (int)(ftpc->wait_data_conn ? 0 : complete); } else { /* download */ @@ -3656,20 +3682,20 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) /* But only if a body transfer was requested. */ if(ftp->transfer == PPTRANSFER_BODY) { - result = ftp_nb_type(data, conn, TRUE, FTP_LIST_TYPE); + result = ftp_nb_type(data, ftpc, ftp, TRUE, FTP_LIST_TYPE); if(result) return result; } /* otherwise just fall through */ } else { - result = ftp_nb_type(data, conn, data->state.prefer_ascii, + result = ftp_nb_type(data, ftpc, ftp, data->state.prefer_ascii, FTP_RETR_TYPE); if(result) return result; } - result = ftp_multi_statemach(data, &complete); + result = ftp_statemach(data, ftpc, &complete); *completep = (int)complete; } return result; @@ -3681,7 +3707,7 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) if(!ftpc->wait_data_conn) { /* no waiting for the data connection so this is now complete */ *completep = 1; - CURL_TRC_FTP(data, "[%s] DO-MORE phase ends with %d", FTP_DSTATE(data), + CURL_TRC_FTP(data, "[%s] DO-MORE phase ends with %d", FTP_CSTATE(ftpc), (int)result); } @@ -3700,41 +3726,42 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) static CURLcode ftp_perform(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, bool *connected, /* connect status after PASV / PORT */ bool *dophase_done) { /* this is FTP and no proxy */ CURLcode result = CURLE_OK; - CURL_TRC_FTP(data, "[%s] DO phase starts", FTP_DSTATE(data)); + CURL_TRC_FTP(data, "[%s] DO phase starts", FTP_CSTATE(ftpc)); if(data->req.no_body) { /* requested no body means no transfer... */ - struct FTP *ftp = data->req.p.ftp; ftp->transfer = PPTRANSFER_INFO; } *dophase_done = FALSE; /* not done yet */ /* start the first command in the DO phase */ - result = ftp_state_quote(data, TRUE, FTP_QUOTE); + result = ftp_state_quote(data, ftpc, ftp, TRUE, FTP_QUOTE); if(result) return result; /* run the state-machine */ - result = ftp_multi_statemach(data, dophase_done); + result = ftp_statemach(data, ftpc, dophase_done); *connected = Curl_conn_is_connected(data->conn, SECONDARYSOCKET); if(*connected) infof(data, "[FTP] [%s] perform, DATA connection established", - FTP_DSTATE(data)); + FTP_CSTATE(ftpc)); else CURL_TRC_FTP(data, "[%s] perform, awaiting DATA connect", - FTP_DSTATE(data)); + FTP_CSTATE(ftpc)); if(*dophase_done) { - CURL_TRC_FTP(data, "[%s] DO phase is complete1", FTP_DSTATE(data)); + CURL_TRC_FTP(data, "[%s] DO phase is complete1", FTP_CSTATE(ftpc)); } return result; @@ -3748,10 +3775,11 @@ static void wc_data_dtor(void *ptr) free(ftpwc); } -static CURLcode init_wc_data(struct Curl_easy *data) +static CURLcode init_wc_data(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { char *last_slash; - struct FTP *ftp = data->req.p.ftp; char *path = ftp->path; struct WildcardData *wildcard = data->wildcard; CURLcode result = CURLE_OK; @@ -3762,7 +3790,7 @@ static CURLcode init_wc_data(struct Curl_easy *data) last_slash++; if(last_slash[0] == '\0') { wildcard->state = CURLWC_CLEAN; - return ftp_parse_url_path(data); + return ftp_parse_url_path(data, ftpc, ftp); } wildcard->pattern = strdup(last_slash); if(!wildcard->pattern) @@ -3778,7 +3806,7 @@ static CURLcode init_wc_data(struct Curl_easy *data) } else { /* only list */ wildcard->state = CURLWC_CLEAN; - return ftp_parse_url_path(data); + return ftp_parse_url_path(data, ftpc, ftp); } } @@ -3807,7 +3835,7 @@ static CURLcode init_wc_data(struct Curl_easy *data) data->set.ftp_filemethod = FTPFILE_MULTICWD; /* try to parse ftp URL */ - result = ftp_parse_url_path(data); + result = ftp_parse_url_path(data, ftpc, ftp); if(result) { goto fail; } @@ -3841,16 +3869,17 @@ fail: return result; } -static CURLcode wc_statemach(struct Curl_easy *data) +static CURLcode wc_statemach(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { struct WildcardData * const wildcard = data->wildcard; - struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; for(;;) { switch(wildcard->state) { case CURLWC_INIT: - result = init_wc_data(data); + result = init_wc_data(data, ftpc, ftp); if(wildcard->state == CURLWC_CLEAN) /* only listing! */ return result; @@ -3882,10 +3911,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_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); if(!tmp_path) @@ -3922,7 +3949,7 @@ static CURLcode wc_statemach(struct Curl_easy *data) if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE) ftpc->known_filesize = finfo->size; - result = ftp_parse_url_path(data); + result = ftp_parse_url_path(data, ftpc, ftp); if(result) return result; @@ -3986,10 +4013,12 @@ static CURLcode wc_statemach(struct Curl_easy *data) static CURLcode ftp_do(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); *done = FALSE; /* default to false */ + if(!ftpc || !ftp) + return CURLE_FAILED_INIT; ftpc->wait_data_conn = FALSE; /* default to no such wait */ #ifdef CURL_PREFER_LF_LINEENDS @@ -4011,7 +4040,7 @@ static CURLcode ftp_do(struct Curl_easy *data, bool *done) #endif /* CURL_PREFER_LF_LINEENDS */ if(data->state.wildcardmatch) { - result = wc_statemach(data); + result = wc_statemach(data, ftpc, ftp); if(data->wildcard->state == CURLWC_SKIP || data->wildcard->state == CURLWC_DONE) { /* do not call ftp_regular_transfer */ @@ -4021,12 +4050,12 @@ static CURLcode ftp_do(struct Curl_easy *data, bool *done) return result; } else { /* no wildcard FSM needed */ - result = ftp_parse_url_path(data); + result = ftp_parse_url_path(data, ftpc, ftp); if(result) return result; } - result = ftp_regular_transfer(data, done); + result = ftp_regular_transfer(data, ftpc, ftp, done); return result; } @@ -4041,24 +4070,26 @@ static CURLcode ftp_do(struct Curl_easy *data, bool *done) * connection. * */ -static CURLcode ftp_quit(struct Curl_easy *data, struct connectdata *conn) +static CURLcode ftp_quit(struct Curl_easy *data, + struct ftp_conn *ftpc) { CURLcode result = CURLE_OK; - if(conn->proto.ftpc.ctl_valid) { - result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "QUIT"); + if(ftpc->ctl_valid) { + CURL_TRC_FTP(data, "sending QUIT to close session"); + result = Curl_pp_sendf(data, &ftpc->pp, "%s", "QUIT"); if(result) { failf(data, "Failure sending QUIT command: %s", curl_easy_strerror(result)); - conn->proto.ftpc.ctl_valid = FALSE; /* mark control connection as bad */ - connclose(conn, "QUIT command failed"); /* mark for connection closure */ - ftp_state(data, FTP_STOP); + ftpc->ctl_valid = FALSE; /* mark control connection as bad */ + connclose(data->conn, "QUIT command failed"); /* mark for closure */ + ftp_state(data, ftpc, FTP_STOP); return result; } - ftp_state(data, FTP_QUIT); + ftp_state(data, ftpc, FTP_QUIT); - result = ftp_block_statemach(data, conn); + result = ftp_block_statemach(data, ftpc); } return result; @@ -4075,9 +4106,10 @@ static CURLcode ftp_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; + struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); + if(!ftpc) + return CURLE_FAILED_INIT; /* We cannot send quit unconditionally. If this connection is stale or 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. @@ -4085,26 +4117,12 @@ static CURLcode ftp_disconnect(struct Curl_easy *data, 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. */ + ftpc->shutdown = TRUE; if(dead_connection) ftpc->ctl_valid = FALSE; /* The FTP session may or may not have been allocated/setup at this point! */ - (void)ftp_quit(data, conn); /* ignore errors on the QUIT */ - - if(ftpc->entrypath) { - if(data->state.most_recent_ftp_entrypath == ftpc->entrypath) { - data->state.most_recent_ftp_entrypath = NULL; - } - Curl_safefree(ftpc->entrypath); - } - - freedirs(ftpc); - Curl_safefree(ftpc->account); - Curl_safefree(ftpc->alternative_to_user); - Curl_safefree(ftpc->prevpath); - Curl_safefree(ftpc->server_os); - Curl_pp_disconnect(pp); - Curl_sec_end(conn); + (void)ftp_quit(data, ftpc); /* ignore errors on the QUIT */ return CURLE_OK; } @@ -4116,12 +4134,10 @@ static CURLcode ftp_disconnect(struct Curl_easy *data, * */ static -CURLcode ftp_parse_url_path(struct Curl_easy *data) +CURLcode ftp_parse_url_path(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp) { - /* the ftp struct is already inited in ftp_connect() */ - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; const char *slashPos = NULL; const char *fileName = NULL; CURLcode result = CURLE_OK; @@ -4244,7 +4260,7 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data) if((data->set.ftp_filemethod == FTPFILE_NOCWD) && (rawPath[0] == '/')) ftpc->cwddone = TRUE; /* skip CWD for absolute paths */ else { /* newly created FTP connections are already in entry path */ - const char *oldPath = conn->bits.reuse ? ftpc->prevpath : ""; + const char *oldPath = data->conn->bits.reuse ? ftpc->prevpath : ""; if(oldPath) { size_t n = pathLen; if(data->set.ftp_filemethod == FTPFILE_NOCWD) @@ -4264,18 +4280,17 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data) } /* call this when the DO phase has completed */ -static CURLcode ftp_dophase_done(struct Curl_easy *data, bool connected) +static CURLcode ftp_dophase_done(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, + bool connected) { - struct connectdata *conn = data->conn; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - if(connected) { int completed; CURLcode result = ftp_do_more(data, &completed); if(result) { - close_secondarysocket(data); + close_secondarysocket(data, ftpc); return result; } } @@ -4285,7 +4300,7 @@ static CURLcode ftp_dophase_done(struct Curl_easy *data, bool connected) Curl_xfer_setup_nop(data); else if(!connected) /* since we did not connect now, we want do_more to get called */ - conn->bits.do_more = TRUE; + data->conn->bits.do_more = TRUE; ftpc->ctl_valid = TRUE; /* seems good */ @@ -4296,14 +4311,20 @@ static CURLcode ftp_dophase_done(struct Curl_easy *data, bool connected) static CURLcode ftp_doing(struct Curl_easy *data, bool *dophase_done) { - CURLcode result = ftp_multi_statemach(data, dophase_done); + struct ftp_conn *ftpc = Curl_conn_meta_get(data->conn, CURL_META_FTP_CONN); + struct FTP *ftp = Curl_meta_get(data, CURL_META_FTP_EASY); + CURLcode result; + + if(!ftpc || !ftp) + return CURLE_FAILED_INIT; + result = ftp_statemach(data, ftpc, dophase_done); if(result) - CURL_TRC_FTP(data, "[%s] DO phase failed", FTP_DSTATE(data)); + CURL_TRC_FTP(data, "[%s] DO phase failed", FTP_CSTATE(ftpc)); else if(*dophase_done) { - result = ftp_dophase_done(data, FALSE /* not connected */); + result = ftp_dophase_done(data, ftpc, ftp, FALSE /* not connected */); - CURL_TRC_FTP(data, "[%s] DO phase is complete2", FTP_DSTATE(data)); + CURL_TRC_FTP(data, "[%s] DO phase is complete2", FTP_CSTATE(ftpc)); } return result; } @@ -4322,12 +4343,12 @@ static CURLcode ftp_doing(struct Curl_easy *data, */ static CURLcode ftp_regular_transfer(struct Curl_easy *data, + struct ftp_conn *ftpc, + struct FTP *ftp, bool *dophase_done) { CURLcode result = CURLE_OK; bool connected = FALSE; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; data->req.size = -1; /* make sure this is unknown at this point */ Curl_pgrsSetUploadCounter(data, 0); @@ -4337,7 +4358,7 @@ CURLcode ftp_regular_transfer(struct Curl_easy *data, ftpc->ctl_valid = TRUE; /* starts good */ - result = ftp_perform(data, + result = ftp_perform(data, ftpc, ftp, &connected, /* have we connected after PASV/PORT */ dophase_done); /* all commands in the DO-phase done? */ @@ -4347,7 +4368,7 @@ CURLcode ftp_regular_transfer(struct Curl_easy *data, /* the DO phase has not completed yet */ return CURLE_OK; - result = ftp_dophase_done(data, connected); + result = ftp_dophase_done(data, ftpc, ftp, connected); if(result) return result; @@ -4358,23 +4379,53 @@ CURLcode ftp_regular_transfer(struct Curl_easy *data, return result; } +static void ftp_easy_dtor(void *key, size_t klen, void *entry) +{ + struct FTP *ftp = entry; + (void)key; + (void)klen; + Curl_safefree(ftp->pathalloc); + free(ftp); +} + +static void ftp_conn_dtor(void *key, size_t klen, void *entry) +{ + struct ftp_conn *ftpc = entry; + (void)key; + (void)klen; + freedirs(ftpc); + Curl_safefree(ftpc->account); + Curl_safefree(ftpc->alternative_to_user); + Curl_safefree(ftpc->entrypath); + Curl_safefree(ftpc->prevpath); + Curl_safefree(ftpc->server_os); + Curl_pp_disconnect(&ftpc->pp); + free(ftpc); +} + static CURLcode ftp_setup_connection(struct Curl_easy *data, struct connectdata *conn) { char *type; struct FTP *ftp; CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; + struct ftp_conn *ftpc; - ftp = calloc(1, sizeof(struct FTP)); - if(!ftp) + ftp = calloc(1, sizeof(*ftp)); + if(!ftp || + Curl_meta_set(data, CURL_META_FTP_EASY, ftp, ftp_easy_dtor)) + return CURLE_OUT_OF_MEMORY; + + ftpc = calloc(1, sizeof(*ftpc)); + if(!ftpc || + Curl_conn_meta_set(conn, CURL_META_FTP_CONN, ftpc, ftp_conn_dtor)) return CURLE_OUT_OF_MEMORY; /* clone connection related data that is FTP specific */ if(data->set.str[STRING_FTP_ACCOUNT]) { ftpc->account = strdup(data->set.str[STRING_FTP_ACCOUNT]); if(!ftpc->account) { - free(ftp); + Curl_conn_meta_remove(conn, CURL_META_FTP_CONN); return CURLE_OUT_OF_MEMORY; } } @@ -4383,11 +4434,10 @@ static CURLcode ftp_setup_connection(struct Curl_easy *data, strdup(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]); if(!ftpc->alternative_to_user) { Curl_safefree(ftpc->account); - free(ftp); + Curl_conn_meta_remove(conn, CURL_META_FTP_CONN); return CURLE_OUT_OF_MEMORY; } } - data->req.p.ftp = ftp; ftp->path = &data->state.up.path[1]; /* do not include the initial slash */ @@ -4427,8 +4477,23 @@ 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); + CURL_TRC_FTP(data, "[%s] setup connection -> %d", FTP_CSTATE(ftpc), result); return result; } +bool ftp_conns_match(struct connectdata *needle, struct connectdata *conn) +{ + struct ftp_conn *nftpc = Curl_conn_meta_get(needle, CURL_META_FTP_CONN); + struct ftp_conn *cftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); + /* Also match ACCOUNT, ALTERNATIVE-TO-USER, USE_SSL and CCC options */ + if(!nftpc || !cftpc || + Curl_timestrcmp(nftpc->account, cftpc->account) || + Curl_timestrcmp(nftpc->alternative_to_user, + cftpc->alternative_to_user) || + (nftpc->use_ssl != cftpc->use_ssl) || + (nftpc->ccc != cftpc->ccc)) + return FALSE; + return TRUE; +} + #endif /* CURL_DISABLE_FTP */ diff --git a/Utilities/cmcurl/lib/ftp.h b/Utilities/cmcurl/lib/ftp.h index 3d0af01587..c31aa93286 100644 --- a/Utilities/cmcurl/lib/ftp.h +++ b/Utilities/cmcurl/lib/ftp.h @@ -37,6 +37,9 @@ extern const struct Curl_handler Curl_handler_ftps; CURLcode Curl_GetFTPResponse(struct Curl_easy *data, ssize_t *nread, int *ftpcode); + +bool ftp_conns_match(struct connectdata *needle, struct connectdata *conn); + #endif /* CURL_DISABLE_FTP */ /**************************************************************************** @@ -125,7 +128,8 @@ struct ftp_conn { char *entrypath; /* the PWD reply when we logged on */ char *file; /* url-decoded filename (or path) */ char **dirs; /* realloc()ed array for path components */ - char *newhost; + char *newhost; /* the (allocated) IP addr or hostname to connect the data + connection to */ char *prevpath; /* url-decoded conn->path from the previous transfer */ char transfertype; /* set by ftp_transfertype for use by Curl_client_write()a and others (A/I or zero) */ @@ -139,9 +143,8 @@ 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 hostname to connect the data - connection to */ - unsigned short newport; + unsigned short newport; /* the port of 'newhost' to connect the data + connection to */ ftpstate state; /* always use ftp.c:state() to change state! */ ftpstate state_saved; /* transfer type saved to be reloaded after data connection is established */ @@ -160,8 +163,14 @@ struct ftp_conn { BIT(cwdfail); /* set TRUE if a CWD command fails, as then we must prevent caching the current directory */ BIT(wait_data_conn); /* this is set TRUE if data connection is waited */ + BIT(shutdown); /* connection is being shutdown, e.g. QUIT */ }; +/* meta key for storing `struct FTP` as easy meta data */ +#define CURL_META_FTP_EASY "meta:proto:ftp:easy" +/* meta key for storing `struct ftp_conn` as connection meta data */ +#define CURL_META_FTP_CONN "meta:proto:ftp:conn" + #define DEFAULT_ACCEPT_TIMEOUT 60000 /* milliseconds == one minute */ #endif /* HEADER_CURL_FTP_H */ diff --git a/Utilities/cmcurl/lib/ftplistparser.c b/Utilities/cmcurl/lib/ftplistparser.c index 3088470abd..70939d6a76 100644 --- a/Utilities/cmcurl/lib/ftplistparser.c +++ b/Utilities/cmcurl/lib/ftplistparser.c @@ -46,13 +46,15 @@ #include "urldata.h" #include "fileinfo.h" #include "llist.h" -#include "strtoofft.h" #include "ftp.h" #include "ftplistparser.h" #include "curl_fnmatch.h" -#include "curl_memory.h" #include "multiif.h" -/* The last #include file should be: */ +#include "curlx/strparse.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" #include "memdebug.h" typedef enum { @@ -316,7 +318,7 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, struct curl_fileinfo *finfo = &infop->info; /* set the finfo pointers */ - char *str = Curl_dyn_ptr(&infop->buf); + char *str = curlx_dyn_ptr(&infop->buf); finfo->filename = str + parser->offsets.filename; finfo->strings.group = parser->offsets.group ? str + parser->offsets.group : NULL; @@ -361,6 +363,574 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, #define MAX_FTPLIST_BUFFER 10000 /* arbitrarily set */ +static CURLcode unix_filetype(const char c, curlfiletype *t) +{ + switch(c) { + case '-': + *t = CURLFILETYPE_FILE; + break; + case 'd': + *t = CURLFILETYPE_DIRECTORY; + break; + case 'l': + *t = CURLFILETYPE_SYMLINK; + break; + case 'p': + *t = CURLFILETYPE_NAMEDPIPE; + break; + case 's': + *t = CURLFILETYPE_SOCKET; + break; + case 'c': + *t = CURLFILETYPE_DEVICE_CHAR; + break; + case 'b': + *t = CURLFILETYPE_DEVICE_BLOCK; + break; + case 'D': + *t = CURLFILETYPE_DOOR; + break; + default: + return CURLE_FTP_BAD_FILE_LIST; + } + return CURLE_OK; +} + +static CURLcode parse_unix(struct Curl_easy *data, + struct ftp_parselist_data *parser, + struct fileinfo *infop, + const char c) +{ + struct curl_fileinfo *finfo = &infop->info; + size_t len = curlx_dyn_len(&infop->buf); + char *mem = curlx_dyn_ptr(&infop->buf); + CURLcode result = CURLE_OK; + + switch(parser->state.UNIX.main) { + case PL_UNIX_TOTALSIZE: + switch(parser->state.UNIX.sub.total_dirsize) { + case PL_UNIX_TOTALSIZE_INIT: + if(c == 't') { + parser->state.UNIX.sub.total_dirsize = PL_UNIX_TOTALSIZE_READING; + parser->item_length++; + } + else { + parser->state.UNIX.main = PL_UNIX_FILETYPE; + /* continue to fall through */ + } + break; + case PL_UNIX_TOTALSIZE_READING: + parser->item_length++; + if(c == '\r') { + parser->item_length--; + if(len) + curlx_dyn_setlen(&infop->buf, --len); + } + else if(c == '\n') { + mem[parser->item_length - 1] = 0; + if(!strncmp("total ", mem, 6)) { + const char *endptr = mem + 6; + /* here we can deal with directory size, pass the leading + whitespace and then the digits */ + curlx_str_passblanks(&endptr); + while(ISDIGIT(*endptr)) + endptr++; + if(*endptr) { + return CURLE_FTP_BAD_FILE_LIST; + } + parser->state.UNIX.main = PL_UNIX_FILETYPE; + curlx_dyn_reset(&infop->buf); + } + else + return CURLE_FTP_BAD_FILE_LIST; + + } + break; + } + if(parser->state.UNIX.main != PL_UNIX_FILETYPE) + break; + FALLTHROUGH(); + case PL_UNIX_FILETYPE: + result = unix_filetype(c, &finfo->filetype); + if(result) + return result; + parser->state.UNIX.main = PL_UNIX_PERMISSION; + parser->item_length = 0; + parser->item_offset = 1; + break; + case PL_UNIX_PERMISSION: + parser->item_length++; + if((parser->item_length <= 9) && !strchr("rwx-tTsS", c)) + return CURLE_FTP_BAD_FILE_LIST; + + else if(parser->item_length == 10) { + unsigned int perm; + if(c != ' ') + return CURLE_FTP_BAD_FILE_LIST; + + mem[10] = 0; /* terminate permissions */ + perm = ftp_pl_get_permission(mem + parser->item_offset); + if(perm & FTP_LP_MALFORMATED_PERM) + return CURLE_FTP_BAD_FILE_LIST; + + parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_PERM; + parser->file_data->info.perm = perm; + parser->offsets.perm = parser->item_offset; + + parser->item_length = 0; + parser->state.UNIX.main = PL_UNIX_HLINKS; + parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_PRESPACE; + } + break; + case PL_UNIX_HLINKS: + switch(parser->state.UNIX.sub.hlinks) { + case PL_UNIX_HLINKS_PRESPACE: + if(c != ' ') { + if(ISDIGIT(c) && len) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER; + } + else + return CURLE_FTP_BAD_FILE_LIST; + } + break; + case PL_UNIX_HLINKS_NUMBER: + parser->item_length ++; + if(c == ' ') { + const char *p = &mem[parser->item_offset]; + curl_off_t hlinks; + mem[parser->item_offset + parser->item_length - 1] = 0; + + if(!curlx_str_number(&p, &hlinks, LONG_MAX)) { + parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT; + parser->file_data->info.hardlinks = (long)hlinks; + } + parser->item_length = 0; + parser->item_offset = 0; + parser->state.UNIX.main = PL_UNIX_USER; + parser->state.UNIX.sub.user = PL_UNIX_USER_PRESPACE; + } + else if(!ISDIGIT(c)) + return CURLE_FTP_BAD_FILE_LIST; + + break; + } + break; + case PL_UNIX_USER: + switch(parser->state.UNIX.sub.user) { + case PL_UNIX_USER_PRESPACE: + if(c != ' ' && len) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING; + } + break; + case PL_UNIX_USER_PARSING: + parser->item_length++; + if(c == ' ') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.user = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_GROUP; + parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE; + parser->item_offset = 0; + parser->item_length = 0; + } + break; + } + break; + case PL_UNIX_GROUP: + switch(parser->state.UNIX.sub.group) { + case PL_UNIX_GROUP_PRESPACE: + if(c != ' ' && len) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME; + } + break; + case PL_UNIX_GROUP_NAME: + parser->item_length++; + if(c == ' ') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.group = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_SIZE; + parser->state.UNIX.sub.size = PL_UNIX_SIZE_PRESPACE; + parser->item_offset = 0; + parser->item_length = 0; + } + break; + } + break; + case PL_UNIX_SIZE: + switch(parser->state.UNIX.sub.size) { + case PL_UNIX_SIZE_PRESPACE: + if(c != ' ') { + if(ISDIGIT(c) && len) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER; + } + else + return CURLE_FTP_BAD_FILE_LIST; + } + break; + case PL_UNIX_SIZE_NUMBER: + parser->item_length++; + if(c == ' ') { + const char *p = mem + parser->item_offset; + curl_off_t fsize; + mem[parser->item_offset + parser->item_length - 1] = 0; + if(!curlx_str_numblanks(&p, &fsize)) { + if(p[0] == '\0' && fsize != CURL_OFF_T_MAX) { + parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE; + parser->file_data->info.size = fsize; + } + parser->item_length = 0; + parser->item_offset = 0; + parser->state.UNIX.main = PL_UNIX_TIME; + parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART1; + } + } + else if(!ISDIGIT(c)) + return CURLE_FTP_BAD_FILE_LIST; + + break; + } + break; + case PL_UNIX_TIME: + switch(parser->state.UNIX.sub.time) { + case PL_UNIX_TIME_PREPART1: + if(c != ' ') { + if(ISALNUM(c) && len) { + parser->item_offset = len -1; + parser->item_length = 1; + parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1; + } + else + return CURLE_FTP_BAD_FILE_LIST; + } + break; + case PL_UNIX_TIME_PART1: + parser->item_length++; + if(c == ' ') + parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2; + + else if(!ISALNUM(c) && c != '.') + return CURLE_FTP_BAD_FILE_LIST; + + break; + case PL_UNIX_TIME_PREPART2: + parser->item_length++; + if(c != ' ') { + if(ISALNUM(c)) + parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2; + else + return CURLE_FTP_BAD_FILE_LIST; + } + break; + case PL_UNIX_TIME_PART2: + parser->item_length++; + if(c == ' ') + parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3; + else if(!ISALNUM(c) && c != '.') + return CURLE_FTP_BAD_FILE_LIST; + break; + case PL_UNIX_TIME_PREPART3: + parser->item_length++; + if(c != ' ') { + if(ISALNUM(c)) + parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3; + else + return CURLE_FTP_BAD_FILE_LIST; + } + break; + case PL_UNIX_TIME_PART3: + parser->item_length++; + if(c == ' ') { + mem[parser->item_offset + parser->item_length -1] = 0; + parser->offsets.time = parser->item_offset; + if(finfo->filetype == CURLFILETYPE_SYMLINK) { + parser->state.UNIX.main = PL_UNIX_SYMLINK; + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE; + } + else { + parser->state.UNIX.main = PL_UNIX_FILENAME; + parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE; + } + } + else if(!ISALNUM(c) && c != '.' && c != ':') + return CURLE_FTP_BAD_FILE_LIST; + break; + } + break; + case PL_UNIX_FILENAME: + switch(parser->state.UNIX.sub.filename) { + case PL_UNIX_FILENAME_PRESPACE: + if(c != ' ' && len) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME; + } + break; + case PL_UNIX_FILENAME_NAME: + parser->item_length++; + if(c == '\r') + parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL; + + else if(c == '\n') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.filename = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_FILETYPE; + result = ftp_pl_insert_finfo(data, infop); + if(result) + return result; + } + break; + case PL_UNIX_FILENAME_WINDOWSEOL: + if(c == '\n') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.filename = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_FILETYPE; + result = ftp_pl_insert_finfo(data, infop); + if(result) + return result; + } + else + return CURLE_FTP_BAD_FILE_LIST; + + break; + } + break; + case PL_UNIX_SYMLINK: + switch(parser->state.UNIX.sub.symlink) { + case PL_UNIX_SYMLINK_PRESPACE: + if(c != ' ' && len) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + } + break; + case PL_UNIX_SYMLINK_NAME: + parser->item_length++; + if(c == ' ') + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1; + + else if(c == '\r' || c == '\n') + return CURLE_FTP_BAD_FILE_LIST; + + break; + case PL_UNIX_SYMLINK_PRETARGET1: + parser->item_length++; + if(c == '-') + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2; + + else if(c == '\r' || c == '\n') + return CURLE_FTP_BAD_FILE_LIST; + else + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + break; + case PL_UNIX_SYMLINK_PRETARGET2: + parser->item_length++; + if(c == '>') + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3; + else if(c == '\r' || c == '\n') + return CURLE_FTP_BAD_FILE_LIST; + else + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + + break; + case PL_UNIX_SYMLINK_PRETARGET3: + parser->item_length++; + if(c == ' ') { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4; + /* now place where is symlink following */ + mem[parser->item_offset + parser->item_length - 4] = 0; + parser->offsets.filename = parser->item_offset; + parser->item_length = 0; + parser->item_offset = 0; + } + else if(c == '\r' || c == '\n') + return CURLE_FTP_BAD_FILE_LIST; + else + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + break; + case PL_UNIX_SYMLINK_PRETARGET4: + if(c != '\r' && c != '\n' && len) { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET; + parser->item_offset = len - 1; + parser->item_length = 1; + } + else + return CURLE_FTP_BAD_FILE_LIST; + + break; + case PL_UNIX_SYMLINK_TARGET: + parser->item_length++; + if(c == '\r') + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL; + + else if(c == '\n') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.symlink_target = parser->item_offset; + result = ftp_pl_insert_finfo(data, infop); + if(result) + return result; + + parser->state.UNIX.main = PL_UNIX_FILETYPE; + } + break; + case PL_UNIX_SYMLINK_WINDOWSEOL: + if(c == '\n') { + mem[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.symlink_target = parser->item_offset; + result = ftp_pl_insert_finfo(data, infop); + if(result) + return result; + + parser->state.UNIX.main = PL_UNIX_FILETYPE; + } + else + return CURLE_FTP_BAD_FILE_LIST; + + break; + } + break; + } + return CURLE_OK; +} + +static CURLcode parse_winnt(struct Curl_easy *data, + struct ftp_parselist_data *parser, + struct fileinfo *infop, + const char c) +{ + struct curl_fileinfo *finfo = &infop->info; + size_t len = curlx_dyn_len(&infop->buf); + char *mem = curlx_dyn_ptr(&infop->buf); + CURLcode result = CURLE_OK; + + switch(parser->state.NT.main) { + case PL_WINNT_DATE: + parser->item_length++; + if(parser->item_length < 9) { + if(!strchr("0123456789-", c)) { /* only simple control */ + return CURLE_FTP_BAD_FILE_LIST; + } + } + else if(parser->item_length == 9) { + if(c == ' ') { + parser->state.NT.main = PL_WINNT_TIME; + parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE; + } + else + return CURLE_FTP_BAD_FILE_LIST; + } + else + return CURLE_FTP_BAD_FILE_LIST; + break; + case PL_WINNT_TIME: + parser->item_length++; + switch(parser->state.NT.sub.time) { + case PL_WINNT_TIME_PRESPACE: + if(!ISBLANK(c)) + parser->state.NT.sub.time = PL_WINNT_TIME_TIME; + break; + case PL_WINNT_TIME_TIME: + if(c == ' ') { + parser->offsets.time = parser->item_offset; + mem[parser->item_offset + parser->item_length -1] = 0; + parser->state.NT.main = PL_WINNT_DIRORSIZE; + parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE; + parser->item_length = 0; + } + else if(!strchr("APM0123456789:", c)) + return CURLE_FTP_BAD_FILE_LIST; + break; + } + break; + case PL_WINNT_DIRORSIZE: + switch(parser->state.NT.sub.dirorsize) { + case PL_WINNT_DIRORSIZE_PRESPACE: + if(c != ' ' && len) { + parser->item_offset = len - 1; + parser->item_length = 1; + parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT; + } + break; + case PL_WINNT_DIRORSIZE_CONTENT: + parser->item_length ++; + if(c == ' ') { + mem[parser->item_offset + parser->item_length - 1] = 0; + if(strcmp("", mem + parser->item_offset) == 0) { + finfo->filetype = CURLFILETYPE_DIRECTORY; + finfo->size = 0; + } + else { + const char *p = mem + parser->item_offset; + if(curlx_str_numblanks(&p, &finfo->size)) { + return CURLE_FTP_BAD_FILE_LIST; + } + /* correct file type */ + parser->file_data->info.filetype = CURLFILETYPE_FILE; + } + + parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE; + parser->item_length = 0; + parser->state.NT.main = PL_WINNT_FILENAME; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; + } + break; + } + break; + case PL_WINNT_FILENAME: + switch(parser->state.NT.sub.filename) { + case PL_WINNT_FILENAME_PRESPACE: + if(c != ' ' && len) { + parser->item_offset = len -1; + parser->item_length = 1; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT; + } + break; + case PL_WINNT_FILENAME_CONTENT: + parser->item_length++; + if(!len) + return CURLE_FTP_BAD_FILE_LIST; + if(c == '\r') { + parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL; + mem[len - 1] = 0; + } + else if(c == '\n') { + parser->offsets.filename = parser->item_offset; + mem[len - 1] = 0; + result = ftp_pl_insert_finfo(data, infop); + if(result) + return result; + + parser->state.NT.main = PL_WINNT_DATE; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; + } + break; + case PL_WINNT_FILENAME_WINEOL: + if(c == '\n') { + parser->offsets.filename = parser->item_offset; + result = ftp_pl_insert_finfo(data, infop); + if(result) + return result; + + parser->state.NT.main = PL_WINNT_DATE; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; + } + else + return CURLE_FTP_BAD_FILE_LIST; + + break; + } + break; + } + + return CURLE_OK; +} + size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, void *connptr) { @@ -388,11 +958,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, } while(i < bufflen) { /* FSM */ - char *mem; - size_t len; /* number of bytes of data in the dynbuf */ char c = buffer[i]; struct fileinfo *infop; - struct curl_fileinfo *finfo; if(!parser->file_data) { /* tmp file data is not allocated yet */ parser->file_data = Curl_fileinfo_alloc(); if(!parser->file_data) { @@ -401,627 +968,31 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, } parser->item_offset = 0; parser->item_length = 0; - Curl_dyn_init(&parser->file_data->buf, MAX_FTPLIST_BUFFER); + curlx_dyn_init(&parser->file_data->buf, MAX_FTPLIST_BUFFER); } infop = parser->file_data; - finfo = &infop->info; - if(Curl_dyn_addn(&infop->buf, &c, 1)) { + if(curlx_dyn_addn(&infop->buf, &c, 1)) { parser->error = CURLE_OUT_OF_MEMORY; goto fail; } - len = Curl_dyn_len(&infop->buf); - mem = Curl_dyn_ptr(&infop->buf); switch(parser->os_type) { case OS_TYPE_UNIX: - switch(parser->state.UNIX.main) { - case PL_UNIX_TOTALSIZE: - switch(parser->state.UNIX.sub.total_dirsize) { - case PL_UNIX_TOTALSIZE_INIT: - if(c == 't') { - parser->state.UNIX.sub.total_dirsize = PL_UNIX_TOTALSIZE_READING; - parser->item_length++; - } - else { - parser->state.UNIX.main = PL_UNIX_FILETYPE; - /* start FSM again not considering size of directory */ - Curl_dyn_reset(&infop->buf); - continue; - } - break; - case PL_UNIX_TOTALSIZE_READING: - parser->item_length++; - if(c == '\r') { - parser->item_length--; - Curl_dyn_setlen(&infop->buf, --len); - } - else if(c == '\n') { - mem[parser->item_length - 1] = 0; - if(!strncmp("total ", mem, 6)) { - char *endptr = mem + 6; - /* here we can deal with directory size, pass the leading - whitespace and then the digits */ - while(ISBLANK(*endptr)) - endptr++; - while(ISDIGIT(*endptr)) - endptr++; - if(*endptr) { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - parser->state.UNIX.main = PL_UNIX_FILETYPE; - Curl_dyn_reset(&infop->buf); - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - break; - } - break; - case PL_UNIX_FILETYPE: - switch(c) { - case '-': - finfo->filetype = CURLFILETYPE_FILE; - break; - case 'd': - finfo->filetype = CURLFILETYPE_DIRECTORY; - break; - case 'l': - finfo->filetype = CURLFILETYPE_SYMLINK; - break; - case 'p': - finfo->filetype = CURLFILETYPE_NAMEDPIPE; - break; - case 's': - finfo->filetype = CURLFILETYPE_SOCKET; - break; - case 'c': - finfo->filetype = CURLFILETYPE_DEVICE_CHAR; - break; - case 'b': - finfo->filetype = CURLFILETYPE_DEVICE_BLOCK; - break; - case 'D': - finfo->filetype = CURLFILETYPE_DOOR; - break; - default: - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - parser->state.UNIX.main = PL_UNIX_PERMISSION; - parser->item_length = 0; - parser->item_offset = 1; - break; - case PL_UNIX_PERMISSION: - parser->item_length++; - if(parser->item_length <= 9) { - if(!strchr("rwx-tTsS", c)) { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - else if(parser->item_length == 10) { - unsigned int perm; - if(c != ' ') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - mem[10] = 0; /* terminate permissions */ - perm = ftp_pl_get_permission(mem + parser->item_offset); - if(perm & FTP_LP_MALFORMATED_PERM) { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_PERM; - parser->file_data->info.perm = perm; - parser->offsets.perm = parser->item_offset; - - parser->item_length = 0; - parser->state.UNIX.main = PL_UNIX_HLINKS; - parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_PRESPACE; - } - break; - case PL_UNIX_HLINKS: - switch(parser->state.UNIX.sub.hlinks) { - case PL_UNIX_HLINKS_PRESPACE: - if(c != ' ') { - if(ISDIGIT(c)) { - parser->item_offset = len - 1; - parser->item_length = 1; - parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - break; - case PL_UNIX_HLINKS_NUMBER: - parser->item_length ++; - if(c == ' ') { - char *p; - long int hlinks; - mem[parser->item_offset + parser->item_length - 1] = 0; - hlinks = strtol(mem + parser->item_offset, &p, 10); - if(p[0] == '\0' && hlinks != LONG_MAX && hlinks != LONG_MIN) { - parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT; - parser->file_data->info.hardlinks = hlinks; - } - parser->item_length = 0; - parser->item_offset = 0; - parser->state.UNIX.main = PL_UNIX_USER; - parser->state.UNIX.sub.user = PL_UNIX_USER_PRESPACE; - } - else if(!ISDIGIT(c)) { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - } - break; - case PL_UNIX_USER: - switch(parser->state.UNIX.sub.user) { - case PL_UNIX_USER_PRESPACE: - if(c != ' ') { - parser->item_offset = len - 1; - parser->item_length = 1; - parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING; - } - break; - case PL_UNIX_USER_PARSING: - parser->item_length++; - if(c == ' ') { - mem[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.user = parser->item_offset; - parser->state.UNIX.main = PL_UNIX_GROUP; - parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE; - parser->item_offset = 0; - parser->item_length = 0; - } - break; - } - break; - case PL_UNIX_GROUP: - switch(parser->state.UNIX.sub.group) { - case PL_UNIX_GROUP_PRESPACE: - if(c != ' ') { - parser->item_offset = len - 1; - parser->item_length = 1; - parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME; - } - break; - case PL_UNIX_GROUP_NAME: - parser->item_length++; - if(c == ' ') { - mem[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.group = parser->item_offset; - parser->state.UNIX.main = PL_UNIX_SIZE; - parser->state.UNIX.sub.size = PL_UNIX_SIZE_PRESPACE; - parser->item_offset = 0; - parser->item_length = 0; - } - break; - } - break; - case PL_UNIX_SIZE: - switch(parser->state.UNIX.sub.size) { - case PL_UNIX_SIZE_PRESPACE: - if(c != ' ') { - if(ISDIGIT(c)) { - parser->item_offset = len - 1; - parser->item_length = 1; - parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - break; - case PL_UNIX_SIZE_NUMBER: - parser->item_length++; - if(c == ' ') { - char *p; - curl_off_t fsize; - mem[parser->item_offset + parser->item_length - 1] = 0; - if(!curlx_strtoofft(mem + parser->item_offset, - &p, 10, &fsize)) { - if(p[0] == '\0' && fsize != CURL_OFF_T_MAX && - fsize != CURL_OFF_T_MIN) { - parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE; - parser->file_data->info.size = fsize; - } - parser->item_length = 0; - parser->item_offset = 0; - parser->state.UNIX.main = PL_UNIX_TIME; - parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART1; - } - } - else if(!ISDIGIT(c)) { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - } - break; - case PL_UNIX_TIME: - switch(parser->state.UNIX.sub.time) { - case PL_UNIX_TIME_PREPART1: - if(c != ' ') { - if(ISALNUM(c)) { - parser->item_offset = len -1; - parser->item_length = 1; - parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - break; - case PL_UNIX_TIME_PART1: - parser->item_length++; - if(c == ' ') { - parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2; - } - else if(!ISALNUM(c) && c != '.') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - case PL_UNIX_TIME_PREPART2: - parser->item_length++; - if(c != ' ') { - if(ISALNUM(c)) { - parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - break; - case PL_UNIX_TIME_PART2: - parser->item_length++; - if(c == ' ') { - parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3; - } - else if(!ISALNUM(c) && c != '.') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - case PL_UNIX_TIME_PREPART3: - parser->item_length++; - if(c != ' ') { - if(ISALNUM(c)) { - parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - break; - case PL_UNIX_TIME_PART3: - parser->item_length++; - if(c == ' ') { - mem[parser->item_offset + parser->item_length -1] = 0; - parser->offsets.time = parser->item_offset; - /* - if(ftp_pl_gettime(parser, finfo->mem + parser->item_offset)) { - parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME; - } - */ - if(finfo->filetype == CURLFILETYPE_SYMLINK) { - parser->state.UNIX.main = PL_UNIX_SYMLINK; - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE; - } - else { - parser->state.UNIX.main = PL_UNIX_FILENAME; - parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE; - } - } - else if(!ISALNUM(c) && c != '.' && c != ':') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - } - break; - case PL_UNIX_FILENAME: - switch(parser->state.UNIX.sub.filename) { - case PL_UNIX_FILENAME_PRESPACE: - if(c != ' ') { - parser->item_offset = len - 1; - parser->item_length = 1; - parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME; - } - break; - case PL_UNIX_FILENAME_NAME: - parser->item_length++; - if(c == '\r') { - parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL; - } - else if(c == '\n') { - mem[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.filename = parser->item_offset; - parser->state.UNIX.main = PL_UNIX_FILETYPE; - result = ftp_pl_insert_finfo(data, infop); - if(result) { - parser->error = result; - goto fail; - } - } - break; - case PL_UNIX_FILENAME_WINDOWSEOL: - if(c == '\n') { - mem[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.filename = parser->item_offset; - parser->state.UNIX.main = PL_UNIX_FILETYPE; - result = ftp_pl_insert_finfo(data, infop); - if(result) { - parser->error = result; - goto fail; - } - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - } - break; - case PL_UNIX_SYMLINK: - switch(parser->state.UNIX.sub.symlink) { - case PL_UNIX_SYMLINK_PRESPACE: - if(c != ' ') { - parser->item_offset = len - 1; - parser->item_length = 1; - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; - } - break; - case PL_UNIX_SYMLINK_NAME: - parser->item_length++; - if(c == ' ') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1; - } - else if(c == '\r' || c == '\n') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - case PL_UNIX_SYMLINK_PRETARGET1: - parser->item_length++; - if(c == '-') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2; - } - else if(c == '\r' || c == '\n') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - else { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; - } - break; - case PL_UNIX_SYMLINK_PRETARGET2: - parser->item_length++; - if(c == '>') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3; - } - else if(c == '\r' || c == '\n') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - else { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; - } - break; - case PL_UNIX_SYMLINK_PRETARGET3: - parser->item_length++; - if(c == ' ') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4; - /* now place where is symlink following */ - mem[parser->item_offset + parser->item_length - 4] = 0; - parser->offsets.filename = parser->item_offset; - parser->item_length = 0; - parser->item_offset = 0; - } - else if(c == '\r' || c == '\n') { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - else { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; - } - break; - case PL_UNIX_SYMLINK_PRETARGET4: - if(c != '\r' && c != '\n') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET; - parser->item_offset = len - 1; - parser->item_length = 1; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - case PL_UNIX_SYMLINK_TARGET: - parser->item_length++; - if(c == '\r') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL; - } - else if(c == '\n') { - mem[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.symlink_target = parser->item_offset; - result = ftp_pl_insert_finfo(data, infop); - if(result) { - parser->error = result; - goto fail; - } - parser->state.UNIX.main = PL_UNIX_FILETYPE; - } - break; - case PL_UNIX_SYMLINK_WINDOWSEOL: - if(c == '\n') { - mem[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.symlink_target = parser->item_offset; - result = ftp_pl_insert_finfo(data, infop); - if(result) { - parser->error = result; - goto fail; - } - parser->state.UNIX.main = PL_UNIX_FILETYPE; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - } - break; - } + result = parse_unix(data, parser, infop, c); break; case OS_TYPE_WIN_NT: - switch(parser->state.NT.main) { - case PL_WINNT_DATE: - parser->item_length++; - if(parser->item_length < 9) { - if(!strchr("0123456789-", c)) { /* only simple control */ - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - else if(parser->item_length == 9) { - if(c == ' ') { - parser->state.NT.main = PL_WINNT_TIME; - parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - case PL_WINNT_TIME: - parser->item_length++; - switch(parser->state.NT.sub.time) { - case PL_WINNT_TIME_PRESPACE: - if(!ISBLANK(c)) { - parser->state.NT.sub.time = PL_WINNT_TIME_TIME; - } - break; - case PL_WINNT_TIME_TIME: - if(c == ' ') { - parser->offsets.time = parser->item_offset; - mem[parser->item_offset + parser->item_length -1] = 0; - parser->state.NT.main = PL_WINNT_DIRORSIZE; - parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE; - parser->item_length = 0; - } - else if(!strchr("APM0123456789:", c)) { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - } - break; - case PL_WINNT_DIRORSIZE: - switch(parser->state.NT.sub.dirorsize) { - case PL_WINNT_DIRORSIZE_PRESPACE: - if(c != ' ') { - parser->item_offset = len - 1; - parser->item_length = 1; - parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT; - } - break; - case PL_WINNT_DIRORSIZE_CONTENT: - parser->item_length ++; - if(c == ' ') { - mem[parser->item_offset + parser->item_length - 1] = 0; - if(strcmp("", mem + parser->item_offset) == 0) { - finfo->filetype = CURLFILETYPE_DIRECTORY; - finfo->size = 0; - } - else { - char *endptr; - if(curlx_strtoofft(mem + - parser->item_offset, - &endptr, 10, &finfo->size)) { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - /* correct file type */ - parser->file_data->info.filetype = CURLFILETYPE_FILE; - } - - parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_SIZE; - parser->item_length = 0; - parser->state.NT.main = PL_WINNT_FILENAME; - parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; - } - break; - } - break; - case PL_WINNT_FILENAME: - switch(parser->state.NT.sub.filename) { - case PL_WINNT_FILENAME_PRESPACE: - if(c != ' ') { - parser->item_offset = len -1; - parser->item_length = 1; - parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT; - } - break; - case PL_WINNT_FILENAME_CONTENT: - parser->item_length++; - if(c == '\r') { - parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL; - mem[len - 1] = 0; - } - else if(c == '\n') { - parser->offsets.filename = parser->item_offset; - mem[len - 1] = 0; - result = ftp_pl_insert_finfo(data, infop); - if(result) { - parser->error = result; - goto fail; - } - parser->state.NT.main = PL_WINNT_DATE; - parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; - } - break; - case PL_WINNT_FILENAME_WINEOL: - if(c == '\n') { - parser->offsets.filename = parser->item_offset; - result = ftp_pl_insert_finfo(data, infop); - if(result) { - parser->error = result; - goto fail; - } - parser->state.NT.main = PL_WINNT_DATE; - parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; - } - else { - parser->error = CURLE_FTP_BAD_FILE_LIST; - goto fail; - } - break; - } - break; - } + result = parse_winnt(data, parser, infop, c); break; default: retsize = bufflen + 1; goto fail; } + if(result) { + parser->error = result; + goto fail; + } i++; } diff --git a/Utilities/cmcurl/lib/getenv.c b/Utilities/cmcurl/lib/getenv.c index 63eaeda0f2..3bfcf707a4 100644 --- a/Utilities/cmcurl/lib/getenv.c +++ b/Utilities/cmcurl/lib/getenv.c @@ -31,7 +31,7 @@ static char *GetEnv(const char *variable) { -#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) || \ +#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE) || \ defined(__ORBIS__) || defined(__PROSPERO__) /* PlayStation 4 and 5 */ (void)variable; return NULL; diff --git a/Utilities/cmcurl/lib/getinfo.c b/Utilities/cmcurl/lib/getinfo.c index ae6b3b8aa0..388646bf75 100644 --- a/Utilities/cmcurl/lib/getinfo.c +++ b/Utilities/cmcurl/lib/getinfo.c @@ -28,10 +28,10 @@ #include "urldata.h" #include "getinfo.h" - #include "vtls/vtls.h" #include "connect.h" /* Curl_getconnectinfo() */ #include "progress.h" +#include "curlx/strparse.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -98,7 +98,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 : ""; break; case CURLINFO_EFFECTIVE_METHOD: { const char *m = data->set.str[STRING_CUSTOMREQUEST]; @@ -204,9 +204,10 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, } lptr; #ifdef DEBUGBUILD - char *timestr = getenv("CURL_TIME"); + const char *timestr = getenv("CURL_TIME"); if(timestr) { - unsigned long val = strtoul(timestr, NULL, 10); + curl_off_t val; + curlx_str_number(×tr, &val, TIME_T_MAX); switch(info) { case CURLINFO_LOCAL_PORT: *param_longp = (long)val; @@ -218,7 +219,8 @@ 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 = strtoul(timestr, NULL, 10); + curl_off_t val; + curlx_str_number(×tr, &val, LONG_MAX); switch(info) { case CURLINFO_HEADER_SIZE: case CURLINFO_REQUEST_SIZE: @@ -379,9 +381,11 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, curl_off_t *param_offt) { #ifdef DEBUGBUILD - char *timestr = getenv("CURL_TIME"); + const char *timestr = getenv("CURL_TIME"); if(timestr) { - unsigned long val = strtoul(timestr, NULL, 10); + curl_off_t val; + curlx_str_number(×tr, &val, CURL_OFF_T_MAX); + switch(info) { case CURLINFO_TOTAL_TIME_T: case CURLINFO_NAMELOOKUP_TIME_T: @@ -418,11 +422,11 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, *param_offt = data->progress.ul.speed; break; case CURLINFO_CONTENT_LENGTH_DOWNLOAD_T: - *param_offt = (data->progress.flags & PGRS_DL_SIZE_KNOWN) ? + *param_offt = data->progress.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) ? + *param_offt = data->progress.ul_size_known ? data->progress.ul.total_size : -1; break; case CURLINFO_TOTAL_TIME_T: @@ -476,9 +480,11 @@ static CURLcode getinfo_double(struct Curl_easy *data, CURLINFO info, double *param_doublep) { #ifdef DEBUGBUILD - char *timestr = getenv("CURL_TIME"); + const char *timestr = getenv("CURL_TIME"); if(timestr) { - unsigned long val = strtoul(timestr, NULL, 10); + curl_off_t val; + curlx_str_number(×tr, &val, CURL_OFF_T_MAX); + switch(info) { case CURLINFO_TOTAL_TIME: case CURLINFO_NAMELOOKUP_TIME: @@ -528,11 +534,11 @@ static CURLcode getinfo_double(struct Curl_easy *data, CURLINFO info, *param_doublep = (double)data->progress.ul.speed; break; case CURLINFO_CONTENT_LENGTH_DOWNLOAD: - *param_doublep = (data->progress.flags & PGRS_DL_SIZE_KNOWN) ? + *param_doublep = data->progress.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) ? + *param_doublep = data->progress.ul_size_known ? (double)data->progress.ul.total_size : -1; break; case CURLINFO_REDIRECT_TIME: diff --git a/Utilities/cmcurl/lib/gopher.c b/Utilities/cmcurl/lib/gopher.c index 00d42f0ec7..68ccb59e40 100644 --- a/Utilities/cmcurl/lib/gopher.c +++ b/Utilities/cmcurl/lib/gopher.c @@ -39,7 +39,7 @@ #include "vtls/vtls.h" #include "url.h" #include "escape.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "curl_printf.h" #include "curl_memory.h" /* The last #include file should be: */ @@ -162,7 +162,7 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done) /* Create selector. Degenerate cases: / and /1 => convert to "" */ if(strlen(gopherpath) <= 2) { - sel = (char *)""; + sel = (char *)CURL_UNCONST(""); len = strlen(sel); free(gopherpath); } @@ -236,7 +236,7 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done) failf(data, "Failed sending Gopher request"); return result; } - result = Curl_client_write(data, CLIENTWRITE_HEADER, (char *)"\r\n", 2); + result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2); if(result) return result; diff --git a/Utilities/cmcurl/lib/hash.c b/Utilities/cmcurl/lib/hash.c index aa9904e709..8d13aae483 100644 --- a/Utilities/cmcurl/lib/hash.c +++ b/Utilities/cmcurl/lib/hash.c @@ -34,29 +34,49 @@ #include "memdebug.h" /* random patterns for API verification */ +#ifdef DEBUGBUILD #define HASHINIT 0x7017e781 #define ITERINIT 0x5FEDCBA9 +#endif -static void -hash_element_dtor(void *user, void *element) + +#if 0 /* useful function for debugging hashes and their contents */ +void Curl_hash_print(struct Curl_hash *h, + void (*func)(void *)) { - struct Curl_hash *h = (struct Curl_hash *) user; - struct Curl_hash_element *e = (struct Curl_hash_element *) element; - DEBUGASSERT(h); - DEBUGASSERT(e); + struct Curl_hash_iterator iter; + struct Curl_hash_element *he; + size_t last_index = UINT_MAX; - if(e->ptr) { - if(e->dtor) - e->dtor(e->key, e->key_len, e->ptr); + if(!h) + return; + + fprintf(stderr, "=Hash dump=\n"); + + Curl_hash_start_iterate(h, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + if(iter.slot_index != last_index) { + fprintf(stderr, "index %d:", (int)iter.slot_index); + if(last_index != UINT_MAX) { + fprintf(stderr, "\n"); + } + last_index = iter.slot_index; + } + + if(func) + func(he->ptr); else - h->dtor(e->ptr); - e->ptr = NULL; + fprintf(stderr, " [key=%.*s, he=%p, ptr=%p]", + (int)he->key_len, (char *)he->key, + (void *)he, (void *)he->ptr); + + he = Curl_hash_next_element(&iter); } - - e->key_len = 0; - - free(e); + fprintf(stderr, "\n"); } +#endif /* Initializes a hash structure. * Return 1 on error, 0 is fine. @@ -89,62 +109,96 @@ Curl_hash_init(struct Curl_hash *h, } static struct Curl_hash_element * -mk_hash_element(const void *key, size_t key_len, const void *p, - Curl_hash_elem_dtor dtor) +hash_elem_create(const void *key, size_t key_len, const void *p, + Curl_hash_elem_dtor dtor) { + struct Curl_hash_element *he; + /* allocate the struct plus memory after it to store the key */ - struct Curl_hash_element *he = malloc(sizeof(struct Curl_hash_element) + - key_len); + he = malloc(sizeof(struct Curl_hash_element) + key_len); if(he) { + he->next = NULL; /* copy the key */ memcpy(he->key, key, key_len); he->key_len = key_len; - he->ptr = (void *) p; + he->ptr = CURL_UNCONST(p); he->dtor = dtor; } return he; } -#define FETCH_LIST(x,y,z) &x->table[x->hash_func(y, z, x->slots)] +static void hash_elem_clear_ptr(struct Curl_hash *h, + struct Curl_hash_element *he) +{ + DEBUGASSERT(h); + DEBUGASSERT(he); + if(he->ptr) { + if(he->dtor) + he->dtor(he->key, he->key_len, he->ptr); + else + h->dtor(he->ptr); + he->ptr = NULL; + } +} + +static void hash_elem_destroy(struct Curl_hash *h, + struct Curl_hash_element *he) +{ + hash_elem_clear_ptr(h, he); + free(he); +} + +static void hash_elem_unlink(struct Curl_hash *h, + struct Curl_hash_element **he_anchor, + struct Curl_hash_element *he) +{ + *he_anchor = he->next; + --h->size; +} + +static void hash_elem_link(struct Curl_hash *h, + struct Curl_hash_element **he_anchor, + struct Curl_hash_element *he) +{ + he->next = *he_anchor; + *he_anchor = he; + ++h->size; +} + +#define CURL_HASH_SLOT(x,y,z) x->table[x->hash_func(y, z, x->slots)] +#define CURL_HASH_SLOT_ADDR(x,y,z) &CURL_HASH_SLOT(x,y,z) 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_node *le; - struct Curl_llist *l; + struct Curl_hash_element *he, **slot; DEBUGASSERT(h); DEBUGASSERT(h->slots); DEBUGASSERT(h->init == HASHINIT); if(!h->table) { - size_t i; - h->table = malloc(h->slots * sizeof(struct Curl_llist)); + h->table = calloc(h->slots, sizeof(struct Curl_hash_element *)); if(!h->table) return NULL; /* OOM */ - for(i = 0; i < h->slots; ++i) - Curl_llist_init(&h->table[i], hash_element_dtor); } - l = FETCH_LIST(h, key, key_len); - - for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) { - he = (struct Curl_hash_element *) Curl_node_elem(le); + slot = CURL_HASH_SLOT_ADDR(h, key, key_len); + for(he = *slot; he; he = he->next) { if(h->comp_func(he->key, he->key_len, key, key_len)) { - Curl_node_uremove(le, (void *)h); - --h->size; - break; + /* existing key entry, overwrite by clearing old pointer */ + hash_elem_clear_ptr(h, he); + he->ptr = (void *)p; + he->dtor = dtor; + return p; } } - he = mk_hash_element(key, key_len, p, dtor); - if(he) { - Curl_llist_append(l, he, &he->list); - ++h->size; - return p; /* return the new entry */ - } + he = hash_elem_create(key, key_len, p, dtor); + if(!he) + return NULL; /* OOM */ - return NULL; /* failure */ + hash_elem_link(h, slot, he); + return p; /* return the new entry */ } /* Insert the data in the hash. If there already was a match in the hash, that @@ -172,16 +226,17 @@ int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len) DEBUGASSERT(h->slots); DEBUGASSERT(h->init == HASHINIT); if(h->table) { - struct Curl_llist_node *le; - struct Curl_llist *l = FETCH_LIST(h, key, key_len); + struct Curl_hash_element *he, **he_anchor; - for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) { - struct Curl_hash_element *he = Curl_node_elem(le); + he_anchor = CURL_HASH_SLOT_ADDR(h, key, key_len); + while(*he_anchor) { + he = *he_anchor; if(h->comp_func(he->key, he->key_len, key, key_len)) { - Curl_node_uremove(le, (void *) h); - --h->size; + hash_elem_unlink(h, he_anchor, he); + hash_elem_destroy(h, he); return 0; } + he_anchor = &he->next; } } return 1; @@ -197,18 +252,16 @@ Curl_hash_pick(struct Curl_hash *h, void *key, size_t key_len) DEBUGASSERT(h); DEBUGASSERT(h->init == HASHINIT); if(h->table) { - struct Curl_llist_node *le; - struct Curl_llist *l; + struct Curl_hash_element *he; DEBUGASSERT(h->slots); - l = FETCH_LIST(h, key, key_len); - for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) { - struct Curl_hash_element *he = Curl_node_elem(le); + he = CURL_HASH_SLOT(h, key, key_len); + while(he) { if(h->comp_func(he->key, he->key_len, key, key_len)) { return he->ptr; } + he = he->next; } } - return NULL; } @@ -224,13 +277,10 @@ Curl_hash_destroy(struct Curl_hash *h) { DEBUGASSERT(h->init == HASHINIT); if(h->table) { - size_t i; - for(i = 0; i < h->slots; ++i) { - Curl_llist_destroy(&h->table[i], (void *) h); - } + Curl_hash_clean(h); Curl_safefree(h->table); } - h->size = 0; + DEBUGASSERT(h->size == 0); h->slots = 0; } @@ -238,10 +288,21 @@ Curl_hash_destroy(struct Curl_hash *h) * * @unittest: 1602 */ -void -Curl_hash_clean(struct Curl_hash *h) +void Curl_hash_clean(struct Curl_hash *h) { - Curl_hash_clean_with_criterium(h, NULL, NULL); + if(h && h->table) { + struct Curl_hash_element *he, **he_anchor; + size_t i; + DEBUGASSERT(h->init == HASHINIT); + for(i = 0; i < h->slots; ++i) { + he_anchor = &h->table[i]; + while(*he_anchor) { + he = *he_anchor; + hash_elem_unlink(h, he_anchor, he); + hash_elem_destroy(h, he); + } + } + } } size_t Curl_hash_count(struct Curl_hash *h) @@ -262,18 +323,16 @@ Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, DEBUGASSERT(h->init == HASHINIT); for(i = 0; i < h->slots; ++i) { - 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 = Curl_node_elem(le); - struct Curl_llist_node *lnext = Curl_node_next(le); + struct Curl_hash_element *he, **he_anchor = &h->table[i]; + while(*he_anchor) { /* ask the callback function if we shall remove this entry or not */ - if(!comp || comp(user, he->ptr)) { - Curl_node_uremove(le, (void *) h); - --h->size; /* one less entry in the hash now */ + if(!comp || comp(user, (*he_anchor)->ptr)) { + he = *he_anchor; + hash_elem_unlink(h, he_anchor, he); + hash_elem_destroy(h, he); } - le = lnext; + else + he_anchor = &(*he_anchor)->next; } } } @@ -293,7 +352,7 @@ size_t Curl_hash_str(void *key, size_t key_length, size_t slots_num) return (h % slots_num); } -size_t Curl_str_key_compare(void *k1, size_t key1_len, +size_t curlx_str_key_compare(void *k1, size_t key1_len, void *k2, size_t key2_len) { if((key1_len == key2_len) && !memcmp(k1, k2, key1_len)) @@ -308,7 +367,7 @@ void Curl_hash_start_iterate(struct Curl_hash *hash, DEBUGASSERT(hash->init == HASHINIT); iter->hash = hash; iter->slot_index = 0; - iter->current_element = NULL; + iter->current = NULL; #ifdef DEBUGBUILD iter->init = ITERINIT; #endif @@ -324,82 +383,20 @@ Curl_hash_next_element(struct Curl_hash_iterator *iter) return NULL; /* empty hash, nothing to return */ /* Get the next element in the current list, if any */ - if(iter->current_element) - iter->current_element = Curl_node_next(iter->current_element); + if(iter->current) + iter->current = iter->current->next; /* If we have reached the end of the list, find the next one */ - if(!iter->current_element) { + if(!iter->current) { size_t i; for(i = iter->slot_index; i < h->slots; i++) { - if(Curl_llist_head(&h->table[i])) { - iter->current_element = Curl_llist_head(&h->table[i]); + if(h->table[i]) { + iter->current = h->table[i]; iter->slot_index = i + 1; break; } } } - if(iter->current_element) { - struct Curl_hash_element *he = Curl_node_elem(iter->current_element); - return he; - } - return NULL; -} - -#if 0 /* useful function for debugging hashes and their contents */ -void Curl_hash_print(struct Curl_hash *h, - void (*func)(void *)) -{ - struct Curl_hash_iterator iter; - struct Curl_hash_element *he; - size_t last_index = ~0; - - if(!h) - return; - - fprintf(stderr, "=Hash dump=\n"); - - Curl_hash_start_iterate(h, &iter); - - he = Curl_hash_next_element(&iter); - while(he) { - if(iter.slot_index != last_index) { - fprintf(stderr, "index %d:", iter.slot_index); - if(last_index != ~0) { - fprintf(stderr, "\n"); - } - last_index = iter.slot_index; - } - - if(func) - func(he->ptr); - else - fprintf(stderr, " [%p]", (void *)he->ptr); - - he = Curl_hash_next_element(&iter); - } - 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)); + return iter->current; } diff --git a/Utilities/cmcurl/lib/hash.h b/Utilities/cmcurl/lib/hash.h index b160395024..314b811e4a 100644 --- a/Utilities/cmcurl/lib/hash.h +++ b/Utilities/cmcurl/lib/hash.h @@ -45,14 +45,24 @@ typedef size_t (*comp_function) (void *key1, typedef void (*Curl_hash_dtor)(void *); +typedef void (*Curl_hash_elem_dtor)(void *key, size_t key_len, void *p); + +struct Curl_hash_element { + struct Curl_hash_element *next; + void *ptr; + Curl_hash_elem_dtor dtor; + size_t key_len; + char key[1]; /* allocated memory following the struct */ +}; + struct Curl_hash { - struct Curl_llist *table; + struct Curl_hash_element **table; /* Hash function to be used for this hash table */ hash_function hash_func; - /* Comparator function to compare keys */ comp_function comp_func; + /* General element construct, unless element itself carries one */ Curl_hash_dtor dtor; size_t slots; size_t size; @@ -61,23 +71,10 @@ struct Curl_hash { #endif }; -typedef void (*Curl_hash_elem_dtor)(void *key, size_t key_len, void *p); - -struct Curl_hash_element { - 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; size_t slot_index; - struct Curl_llist_node *current_element; + struct Curl_hash_element *current; #ifdef DEBUGBUILD int init; #endif @@ -101,7 +98,7 @@ void Curl_hash_clean(struct Curl_hash *h); void Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, int (*comp)(void *, void *)); size_t Curl_hash_str(void *key, size_t key_length, size_t slots_num); -size_t Curl_str_key_compare(void *k1, size_t key1_len, void *k2, +size_t curlx_str_key_compare(void *k1, size_t key1_len, void *k2, size_t key2_len); void Curl_hash_start_iterate(struct Curl_hash *hash, struct Curl_hash_iterator *iter); @@ -111,13 +108,4 @@ 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/Utilities/cmcurl/lib/headers.c b/Utilities/cmcurl/lib/headers.c index 2985e1e185..71592a9c73 100644 --- a/Utilities/cmcurl/lib/headers.c +++ b/Utilities/cmcurl/lib/headers.c @@ -29,6 +29,7 @@ #include "strcase.h" #include "sendf.h" #include "headers.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -208,15 +209,15 @@ static CURLcode namevalue(char *header, size_t hlen, unsigned int type, else return CURLE_BAD_FUNCTION_ARGUMENT; - /* skip all leading space letters */ - while(*header && ISBLANK(*header)) + /* skip all leading blank letters */ + while(ISBLANK(*header)) header++; *value = header; /* skip all trailing space letters */ - while((end > header) && ISSPACE(*end)) - *end-- = 0; /* nul terminate */ + while((end > header) && ISBLANK(*end)) + *end-- = 0; /* null-terminate */ return CURLE_OK; } @@ -235,7 +236,7 @@ static CURLcode unfold_value(struct Curl_easy *data, const char *value, oalloc = olen + offset + 1; /* skip all trailing space letters */ - while(vlen && ISSPACE(value[vlen - 1])) + while(vlen && ISBLANK(value[vlen - 1])) vlen--; /* save only one leading space */ @@ -312,12 +313,17 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, return CURLE_WEIRD_SERVER_REPLY; } } + if(Curl_llist_count(&data->state.httphdrs) >= MAX_HTTP_RESP_HEADER_COUNT) { + failf(data, "Too many response headers, %d is max", + MAX_HTTP_RESP_HEADER_COUNT); + return CURLE_TOO_LARGE; + } hs = calloc(1, sizeof(*hs) + hlen); if(!hs) return CURLE_OUT_OF_MEMORY; memcpy(hs->buffer, header, hlen); - hs->buffer[hlen] = 0; /* nul terminate */ + hs->buffer[hlen] = 0; /* null-terminate */ result = namevalue(hs->buffer, hlen, type, &name, &value); if(!result) { @@ -330,8 +336,10 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, Curl_llist_append(&data->state.httphdrs, hs, &hs->node); data->state.prevhead = hs; } - else + else { + failf(data, "Invalid response header"); free(hs); + } return result; } diff --git a/Utilities/cmcurl/lib/hmac.c b/Utilities/cmcurl/lib/hmac.c index 7b8a29e692..3af1f292da 100644 --- a/Utilities/cmcurl/lib/hmac.c +++ b/Utilities/cmcurl/lib/hmac.c @@ -34,7 +34,7 @@ #include "curl_hmac.h" #include "curl_memory.h" -#include "warnless.h" +#include "curlx/warnless.h" /* The last #include file should be: */ #include "memdebug.h" diff --git a/Utilities/cmcurl/lib/hostasyn.c b/Utilities/cmcurl/lib/hostasyn.c deleted file mode 100644 index 4d6a8e8596..0000000000 --- a/Utilities/cmcurl/lib/hostasyn.c +++ /dev/null @@ -1,123 +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" - -/*********************************************************************** - * Only for builds using asynchronous name resolves - **********************************************************************/ -#ifdef CURLRES_ASYNCH - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#include "urldata.h" -#include "sendf.h" -#include "hostip.h" -#include "hash.h" -#include "share.h" -#include "url.h" -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -/* - * Curl_addrinfo_callback() gets called by ares, gethostbyname_thread() - * or getaddrinfo_thread() when we got the name resolved (or not!). - * - * If the status argument is CURL_ASYNC_SUCCESS, this function takes - * ownership of the Curl_addrinfo passed, storing the resolved data - * in the DNS cache. - * - * The storage operation locks and unlocks the DNS cache. - */ -CURLcode Curl_addrinfo_callback(struct Curl_easy *data, - int status, - struct Curl_addrinfo *ai) -{ - struct Curl_dns_entry *dns = NULL; - CURLcode result = CURLE_OK; - - data->state.async.status = status; - - if(CURL_ASYNC_SUCCESS == status) { - if(ai) { - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - dns = Curl_cache_addr(data, ai, - data->state.async.hostname, 0, - data->state.async.port, FALSE); - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); - - if(!dns) { - /* failed to store, cleanup and return error */ - Curl_freeaddrinfo(ai); - result = CURLE_OUT_OF_MEMORY; - } - } - else { - result = CURLE_OUT_OF_MEMORY; - } - } - - data->state.async.dns = dns; - - /* Set async.done TRUE last in this function since it may be used multi- - threaded and once this is TRUE the other thread may read fields from the - async struct */ - data->state.async.done = TRUE; - - /* IPv4: The input hostent struct will be freed by ares when we return from - this function */ - return result; -} - -/* - * Curl_getaddrinfo() is the generic low-level name resolve API within this - * source file. There are several versions of this function - for different - * name resolve layers (selected at build-time). They all take this same set - * of arguments - */ -struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) -{ - return Curl_resolver_getaddrinfo(data, hostname, port, waitp); -} - -#endif /* CURLRES_ASYNCH */ diff --git a/Utilities/cmcurl/lib/hostip.c b/Utilities/cmcurl/lib/hostip.c index 5ab854d1ce..ca6724ed55 100644 --- a/Utilities/cmcurl/lib/hostip.c +++ b/Utilities/cmcurl/lib/hostip.c @@ -42,22 +42,28 @@ #endif #include +#ifndef UNDER_CE #include +#endif #include "urldata.h" #include "sendf.h" +#include "connect.h" #include "hostip.h" #include "hash.h" #include "rand.h" #include "share.h" #include "url.h" #include "inet_ntop.h" -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "multiif.h" #include "doh.h" -#include "warnless.h" +#include "curlx/warnless.h" +#include "select.h" #include "strcase.h" #include "easy_lock.h" +#include "curlx/strparse.h" + /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -102,20 +108,18 @@ * The host*.c sources files are split up like this: * * hostip.c - method-independent resolver functions and utility functions - * hostasyn.c - functions for asynchronous name resolves - * hostsyn.c - functions for synchronous name resolves * hostip4.c - IPv4 specific functions * hostip6.c - IPv6 specific functions - * + * asyn.h - common functions for all async resolvers * The two asynchronous name resolver backends are implemented in: - * asyn-ares.c - functions for ares-using name resolves - * asyn-thread.c - functions for threaded name resolves - + * asyn-ares.c - async resolver using c-ares + * asyn-thread.c - async resolver using POSIX threads + * * The hostip.h is the united header file for all this. It defines the * CURLRES_* defines based on the config*.h and curl_setup.h defines. */ -static void hostcache_unlink_entry(void *entry); +static void dnscache_entry_free(struct Curl_dns_entry *dns); #ifndef CURL_DISABLE_VERBOSE_STRINGS static void show_resolve_info(struct Curl_easy *data, @@ -162,9 +166,9 @@ void Curl_printable_address(const struct Curl_addrinfo *ai, char *buf, * the DNS caching. Without alloc. Return length of the id string. */ static size_t -create_hostcache_id(const char *name, - size_t nlen, /* 0 or actual name length */ - int port, char *ptr, size_t buflen) +create_dnscache_id(const char *name, + size_t nlen, /* 0 or actual name length */ + int port, char *ptr, size_t buflen) { size_t len = nlen ? nlen : strlen(name); DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN); @@ -175,7 +179,7 @@ create_hostcache_id(const char *name, return msnprintf(&ptr[len], 7, ":%u", port) + len; } -struct hostcache_prune_data { +struct dnscache_prune_data { time_t now; time_t oldest; /* oldest time in cache not pruned. */ int max_age_sec; @@ -189,10 +193,10 @@ struct hostcache_prune_data { * cache. */ static int -hostcache_entry_is_stale(void *datap, void *hc) +dnscache_entry_is_stale(void *datap, void *hc) { - struct hostcache_prune_data *prune = - (struct hostcache_prune_data *) datap; + struct dnscache_prune_data *prune = + (struct dnscache_prune_data *) datap; struct Curl_dns_entry *dns = (struct Curl_dns_entry *) hc; if(dns->timestamp) { @@ -211,10 +215,10 @@ hostcache_entry_is_stale(void *datap, void *hc) * Returns the 'age' of the oldest still kept entry. */ static time_t -hostcache_prune(struct Curl_hash *hostcache, int cache_timeout, - time_t now) +dnscache_prune(struct Curl_hash *hostcache, int cache_timeout, + time_t now) { - struct hostcache_prune_data user; + struct dnscache_prune_data user; user.max_age_sec = cache_timeout; user.now = now; @@ -222,33 +226,56 @@ hostcache_prune(struct Curl_hash *hostcache, int cache_timeout, Curl_hash_clean_with_criterium(hostcache, (void *) &user, - hostcache_entry_is_stale); + dnscache_entry_is_stale); return user.oldest; } +static struct Curl_dnscache *dnscache_get(struct Curl_easy *data) +{ + if(data->share && data->share->specifier & (1 << CURL_LOCK_DATA_DNS)) + return &data->share->dnscache; + if(data->multi) + return &data->multi->dnscache; + return NULL; +} + +static void dnscache_lock(struct Curl_easy *data, + struct Curl_dnscache *dnscache) +{ + if(data->share && dnscache == &data->share->dnscache) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); +} + +static void dnscache_unlock(struct Curl_easy *data, + struct Curl_dnscache *dnscache) +{ + if(data->share && dnscache == &data->share->dnscache) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); +} + /* * Library-wide function for pruning the DNS cache. This function takes and * returns the appropriate locks. */ -void Curl_hostcache_prune(struct Curl_easy *data) +void Curl_dnscache_prune(struct Curl_easy *data) { + struct Curl_dnscache *dnscache = dnscache_get(data); time_t now; /* the timeout may be set -1 (forever) */ int timeout = data->set.dns_cache_timeout; - if(!data->dns.hostcache) + if(!dnscache) /* NULL hostcache means we cannot do it */ return; - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + dnscache_lock(data, dnscache); now = time(NULL); do { /* Remove outdated and unused entries from the hostcache */ - time_t oldest = hostcache_prune(data->dns.hostcache, timeout, now); + time_t oldest = dnscache_prune(&dnscache->entries, timeout, now); if(oldest < INT_MAX) timeout = (int)oldest; /* we know it fits */ @@ -258,10 +285,9 @@ 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 && - (Curl_hash_count(data->dns.hostcache) > MAX_DNS_CACHE_SIZE)); + (Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE)); - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + dnscache_unlock(data, dnscache); } #ifdef USE_ALARM_TIMEOUT @@ -274,50 +300,56 @@ static curl_simple_lock curl_jmpenv_lock; /* lookup address, returns entry if found and not stale */ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, + struct Curl_dnscache *dnscache, const char *hostname, - int port) + int port, + int ip_version) { struct Curl_dns_entry *dns = NULL; char entry_id[MAX_HOSTCACHE_LEN]; + size_t entry_len; + + if(!dnscache) + return NULL; /* Create an entry id, based upon the hostname and port */ - size_t entry_len = create_hostcache_id(hostname, 0, port, - entry_id, sizeof(entry_id)); + entry_len = create_dnscache_id(hostname, 0, port, + entry_id, sizeof(entry_id)); /* See if it is already in our dns cache */ - dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1); + dns = Curl_hash_pick(&dnscache->entries, 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)); + entry_len = create_dnscache_id("*", 1, port, entry_id, sizeof(entry_id)); /* See if it is already in our dns cache */ - dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1); + dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); } if(dns && (data->set.dns_cache_timeout != -1)) { /* See whether the returned entry is stale. Done before we release lock */ - struct hostcache_prune_data user; + struct dnscache_prune_data user; user.now = time(NULL); user.max_age_sec = data->set.dns_cache_timeout; user.oldest = 0; - if(hostcache_entry_is_stale(&user, dns)) { + if(dnscache_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); + Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); } } /* See if the returned entry matches the required resolve mode */ - if(dns && data->conn->ip_version != CURL_IPRESOLVE_WHATEVER) { + if(dns && ip_version != CURL_IPRESOLVE_WHATEVER) { int pf = PF_INET; bool found = FALSE; struct Curl_addrinfo *addr = dns->addr; #ifdef PF_INET6 - if(data->conn->ip_version == CURL_IPRESOLVE_V6) + if(ip_version == CURL_IPRESOLVE_V6) pf = PF_INET6; #endif @@ -332,14 +364,14 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, if(!found) { 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); + Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); } } return dns; } /* - * Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache. + * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache. * * Curl_resolv() checks initially and multi_runsingle() checks each time * it discovers the handle in the state WAITRESOLVE whether the hostname @@ -353,22 +385,21 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, * use, or we will leak memory! */ struct Curl_dns_entry * -Curl_fetch_addr(struct Curl_easy *data, - const char *hostname, - int port) +Curl_dnscache_get(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version) { + struct Curl_dnscache *dnscache = dnscache_get(data); struct Curl_dns_entry *dns = NULL; - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - dns = fetch_addr(data, hostname, port); + dnscache_lock(data, dnscache); + dns = fetch_addr(data, dnscache, hostname, port, ip_version); if(dns) dns->refcount++; /* we use it! */ - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + dnscache_unlock(data, dnscache); return dns; } @@ -455,34 +486,24 @@ UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data, } #endif -/* - * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache. - * - * When calling Curl_resolv() has resulted in a response with a returned - * address, we call this function to store the information in the dns - * cache etc - * - * 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, /* length or zero */ - int port, - bool permanent) +Curl_dnscache_mk_entry(struct Curl_easy *data, + struct Curl_addrinfo *addr, + const char *hostname, + size_t hostlen, /* length or zero */ + int port, + bool permanent) { - char entry_id[MAX_HOSTCACHE_LEN]; - size_t entry_len; struct Curl_dns_entry *dns; - struct Curl_dns_entry *dns2; #ifndef CURL_DISABLE_SHUFFLE_DNS /* shuffle addresses if requested */ if(data->set.dns_shuffle_addresses) { CURLcode result = Curl_shuffle_addr(data, &addr); - if(result) + if(result) { + Curl_freeaddrinfo(addr); return NULL; + } } #endif if(!hostlen) @@ -491,13 +512,10 @@ Curl_cache_addr(struct Curl_easy *data, /* Create a new cache entry */ dns = calloc(1, sizeof(struct Curl_dns_entry) + hostlen); if(!dns) { + Curl_freeaddrinfo(addr); return NULL; } - /* Create an entry id, based upon the hostname and port */ - entry_len = create_hostcache_id(hostname, hostlen, port, - entry_id, sizeof(entry_id)); - dns->refcount = 1; /* the cache has the first reference */ dns->addr = addr; /* this is the address(es) */ if(permanent) @@ -511,11 +529,36 @@ Curl_cache_addr(struct Curl_easy *data, if(hostlen) memcpy(dns->hostname, hostname, hostlen); + return dns; +} + +static struct Curl_dns_entry * +dnscache_add_addr(struct Curl_easy *data, + struct Curl_dnscache *dnscache, + struct Curl_addrinfo *addr, + const char *hostname, + size_t hlen, /* length or zero */ + int port, + bool permanent) +{ + char entry_id[MAX_HOSTCACHE_LEN]; + size_t entry_len; + struct Curl_dns_entry *dns; + struct Curl_dns_entry *dns2; + + dns = Curl_dnscache_mk_entry(data, addr, hostname, hlen, port, permanent); + if(!dns) + return NULL; + + /* Create an entry id, based upon the hostname and port */ + entry_len = create_dnscache_id(hostname, hlen, port, + entry_id, sizeof(entry_id)); + /* Store the resolved data in our DNS cache. */ - dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len + 1, + dns2 = Curl_hash_add(&dnscache->entries, entry_id, entry_len + 1, (void *)dns); if(!dns2) { - free(dns); + dnscache_entry_free(dns); return NULL; } @@ -524,6 +567,30 @@ Curl_cache_addr(struct Curl_easy *data, return dns; } +CURLcode Curl_dnscache_add(struct Curl_easy *data, + struct Curl_dns_entry *entry) +{ + struct Curl_dnscache *dnscache = dnscache_get(data); + char id[MAX_HOSTCACHE_LEN]; + size_t idlen; + + if(!dnscache) + return CURLE_FAILED_INIT; + /* Create an entry id, based upon the hostname and port */ + idlen = create_dnscache_id(entry->hostname, 0, entry->hostport, + id, sizeof(id)); + + /* Store the resolved data in our DNS cache and up ref count */ + dnscache_lock(data, dnscache); + if(!Curl_hash_add(&dnscache->entries, id, idlen + 1, (void *)entry)) { + dnscache_unlock(data, dnscache); + return CURLE_OUT_OF_MEMORY; + } + entry->refcount++; + dnscache_unlock(data, dnscache); + return CURLE_OK; +} + #ifdef USE_IPV6 /* return a static IPv6 ::1 for the name */ static struct Curl_addrinfo *get_localhost6(int port, const char *name) @@ -545,7 +612,7 @@ static struct Curl_addrinfo *get_localhost6(int port, const char *name) sa6.sin6_scope_id = 0; #endif - (void)Curl_inet_pton(AF_INET6, "::1", ipv6); + (void)curlx_inet_pton(AF_INET6, "::1", ipv6); memcpy(&sa6.sin6_addr, ipv6, sizeof(ipv6)); ca->ai_flags = 0; @@ -579,7 +646,7 @@ static struct Curl_addrinfo *get_localhost(int port, const char *name) memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(port16); - if(Curl_inet_pton(AF_INET, "127.0.0.1", (char *)&ipv4) < 1) + if(curlx_inet_pton(AF_INET, "127.0.0.1", (char *)&ipv4) < 1) return NULL; memcpy(&sa.sin_addr, &ipv4, sizeof(ipv4)); @@ -647,9 +714,9 @@ bool Curl_host_is_ipnum(const char *hostname) #ifdef USE_IPV6 struct in6_addr in6; #endif - if(Curl_inet_pton(AF_INET, hostname, &in) > 0 + if(curlx_inet_pton(AF_INET, hostname, &in) > 0 #ifdef USE_IPV6 - || Curl_inet_pton(AF_INET6, hostname, &in6) > 0 + || curlx_inet_pton(AF_INET6, hostname, &in6) > 0 #endif ) return TRUE; @@ -658,15 +725,63 @@ bool Curl_host_is_ipnum(const char *hostname) /* return TRUE if 'part' is a case insensitive tail of 'full' */ -static bool tailmatch(const char *full, const char *part) +static bool tailmatch(const char *full, size_t flen, + const char *part, size_t plen) { - size_t plen = strlen(part); - size_t flen = strlen(full); if(plen > flen) return FALSE; return strncasecompare(part, &full[flen - plen], plen); } +static struct Curl_addrinfo * +convert_ipaddr_direct(const char *hostname, int port, bool *is_ipaddr) +{ + struct in_addr in; + *is_ipaddr = FALSE; + /* First check if this is an IPv4 address string */ + if(curlx_inet_pton(AF_INET, hostname, &in) > 0) { + /* This is a dotted IP address 123.123.123.123-style */ + *is_ipaddr = TRUE; +#ifdef USE_RESOLVE_ON_IPS + (void)port; + return NULL; +#else + return Curl_ip2addr(AF_INET, &in, hostname, port); +#endif + } +#ifdef USE_IPV6 + else { + struct in6_addr in6; + /* check if this is an IPv6 address string */ + if(curlx_inet_pton(AF_INET6, hostname, &in6) > 0) { + /* This is an IPv6 address literal */ + *is_ipaddr = TRUE; +#ifdef USE_RESOLVE_ON_IPS + return NULL; +#else + return Curl_ip2addr(AF_INET6, &in6, hostname, port); +#endif + } + } +#endif /* USE_IPV6 */ + return NULL; +} + +static bool can_resolve_ip_version(struct Curl_easy *data, int ip_version) +{ +#ifdef CURLRES_IPV6 + if(ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data)) + return FALSE; +#elif defined(CURLRES_IPV4) + (void)data; + if(ip_version == CURL_IPRESOLVE_V6) + return FALSE; +#else +#error either CURLRES_IPV6 or CURLRES_IPV4 need to be defined +#endif + return TRUE; +} + /* * 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 @@ -678,184 +793,168 @@ static bool tailmatch(const char *full, const char *part) * done using this struct) to decrease the reference counter again. * * Return codes: - * - * CURLRESOLV_ERROR (-1) = error, no pointer - * CURLRESOLV_RESOLVED (0) = OK, pointer provided - * CURLRESOLV_PENDING (1) = waiting for response, no pointer + * CURLE_OK = success, *entry set to non-NULL + * CURLE_AGAIN = resolving in progress, *entry == NULL + * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL + * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL */ - -enum resolve_t Curl_resolv(struct Curl_easy *data, - const char *hostname, - int port, - bool allowDOH, - struct Curl_dns_entry **entry) +CURLcode Curl_resolv(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + bool allowDOH, + struct Curl_dns_entry **entry) { + struct Curl_dnscache *dnscache = dnscache_get(data); struct Curl_dns_entry *dns = NULL; - CURLcode result; - enum resolve_t rc = CURLRESOLV_ERROR; /* default to failure */ - struct connectdata *conn = data->conn; + struct Curl_addrinfo *addr = NULL; + int respwait = 0; + bool is_ipaddr; + size_t hostname_len; + +#ifndef CURL_DISABLE_DOH + data->conn->bits.doh = FALSE; /* default is not */ +#else + (void)allowDOH; +#endif + if(!dnscache) + goto error; + /* We should intentionally error and not resolve .onion TLDs */ - size_t hostname_len = strlen(hostname); + hostname_len = strlen(hostname); if(hostname_len >= 7 && (curl_strequal(&hostname[hostname_len - 6], ".onion") || curl_strequal(&hostname[hostname_len - 7], ".onion."))) { failf(data, "Not resolving .onion address (RFC 7686)"); - return CURLRESOLV_ERROR; + goto error; } - *entry = NULL; -#ifndef CURL_DISABLE_DOH - conn->bits.doh = FALSE; /* default is not */ -#else - (void)allowDOH; -#endif - - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - dns = fetch_addr(data, hostname, port); + /* Let's check our DNS cache first */ + dnscache_lock(data, dnscache); + dns = fetch_addr(data, dnscache, hostname, port, ip_version); + if(dns) + dns->refcount++; /* we pass out the reference. */ + dnscache_unlock(data, dnscache); if(dns) { infof(data, "Hostname %s was found in DNS cache", hostname); - dns->refcount++; /* we use it! */ - rc = CURLRESOLV_RESOLVED; + goto out; } - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); - - if(!dns) { - /* The entry was not in the cache. Resolve it to IP address */ - - struct Curl_addrinfo *addr = NULL; - int respwait = 0; -#if !defined(CURL_DISABLE_DOH) || !defined(USE_RESOLVE_ON_IPS) - struct in_addr in; + /* No luck, we need to resolve hostname. Notify user callback. */ + if(data->set.resolver_start) { + void *resolver = NULL; + int st; +#ifdef CURLRES_ASYNCH + if(Curl_async_get_impl(data, &resolver)) + goto error; #endif -#ifndef CURL_DISABLE_DOH + Curl_set_in_callback(data, TRUE); + st = data->set.resolver_start(resolver, NULL, + data->set.resolver_start_client); + Curl_set_in_callback(data, FALSE); + if(st) + goto error; + } + + /* shortcut literal IP addresses, if we are not told to resolve them. */ + addr = convert_ipaddr_direct(hostname, port, &is_ipaddr); + if(addr) + goto out; + #ifndef USE_RESOLVE_ON_IPS - const -#endif - bool ipnum = FALSE; + /* allowed to convert, hostname is IP address, then NULL means error */ + if(is_ipaddr) + goto error; #endif - /* notify the resolver start callback */ - if(data->set.resolver_start) { - int st; - Curl_set_in_callback(data, TRUE); - st = data->set.resolver_start( -#ifdef USE_CURL_ASYNC - data->state.async.resolver, + /* Really need a resolver for hostname. */ + if(ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data)) + goto error; + + if(!is_ipaddr && + (strcasecompare(hostname, "localhost") || + strcasecompare(hostname, "localhost.") || + tailmatch(hostname, hostname_len, STRCONST(".localhost")) || + tailmatch(hostname, hostname_len, STRCONST(".localhost.")))) { + addr = get_localhost(port, hostname); + } +#ifndef CURL_DISABLE_DOH + else if(!is_ipaddr && allowDOH && data->set.doh) { + addr = Curl_doh(data, hostname, port, ip_version, &respwait); + } +#endif + else { + /* Can we provide the requested IP specifics in resolving? */ + if(!can_resolve_ip_version(data, ip_version)) + goto error; + +#ifdef CURLRES_ASYNCH + addr = Curl_async_getaddrinfo(data, hostname, port, ip_version, &respwait); #else - NULL, + respwait = 0; /* no async waiting here */ + addr = Curl_sync_getaddrinfo(data, hostname, port, ip_version); #endif - NULL, - data->set.resolver_start_client); - 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) { - /* This is a dotted IP address 123.123.123.123-style */ - addr = Curl_ip2addr(AF_INET, &in, hostname, port); - 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) { - /* This is an IPv6 address literal */ - addr = Curl_ip2addr(AF_INET6, &in6, hostname, port); - if(!addr) - return CURLRESOLV_ERROR; - } - } -#endif /* USE_IPV6 */ - -#else /* if USE_RESOLVE_ON_IPS */ -#ifndef CURL_DISABLE_DOH - /* First check if this is an IPv4 address string */ - if(Curl_inet_pton(AF_INET, hostname, &in) > 0) - /* This is a dotted IP address 123.123.123.123-style */ - ipnum = TRUE; -#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) - /* This is an IPv6 address literal */ - ipnum = TRUE; - } -#endif /* USE_IPV6 */ -#endif /* CURL_DISABLE_DOH */ - -#endif /* !USE_RESOLVE_ON_IPS */ - - if(!addr) { - if(conn->ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data)) - return CURLRESOLV_ERROR; - - if(strcasecompare(hostname, "localhost") || - strcasecompare(hostname, "localhost.") || - tailmatch(hostname, ".localhost") || - tailmatch(hostname, ".localhost.")) - addr = get_localhost(port, hostname); -#ifndef CURL_DISABLE_DOH - else if(allowDOH && data->set.doh && !ipnum) - addr = Curl_doh(data, hostname, port, &respwait); -#endif - else { - /* Check what IP specifics the app has requested and if we can provide - * it. If not, bail out. */ - if(!Curl_ipvalid(data, conn)) - return CURLRESOLV_ERROR; - /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a - non-zero value indicating that we need to wait for the response to - the resolve call */ - addr = Curl_getaddrinfo(data, hostname, port, &respwait); - } - } - if(!addr) { - if(respwait) { - /* the response to our resolve call will come asynchronously at - a later time, good or bad */ - /* First, check that we have not received the info by now */ - result = Curl_resolv_check(data, &dns); - if(result) /* error detected */ - return CURLRESOLV_ERROR; - if(dns) - rc = CURLRESOLV_RESOLVED; /* pointer provided */ - else - rc = CURLRESOLV_PENDING; /* no info yet */ - } - } - else { - 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, addr, hostname, 0, port, FALSE); - - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); - - if(!dns) - /* returned failure, bail out nicely */ - Curl_freeaddrinfo(addr); - else { - rc = CURLRESOLV_RESOLVED; - show_resolve_info(data, dns); - } - } } - *entry = dns; +out: + /* We either have found a `dns` or looked up the `addr` + * or `respwait` is set for an async operation. + * Everything else is a failure to resolve. */ + if(dns) { + *entry = dns; + return CURLE_OK; + } + else if(addr) { + /* we got a response, create a dns entry, add to cache, return */ + dns = Curl_dnscache_mk_entry(data, addr, hostname, 0, port, FALSE); + if(!dns) + goto error; + if(Curl_dnscache_add(data, dns)) + goto error; + show_resolve_info(data, dns); + *entry = dns; + return CURLE_OK; + } + else if(respwait) { + if(!Curl_resolv_check(data, &dns)) { + *entry = dns; + return dns ? CURLE_OK : CURLE_AGAIN; + } + } +error: + if(dns) + Curl_resolv_unlink(data, &dns); + *entry = NULL; + Curl_async_shutdown(data); + return CURLE_COULDNT_RESOLVE_HOST; +} - return rc; +CURLcode Curl_resolv_blocking(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + struct Curl_dns_entry **dnsentry) +{ + CURLcode result; + + *dnsentry = NULL; + result = Curl_resolv(data, hostname, port, ip_version, FALSE, dnsentry); + switch(result) { + case CURLE_OK: + DEBUGASSERT(*dnsentry); + return CURLE_OK; + case CURLE_AGAIN: + DEBUGASSERT(!*dnsentry); + result = Curl_async_await(data, dnsentry); + if(result || !*dnsentry) { + /* close the connection, since we cannot return failure here without + cleaning up this connection properly. */ + connclose(data->conn, "async resolve failed"); + } + return result; + default: + return result; + } } #ifdef USE_ALARM_TIMEOUT @@ -887,18 +986,18 @@ void alarmfunc(int sig) * is ignored. * * Return codes: - * - * CURLRESOLV_TIMEDOUT(-2) = warning, time too short or previous alarm expired - * CURLRESOLV_ERROR (-1) = error, no pointer - * CURLRESOLV_RESOLVED (0) = OK, pointer provided - * CURLRESOLV_PENDING (1) = waiting for response, no pointer + * CURLE_OK = success, *entry set to non-NULL + * CURLE_AGAIN = resolving in progress, *entry == NULL + * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL + * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL */ -enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, - const char *hostname, - int port, - struct Curl_dns_entry **entry, - timediff_t timeoutms) +CURLcode Curl_resolv_timeout(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + struct Curl_dns_entry **entry, + timediff_t timeoutms) { #ifdef USE_ALARM_TIMEOUT #ifdef HAVE_SIGACTION @@ -913,13 +1012,13 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, volatile long timeout; volatile unsigned int prev_alarm = 0; #endif /* USE_ALARM_TIMEOUT */ - enum resolve_t rc; + CURLcode result; *entry = NULL; if(timeoutms < 0) /* got an already expired timeout */ - return CURLRESOLV_TIMEDOUT; + return CURLE_OPERATION_TIMEDOUT; #ifdef USE_ALARM_TIMEOUT if(data->set.no_signal) @@ -928,9 +1027,14 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, else timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms; - if(!timeout) - /* USE_ALARM_TIMEOUT defined, but no timeout actually requested */ - return Curl_resolv(data, hostname, port, TRUE, entry); + if(!timeout +#ifndef CURL_DISABLE_DOH + || data->set.doh +#endif + ) + /* USE_ALARM_TIMEOUT defined, but no timeout actually requested or resolve + done using DoH */ + return Curl_resolv(data, hostname, port, ip_version, TRUE, entry); if(timeout < 1000) { /* The alarm() function only provides integer second resolution, so if @@ -938,7 +1042,7 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, failf(data, "remaining timeout of %ld too small to resolve via SIGALRM method", timeout); - return CURLRESOLV_TIMEDOUT; + return CURLE_OPERATION_TIMEDOUT; } /* This allows us to time-out from the name resolver, as the timeout will generate a signal and we will siglongjmp() from that here. @@ -951,7 +1055,7 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, if(sigsetjmp(curl_jmpenv, 1)) { /* this is coming from a siglongjmp() after an alarm signal */ failf(data, "name lookup timed out"); - rc = CURLRESOLV_ERROR; + result = CURLE_OPERATION_TIMEDOUT; goto clean_up; } else { @@ -982,19 +1086,19 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, prev_alarm = alarm(curlx_sltoui(timeout/1000L)); } -#else +#else /* USE_ALARM_TIMEOUT */ #ifndef CURLRES_ASYNCH if(timeoutms) infof(data, "timeout on name lookup is not supported"); #else (void)timeoutms; /* timeoutms not used with an async resolver */ #endif -#endif /* USE_ALARM_TIMEOUT */ +#endif /* else USE_ALARM_TIMEOUT */ /* Perform the actual name resolution. This might be interrupted by an * alarm if it takes too long. */ - rc = Curl_resolv(data, hostname, port, TRUE, entry); + result = Curl_resolv(data, hostname, port, ip_version, TRUE, entry); #ifdef USE_ALARM_TIMEOUT clean_up: @@ -1022,7 +1126,7 @@ clean_up: the time we spent until now! */ if(prev_alarm) { /* there was an alarm() set before us, now put it back */ - timediff_t elapsed_secs = Curl_timediff(Curl_now(), + timediff_t elapsed_secs = curlx_timediff(curlx_now(), data->conn->created) / 1000; /* the alarm period is counted in even number of seconds */ @@ -1035,7 +1139,7 @@ clean_up: will not, and zero would be to switch it off so we never set it to less than 1! */ alarm(1); - rc = CURLRESOLV_TIMEDOUT; + result = CURLE_OPERATION_TIMEDOUT; failf(data, "Previous alarm fired off"); } else @@ -1043,7 +1147,19 @@ clean_up: } #endif /* USE_ALARM_TIMEOUT */ - return rc; + return result; +} + +static void dnscache_entry_free(struct Curl_dns_entry *dns) +{ + Curl_freeaddrinfo(dns->addr); +#ifdef USE_HTTPSRR + if(dns->hinfo) { + Curl_httpsrr_cleanup(dns->hinfo); + free(dns->hinfo); + } +#endif + free(dns); } /* @@ -1055,110 +1171,85 @@ clean_up: */ 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); - - hostcache_unlink_entry(dns); - - if(data && data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); -} - -/* - * File-internal: release cache dns entry reference, free if inuse drops to 0 - */ -static void hostcache_unlink_entry(void *entry) -{ - struct Curl_dns_entry *dns = (struct Curl_dns_entry *) entry; - DEBUGASSERT(dns && (dns->refcount > 0)); - - dns->refcount--; - if(dns->refcount == 0) { - Curl_freeaddrinfo(dns->addr); -#ifdef USE_HTTPSRR - if(dns->hinfo) { - free(dns->hinfo->target); - free(dns->hinfo->ipv4hints); - free(dns->hinfo->echconfiglist); - free(dns->hinfo->ipv6hints); - free(dns->hinfo); - } -#endif - free(dns); + if(*pdns) { + struct Curl_dnscache *dnscache = dnscache_get(data); + struct Curl_dns_entry *dns = *pdns; + *pdns = NULL; + dnscache_lock(data, dnscache); + dns->refcount--; + if(dns->refcount == 0) + dnscache_entry_free(dns); + dnscache_unlock(data, dnscache); } } -/* - * Curl_init_dnscache() inits a new DNS cache. - */ -void Curl_init_dnscache(struct Curl_hash *hash, size_t size) +static void dnscache_entry_dtor(void *entry) { - Curl_hash_init(hash, size, Curl_hash_str, Curl_str_key_compare, - hostcache_unlink_entry); + struct Curl_dns_entry *dns = (struct Curl_dns_entry *) entry; + DEBUGASSERT(dns && (dns->refcount > 0)); + dns->refcount--; + if(dns->refcount == 0) + dnscache_entry_free(dns); } /* - * Curl_hostcache_clean() - * - * This _can_ be called with 'data' == NULL but then of course no locking - * can be done! + * Curl_dnscache_init() inits a new DNS cache. */ - -void Curl_hostcache_clean(struct Curl_easy *data, - struct Curl_hash *hash) +void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size) { - if(data && data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - Curl_hash_clean(hash); - - if(data && data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + Curl_hash_init(&dns->entries, size, Curl_hash_str, curlx_str_key_compare, + dnscache_entry_dtor); } +void Curl_dnscache_destroy(struct Curl_dnscache *dns) +{ + Curl_hash_destroy(&dns->entries); +} CURLcode Curl_loadhostpairs(struct Curl_easy *data) { + struct Curl_dnscache *dnscache = dnscache_get(data); struct curl_slist *hostp; - char *host_end; + + if(!dnscache) + return CURLE_FAILED_INIT; /* Default is no wildcard found */ data->state.wildcard_resolve = FALSE; for(hostp = data->state.resolve; hostp; hostp = hostp->next) { char entry_id[MAX_HOSTCACHE_LEN]; - if(!hostp->data) + const char *host = hostp->data; + struct Curl_str source; + if(!host) continue; - if(hostp->data[0] == '-') { - unsigned long num = 0; + if(*host == '-') { + curl_off_t num = 0; size_t entry_len; - size_t hlen = 0; - host_end = strchr(&hostp->data[1], ':'); - - if(host_end) { - hlen = host_end - &hostp->data[1]; - num = strtoul(++host_end, NULL, 10); - if(!hlen || (num > 0xffff)) - host_end = NULL; + host++; + if(!curlx_str_single(&host, '[')) { + if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') || + curlx_str_single(&host, ']') || + curlx_str_single(&host, ':')) + continue; } - if(!host_end) { - infof(data, "Bad syntax CURLOPT_RESOLVE removal entry '%s'", - hostp->data); - continue; + else { + if(curlx_str_until(&host, &source, 4096, ':') || + curlx_str_single(&host, ':')) { + continue; + } } - /* Create an entry id, based upon the hostname and port */ - entry_len = create_hostcache_id(&hostp->data[1], hlen, (int)num, - entry_id, sizeof(entry_id)); - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - /* delete entry, ignore if it did not exist */ - Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); - - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + if(!curlx_str_number(&host, &num, 0xffff)) { + /* Create an entry id, based upon the hostname and port */ + entry_len = create_dnscache_id(curlx_str(&source), + curlx_strlen(&source), (int)num, + entry_id, sizeof(entry_id)); + dnscache_lock(data, dnscache); + /* delete entry, ignore if it did not exist */ + Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); + dnscache_unlock(data, dnscache); + } } else { struct Curl_dns_entry *dns; @@ -1166,75 +1257,69 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) size_t entry_len; char address[64]; #if !defined(CURL_DISABLE_VERBOSE_STRINGS) - char *addresses = NULL; + const char *addresses = NULL; #endif - char *addr_begin; - char *addr_end; - char *port_ptr; - int port = 0; - char *end_ptr; + curl_off_t port = 0; bool permanent = TRUE; - unsigned long tmp_port; bool error = TRUE; - char *host_begin = hostp->data; - size_t hlen = 0; - if(host_begin[0] == '+') { - host_begin++; + if(*host == '+') { + host++; permanent = FALSE; } - host_end = strchr(host_begin, ':'); - if(!host_end) - goto err; - hlen = host_end - host_begin; - - port_ptr = host_end + 1; - tmp_port = strtoul(port_ptr, &end_ptr, 10); - if(tmp_port > USHRT_MAX || end_ptr == port_ptr || *end_ptr != ':') + if(!curlx_str_single(&host, '[')) { + if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') || + curlx_str_single(&host, ']')) + continue; + } + else { + if(curlx_str_until(&host, &source, 4096, ':')) + continue; + } + if(curlx_str_single(&host, ':') || + curlx_str_number(&host, &port, 0xffff) || + curlx_str_single(&host, ':')) goto err; - port = (int)tmp_port; #if !defined(CURL_DISABLE_VERBOSE_STRINGS) - addresses = end_ptr + 1; + addresses = host; #endif - while(*end_ptr) { - size_t alen; + /* start the address section */ + while(*host) { + struct Curl_str target; struct Curl_addrinfo *ai; - addr_begin = end_ptr + 1; - addr_end = strchr(addr_begin, ','); - if(!addr_end) - addr_end = addr_begin + strlen(addr_begin); - end_ptr = addr_end; - - /* allow IP(v6) address within [brackets] */ - if(*addr_begin == '[') { - if(addr_end == addr_begin || *(addr_end - 1) != ']') + if(!curlx_str_single(&host, '[')) { + if(curlx_str_until(&host, &target, MAX_IPADR_LEN, ']') || + curlx_str_single(&host, ']')) goto err; - ++addr_begin; - --addr_end; } - - alen = addr_end - addr_begin; - if(!alen) - continue; - - if(alen >= sizeof(address)) - goto err; - - memcpy(address, addr_begin, alen); - address[alen] = '\0'; - + else { + if(curlx_str_until(&host, &target, 4096, ',')) { + if(curlx_str_single(&host, ',')) + goto err; + /* survive nothing but just a comma */ + continue; + } + } #ifndef USE_IPV6 - if(strchr(address, ':')) { + if(memchr(target.str, ':', target.len)) { infof(data, "Ignoring resolve address '%s', missing IPv6 support.", address); + if(curlx_str_single(&host, ',')) + goto err; continue; } #endif - ai = Curl_str2addr(address, port); + if(curlx_strlen(&target) >= sizeof(address)) + goto err; + + memcpy(address, curlx_str(&target), curlx_strlen(&target)); + address[curlx_strlen(&target)] = '\0'; + + ai = Curl_str2addr(address, (int)port); if(!ai) { infof(data, "Resolve address '%s' found illegal", address); goto err; @@ -1247,6 +1332,8 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data) else { head = tail = ai; } + if(curlx_str_single(&host, ',')) + break; } if(!head) @@ -1262,18 +1349,19 @@ err: } /* Create an entry id, based upon the hostname and port */ - entry_len = create_hostcache_id(host_begin, hlen, port, - entry_id, sizeof(entry_id)); + entry_len = create_dnscache_id(curlx_str(&source), curlx_strlen(&source), + (int)port, + entry_id, sizeof(entry_id)); - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + dnscache_lock(data, dnscache); /* See if it is already in our dns cache */ - dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1); + dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1); if(dns) { - infof(data, "RESOLVE %.*s:%d - old addresses discarded", - (int)hlen, host_begin, port); + infof(data, "RESOLVE %.*s:%" CURL_FORMAT_CURL_OFF_T + " - old addresses discarded", (int)curlx_strlen(&source), + curlx_str(&source), port); /* delete old entry, there are two reasons for this 1. old entry may have different addresses. 2. even if entry with correct addresses is already in the cache, @@ -1285,33 +1373,33 @@ err: 4. when adding a non-permanent entry, we want it to get a "fresh" timeout that starts _now_. */ - Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); + Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1); } /* put this new host in the cache */ - dns = Curl_cache_addr(data, head, host_begin, hlen, port, permanent); + dns = dnscache_add_addr(data, dnscache, head, curlx_str(&source), + curlx_strlen(&source), (int)port, permanent); if(dns) { /* release the returned reference; the cache itself will keep the * entry alive: */ dns->refcount--; } - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + dnscache_unlock(data, dnscache); - if(!dns) { - Curl_freeaddrinfo(head); + if(!dns) 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, + infof(data, "Added %.*s:%" CURL_FORMAT_CURL_OFF_T ":%s to DNS cache%s", + (int)curlx_strlen(&source), curlx_str(&source), port, addresses, permanent ? "" : " (non-permanent)"); #endif /* Wildcard hostname */ - if((hlen == 1) && (host_begin[0] == '*')) { - infof(data, "RESOLVE *:%d using wildcard", port); + if(curlx_str_casecompare(&source, "*")) { + infof(data, "RESOLVE *:%" CURL_FORMAT_CURL_OFF_T " using wildcard", + port); data->state.wildcard_resolve = TRUE; } } @@ -1345,9 +1433,9 @@ static void show_resolve_info(struct Curl_easy *data, infof(data, "Host %s:%d was resolved.", (dns->hostname[0] ? dns->hostname : "(none)"), dns->hostport); - Curl_dyn_init(&out[0], 1024); + curlx_dyn_init(&out[0], 1024); #ifdef CURLRES_IPV6 - Curl_dyn_init(&out[1], 1024); + curlx_dyn_init(&out[1], 1024); #endif while(a) { @@ -1359,10 +1447,10 @@ static void show_resolve_info(struct Curl_easy *data, 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(curlx_dyn_len(d)) + result = curlx_dyn_addn(d, ", ", 2); if(!result) - result = Curl_dyn_add(d, buf); + result = curlx_dyn_add(d, buf); if(result) { infof(data, "too many IP, cannot show"); goto fail; @@ -1373,38 +1461,55 @@ static void show_resolve_info(struct Curl_easy *data, #ifdef CURLRES_IPV6 infof(data, "IPv6: %s", - (Curl_dyn_len(&out[1]) ? Curl_dyn_ptr(&out[1]) : "(none)")); + (curlx_dyn_len(&out[1]) ? curlx_dyn_ptr(&out[1]) : "(none)")); #endif infof(data, "IPv4: %s", - (Curl_dyn_len(&out[0]) ? Curl_dyn_ptr(&out[0]) : "(none)")); + (curlx_dyn_len(&out[0]) ? curlx_dyn_ptr(&out[0]) : "(none)")); fail: - Curl_dyn_free(&out[0]); + curlx_dyn_free(&out[0]); #ifdef CURLRES_IPV6 - Curl_dyn_free(&out[1]); + curlx_dyn_free(&out[1]); #endif } #endif +#ifdef USE_CURL_ASYNC 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 + + /* If async resolving is ongoing, this must be set */ + if(!data->state.async.hostname) + return CURLE_FAILED_INIT; + + /* check if we have the name resolved by now (from someone else) */ + *dns = Curl_dnscache_get(data, data->state.async.hostname, + data->state.async.port, + data->state.async.ip_version); + if(*dns) { + /* Tell a possibly async resolver we no longer need the results. */ + infof(data, "Hostname '%s' was found in DNS cache", + data->state.async.hostname); + Curl_async_shutdown(data); + data->state.async.dns = *dns; + data->state.async.done = TRUE; + return CURLE_OK; + } + #ifndef CURL_DISABLE_DOH if(data->conn->bits.doh) { result = Curl_doh_is_resolved(data, dns); } else #endif - result = Curl_resolver_is_resolved(data, dns); + result = Curl_async_is_resolved(data, dns); if(*dns) show_resolve_info(data, *dns); return result; } +#endif int Curl_resolv_getsock(struct Curl_easy *data, curl_socket_t *socks) @@ -1416,7 +1521,7 @@ int Curl_resolv_getsock(struct Curl_easy *data, sockets */ return GETSOCK_BLANK; #endif - return Curl_resolver_getsock(data, socks); + return Curl_async_getsock(data, socks); #else (void)data; (void)socks; @@ -1429,23 +1534,25 @@ int Curl_resolv_getsock(struct Curl_easy *data, Note: this function disconnects and frees the conn data in case of resolve failure */ -CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_done) +CURLcode Curl_once_resolved(struct Curl_easy *data, + struct Curl_dns_entry *dns, + bool *protocol_done) { CURLcode result; struct connectdata *conn = data->conn; #ifdef USE_CURL_ASYNC if(data->state.async.dns) { - conn->dns_entry = data->state.async.dns; + DEBUGASSERT(data->state.async.dns == dns); data->state.async.dns = NULL; } #endif - result = Curl_setup_conn(data, protocol_done); + result = Curl_setup_conn(data, dns, protocol_done); if(result) { Curl_detach_connection(data); - Curl_cpool_disconnect(data, conn, TRUE); + Curl_conn_terminate(data, conn, TRUE); } return result; } @@ -1458,25 +1565,21 @@ CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_done) #ifdef USE_CURL_ASYNC CURLcode Curl_resolver_error(struct Curl_easy *data) { - const char *host_or_proxy; - CURLcode result; + struct connectdata *conn = data->conn; + const char *host_or_proxy = "host"; + const char *name = conn->host.dispname; + CURLcode result = CURLE_COULDNT_RESOLVE_HOST; #ifndef CURL_DISABLE_PROXY - struct connectdata *conn = data->conn; - if(conn->bits.httpproxy) { + if(conn->bits.proxy) { host_or_proxy = "proxy"; result = CURLE_COULDNT_RESOLVE_PROXY; + name = conn->socks_proxy.host.name ? conn->socks_proxy.host.dispname : + conn->http_proxy.host.dispname; } - else #endif - { - host_or_proxy = "host"; - result = CURLE_COULDNT_RESOLVE_HOST; - } - - failf(data, "Could not resolve %s: %s", host_or_proxy, - data->state.async.hostname); + failf(data, "Could not resolve %s: %s", host_or_proxy, name); return result; } #endif /* USE_CURL_ASYNC */ diff --git a/Utilities/cmcurl/lib/hostip.h b/Utilities/cmcurl/lib/hostip.h index 10f70b2ba8..cd3d957e1e 100644 --- a/Utilities/cmcurl/lib/hostip.h +++ b/Utilities/cmcurl/lib/hostip.h @@ -27,7 +27,7 @@ #include "curl_setup.h" #include "hash.h" #include "curl_addrinfo.h" -#include "timeval.h" /* for timediff_t */ +#include "curlx/timeval.h" /* for timediff_t */ #include "asyn.h" #include "httpsrr.h" @@ -61,15 +61,6 @@ enum alpnid { ALPN_h3 = CURLALTSVC_H3 }; -/* - * Curl_global_host_cache_init() initializes and sets up a global DNS cache. - * Global DNS cache is general badness. Do not use. This will be removed in - * a future version. Use the share interface instead! - * - * Returns a struct Curl_hash pointer on success, NULL on failure. - */ -struct Curl_hash *Curl_global_host_cache_init(void); - struct Curl_dns_entry { struct Curl_addrinfo *addr; #ifdef USE_HTTPSRR @@ -85,6 +76,10 @@ struct Curl_dns_entry { char hostname[1]; }; +struct Curl_dnscache { + struct Curl_hash entries; +}; + bool Curl_host_is_ipnum(const char *hostname); /* @@ -94,22 +89,24 @@ bool Curl_host_is_ipnum(const char *hostname); * The returned data *MUST* be "released" with Curl_resolv_unlink() after * use, or we will leak memory! */ -/* return codes */ -enum resolve_t { - CURLRESOLV_TIMEDOUT = -2, - CURLRESOLV_ERROR = -1, - CURLRESOLV_RESOLVED = 0, - CURLRESOLV_PENDING = 1 -}; -enum resolve_t Curl_resolv(struct Curl_easy *data, - const char *hostname, - int port, - bool allowDOH, - struct Curl_dns_entry **dnsentry); -enum resolve_t Curl_resolv_timeout(struct Curl_easy *data, - const char *hostname, int port, - struct Curl_dns_entry **dnsentry, - timediff_t timeoutms); +CURLcode Curl_resolv(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + bool allowDOH, + struct Curl_dns_entry **dnsentry); + +CURLcode Curl_resolv_blocking(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version, + struct Curl_dns_entry **dnsentry); + +CURLcode Curl_resolv_timeout(struct Curl_easy *data, + const char *hostname, int port, + int ip_version, + struct Curl_dns_entry **dnsentry, + timediff_t timeoutms); #ifdef USE_IPV6 /* @@ -120,49 +117,25 @@ bool Curl_ipv6works(struct Curl_easy *data); #define Curl_ipv6works(x) FALSE #endif -/* - * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've - * been set and returns TRUE if they are OK. - */ -bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn); - - -/* - * Curl_getaddrinfo() is the generic low-level name resolve API within this - * source file. There are several versions of this function - for different - * name resolve layers (selected at build-time). They all take this same set - * of arguments - */ -struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp); - /* 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, size_t hashsize); +void Curl_dnscache_init(struct Curl_dnscache *dns, size_t hashsize); + +void Curl_dnscache_destroy(struct Curl_dnscache *dns); /* prune old entries from the DNS cache */ -void Curl_hostcache_prune(struct Curl_easy *data); +void Curl_dnscache_prune(struct Curl_easy *data); /* IPv4 threadsafe resolve function used for synch and asynch builds */ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port); -CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_connect); - -/* - * Curl_addrinfo_callback() is used when we build with any asynch specialty. - * Handles end of async request processing. Inserts ai into hostcache when - * status is CURL_ASYNC_SUCCESS. Twiddles fields in conn to indicate async - * request completed whether successful or failed. - */ -CURLcode Curl_addrinfo_callback(struct Curl_easy *data, - int status, - struct Curl_addrinfo *ai); +CURLcode Curl_once_resolved(struct Curl_easy *data, + struct Curl_dns_entry *dns, + bool *protocol_connect); /* * Curl_printable_address() returns a printable version of the 1st address @@ -173,7 +146,27 @@ void Curl_printable_address(const struct Curl_addrinfo *ip, char *buf, size_t bufsize); /* - * Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache. + * Make a `Curl_dns_entry`. + * Creates a dnscache entry *without* adding it to a dnscache. This allows + * further modifications of the entry *before* then adding it to a cache. + * + * The entry is created with a reference count of 1. + * Use `Curl_resolv_unlink()` to release your hold on it. + * + * The call takes ownership of `addr`and makes a copy of `hostname`. + * + * Returns entry or NULL on OOM. + */ +struct Curl_dns_entry * +Curl_dnscache_mk_entry(struct Curl_easy *data, + struct Curl_addrinfo *addr, + const char *hostname, + size_t hostlen, /* length or zero */ + int port, + bool permanent); + +/* + * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache. * * Returns the Curl_dns_entry entry pointer or NULL if not in the cache. * @@ -181,65 +174,44 @@ void Curl_printable_address(const struct Curl_addrinfo *ip, * use, or we will leak memory! */ struct Curl_dns_entry * -Curl_fetch_addr(struct Curl_easy *data, - const char *hostname, - int port); +Curl_dnscache_get(struct Curl_easy *data, + const char *hostname, + int port, int ip_version); /* - * 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. + * Curl_dnscache_addr() adds `entry` to the cache, increasing its + * reference count on success. */ -struct Curl_dns_entry * -Curl_cache_addr(struct Curl_easy *data, struct Curl_addrinfo *addr, - const char *hostname, size_t hostlen, int port, - bool permanent); - -#ifndef INADDR_NONE -#define CURL_INADDR_NONE (in_addr_t) ~0 -#else -#define CURL_INADDR_NONE INADDR_NONE -#endif - -/* - * Function provided by the resolver backend to set DNS servers to use. - */ -CURLcode Curl_set_dns_servers(struct Curl_easy *data, char *servers); - -/* - * Function provided by the resolver backend to set - * outgoing interface to use for DNS requests - */ -CURLcode Curl_set_dns_interface(struct Curl_easy *data, - const char *interf); - -/* - * Function provided by the resolver backend to set - * local IPv4 address to use as source address for DNS requests - */ -CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, - const char *local_ip4); - -/* - * Function provided by the resolver backend to set - * local IPv6 address to use as source address for DNS requests - */ -CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, - const char *local_ip6); - -/* - * Clean off entries from the cache - */ -void Curl_hostcache_clean(struct Curl_easy *data, struct Curl_hash *hash); +CURLcode Curl_dnscache_add(struct Curl_easy *data, + struct Curl_dns_entry *entry); /* * Populate the cache with specified entries from CURLOPT_RESOLVE. */ CURLcode Curl_loadhostpairs(struct Curl_easy *data); + +#ifdef USE_CURL_ASYNC CURLcode Curl_resolv_check(struct Curl_easy *data, struct Curl_dns_entry **dns); +#else +#define Curl_resolv_check(x,y) CURLE_NOT_BUILT_IN +#endif int Curl_resolv_getsock(struct Curl_easy *data, curl_socket_t *socks); CURLcode Curl_resolver_error(struct Curl_easy *data); + +#ifdef CURLRES_SYNCH +/* + * Curl_sync_getaddrinfo() is the non-async low-level name resolve API. + * There are several versions of this function - depending on IPV6 + * support and platform. + */ +struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version); + +#endif + #endif /* HEADER_CURL_HOSTIP_H */ diff --git a/Utilities/cmcurl/lib/hostip4.c b/Utilities/cmcurl/lib/hostip4.c index 58333fbc60..14e4d98a86 100644 --- a/Utilities/cmcurl/lib/hostip4.c +++ b/Utilities/cmcurl/lib/hostip4.c @@ -54,24 +54,11 @@ #include "curl_memory.h" #include "memdebug.h" -/* - * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've - * been set and returns TRUE if they are OK. - */ -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 cannot get/use one */ - return FALSE; - - return TRUE; /* OK, proceed */ -} #ifdef CURLRES_SYNCH /* - * Curl_getaddrinfo() - the IPv4 synchronous version. + * Curl_sync_getaddrinfo() - the IPv4 synchronous version. * * The original code to this function was from the Dancer source code, written * by Bjorn Reese, it has since been patched and modified considerably. @@ -86,19 +73,18 @@ bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn) * flavours have thread-safe versions of the plain gethostbyname() etc. * */ -struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version) { struct Curl_addrinfo *ai = NULL; + (void)ip_version; #ifdef CURL_DISABLE_VERBOSE_STRINGS (void)data; #endif - *waitp = 0; /* synchronous response only */ - ai = Curl_ipv4_resolve_r(hostname, port); if(!ai) infof(data, "Curl_ipv4_resolve_r failed for %s", hostname); @@ -286,7 +272,7 @@ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, * getaddrinfo() nor gethostbyname_r() function or for which * gethostbyname() is the preferred one. */ - h = gethostbyname((void *)hostname); + h = gethostbyname(CURL_UNCONST(hostname)); #endif /* (HAVE_GETADDRINFO && HAVE_GETADDRINFO_THREADSAFE) || HAVE_GETHOSTBYNAME_R */ diff --git a/Utilities/cmcurl/lib/hostip6.c b/Utilities/cmcurl/lib/hostip6.c index c16ddfe58d..35cc2d737b 100644 --- a/Utilities/cmcurl/lib/hostip6.c +++ b/Utilities/cmcurl/lib/hostip6.c @@ -49,25 +49,13 @@ #include "hash.h" #include "share.h" #include "url.h" -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "connect.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -/* - * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've - * been set and returns TRUE if they are OK. - */ -bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn) -{ - if(conn->ip_version == CURL_IPRESOLVE_V6) - return Curl_ipv6works(data); - - return TRUE; -} - #if defined(CURLRES_SYNCH) #ifdef DEBUG_ADDRINFO @@ -87,7 +75,7 @@ static void dump_addrinfo(const struct Curl_addrinfo *ai) #endif /* - * Curl_getaddrinfo() when built IPv6-enabled (non-threading and + * Curl_sync_getaddrinfo() when built IPv6-enabled (non-threading and * non-ares version). * * Returns name information about the given hostname and port number. If @@ -95,10 +83,10 @@ static void dump_addrinfo(const struct Curl_addrinfo *ai) * to memory we need to free after use. That memory *MUST* be freed with * Curl_freeaddrinfo(), nothing else. */ -struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int ip_version) { struct addrinfo hints; struct Curl_addrinfo *res; @@ -110,9 +98,7 @@ struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, #endif int pf = PF_INET; - *waitp = 0; /* synchronous response only */ - - if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) + if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) /* The stack seems to be IPv6-enabled */ pf = PF_UNSPEC; @@ -126,8 +112,8 @@ struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, * The AI_NUMERICHOST must not be set to get synthesized IPv6 address from * an IPv4 address on iOS and macOS. */ - if((1 == Curl_inet_pton(AF_INET, hostname, addrbuf)) || - (1 == Curl_inet_pton(AF_INET6, hostname, addrbuf))) { + if((1 == curlx_inet_pton(AF_INET, hostname, addrbuf)) || + (1 == curlx_inet_pton(AF_INET6, hostname, addrbuf))) { /* the given address is numerical only, prevent a reverse lookup */ hints.ai_flags = AI_NUMERICHOST; } diff --git a/Utilities/cmcurl/lib/hostsyn.c b/Utilities/cmcurl/lib/hostsyn.c deleted file mode 100644 index ca8b0758c4..0000000000 --- a/Utilities/cmcurl/lib/hostsyn.c +++ /dev/null @@ -1,104 +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" - -/*********************************************************************** - * Only for builds using synchronous name resolves - **********************************************************************/ -#ifdef CURLRES_SYNCH - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#include "urldata.h" -#include "sendf.h" -#include "hostip.h" -#include "hash.h" -#include "share.h" -#include "url.h" -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -/* - * Function provided by the resolver backend to set DNS servers to use. - */ -CURLcode Curl_set_dns_servers(struct Curl_easy *data, - char *servers) -{ - (void)data; - (void)servers; - return CURLE_NOT_BUILT_IN; - -} - -/* - * Function provided by the resolver backend to set - * outgoing interface to use for DNS requests - */ -CURLcode Curl_set_dns_interface(struct Curl_easy *data, - const char *interf) -{ - (void)data; - (void)interf; - return CURLE_NOT_BUILT_IN; -} - -/* - * Function provided by the resolver backend to set - * local IPv4 address to use as source address for DNS requests - */ -CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, - const char *local_ip4) -{ - (void)data; - (void)local_ip4; - return CURLE_NOT_BUILT_IN; -} - -/* - * Function provided by the resolver backend to set - * local IPv6 address to use as source address for DNS requests - */ -CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, - const char *local_ip6) -{ - (void)data; - (void)local_ip6; - return CURLE_NOT_BUILT_IN; -} - -#endif /* truly sync */ diff --git a/Utilities/cmcurl/lib/hsts.c b/Utilities/cmcurl/lib/hsts.c index 99452b61a6..62a3f89058 100644 --- a/Utilities/cmcurl/lib/hsts.c +++ b/Utilities/cmcurl/lib/hsts.c @@ -35,13 +35,12 @@ #include "curl_get_line.h" #include "strcase.h" #include "sendf.h" -#include "strtoofft.h" #include "parsedate.h" #include "fopen.h" #include "rename.h" #include "share.h" #include "strdup.h" -#include "strparse.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -59,13 +58,12 @@ time_t deltatime; /* allow for "adjustments" for unit test purposes */ static time_t hsts_debugtime(void *unused) { - char *timestr = getenv("CURL_TIME"); + const char *timestr = getenv("CURL_TIME"); (void)unused; if(timestr) { curl_off_t val; - (void)curlx_strtoofft(timestr, NULL, 10, &val); - - val += (curl_off_t)deltatime; + if(!curlx_str_number(×tr, &val, TIME_T_MAX)) + val += (curl_off_t)deltatime; return (time_t)val; } return time(NULL); @@ -85,7 +83,7 @@ struct hsts *Curl_hsts_init(void) static void hsts_free(struct stsentry *e) { - free((char *)e->host); + free(CURL_UNCONST(e->host)); free(e); } @@ -156,35 +154,30 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, return CURLE_OK; do { - while(*p && ISBLANK(*p)) - p++; + curlx_str_passblanks(&p); if(strncasecompare("max-age", p, 7)) { bool quoted = FALSE; - CURLofft offt; - char *endp; + int rc; if(gotma) return CURLE_BAD_FUNCTION_ARGUMENT; p += 7; - while(*p && ISBLANK(*p)) - p++; - if(*p++ != '=') + curlx_str_passblanks(&p); + if(curlx_str_single(&p, '=')) return CURLE_BAD_FUNCTION_ARGUMENT; - while(*p && ISBLANK(*p)) - p++; + curlx_str_passblanks(&p); - if(*p == '\"') { - p++; + if(!curlx_str_single(&p, '\"')) quoted = TRUE; - } - offt = curlx_strtoofft(p, &endp, 10, &expires); - if(offt == CURL_OFFT_FLOW) + + rc = curlx_str_number(&p, &expires, TIME_T_MAX); + if(rc == STRE_OVERFLOW) expires = CURL_OFF_T_MAX; - else if(offt) + else if(rc) /* invalid max-age */ return CURLE_BAD_FUNCTION_ARGUMENT; - p = endp; + if(quoted) { if(*p != '\"') return CURLE_BAD_FUNCTION_ARGUMENT; @@ -205,8 +198,7 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, p++; } - while(*p && ISBLANK(*p)) - p++; + curlx_str_passblanks(&p); if(*p == ';') p++; } while(*p); @@ -287,7 +279,7 @@ struct stsentry *Curl_hsts(struct hsts *h, const char *hostname, blen = ntail; } } - /* avoid strcasecompare because the host name is not null terminated */ + /* avoid strcasecompare because the host name is not null-terminated */ if((hlen == ntail) && strncasecompare(hostname, sts->host, hlen)) return sts; } @@ -308,7 +300,7 @@ static CURLcode hsts_push(struct Curl_easy *data, struct tm stamp; CURLcode result; - e.name = (char *)sts->host; + e.name = (char *)CURL_UNCONST(sts->host); e.namelen = strlen(sts->host); e.includeSubDomains = sts->includeSubDomains; @@ -416,7 +408,7 @@ skipsave: } /* only returns SERIOUS errors */ -static CURLcode hsts_add(struct hsts *h, char *line) +static CURLcode hsts_add(struct hsts *h, const char *line) { /* Example lines: example.com "20191231 10:00:00" @@ -425,10 +417,10 @@ static CURLcode hsts_add(struct hsts *h, char *line) struct Curl_str host; struct Curl_str date; - if(Curl_str_word(&line, &host, MAX_HSTS_HOSTLEN) || - Curl_str_singlespace(&line) || - Curl_str_quotedword(&line, &date, MAX_HSTS_DATELEN) || - Curl_str_newline(&line)) + if(curlx_str_word(&line, &host, MAX_HSTS_HOSTLEN) || + curlx_str_singlespace(&line) || + curlx_str_quotedword(&line, &date, MAX_HSTS_DATELEN) || + curlx_str_newline(&line)) ; else { CURLcode result = CURLE_OK; @@ -436,26 +428,26 @@ static CURLcode hsts_add(struct hsts *h, char *line) struct stsentry *e; char dbuf[MAX_HSTS_DATELEN + 1]; time_t expires; + const char *hp = curlx_str(&host); - /* The date parser works on a null terminated string. The maximum length - is upheld by Curl_str_quotedword(). */ - memcpy(dbuf, date.str, date.len); - dbuf[date.len] = 0; + /* The date parser works on a null-terminated string. The maximum length + is upheld by curlx_str_quotedword(). */ + memcpy(dbuf, curlx_str(&date), curlx_strlen(&date)); + dbuf[curlx_strlen(&date)] = 0; expires = strcmp(dbuf, UNLIMITED) ? Curl_getdate_capped(dbuf) : TIME_T_MAX; - if(host.str[0] == '.') { - host.str++; - host.len--; + if(hp[0] == '.') { + curlx_str_nudge(&host, 1); subdomain = TRUE; } /* only add it if not already present */ - e = Curl_hsts(h, host.str, host.len, subdomain); + e = Curl_hsts(h, curlx_str(&host), curlx_strlen(&host), subdomain); if(!e) - result = hsts_create(h, host.str, host.len, subdomain, expires); - else if((strlen(e->host) == host.len) && - strncasecompare(host.str, e->host, host.len)) { + result = hsts_create(h, curlx_str(&host), curlx_strlen(&host), + subdomain, expires); + else if(curlx_str_casecompare(&host, e->host)) { /* the same hostname, use the largest expire time */ if(expires > e->expires) e->expires = expires; @@ -534,11 +526,11 @@ static CURLcode hsts_load(struct hsts *h, const char *file) fp = fopen(file, FOPEN_READTEXT); if(fp) { struct dynbuf buf; - Curl_dyn_init(&buf, MAX_HSTS_LINE); + curlx_dyn_init(&buf, MAX_HSTS_LINE); while(Curl_get_line(&buf, fp)) { - char *lineptr = Curl_dyn_ptr(&buf); - while(*lineptr && ISBLANK(*lineptr)) - lineptr++; + const char *lineptr = curlx_dyn_ptr(&buf); + curlx_str_passblanks(&lineptr); + /* * 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. @@ -548,7 +540,7 @@ static CURLcode hsts_load(struct hsts *h, const char *file) hsts_add(h, lineptr); } - Curl_dyn_free(&buf); /* free the line buffer */ + curlx_dyn_free(&buf); /* free the line buffer */ fclose(fp); } return result; diff --git a/Utilities/cmcurl/lib/hsts.h b/Utilities/cmcurl/lib/hsts.h index e8d0f9d552..8ec9637cb0 100644 --- a/Utilities/cmcurl/lib/hsts.h +++ b/Utilities/cmcurl/lib/hsts.h @@ -36,8 +36,8 @@ extern time_t deltatime; struct stsentry { struct Curl_llist_node node; const char *host; - bool includeSubDomains; curl_off_t expires; /* the timestamp of this entry's expiry */ + BIT(includeSubDomains); }; /* The HSTS cache. Needs to be able to tailmatch hostnames. */ diff --git a/Utilities/cmcurl/lib/http.c b/Utilities/cmcurl/lib/http.c index 5bd2bd4166..5942d313b8 100644 --- a/Utilities/cmcurl/lib/http.c +++ b/Utilities/cmcurl/lib/http.c @@ -54,7 +54,7 @@ #include "formdata.h" #include "mime.h" #include "progress.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "cookie.h" #include "vauth/vauth.h" #include "vtls/vtls.h" @@ -72,12 +72,11 @@ #include "headers.h" #include "select.h" #include "parsedate.h" /* for the week day and month names */ -#include "strtoofft.h" #include "multiif.h" #include "strcase.h" #include "content_encoding.h" #include "http_proxy.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "http2.h" #include "cfilters.h" #include "connect.h" @@ -86,6 +85,7 @@ #include "hsts.h" #include "ws.h" #include "curl_ctype.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -116,7 +116,7 @@ static CURLcode http_req_set_reader(struct Curl_easy *data, const char **tep); static CURLcode http_size(struct Curl_easy *data); static CURLcode http_statusline(struct Curl_easy *data, - struct connectdata *conn); + struct connectdata *conn); static CURLcode http_target(struct Curl_easy *data, struct connectdata *conn, struct dynbuf *req); static CURLcode http_useragent(struct Curl_easy *data); @@ -186,19 +186,56 @@ const struct Curl_handler Curl_handler_https = { #endif +void Curl_http_neg_init(struct Curl_easy *data, struct http_negotiation *neg) +{ + memset(neg, 0, sizeof(*neg)); + neg->accept_09 = data->set.http09_allowed; + switch(data->set.httpwant) { + case CURL_HTTP_VERSION_1_0: + neg->wanted = neg->allowed = (CURL_HTTP_V1x); + neg->only_10 = TRUE; + break; + case CURL_HTTP_VERSION_1_1: + neg->wanted = neg->allowed = (CURL_HTTP_V1x); + break; + case CURL_HTTP_VERSION_2_0: + neg->wanted = neg->allowed = (CURL_HTTP_V1x | CURL_HTTP_V2x); + neg->h2_upgrade = TRUE; + break; + case CURL_HTTP_VERSION_2TLS: + neg->wanted = neg->allowed = (CURL_HTTP_V1x | CURL_HTTP_V2x); + break; + case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE: + neg->wanted = neg->allowed = (CURL_HTTP_V2x); + data->state.http_neg.h2_prior_knowledge = TRUE; + break; + case CURL_HTTP_VERSION_3: + neg->wanted = (CURL_HTTP_V1x | CURL_HTTP_V2x | CURL_HTTP_V3x); + neg->allowed = neg->wanted; + break; + case CURL_HTTP_VERSION_3ONLY: + neg->wanted = neg->allowed = (CURL_HTTP_V3x); + break; + case CURL_HTTP_VERSION_NONE: + default: + neg->wanted = (CURL_HTTP_V1x | CURL_HTTP_V2x); + neg->allowed = (CURL_HTTP_V1x | CURL_HTTP_V2x | CURL_HTTP_V3x); + break; + } +} + 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 */ connkeep(conn, "HTTP default"); - - if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) { + if(data->state.http_neg.wanted == CURL_HTTP_V3x) { + /* only HTTP/3, needs to work */ CURLcode result = Curl_conn_may_http3(data, conn); if(result) return result; } - return CURLE_OK; } @@ -235,46 +272,28 @@ char *Curl_checkProxyheaders(struct Curl_easy *data, #endif /* - * Strip off leading and trailing whitespace from the value in the - * given HTTP header line and return a strdupped copy. Returns NULL in - * case of allocation failure. Returns an empty string if the header value - * consists entirely of whitespace. + * Strip off leading and trailing whitespace from the value in the given HTTP + * header line and return a strdup()ed copy. Returns NULL in case of + * allocation failure or bad input. Returns an empty string if the header + * value consists entirely of whitespace. + * + * If the header is provided as "name;", ending with a semicolon, it must + * return a blank string. */ char *Curl_copy_header_value(const char *header) { - const char *start; - const char *end; - size_t len; + struct Curl_str out; - /* Find the end of the header name */ - while(*header && (*header != ':')) - ++header; + /* find the end of the header name */ + if(!curlx_str_cspn(&header, &out, ";:") && + (!curlx_str_single(&header, ':') || !curlx_str_single(&header, ';'))) { + curlx_str_untilnl(&header, &out, MAX_HTTP_RESP_HEADER_SIZE); + curlx_str_trimblanks(&out); - if(*header) - /* Skip over colon */ - ++header; - - /* Find the first non-space letter */ - start = header; - while(*start && ISSPACE(*start)) - start++; - - end = strchr(start, '\r'); - if(!end) - end = strchr(start, '\n'); - if(!end) - end = strchr(start, '\0'); - if(!end) - return NULL; - - /* skip all trailing space letters */ - while((end > start) && ISSPACE(*end)) - end--; - - /* get length of the type */ - len = end - start + 1; - - return Curl_memdup0(start, len); + return Curl_memdup0(curlx_str(&out), curlx_strlen(&out)); + } + /* bad input */ + return NULL; } #ifndef CURL_DISABLE_HTTP_AUTH @@ -317,7 +336,7 @@ static CURLcode http_output_basic(struct Curl_easy *data, bool proxy) if(!out) return CURLE_OUT_OF_MEMORY; - result = Curl_base64_encode(out, strlen(out), &authorization, &size); + result = curlx_base64_encode(out, strlen(out), &authorization, &size); if(result) goto fail; @@ -458,7 +477,7 @@ static CURLcode http_perhapsrewind(struct Curl_easy *data, #if defined(USE_NTLM) if((data->state.authproxy.picked == CURLAUTH_NTLM) || (data->state.authhost.picked == CURLAUTH_NTLM)) { - ongoing_auth = "NTML"; + ongoing_auth = "NTLM"; if((conn->http_ntlm_state != NTLMSTATE_NONE) || (conn->proxy_ntlm_state != NTLMSTATE_NONE)) { /* The NTLM-negotiation has started, keep on sending. @@ -537,7 +556,8 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) (data->req.httpversion_sent > 11)) { infof(data, "Forcing HTTP/1.1 for NTLM"); connclose(conn, "Force HTTP/1.1 connection"); - data->state.httpwant = CURL_HTTP_VERSION_1_1; + data->state.http_neg.wanted = CURL_HTTP_V1x; + data->state.http_neg.allowed = CURL_HTTP_V1x; } } #ifndef CURL_DISABLE_PROXY @@ -562,7 +582,7 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) /* 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 */ - Curl_safefree(data->req.newurl); + free(data->req.newurl); data->req.newurl = strdup(data->state.url); /* clone URL */ if(!data->req.newurl) return CURLE_OUT_OF_MEMORY; @@ -613,9 +633,10 @@ output_auth_headers(struct Curl_easy *data, (void)path; #endif #ifndef CURL_DISABLE_AWS - if(authstatus->picked == CURLAUTH_AWS_SIGV4) { + if((authstatus->picked == CURLAUTH_AWS_SIGV4) && !proxy) { + /* this method is never for proxy */ auth = "AWS_SIGV4"; - result = Curl_output_aws_sigv4(data, proxy); + result = Curl_output_aws_sigv4(data); if(result) return result; } @@ -747,12 +768,12 @@ Curl_http_output_auth(struct Curl_easy *data, #ifndef CURL_DISABLE_PROXY (conn->bits.httpproxy && conn->bits.proxy_user_passwd) || #endif - data->state.aptr.user || + data->state.aptr.user || #ifdef USE_SPNEGO - authhost->want & CURLAUTH_NEGOTIATE || - authproxy->want & CURLAUTH_NEGOTIATE || + authhost->want & CURLAUTH_NEGOTIATE || + authproxy->want & CURLAUTH_NEGOTIATE || #endif - data->set.str[STRING_BEARER]) + data->set.str[STRING_BEARER]) /* continue please */; else { authhost->done = TRUE; @@ -837,9 +858,137 @@ Curl_http_output_auth(struct Curl_easy *data, !defined(CURL_DISABLE_DIGEST_AUTH) || \ !defined(CURL_DISABLE_BASIC_AUTH) || \ !defined(CURL_DISABLE_BEARER_AUTH) -static int is_valid_auth_separator(char ch) +static bool authcmp(const char *auth, const char *line) { - return ch == '\0' || ch == ',' || ISSPACE(ch); + /* the auth string must not have an alnum following */ + size_t n = strlen(auth); + return strncasecompare(auth, line, n) && !ISALNUM(line[n]); +} +#endif + +#ifdef USE_SPNEGO +static CURLcode auth_spnego(struct Curl_easy *data, + bool proxy, + const char *auth, + struct auth *authp, + unsigned long *availp) +{ + if((authp->avail & CURLAUTH_NEGOTIATE) || Curl_auth_is_spnego_supported()) { + *availp |= CURLAUTH_NEGOTIATE; + authp->avail |= CURLAUTH_NEGOTIATE; + + if(authp->picked == CURLAUTH_NEGOTIATE) { + struct connectdata *conn = data->conn; + CURLcode result = Curl_input_negotiate(data, conn, proxy, auth); + curlnegotiate *negstate = proxy ? &conn->proxy_negotiate_state : + &conn->http_negotiate_state; + if(!result) { + free(data->req.newurl); + data->req.newurl = strdup(data->state.url); + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; + data->state.authproblem = FALSE; + /* we received a GSS auth token and we dealt with it fine */ + *negstate = GSS_AUTHRECV; + } + else + data->state.authproblem = TRUE; + } + } + return CURLE_OK; +} +#endif + +#ifdef USE_NTLM +static CURLcode auth_ntlm(struct Curl_easy *data, + bool proxy, + const char *auth, + struct auth *authp, + unsigned long *availp) +{ + /* NTLM support requires the SSL crypto libs */ + if((authp->avail & CURLAUTH_NTLM) || Curl_auth_is_ntlm_supported()) { + *availp |= CURLAUTH_NTLM; + authp->avail |= CURLAUTH_NTLM; + + 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; + else { + infof(data, "NTLM authentication problem, ignoring."); + data->state.authproblem = TRUE; + } + } + } + return CURLE_OK; +} +#endif + +#ifndef CURL_DISABLE_DIGEST_AUTH +static CURLcode auth_digest(struct Curl_easy *data, + bool proxy, + const char *auth, + struct auth *authp, + unsigned long *availp) +{ + if(authp->avail & CURLAUTH_DIGEST) + infof(data, "Ignoring duplicate digest auth header."); + else if(Curl_auth_is_digest_supported()) { + CURLcode result; + + *availp |= CURLAUTH_DIGEST; + authp->avail |= CURLAUTH_DIGEST; + + /* We call this function on input Digest headers even if Digest + * 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); + if(result) { + infof(data, "Digest authentication problem, ignoring."); + data->state.authproblem = TRUE; + } + } + return CURLE_OK; +} +#endif + +#ifndef CURL_DISABLE_BASIC_AUTH +static CURLcode auth_basic(struct Curl_easy *data, + struct auth *authp, + unsigned long *availp) +{ + *availp |= CURLAUTH_BASIC; + 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 is not + valid. */ + authp->avail = CURLAUTH_NONE; + infof(data, "Basic authentication problem, ignoring."); + data->state.authproblem = TRUE; + } + return CURLE_OK; +} +#endif + +#ifndef CURL_DISABLE_BEARER_AUTH +static CURLcode auth_bearer(struct Curl_easy *data, + struct auth *authp, + unsigned long *availp) +{ + *availp |= CURLAUTH_BEARER; + 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 is not valid. */ + authp->avail = CURLAUTH_NONE; + infof(data, "Bearer authentication problem, ignoring."); + data->state.authproblem = TRUE; + } + return CURLE_OK; } #endif @@ -847,6 +996,8 @@ static int is_valid_auth_separator(char ch) * 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. + * + * The 'auth' line ends with a null byte without CR or LF present. */ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, const char *auth) /* the first non-space */ @@ -854,11 +1005,6 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, /* * This resource requires authentication */ - struct connectdata *conn = data->conn; -#ifdef USE_SPNEGO - curlnegotiate *negstate = proxy ? &conn->proxy_negotiate_state : - &conn->http_negotiate_state; -#endif #if defined(USE_SPNEGO) || \ defined(USE_NTLM) || \ !defined(CURL_DISABLE_DIGEST_AUTH) || \ @@ -867,6 +1013,9 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, unsigned long *availp; struct auth *authp; + CURLcode result = CURLE_OK; + DEBUGASSERT(auth); + DEBUGASSERT(data); if(proxy) { availp = &data->info.proxyauthavail; @@ -876,11 +1025,6 @@ 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 @@ -900,126 +1044,43 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, while(*auth) { #ifdef USE_SPNEGO - if(checkprefix("Negotiate", auth) && is_valid_auth_separator(auth[9])) { - if((authp->avail & CURLAUTH_NEGOTIATE) || - Curl_auth_is_spnego_supported()) { - *availp |= CURLAUTH_NEGOTIATE; - authp->avail |= CURLAUTH_NEGOTIATE; - - if(authp->picked == CURLAUTH_NEGOTIATE) { - CURLcode result = Curl_input_negotiate(data, conn, proxy, auth); - if(!result) { - free(data->req.newurl); - data->req.newurl = strdup(data->state.url); - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - data->state.authproblem = FALSE; - /* we received a GSS auth token and we dealt with it fine */ - *negstate = GSS_AUTHRECV; - } - else - data->state.authproblem = TRUE; - } - } - } - else + if(authcmp("Negotiate", auth)) + result = auth_spnego(data, proxy, auth, authp, availp); #endif #ifdef USE_NTLM - /* NTLM support requires the SSL crypto libs */ - if(checkprefix("NTLM", auth) && is_valid_auth_separator(auth[4])) { - if((authp->avail & CURLAUTH_NTLM) || - Curl_auth_is_ntlm_supported()) { - *availp |= CURLAUTH_NTLM; - authp->avail |= CURLAUTH_NTLM; - - 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; - } - else { - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } - } - } - else + if(!result && authcmp("NTLM", auth)) + result = auth_ntlm(data, proxy, auth, authp, availp); #endif #ifndef CURL_DISABLE_DIGEST_AUTH - if(checkprefix("Digest", auth) && is_valid_auth_separator(auth[6])) { - if((authp->avail & CURLAUTH_DIGEST) != 0) - infof(data, "Ignoring duplicate digest auth header."); - else if(Curl_auth_is_digest_supported()) { - CURLcode result; - - *availp |= CURLAUTH_DIGEST; - authp->avail |= CURLAUTH_DIGEST; - - /* We call this function on input Digest headers even if Digest - * 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); - if(result) { - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } - } - else + if(!result && authcmp("Digest", auth)) + result = auth_digest(data, proxy, auth, authp, availp); #endif #ifndef CURL_DISABLE_BASIC_AUTH - if(checkprefix("Basic", auth) && - is_valid_auth_separator(auth[5])) { - *availp |= CURLAUTH_BASIC; - 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 is not - valid. */ - authp->avail = CURLAUTH_NONE; - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } - else + if(!result && authcmp("Basic", auth)) + result = auth_basic(data, authp, availp); #endif #ifndef CURL_DISABLE_BEARER_AUTH - if(checkprefix("Bearer", auth) && - is_valid_auth_separator(auth[6])) { - *availp |= CURLAUTH_BEARER; - 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 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. - */ - } + if(authcmp("Bearer", auth)) + result = auth_bearer(data, authp, availp); #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 are on a comma, skip it */ - auth++; - while(*auth && ISSPACE(*auth)) - auth++; - } + if(result) + break; + /* there may be multiple methods on one line, so keep reading */ + auth = strchr(auth, ','); + if(auth) /* if we are on a comma, skip it */ + auth++; + else + break; + curlx_str_passblanks(&auth); + } + return result; +#else + (void) proxy; + /* nothing to do when disabled */ return CURLE_OK; +#endif } /** @@ -1093,6 +1154,21 @@ static bool http_should_fail(struct Curl_easy *data, int httpcode) return data->state.authproblem; } +static void http_switch_to_get(struct Curl_easy *data, int code) +{ + const char *req = data->set.str[STRING_CUSTOMREQUEST]; + if((req || data->state.httpreq != HTTPREQ_GET) && + (data->set.http_follow_mode == CURLFOLLOW_OBEYCODE)) { + infof(data, "Switch to GET because of %d response", code); + data->state.http_ignorecustom = TRUE; + } + else if(req && (data->set.http_follow_mode != CURLFOLLOW_FIRSTONLY)) + infof(data, "Stick to %s instead of GET", req); + + data->state.httpreq = HTTPREQ_GET; + Curl_creader_set_rewind(data, FALSE); +} + CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, followtype type) { @@ -1100,6 +1176,8 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, bool reachedmax = FALSE; char *follow_url = NULL; CURLUcode uc; + CURLcode rewind_result; + bool switch_to_get = FALSE; DEBUGASSERT(type != FOLLOW_NONE); @@ -1257,8 +1335,14 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, data->state.url = follow_url; data->state.url_alloc = TRUE; - Curl_req_soft_reset(&data->req, data); + rewind_result = Curl_req_soft_reset(&data->req, data); infof(data, "Issue another request to this URL: '%s'", data->state.url); + if((data->set.http_follow_mode == CURLFOLLOW_FIRSTONLY) && + data->set.str[STRING_CUSTOMREQUEST] && + !data->state.http_ignorecustom) { + data->state.http_ignorecustom = TRUE; + infof(data, "Drop custom request method for next request"); + } /* * We get here when the HTTP code is 300-399 (and 401). We need to perform @@ -1301,9 +1385,8 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, || 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); + http_switch_to_get(data, 301); + switch_to_get = TRUE; } break; case 302: /* Found */ @@ -1327,9 +1410,8 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, || 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); + http_switch_to_get(data, 302); + switch_to_get = TRUE; } break; @@ -1344,9 +1426,8 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, 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"); + http_switch_to_get(data, 303); + switch_to_get = TRUE; } break; case 304: /* Not Modified */ @@ -1364,6 +1445,12 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, */ break; } + + /* When rewind of upload data failed and we are not switching to GET, + * we need to fail the follow, as we cannot send the data again. */ + if(rewind_result && !switch_to_get) + return rewind_result; + Curl_pgrsTime(data, TIMER_REDIRECT); Curl_pgrsResetTransferSizes(data); @@ -1388,9 +1475,8 @@ Curl_compareheader(const char *headerline, /* line to check */ * The field value MAY be preceded by any amount of LWS, though a single SP * is preferred." */ - size_t len; - const char *start; - const char *end; + const char *p; + struct Curl_str val; DEBUGASSERT(hlen); DEBUGASSERT(clen); DEBUGASSERT(header); @@ -1400,31 +1486,21 @@ Curl_compareheader(const char *headerline, /* line to check */ return FALSE; /* does not start with header */ /* pass the header */ - start = &headerline[hlen]; + p = &headerline[hlen]; - /* pass all whitespace */ - while(*start && ISSPACE(*start)) - start++; - - /* find the end of the header line */ - end = strchr(start, '\r'); /* lines end with CRLF */ - if(!end) { - /* in case there is a non-standard compliant line here */ - end = strchr(start, '\n'); - - if(!end) - /* hm, there is no line ending here, use the zero byte! */ - end = strchr(start, '\0'); - } - - len = end-start; /* length of the content part of the input line */ + if(curlx_str_untilnl(&p, &val, MAX_HTTP_RESP_HEADER_SIZE)) + return FALSE; + curlx_str_trimblanks(&val); /* find the content string in the rest of the line */ - for(; len >= clen; len--, start++) { - if(strncasecompare(start, content, clen)) - return TRUE; /* match! */ + if(curlx_strlen(&val) >= clen) { + size_t len; + p = curlx_str(&val); + for(len = curlx_strlen(&val); len >= curlx_strlen(&val); len--, p++) { + if(strncasecompare(p, content, clen)) + return TRUE; /* match! */ + } } - return FALSE; /* no match */ } @@ -1471,7 +1547,7 @@ CURLcode Curl_http_done(struct Curl_easy *data, data->state.authhost.multipass = FALSE; data->state.authproxy.multipass = FALSE; - Curl_dyn_reset(&data->state.headerb); + curlx_dyn_reset(&data->state.headerb); if(status) return status; @@ -1501,29 +1577,28 @@ static bool http_may_use_1_1(const struct Curl_easy *data) const struct connectdata *conn = data->conn; /* We have seen a previous response for *this* transfer with 1.0, * on another connection or the same one. */ - if(data->state.httpversion == 10) + if(data->state.http_neg.rcvd_min == 10) return FALSE; /* We have seen a previous response on *this* connection with 1.0. */ - if(conn->httpversion_seen == 10) + if(conn && conn->httpversion_seen == 10) return FALSE; /* We want 1.0 and have seen no previous response on *this* connection with a higher version (maybe no response at all yet). */ - if((data->state.httpwant == CURL_HTTP_VERSION_1_0) && - (conn->httpversion_seen <= 10)) + if((data->state.http_neg.only_10) && + (!conn || conn->httpversion_seen <= 10)) return FALSE; - /* We want something newer than 1.0 or have no preferences. */ - return (data->state.httpwant == CURL_HTTP_VERSION_NONE) || - (data->state.httpwant >= CURL_HTTP_VERSION_1_1); + /* We are not restricted to use 1.0 only. */ + return !data->state.http_neg.only_10; } static unsigned char http_request_version(struct Curl_easy *data) { - unsigned char httpversion = Curl_conn_http_version(data); - if(!httpversion) { + unsigned char v = Curl_conn_http_version(data, data->conn); + if(!v) { /* No specific HTTP connection filter installed. */ - httpversion = http_may_use_1_1(data) ? 11 : 10; + v = http_may_use_1_1(data) ? 11 : 10; } - return httpversion; + return v; } static const char *get_http_string(int httpversion) @@ -1544,7 +1619,6 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, bool is_connect, int httpversion, struct dynbuf *req) { - char *ptr; struct curl_slist *h[2]; struct curl_slist *headers; int numlists = 1; /* by default */ @@ -1584,98 +1658,81 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, /* loop through one or two lists */ for(i = 0; i < numlists; i++) { - headers = h[i]; + for(headers = h[i]; headers; headers = headers->next) { + CURLcode result = CURLE_OK; + bool blankheader = FALSE; + struct Curl_str name; + const char *p = headers->data; + const char *origp = p; - while(headers) { - char *semicolonp = NULL; - ptr = strchr(headers->data, ':'); - if(!ptr) { - char *optr; - /* no colon, semicolon? */ - ptr = strchr(headers->data, ';'); - if(ptr) { - optr = ptr; - ptr++; /* pass the semicolon */ - while(*ptr && ISSPACE(*ptr)) - ptr++; - - if(*ptr) { - /* this may be used for something else in the future */ - optr = NULL; - } - else { - if(*(--ptr) == ';') { - /* copy the source */ - semicolonp = strdup(headers->data); - if(!semicolonp) { - Curl_dyn_free(req); - return CURLE_OUT_OF_MEMORY; - } - /* put a colon where the semicolon is */ - semicolonp[ptr - headers->data] = ':'; - /* point at the colon */ - optr = &semicolonp [ptr - headers->data]; - } - } - ptr = optr; + /* explicitly asked to send header without content is done by a header + that ends with a semicolon, but there must be no colon present in the + name */ + if(!curlx_str_until(&p, &name, MAX_HTTP_RESP_HEADER_SIZE, ';') && + !curlx_str_single(&p, ';') && + !curlx_str_single(&p, '\0') && + !memchr(curlx_str(&name), ':', curlx_strlen(&name))) + blankheader = TRUE; + else { + p = origp; + if(!curlx_str_until(&p, &name, MAX_HTTP_RESP_HEADER_SIZE, ':') && + !curlx_str_single(&p, ':')) { + struct Curl_str val; + curlx_str_untilnl(&p, &val, MAX_HTTP_RESP_HEADER_SIZE); + curlx_str_trimblanks(&val); + if(!curlx_strlen(&val)) + /* no content, don't send this */ + continue; } + else + /* no colon */ + continue; } - if(ptr && (ptr != headers->data)) { - /* we require a colon for this to be a true header */ - ptr++; /* pass the colon */ - while(*ptr && ISSPACE(*ptr)) - ptr++; + /* only send this if the contents was non-blank or done special */ - if(*ptr || semicolonp) { - /* only send this if the contents was non-blank or done special */ - CURLcode result = CURLE_OK; - char *compare = semicolonp ? semicolonp : headers->data; + if(data->state.aptr.host && + /* a Host: header was sent already, do not pass on any custom + Host: header as that will produce *two* in the same + request! */ + curlx_str_casecompare(&name, "Host")) + ; + else if(data->state.httpreq == HTTPREQ_POST_FORM && + /* this header (extended by formdata.c) is sent later */ + curlx_str_casecompare(&name, "Content-Type")) + ; + else if(data->state.httpreq == HTTPREQ_POST_MIME && + /* this header is sent later */ + curlx_str_casecompare(&name, "Content-Type")) + ; + else if(data->req.authneg && + /* while doing auth neg, do not allow the custom length since + we will force length zero then */ + curlx_str_casecompare(&name, "Content-Length")) + ; + else if(data->state.aptr.te && + /* when asking for Transfer-Encoding, do not pass on a custom + Connection: */ + curlx_str_casecompare(&name, "Connection")) + ; + else if((httpversion >= 20) && + curlx_str_casecompare(&name, "Transfer-Encoding")) + /* HTTP/2 does not support chunked requests */ + ; + else if((curlx_str_casecompare(&name, "Authorization") || + curlx_str_casecompare(&name, "Cookie")) && + /* be careful of sending this potentially sensitive header to + other hosts */ + !Curl_auth_allowed_to_host(data)) + ; + else if(blankheader) + result = curlx_dyn_addf(req, "%.*s:\r\n", (int)curlx_strlen(&name), + curlx_str(&name)); + else + result = curlx_dyn_addf(req, "%s\r\n", origp); - if(data->state.aptr.host && - /* 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 && - /* this header (extended by formdata.c) is sent later */ - checkprefix("Content-Type:", compare)) - ; - else if(data->state.httpreq == HTTPREQ_POST_MIME && - /* this header is sent later */ - checkprefix("Content-Type:", compare)) - ; - 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, do not pass on a custom - Connection: */ - checkprefix("Connection:", compare)) - ; - else if((httpversion >= 20) && - checkprefix("Transfer-Encoding:", compare)) - /* HTTP/2 does not support chunked requests */ - ; - else if((checkprefix("Authorization:", compare) || - checkprefix("Cookie:", compare)) && - /* be careful of sending this potentially sensitive header to - other hosts */ - !Curl_auth_allowed_to_host(data)) - ; - else { - result = Curl_dyn_addf(req, "%s\r\n", compare); - } - if(semicolonp) - free(semicolonp); - if(result) - return result; - } - } - headers = headers->next; + if(result) + return result; } } @@ -1747,7 +1804,7 @@ CURLcode Curl_add_timecondition(struct Curl_easy *data, tm->tm_min, tm->tm_sec); - result = Curl_dyn_add(req, datestr); + result = curlx_dyn_add(req, datestr); return result; } #else @@ -1771,8 +1828,10 @@ void Curl_http_method(struct Curl_easy *data, struct connectdata *conn, httpreq = HTTPREQ_PUT; /* Now set the 'request' pointer to the proper request string */ - if(data->set.str[STRING_CUSTOMREQUEST]) + if(data->set.str[STRING_CUSTOMREQUEST] && + !data->state.http_ignorecustom) { request = data->set.str[STRING_CUSTOMREQUEST]; + } else { if(data->req.no_body) request = "HEAD"; @@ -1865,7 +1924,7 @@ static CURLcode http_host(struct Curl_easy *data, struct connectdata *conn) if(colon) *colon = 0; /* The host must not include an embedded port number */ } - Curl_safefree(aptr->cookiehost); + free(aptr->cookiehost); aptr->cookiehost = cookiehost; } #endif @@ -1969,7 +2028,7 @@ static CURLcode http_target(struct Curl_easy *data, curl_url_cleanup(h); /* target or URL */ - result = Curl_dyn_add(r, data->set.str[STRING_TARGET] ? + result = curlx_dyn_add(r, data->set.str[STRING_TARGET] ? data->set.str[STRING_TARGET] : url); free(url); if(result) @@ -1990,8 +2049,8 @@ static CURLcode http_target(struct Curl_easy *data, } } if(!type) { - result = Curl_dyn_addf(r, ";type=%c", - data->state.prefer_ascii ? 'a' : 'i'); + result = curlx_dyn_addf(r, ";type=%c", + data->state.prefer_ascii ? 'a' : 'i'); if(result) return result; } @@ -2004,11 +2063,11 @@ static CURLcode http_target(struct Curl_easy *data, (void)conn; /* not used in disabled-proxy builds */ #endif { - result = Curl_dyn_add(r, path); + result = curlx_dyn_add(r, path); if(result) return result; if(query) - result = Curl_dyn_addf(r, "?%s", query); + result = curlx_dyn_addf(r, "?%s", query); } return result; @@ -2266,7 +2325,7 @@ static CURLcode addexpect(struct Curl_easy *data, struct dynbuf *r, 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")); + result = curlx_dyn_addn(r, STRCONST("Expect: 100-continue\r\n")); if(result) return result; *announced_exp100 = TRUE; @@ -2310,8 +2369,8 @@ static CURLcode http_req_complete(struct Curl_easy *data, !Curl_checkheaders(data, STRCONST("Content-Length")))) { /* we allow replacing this header if not during auth negotiation, 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); + result = curlx_dyn_addf(r, "Content-Length: %" FMT_OFF_T "\r\n", + req_clen); } if(result) goto out; @@ -2323,7 +2382,7 @@ static CURLcode http_req_complete(struct Curl_easy *data, struct curl_slist *hdr; for(hdr = data->state.mimepost->curlheaders; hdr; hdr = hdr->next) { - result = Curl_dyn_addf(r, "%s\r\n", hdr->data); + result = curlx_dyn_addf(r, "%s\r\n", hdr->data); if(result) goto out; } @@ -2331,8 +2390,8 @@ static CURLcode http_req_complete(struct Curl_easy *data, #endif 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")); + result = curlx_dyn_addn(r, STRCONST("Content-Type: application/" + "x-www-form-urlencoded\r\n")); if(result) goto out; } @@ -2346,7 +2405,7 @@ static CURLcode http_req_complete(struct Curl_easy *data, } /* end of headers */ - result = Curl_dyn_addn(r, STRCONST("\r\n")); + result = curlx_dyn_addn(r, STRCONST("\r\n")); if(!result) { Curl_pgrsSetUploadSize(data, req_clen); if(announced_exp100) @@ -2402,7 +2461,7 @@ static CURLcode http_cookies(struct Curl_easy *data, if(co->value) { size_t add; if(!count) { - result = Curl_dyn_addn(r, STRCONST("Cookie: ")); + result = curlx_dyn_addn(r, STRCONST("Cookie: ")); if(result) break; } @@ -2413,8 +2472,8 @@ static CURLcode http_cookies(struct Curl_easy *data, linecap = TRUE; break; } - result = Curl_dyn_addf(r, "%s%s=%s", count ? "; " : "", - co->name, co->value); + result = curlx_dyn_addf(r, "%s%s=%s", count ? "; " : "", + co->name, co->value); if(result) break; clen += add + (count ? 2 : 0); @@ -2425,14 +2484,14 @@ static CURLcode http_cookies(struct Curl_easy *data, } if(addcookies && !result && !linecap) { if(!count) - result = Curl_dyn_addn(r, STRCONST("Cookie: ")); + result = curlx_dyn_addn(r, STRCONST("Cookie: ")); if(!result) { - result = Curl_dyn_addf(r, "%s%s", count ? "; " : "", addcookies); + result = curlx_dyn_addf(r, "%s%s", count ? "; " : "", addcookies); count++; } } if(count && !result) - result = Curl_dyn_addn(r, STRCONST("\r\n")); + result = curlx_dyn_addn(r, STRCONST("\r\n")); if(result) return result; @@ -2620,11 +2679,11 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) switch(conn->alpn) { case CURL_HTTP_VERSION_3: - DEBUGASSERT(Curl_conn_http_version(data) == 30); + DEBUGASSERT(Curl_conn_http_version(data, conn) == 30); break; case CURL_HTTP_VERSION_2: #ifndef CURL_DISABLE_PROXY - if((Curl_conn_http_version(data) != 20) && + if((Curl_conn_http_version(data, conn) != 20) && conn->bits.proxy && !conn->bits.tunnel_proxy ) { result = Curl_http2_switch(data); @@ -2633,7 +2692,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) } else #endif - DEBUGASSERT(Curl_conn_http_version(data) == 20); + DEBUGASSERT(Curl_conn_http_version(data, conn) == 20); break; case CURL_HTTP_VERSION_1_1: /* continue with HTTP/1.x when explicitly requested */ @@ -2690,7 +2749,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && data->set.str[STRING_ENCODING]) { - Curl_safefree(data->state.aptr.accept_encoding); + free(data->state.aptr.accept_encoding); data->state.aptr.accept_encoding = aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); if(!data->state.aptr.accept_encoding) @@ -2721,19 +2780,19 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) goto fail; /* initialize a dynamic send-buffer */ - Curl_dyn_init(&req, DYN_HTTP_REQUEST); + curlx_dyn_init(&req, DYN_HTTP_REQUEST); /* make sure the header buffer is reset - if there are leftovers from a previous transfer */ - Curl_dyn_reset(&data->state.headerb); + curlx_dyn_reset(&data->state.headerb); /* add the main request stuff */ /* GET/HEAD/POST/PUT */ - result = Curl_dyn_addf(&req, "%s ", request); + result = curlx_dyn_addf(&req, "%s ", request); if(!result) result = http_target(data, conn, &req); if(result) { - Curl_dyn_free(&req); + curlx_dyn_free(&req); goto fail; } @@ -2742,62 +2801,62 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) altused = aprintf("Alt-Used: %s:%d\r\n", conn->conn_to_host.name, conn->conn_to_port); if(!altused) { - Curl_dyn_free(&req); + curlx_dyn_free(&req); return CURLE_OUT_OF_MEMORY; } } #endif result = - Curl_dyn_addf(&req, - " HTTP/%s\r\n" /* HTTP version */ - "%s" /* host */ - "%s" /* proxyuserpwd */ - "%s" /* userpwd */ - "%s" /* range */ - "%s" /* user agent */ - "%s" /* accept */ - "%s" /* TE: */ - "%s" /* accept-encoding */ - "%s" /* referer */ - "%s" /* Proxy-Connection */ - "%s" /* transfer-encoding */ - "%s",/* Alt-Used */ + curlx_dyn_addf(&req, + " HTTP/%s\r\n" /* HTTP version */ + "%s" /* host */ + "%s" /* proxyuserpwd */ + "%s" /* userpwd */ + "%s" /* range */ + "%s" /* user agent */ + "%s" /* accept */ + "%s" /* TE: */ + "%s" /* accept-encoding */ + "%s" /* referer */ + "%s" /* Proxy-Connection */ + "%s" /* transfer-encoding */ + "%s",/* Alt-Used */ - httpstring, - (data->state.aptr.host ? data->state.aptr.host : ""), + httpstring, + (data->state.aptr.host ? data->state.aptr.host : ""), #ifndef CURL_DISABLE_PROXY - data->state.aptr.proxyuserpwd ? - data->state.aptr.proxyuserpwd : "", + 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->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.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->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: */, #ifndef CURL_DISABLE_PROXY - (conn->bits.httpproxy && - !conn->bits.tunnel_proxy && - !Curl_checkheaders(data, STRCONST("Proxy-Connection")) && - !Curl_checkProxyheaders(data, conn, - STRCONST("Proxy-Connection"))) ? - "Proxy-Connection: Keep-Alive\r\n":"", + (conn->bits.httpproxy && + !conn->bits.tunnel_proxy && + !Curl_checkheaders(data, STRCONST("Proxy-Connection")) && + !Curl_checkProxyheaders(data, conn, + STRCONST("Proxy-Connection"))) ? + "Proxy-Connection: Keep-Alive\r\n":"", #else - "", + "", #endif - te, - altused ? altused : "" + te, + altused ? altused : "" ); /* clear userpwd and proxyuserpwd to avoid reusing old credentials @@ -2809,17 +2868,18 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) free(altused); if(result) { - Curl_dyn_free(&req); + curlx_dyn_free(&req); goto fail; } if(!Curl_conn_is_ssl(conn, FIRSTSOCKET) && (httpversion < 20) && - (data->state.httpwant == CURL_HTTP_VERSION_2)) { + (data->state.http_neg.wanted & CURL_HTTP_V2x) && + data->state.http_neg.h2_upgrade) { /* 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) { - Curl_dyn_free(&req); + curlx_dyn_free(&req); return result; } } @@ -2840,7 +2900,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) if(!result) result = Curl_req_send(data, &req, httpversion); } - Curl_dyn_free(&req); + curlx_dyn_free(&req); if(result) goto fail; @@ -2980,13 +3040,13 @@ static CURLcode http_header(struct Curl_easy *data, HD_VAL(hd, hdlen, "Content-Length:") : NULL; if(v) { curl_off_t contentlength; - CURLofft offt = curlx_strtoofft(v, NULL, 10, &contentlength); + int offt = curlx_str_numblanks(&v, &contentlength); - if(offt == CURL_OFFT_OK) { + if(offt == STRE_OK) { k->size = contentlength; k->maxdownload = k->size; } - else if(offt == CURL_OFFT_FLOW) { + else if(offt == STRE_OVERFLOW) { /* out of range */ if(data->set.max_filesize) { failf(data, "Maximum file size exceeded"); @@ -3024,7 +3084,7 @@ static CURLcode http_header(struct Curl_easy *data, /* ignore empty data */ free(contenttype); else { - Curl_safefree(data->info.contenttype); + free(data->info.contenttype); data->info.contenttype = contenttype; } return CURLE_OK; @@ -3072,11 +3132,10 @@ static CURLcode http_header(struct Curl_easy *data, /* 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; - } + if(!curlx_str_number(&ptr, &k->offset, CURL_OFF_T_MAX) && + (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 */ @@ -3106,7 +3165,7 @@ static CURLcode http_header(struct Curl_easy *data, else { data->req.location = location; - if(data->set.http_follow_location) { + if(data->set.http_follow_mode) { DEBUGASSERT(!data->req.newurl); data->req.newurl = strdup(data->req.location); /* clone */ if(!data->req.newurl) @@ -3183,18 +3242,22 @@ static CURLcode http_header(struct Curl_easy *data, 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); + time_t date; + curlx_str_passblanks(&v); + + /* try it as a date first, because a date can otherwise start with and + get treated as a number */ + date = Curl_getdate_capped(v); + + if((time_t)-1 != date) { time_t current = time(NULL); - if((time_t)-1 != date && date > current) { + if(date >= current) /* convert date to number of seconds into the future */ retry_after = date - current; - } } - if(retry_after < 0) - retry_after = 0; + else + /* Try it as a decimal number */ + curlx_str_number(&v, &retry_after, CURL_OFF_T_MAX); /* limit to 6 hours max. this is not documented so that it can be changed in the future if necessary. */ if(retry_after > 21600) @@ -3346,9 +3409,10 @@ static CURLcode http_statusline(struct Curl_easy *data, data->info.httpversion = k->httpversion; conn->httpversion_seen = (unsigned char)k->httpversion; - if(!data->state.httpversion || data->state.httpversion > k->httpversion) + if(!data->state.http_neg.rcvd_min || + data->state.http_neg.rcvd_min > k->httpversion) /* store the lowest server version we encounter */ - data->state.httpversion = (unsigned char)k->httpversion; + data->state.http_neg.rcvd_min = (unsigned char)k->httpversion; /* * This code executes as part of processing the header. As a @@ -3491,7 +3555,7 @@ static CURLcode http_write_header(struct Curl_easy *data, /* now, only output this if the header AND body are requested: */ - Curl_debug(data, CURLINFO_HEADER_IN, (char *)hd, hdlen); + Curl_debug(data, CURLINFO_HEADER_IN, hd, hdlen); writetype = CLIENTWRITE_HEADER | ((data->req.httpcode/100 == 1) ? CLIENTWRITE_1XX : 0); @@ -3574,7 +3638,7 @@ static CURLcode http_on_response(struct Curl_easy *data, /* We expect more response from HTTP/2 later */ k->header = TRUE; k->headerline = 0; /* restart the header line counter */ - k->httpversion_sent = 20; /* It's a HTTP/2 request now */ + k->httpversion_sent = 20; /* It's an HTTP/2 request now */ /* Any remaining `buf` bytes are already HTTP/2 and passed to * be processed. */ result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen); @@ -3808,8 +3872,8 @@ static CURLcode http_rw_hd(struct Curl_easy *data, struct dynbuf last_header; size_t consumed; - Curl_dyn_init(&last_header, hdlen + 1); - result = Curl_dyn_addn(&last_header, hd, hdlen); + curlx_dyn_init(&last_header, hdlen + 1); + result = curlx_dyn_addn(&last_header, hd, hdlen); if(result) return result; @@ -3817,12 +3881,12 @@ static CURLcode http_rw_hd(struct Curl_easy *data, /* 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), + curlx_dyn_reset(&data->state.headerb); + result = http_on_response(data, curlx_dyn_ptr(&last_header), + curlx_dyn_len(&last_header), buf_remain, blen, &consumed); *pconsumed += consumed; - Curl_dyn_free(&last_header); + curlx_dyn_free(&last_header); return result; } @@ -3847,8 +3911,7 @@ static CURLcode http_rw_hd(struct Curl_easy *data, */ const char *p = hd; - while(*p && ISBLANK(*p)) - p++; + curlx_str_passblanks(&p); if(!strncmp(p, "HTTP/", 5)) { p += 5; switch(*p) { @@ -3861,9 +3924,9 @@ static CURLcode http_rw_hd(struct Curl_easy *data, 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; + /* RFC 9112 requires a single space following the status code, + but the browsers don't so let's not insist */ + fine_statusline = TRUE; } } } @@ -3882,7 +3945,7 @@ static CURLcode http_rw_hd(struct Curl_easy *data, k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + (p[2] - '0'); p += 3; - if(!ISSPACE(*p)) + if(!ISBLANK(*p)) break; fine_statusline = TRUE; } @@ -3907,30 +3970,22 @@ static CURLcode http_rw_hd(struct Curl_easy *data, } 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 */ - } - } - } - } + struct Curl_str ver; + curl_off_t status; + /* we set the max string a little excessive to forgive some leading + spaces */ + if(!curlx_str_until(&p, &ver, 32, ' ') && + !curlx_str_single(&p, ' ') && + !curlx_str_number(&p, &status, 999)) { + curlx_str_trimblanks(&ver); + if(curlx_str_cmp(&ver, "RTSP/1.0")) { + k->httpcode = (int)status; + fine_statusline = TRUE; + k->httpversion = 11; /* RTSP acts like HTTP 1.1 */ } - if(!fine_statusline) - return CURLE_WEIRD_SERVER_REPLY; } + if(!fine_statusline) + return CURLE_WEIRD_SERVER_REPLY; } if(fine_statusline) { @@ -3956,7 +4011,7 @@ static CURLcode http_rw_hd(struct Curl_easy *data, /* * Taken in one (more) header. Write it to the client. */ - Curl_debug(data, CURLINFO_HEADER_IN, (char *)hd, hdlen); + Curl_debug(data, CURLINFO_HEADER_IN, hd, hdlen); if(k->httpcode/100 == 1) writetype |= CLIENTWRITE_1XX; @@ -3993,7 +4048,7 @@ static CURLcode http_parse_headers(struct Curl_easy *data, 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, buf, blen); + result = curlx_dyn_addn(&data->state.headerb, buf, blen); if(result) return result; *pconsumed += blen; @@ -4002,8 +4057,8 @@ static CURLcode http_parse_headers(struct Curl_easy *data, /* check if this looks like a protocol header */ statusline st = checkprotoprefix(data, conn, - Curl_dyn_ptr(&data->state.headerb), - Curl_dyn_len(&data->state.headerb)); + curlx_dyn_ptr(&data->state.headerb), + curlx_dyn_len(&data->state.headerb)); if(st == STATUS_BAD) { /* this is not the beginning of a protocol first header line. @@ -4014,7 +4069,7 @@ static CURLcode http_parse_headers(struct Curl_easy *data, failf(data, "Invalid status line"); return CURLE_WEIRD_SERVER_REPLY; } - if(!data->set.http09_allowed) { + if(!data->state.http_neg.accept_09) { failf(data, "Received HTTP/0.9 when not allowed"); return CURLE_UNSUPPORTED_PROTOCOL; } @@ -4027,7 +4082,7 @@ static CURLcode http_parse_headers(struct Curl_easy *data, /* decrease the size of the remaining (supposed) header line */ consumed = (end_ptr - buf) + 1; - result = Curl_dyn_addn(&data->state.headerb, buf, consumed); + result = curlx_dyn_addn(&data->state.headerb, buf, consumed); if(result) return result; blen -= consumed; @@ -4041,8 +4096,8 @@ static CURLcode http_parse_headers(struct Curl_easy *data, if(!k->headerline) { /* the first read header */ statusline st = checkprotoprefix(data, conn, - Curl_dyn_ptr(&data->state.headerb), - Curl_dyn_len(&data->state.headerb)); + curlx_dyn_ptr(&data->state.headerb), + curlx_dyn_len(&data->state.headerb)); if(st == STATUS_BAD) { streamclose(conn, "bad HTTP: No end-of-message indicator"); /* this is not the beginning of a protocol first header line. @@ -4051,7 +4106,7 @@ static CURLcode http_parse_headers(struct Curl_easy *data, failf(data, "Invalid status line"); return CURLE_WEIRD_SERVER_REPLY; } - if(!data->set.http09_allowed) { + if(!data->state.http_neg.accept_09) { failf(data, "Received HTTP/0.9 when not allowed"); return CURLE_UNSUPPORTED_PROTOCOL; } @@ -4061,13 +4116,13 @@ static CURLcode http_parse_headers(struct Curl_easy *data, } } - result = http_rw_hd(data, Curl_dyn_ptr(&data->state.headerb), - Curl_dyn_len(&data->state.headerb), + result = http_rw_hd(data, curlx_dyn_ptr(&data->state.headerb), + curlx_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); + curlx_dyn_reset(&data->state.headerb); if(consumed) { blen -= consumed; buf += consumed; @@ -4082,7 +4137,7 @@ static CURLcode http_parse_headers(struct Curl_easy *data, buffer. */ out: if(!k->header && !leftover_body) { - Curl_dyn_free(&data->state.headerb); + curlx_dyn_free(&data->state.headerb); } return CURLE_OK; } @@ -4120,15 +4175,15 @@ CURLcode Curl_http_write_resp_hds(struct Curl_easy *data, result = http_parse_headers(data, buf, blen, pconsumed); if(!result && !data->req.header) { - if(!data->req.no_body && Curl_dyn_len(&data->state.headerb)) { + if(!data->req.no_body && curlx_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)); + curlx_dyn_ptr(&data->state.headerb), + curlx_dyn_len(&data->state.headerb)); } - Curl_dyn_free(&data->state.headerb); + curlx_dyn_free(&data->state.headerb); } return result; } @@ -4157,7 +4212,7 @@ CURLcode Curl_http_write_resp(struct Curl_easy *data, flags = CLIENTWRITE_BODY; if(is_eos) flags |= CLIENTWRITE_EOS; - result = Curl_client_write(data, flags, (char *)buf, blen); + result = Curl_client_write(data, flags, buf, blen); } out: return result; @@ -4197,11 +4252,9 @@ CURLcode Curl_http_req_make(struct httpreq **preq, struct httpreq *req; CURLcode result = CURLE_OUT_OF_MEMORY; - DEBUGASSERT(method); - if(m_len + 1 > sizeof(req->method)) - return CURLE_BAD_FUNCTION_ARGUMENT; + DEBUGASSERT(method && m_len); - req = calloc(1, sizeof(*req)); + req = calloc(1, sizeof(*req) + m_len); if(!req) goto out; memcpy(req->method, method, m_len); @@ -4239,7 +4292,7 @@ static CURLcode req_assign_url_authority(struct httpreq *req, CURLU *url) CURLcode result = CURLE_URL_MALFORMAT; user = pass = host = port = NULL; - Curl_dyn_init(&buf, DYN_HTTP_REQUEST); + curlx_dyn_init(&buf, DYN_HTTP_REQUEST); uc = curl_url_get(url, CURLUPART_HOST, &host, 0); if(uc && uc != CURLUE_NO_HOST) @@ -4263,27 +4316,27 @@ static CURLcode req_assign_url_authority(struct httpreq *req, CURLU *url) } if(user) { - result = Curl_dyn_add(&buf, user); + result = curlx_dyn_add(&buf, user); if(result) goto out; if(pass) { - result = Curl_dyn_addf(&buf, ":%s", pass); + result = curlx_dyn_addf(&buf, ":%s", pass); if(result) goto out; } - result = Curl_dyn_add(&buf, "@"); + result = curlx_dyn_add(&buf, "@"); if(result) goto out; } - result = Curl_dyn_add(&buf, host); + result = curlx_dyn_add(&buf, host); if(result) goto out; if(port) { - result = Curl_dyn_addf(&buf, ":%s", port); + result = curlx_dyn_addf(&buf, ":%s", port); if(result) goto out; } - req->authority = strdup(Curl_dyn_ptr(&buf)); + req->authority = strdup(curlx_dyn_ptr(&buf)); if(!req->authority) goto out; result = CURLE_OK; @@ -4293,7 +4346,7 @@ out: free(pass); free(host); free(port); - Curl_dyn_free(&buf); + curlx_dyn_free(&buf); return result; } @@ -4305,7 +4358,7 @@ static CURLcode req_assign_url_path(struct httpreq *req, CURLU *url) CURLcode result = CURLE_URL_MALFORMAT; path = query = NULL; - Curl_dyn_init(&buf, DYN_HTTP_REQUEST); + curlx_dyn_init(&buf, DYN_HTTP_REQUEST); uc = curl_url_get(url, CURLUPART_PATH, &path, CURLU_PATH_AS_IS); if(uc) @@ -4323,16 +4376,16 @@ static CURLcode req_assign_url_path(struct httpreq *req, CURLU *url) } else { if(path) { - result = Curl_dyn_add(&buf, path); + result = curlx_dyn_add(&buf, path); if(result) goto out; } if(query) { - result = Curl_dyn_addf(&buf, "?%s", query); + result = curlx_dyn_addf(&buf, "?%s", query); if(result) goto out; } - req->path = strdup(Curl_dyn_ptr(&buf)); + req->path = strdup(curlx_dyn_ptr(&buf)); if(!req->path) goto out; } @@ -4341,7 +4394,7 @@ static CURLcode req_assign_url_path(struct httpreq *req, CURLU *url) out: free(path); free(query); - Curl_dyn_free(&buf); + curlx_dyn_free(&buf); return result; } @@ -4353,11 +4406,9 @@ CURLcode Curl_http_req_make2(struct httpreq **preq, CURLcode result = CURLE_OUT_OF_MEMORY; CURLUcode uc; - DEBUGASSERT(method); - if(m_len + 1 > sizeof(req->method)) - return CURLE_BAD_FUNCTION_ARGUMENT; + DEBUGASSERT(method && m_len); - req = calloc(1, sizeof(*req)); + req = calloc(1, sizeof(*req) + m_len); if(!req) goto out; memcpy(req->method, method, m_len); @@ -4408,7 +4459,6 @@ struct name_const { /* keep them sorted by length! */ static struct name_const H2_NON_FIELD[] = { - { STRCONST("TE") }, { STRCONST("Host") }, { STRCONST("Upgrade") }, { STRCONST("Connection") }, @@ -4417,15 +4467,44 @@ static struct name_const H2_NON_FIELD[] = { { STRCONST("Transfer-Encoding") }, }; -static bool h2_non_field(const char *name, size_t namelen) +static bool h2_permissible_field(struct dynhds_entry *e) { size_t i; - for(i = 0; i < sizeof(H2_NON_FIELD)/sizeof(H2_NON_FIELD[0]); ++i) { - if(namelen < H2_NON_FIELD[i].namelen) - return FALSE; - if(namelen == H2_NON_FIELD[i].namelen && - strcasecompare(H2_NON_FIELD[i].name, name)) + for(i = 0; i < CURL_ARRAYSIZE(H2_NON_FIELD); ++i) { + if(e->namelen < H2_NON_FIELD[i].namelen) return TRUE; + if(e->namelen == H2_NON_FIELD[i].namelen && + strcasecompare(H2_NON_FIELD[i].name, e->name)) + return FALSE; + } + return TRUE; +} + +static bool http_TE_has_token(const char *fvalue, const char *token) +{ + while(*fvalue) { + struct Curl_str name; + + /* skip to first token */ + while(ISBLANK(*fvalue) || *fvalue == ',') + fvalue++; + if(curlx_str_cspn(&fvalue, &name, " \t\r;,")) + return FALSE; + if(curlx_str_casecompare(&name, token)) + return TRUE; + + /* skip any remainder after token, e.g. parameters with quoted strings */ + while(*fvalue && *fvalue != ',') { + if(*fvalue == '"') { + struct Curl_str qw; + /* if we do not cleanly find a quoted word here, the header value + * does not follow HTTP syntax and we reject */ + if(curlx_str_quotedword(&fvalue, &qw, CURL_MAX_HTTP_HEADER)) + return FALSE; + } + else + fvalue++; + } } return FALSE; } @@ -4448,8 +4527,7 @@ CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers, scheme = Curl_checkheaders(data, STRCONST(HTTP_PSEUDO_SCHEME)); if(scheme) { scheme += sizeof(HTTP_PSEUDO_SCHEME); - while(*scheme && ISBLANK(*scheme)) - scheme++; + curlx_str_passblanks(&scheme); infof(data, "set pseudo header %s to %s", HTTP_PSEUDO_SCHEME, scheme); } else { @@ -4485,7 +4563,14 @@ CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers, } for(i = 0; !result && i < Curl_dynhds_count(&req->headers); ++i) { e = Curl_dynhds_getn(&req->headers, i); - if(!h2_non_field(e->name, e->namelen)) { + /* "TE" is special in that it is only permissible when it + * has only value "trailers". RFC 9113 ch. 8.2.2 */ + if(e->namelen == 2 && strcasecompare("TE", e->name)) { + if(http_TE_has_token(e->value, "trailers")) + result = Curl_dynhds_add(h2_headers, e->name, e->namelen, + "trailers", sizeof("trailers") - 1); + } + else if(h2_permissible_field(e)) { result = Curl_dynhds_add(h2_headers, e->name, e->namelen, e->value, e->valuelen); } @@ -4577,7 +4662,7 @@ static CURLcode cr_exp100_read(struct Curl_easy *data, 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(); + ctx->start = curlx_now(); Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT); data->req.keepon &= ~KEEP_SEND; data->req.keepon |= KEEP_SEND_TIMED; @@ -4590,7 +4675,7 @@ static CURLcode cr_exp100_read(struct Curl_easy *data, *eos = FALSE; return CURLE_READ_ERROR; case EXP100_AWAITING_CONTINUE: - ms = Curl_timediff(Curl_now(), ctx->start); + ms = curlx_timediff(curlx_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; diff --git a/Utilities/cmcurl/lib/http.h b/Utilities/cmcurl/lib/http.h index a15a982356..8876f38044 100644 --- a/Utilities/cmcurl/lib/http.h +++ b/Utilities/cmcurl/lib/http.h @@ -53,6 +53,12 @@ typedef enum { FOLLOW_REDIR /* a full true redirect */ } followtype; +#define CURL_HTTP_V1x (1 << 0) +#define CURL_HTTP_V2x (1 << 1) +#define CURL_HTTP_V3x (1 << 2) +/* bitmask of CURL_HTTP_V* values */ +typedef unsigned char http_majors; + #ifndef CURL_DISABLE_HTTP @@ -68,6 +74,18 @@ extern const struct Curl_handler Curl_handler_https; struct dynhds; +struct http_negotiation { + unsigned char rcvd_min; /* minimum version seen in responses, 09, 10, 11 */ + http_majors wanted; /* wanted major versions when talking to server */ + http_majors allowed; /* allowed major versions when talking to server */ + BIT(h2_upgrade); /* Do HTTP Upgrade from 1.1 to 2 */ + BIT(h2_prior_knowledge); /* Directly do HTTP/2 without ALPN/SSL */ + BIT(accept_09); /* Accept an HTTP/0.9 response */ + BIT(only_10); /* When using major version 1x, use only 1.0 */ +}; + +void Curl_http_neg_init(struct Curl_easy *data, struct http_negotiation *neg); + CURLcode Curl_bump_headersize(struct Curl_easy *data, size_t delta, bool connect_only); @@ -113,6 +131,7 @@ CURLcode Curl_http_write_resp_hd(struct Curl_easy *data, /* These functions are in http.c */ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, const char *auth); + CURLcode Curl_http_auth_act(struct Curl_easy *data); /* follow a redirect or not */ @@ -155,6 +174,10 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, version. This count includes CONNECT response headers. */ #define MAX_HTTP_RESP_HEADER_SIZE (300*1024) +/* MAX_HTTP_RESP_HEADER_COUNT is the maximum number of response headers that + libcurl allows for a single HTTP response, including CONNECT and + redirects. */ +#define MAX_HTTP_RESP_HEADER_COUNT 5000 #endif /* CURL_DISABLE_HTTP */ @@ -199,12 +222,12 @@ 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[24]; + struct dynhds headers; + struct dynhds trailers; char *scheme; char *authority; char *path; - struct dynhds headers; - struct dynhds trailers; + char method[1]; }; /** diff --git a/Utilities/cmcurl/lib/http1.c b/Utilities/cmcurl/lib/http1.c index 9d2461e813..537e6db2ff 100644 --- a/Utilities/cmcurl/lib/http1.c +++ b/Utilities/cmcurl/lib/http1.c @@ -44,14 +44,14 @@ void Curl_h1_req_parse_init(struct h1_req_parser *parser, size_t max_line_len) { memset(parser, 0, sizeof(*parser)); parser->max_line_len = max_line_len; - Curl_dyn_init(&parser->scratch, max_line_len); + curlx_dyn_init(&parser->scratch, max_line_len); } void Curl_h1_req_parse_free(struct h1_req_parser *parser) { if(parser) { Curl_http_req_free(parser->req); - Curl_dyn_free(&parser->scratch); + curlx_dyn_free(&parser->scratch); parser->req = NULL; parser->done = FALSE; } @@ -108,18 +108,18 @@ static ssize_t next_line(struct h1_req_parser *parser, if(parser->line) { parser->line = NULL; parser->line_len = 0; - Curl_dyn_reset(&parser->scratch); + curlx_dyn_reset(&parser->scratch); } nread = detect_line(parser, buf, buflen, err); if(nread >= 0) { - if(Curl_dyn_len(&parser->scratch)) { + if(curlx_dyn_len(&parser->scratch)) { /* append detected line to scratch to have the complete line */ - *err = Curl_dyn_addn(&parser->scratch, parser->line, parser->line_len); + *err = curlx_dyn_addn(&parser->scratch, parser->line, parser->line_len); if(*err) return -1; - parser->line = Curl_dyn_ptr(&parser->scratch); - parser->line_len = Curl_dyn_len(&parser->scratch); + parser->line = curlx_dyn_ptr(&parser->scratch); + parser->line_len = curlx_dyn_len(&parser->scratch); } *err = trim_line(parser, options); if(*err) @@ -127,7 +127,8 @@ 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); + *err = curlx_dyn_addn(&parser->scratch, (const unsigned char *)buf, + buflen); nread = (*err) ? -1 : (ssize_t)buflen; } return nread; @@ -208,7 +209,7 @@ static CURLcode start_req(struct h1_req_parser *parser, path = target; path_len = target_len; - /* URL parser wants 0-termination */ + /* URL parser wants null-termination */ if(target_len >= sizeof(tmp)) goto out; memcpy(tmp, target, target_len); @@ -299,7 +300,7 @@ ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser, goto out; } parser->done = TRUE; - Curl_dyn_reset(&parser->scratch); + curlx_dyn_reset(&parser->scratch); /* last chance adjustments */ } else { @@ -321,13 +322,13 @@ CURLcode Curl_h1_req_write_head(struct httpreq *req, int http_minor, { 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); + result = curlx_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; @@ -335,7 +336,7 @@ CURLcode Curl_h1_req_write_head(struct httpreq *req, int http_minor, if(result) goto out; - result = Curl_dyn_addn(dbuf, STRCONST("\r\n")); + result = curlx_dyn_addn(dbuf, STRCONST("\r\n")); out: return result; diff --git a/Utilities/cmcurl/lib/http1.h b/Utilities/cmcurl/lib/http1.h index 2de302f1f6..b38b32f591 100644 --- a/Utilities/cmcurl/lib/http1.h +++ b/Utilities/cmcurl/lib/http1.h @@ -42,7 +42,7 @@ struct h1_req_parser { const char *line; size_t max_line_len; size_t line_len; - bool done; + BIT(done); }; void Curl_h1_req_parse_init(struct h1_req_parser *parser, size_t max_line_len); diff --git a/Utilities/cmcurl/lib/http2.c b/Utilities/cmcurl/lib/http2.c index 821f2889dd..dd82aa963e 100644 --- a/Utilities/cmcurl/lib/http2.c +++ b/Utilities/cmcurl/lib/http2.c @@ -29,13 +29,13 @@ #include #include "urldata.h" #include "bufq.h" -#include "hash.h" +#include "uint-hash.h" #include "http1.h" #include "http2.h" #include "http.h" #include "sendf.h" #include "select.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "strcase.h" #include "multiif.h" #include "url.h" @@ -43,10 +43,10 @@ #include "cfilters.h" #include "connect.h" #include "rand.h" -#include "strtoofft.h" #include "strdup.h" +#include "curlx/strparse.h" #include "transfer.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "headers.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -136,12 +136,15 @@ struct cf_h2_ctx { 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` */ + struct uint_hash streams; /* hash of `data->mid` to `h2_stream_ctx` */ size_t drain_total; /* sum of all stream's UrlState drain */ 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 */ +#ifdef DEBUGBUILD + int32_t stream_win_max; /* max h2 stream window size */ +#endif BIT(initialized); BIT(via_h1_upgrade); BIT(conn_closed); @@ -156,17 +159,29 @@ struct cf_h2_ctx { #define CF_CTX_CALL_DATA(cf) \ ((struct cf_h2_ctx *)(cf)->ctx)->call_data -static void h2_stream_hash_free(void *stream); +static void h2_stream_hash_free(unsigned int id, void *stream); static void cf_h2_ctx_init(struct cf_h2_ctx *ctx, bool via_h1_upgrade) { 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); + curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); + Curl_uint_hash_init(&ctx->streams, 63, h2_stream_hash_free); ctx->remote_max_sid = 2147483647; ctx->via_h1_upgrade = via_h1_upgrade; +#ifdef DEBUGBUILD + { + const char *p = getenv("CURL_H2_STREAM_WIN_MAX"); + + ctx->stream_win_max = H2_STREAM_WINDOW_SIZE_MAX; + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, INT_MAX)) + ctx->stream_win_max = (int32_t)l; + } + } +#endif ctx->initialized = TRUE; } @@ -176,9 +191,8 @@ static void cf_h2_ctx_free(struct cf_h2_ctx *ctx) 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); + curlx_dyn_free(&ctx->scratch); + Curl_uint_hash_destroy(&ctx->streams); memset(ctx, 0, sizeof(*ctx)); } free(ctx); @@ -192,7 +206,7 @@ static void cf_h2_ctx_close(struct cf_h2_ctx *ctx) } static CURLcode h2_progress_egress(struct Curl_cfilter *cf, - struct Curl_easy *data); + struct Curl_easy *data); /** * All about the H2 internals of a stream @@ -220,10 +234,12 @@ struct h2_stream_ctx { BIT(bodystarted); BIT(body_eos); /* the complete body has been added to `sendbuf` and * is being/has been processed from there. */ + BIT(write_paused); /* stream write is paused */ }; -#define H2_STREAM_CTX(ctx,data) ((struct h2_stream_ctx *)(\ - data? Curl_hash_offt_get(&(ctx)->streams, (data)->mid) : NULL)) +#define H2_STREAM_CTX(ctx,data) \ + ((struct h2_stream_ctx *)( \ + data? Curl_uint_hash_get(&(ctx)->streams, (data)->mid) : NULL)) static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx) { @@ -267,8 +283,9 @@ static void h2_stream_ctx_free(struct h2_stream_ctx *stream) free(stream); } -static void h2_stream_hash_free(void *stream) +static void h2_stream_hash_free(unsigned int id, void *stream) { + (void)id; DEBUGASSERT(stream); h2_stream_ctx_free((struct h2_stream_ctx *)stream); } @@ -285,23 +302,38 @@ static int32_t cf_h2_get_desired_local_win(struct Curl_cfilter *cf, * This gets less precise the higher the latency. */ return (int32_t)data->set.max_recv_speed; } +#ifdef DEBUGBUILD + else { + struct cf_h2_ctx *ctx = cf->ctx; + CURL_TRC_CF(data, cf, "stream_win_max=%d", ctx->stream_win_max); + return ctx->stream_win_max; + } +#else return H2_STREAM_WINDOW_SIZE_MAX; +#endif } static CURLcode cf_h2_update_local_win(struct Curl_cfilter *cf, struct Curl_easy *data, - struct h2_stream_ctx *stream, - bool paused) + struct h2_stream_ctx *stream) { struct cf_h2_ctx *ctx = cf->ctx; int32_t dwsize; int rv; - dwsize = paused ? 0 : cf_h2_get_desired_local_win(cf, data); + dwsize = (stream->write_paused || stream->xfer_result) ? + 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_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, dwsize); + if(rv) { + failf(data, "[%d] nghttp2 set_local_window_size(%d) failed: " + "%s(%d)", stream->id, dwsize, nghttp2_strerror(rv), rv); + return CURLE_HTTP2; + } rv = nghttp2_submit_window_update(ctx->h2, NGHTTP2_FLAG_NONE, stream->id, dwsize - wsize); if(rv) { @@ -333,13 +365,11 @@ static CURLcode cf_h2_update_local_win(struct Curl_cfilter *cf, static CURLcode cf_h2_update_local_win(struct Curl_cfilter *cf, struct Curl_easy *data, - struct h2_stream_ctx *stream, - bool paused) + struct h2_stream_ctx *stream) { (void)cf; (void)data; (void)stream; - (void)paused; return CURLE_OK; } #endif /* !NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE */ @@ -385,7 +415,7 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf, if(!stream) return CURLE_OUT_OF_MEMORY; - if(!Curl_hash_offt_set(&ctx->streams, data->mid, stream)) { + if(!Curl_uint_hash_set(&ctx->streams, data->mid, stream)) { h2_stream_ctx_free(stream); return CURLE_OUT_OF_MEMORY; } @@ -423,7 +453,7 @@ static void http2_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) nghttp2_session_send(ctx->h2); } - Curl_hash_offt_remove(&ctx->streams, data->mid); + Curl_uint_hash_remove(&ctx->streams, data->mid); } static int h2_client_new(struct Curl_cfilter *cf, @@ -432,7 +462,7 @@ static int h2_client_new(struct Curl_cfilter *cf, struct cf_h2_ctx *ctx = cf->ctx; nghttp2_option *o; nghttp2_mem mem = {NULL, Curl_nghttp2_malloc, Curl_nghttp2_free, - Curl_nghttp2_calloc, Curl_nghttp2_realloc}; + Curl_nghttp2_calloc, Curl_nghttp2_realloc}; int rc = nghttp2_option_new(&o); if(rc) @@ -482,6 +512,10 @@ static ssize_t send_callback(nghttp2_session *h2, void *userp); static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp); +static int cf_h2_on_invalid_frame_recv(nghttp2_session *session, + const nghttp2_frame *frame, + int lib_error_code, + void *user_data); #ifndef CURL_DISABLE_VERBOSE_STRINGS static int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, void *userp); @@ -522,6 +556,8 @@ static CURLcode cf_h2_ctx_open(struct Curl_cfilter *cf, nghttp2_session_callbacks_set_send_callback(cbs, send_callback); nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv); + nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(cbs, + cf_h2_on_invalid_frame_recv); #ifndef CURL_DISABLE_VERBOSE_STRINGS nghttp2_session_callbacks_set_on_frame_send_callback(cbs, on_frame_send); #endif @@ -644,10 +680,8 @@ static int h2_process_pending_input(struct Curl_cfilter *cf, rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)buf, blen); if(rv < 0) { - failf(data, - "process_pending_input: nghttp2_session_mem_recv() returned " - "%zd:%s", rv, nghttp2_strerror((int)rv)); - *err = CURLE_RECV_ERROR; + failf(data, "nghttp2 recv error %zd: %s", rv, nghttp2_strerror((int)rv)); + *err = CURLE_HTTP2; return -1; } Curl_bufq_skip(&ctx->inbufq, (size_t)rv); @@ -965,9 +999,6 @@ static int push_promise(struct Curl_cfilter *cf, goto fail; } - /* ask the application */ - CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ask application"); - stream = H2_STREAM_CTX(ctx, data); if(!stream) { failf(data, "Internal NULL stream"); @@ -982,20 +1013,13 @@ static int push_promise(struct Curl_cfilter *cf, rv = set_transfer_url(newhandle, &heads); if(rv) { + CURL_TRC_CF(data, cf, "[%d] PUSH_PROMISE, failed to set url -> %d", + frame->promised_stream_id, rv); discard_newhandle(cf, newhandle); rv = CURL_PUSH_DENY; goto fail; } - result = http2_data_setup(cf, newhandle, &newstream); - if(result) { - failf(data, "error setting up stream: %d", result); - discard_newhandle(cf, newhandle); - rv = CURL_PUSH_DENY; - goto fail; - } - DEBUGASSERT(stream); - Curl_set_in_callback(data, TRUE); rv = data->multi->push_cb(data, newhandle, stream->push_headers_used, &heads, @@ -1008,16 +1032,15 @@ static int push_promise(struct Curl_cfilter *cf, if(rv) { DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT)); /* denied, kill off the new handle again */ + CURL_TRC_CF(data, cf, "[%d] PUSH_PROMISE, denied by application -> %d", + frame->promised_stream_id, rv); discard_newhandle(cf, newhandle); goto fail; } - newstream->id = frame->promised_stream_id; - newhandle->req.maxdownload = -1; - newhandle->req.size = -1; - - /* approved, add to the multi handle and immediately switch to PERFORM - state with the given connection !*/ + /* approved, add to the multi handle for processing. This + * assigns newhandle->mid. For the new `mid` we assign the + * h2_stream instance and remember the stream_id already known. */ rc = Curl_multi_add_perform(data->multi, newhandle, cf->conn); if(rc) { infof(data, "failed to add handle to multi"); @@ -1026,6 +1049,21 @@ static int push_promise(struct Curl_cfilter *cf, goto fail; } + result = http2_data_setup(cf, newhandle, &newstream); + if(result) { + failf(data, "error setting up stream: %d", result); + discard_newhandle(cf, newhandle); + rv = CURL_PUSH_DENY; + goto fail; + } + + DEBUGASSERT(newstream); + newstream->id = frame->promised_stream_id; + newhandle->req.maxdownload = -1; + newhandle->req.size = -1; + + CURL_TRC_CF(data, cf, "promise easy handle added to multi, mid=%u", + newhandle->mid); rv = nghttp2_session_set_stream_user_data(ctx->h2, newstream->id, newhandle); @@ -1059,7 +1097,7 @@ static void h2_xfer_write_resp_hd(struct Curl_cfilter *cf, 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); + stream->xfer_result = cf_h2_update_local_win(cf, data, stream); if(stream->xfer_result) CURL_TRC_CF(data, cf, "[%d] error %d writing %zu bytes of headers", stream->id, stream->xfer_result, blen); @@ -1075,8 +1113,6 @@ static void h2_xfer_write_resp(struct Curl_cfilter *cf, /* 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; @@ -1086,6 +1122,17 @@ static void h2_xfer_write_resp(struct Curl_cfilter *cf, nghttp2_submit_rst_stream(ctx->h2, 0, stream->id, (uint32_t)NGHTTP2_ERR_CALLBACK_FAILURE); } + else if(!stream->write_paused && Curl_xfer_write_is_paused(data)) { + CURL_TRC_CF(data, cf, "[%d] stream output paused", stream->id); + stream->write_paused = TRUE; + } + else if(stream->write_paused && !Curl_xfer_write_is_paused(data)) { + CURL_TRC_CF(data, cf, "[%d] stream output unpaused", stream->id); + stream->write_paused = FALSE; + } + + if(!stream->xfer_result && !eos) + stream->xfer_result = cf_h2_update_local_win(cf, data, stream); } static CURLcode on_stream_frame(struct Curl_cfilter *cf, @@ -1253,7 +1300,7 @@ 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 s_len = CURL_ARRAYSIZE(scratch); size_t len = (frame->goaway.opaque_data_len < s_len) ? frame->goaway.opaque_data_len : s_len-1; if(len) @@ -1279,6 +1326,7 @@ static int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data = CF_DATA_CURRENT(cf); (void)session; @@ -1290,6 +1338,13 @@ static int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, buffer[len] = 0; CURL_TRC_CF(data, cf, "[%d] -> %s", frame->hd.stream_id, buffer); } + if((frame->hd.type == NGHTTP2_GOAWAY) && !ctx->sent_goaway) { + /* A GOAWAY not initiated by us, but by nghttp2 itself on detecting + * a protocol error on the connection */ + failf(data, "nghttp2 shuts down connection with error %d: %s", + frame->goaway.error_code, + nghttp2_http2_strerror(frame->goaway.error_code)); + } return 0; } #endif /* !CURL_DISABLE_VERBOSE_STRINGS */ @@ -1372,6 +1427,39 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, return on_stream_frame(cf, data_s, frame) ? NGHTTP2_ERR_CALLBACK_FAILURE : 0; } +static int cf_h2_on_invalid_frame_recv(nghttp2_session *session, + const nghttp2_frame *frame, + int ngerr, void *userp) +{ + struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; + struct Curl_easy *data; + int32_t stream_id = frame->hd.stream_id; + + data = nghttp2_session_get_stream_user_data(session, stream_id); + if(data) { + struct h2_stream_ctx *stream; +#ifndef CURL_DISABLE_VERBOSE_STRINGS + char buffer[256]; + int len; + len = fr_print(frame, buffer, sizeof(buffer)-1); + buffer[len] = 0; + failf(data, "[HTTP2] [%d] received invalid frame: %s, error %d: %s", + stream_id, buffer, ngerr, nghttp2_strerror(ngerr)); +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + stream = H2_STREAM_CTX(ctx, data); + if(stream) { + nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, NGHTTP2_STREAM_CLOSED); + stream->error = ngerr; + stream->closed = TRUE; + stream->reset = TRUE; + return 0; /* keep the connection alive */ + } + } + return NGHTTP2_ERR_CALLBACK_FAILURE; +} + static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *mem, size_t len, void *userp) @@ -1402,7 +1490,7 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; - h2_xfer_write_resp(cf, data_s, stream, (char *)mem, len, FALSE); + h2_xfer_write_resp(cf, data_s, stream, (const char *)mem, len, FALSE); nghttp2_session_consume(ctx->h2, stream_id, len); stream->nrcvd_data += (curl_off_t)len; @@ -1493,6 +1581,23 @@ static int on_begin_headers(nghttp2_session *session, return 0; } +static void cf_h2_header_error(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + CURLcode result) +{ + struct cf_h2_ctx *ctx = cf->ctx; + + failf(data, "Error receiving HTTP2 header: %d(%s)", result, + curl_easy_strerror(result)); + if(stream) { + nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, NGHTTP2_STREAM_CLOSED); + stream->closed = TRUE; + stream->reset = TRUE; + } +} + /* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, @@ -1512,7 +1617,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, /* get the stream from the hash based on Stream ID */ data_s = nghttp2_session_get_stream_user_data(session, stream_id); - if(!data_s) + if(!GOOD_EASY_HANDLE(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; @@ -1592,8 +1697,10 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, result = Curl_dynhds_add(&stream->resp_trailers, (const char *)name, namelen, (const char *)value, valuelen); - if(result) + if(result) { + cf_h2_header_error(cf, data_s, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; + } return 0; } @@ -1604,24 +1711,30 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, char buffer[32]; result = Curl_http_decode_status(&stream->status_code, (const char *)value, valuelen); - if(result) + if(result) { + cf_h2_header_error(cf, data_s, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; + } msnprintf(buffer, sizeof(buffer), HTTP_PSEUDO_STATUS ":%u\r", stream->status_code); result = Curl_headers_push(data_s, buffer, CURLH_PSEUDO); - if(result) + if(result) { + cf_h2_header_error(cf, data_s, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; - Curl_dyn_reset(&ctx->scratch); - result = Curl_dyn_addn(&ctx->scratch, STRCONST("HTTP/2 ")); + } + curlx_dyn_reset(&ctx->scratch); + result = curlx_dyn_addn(&ctx->scratch, STRCONST("HTTP/2 ")); if(!result) - result = Curl_dyn_addn(&ctx->scratch, value, valuelen); + result = curlx_dyn_addn(&ctx->scratch, value, valuelen); if(!result) - result = Curl_dyn_addn(&ctx->scratch, STRCONST(" \r\n")); + result = curlx_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) + h2_xfer_write_resp_hd(cf, data_s, stream, curlx_dyn_ptr(&ctx->scratch), + curlx_dyn_len(&ctx->scratch), FALSE); + if(result) { + cf_h2_header_error(cf, data_s, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; + } /* if we receive data for another handle, wake that up */ if(CF_DATA_CURRENT(cf) != data_s) Curl_expire(data_s, 0, EXPIRE_RUN_NOW); @@ -1634,19 +1747,21 @@ 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 */ - Curl_dyn_reset(&ctx->scratch); - result = Curl_dyn_addn(&ctx->scratch, (const char *)name, namelen); + curlx_dyn_reset(&ctx->scratch); + result = curlx_dyn_addn(&ctx->scratch, (const char *)name, namelen); if(!result) - result = Curl_dyn_addn(&ctx->scratch, STRCONST(": ")); + result = curlx_dyn_addn(&ctx->scratch, STRCONST(": ")); if(!result) - result = Curl_dyn_addn(&ctx->scratch, (const char *)value, valuelen); + result = curlx_dyn_addn(&ctx->scratch, (const char *)value, valuelen); if(!result) - result = Curl_dyn_addn(&ctx->scratch, STRCONST("\r\n")); + result = curlx_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) + h2_xfer_write_resp_hd(cf, data_s, stream, curlx_dyn_ptr(&ctx->scratch), + curlx_dyn_len(&ctx->scratch), FALSE); + if(result) { + cf_h2_header_error(cf, data_s, stream, result); return NGHTTP2_ERR_CALLBACK_FAILURE; + } /* if we receive data for another handle, wake that up */ if(CF_DATA_CURRENT(cf) != data_s) Curl_expire(data_s, 0, EXPIRE_RUN_NOW); @@ -1735,22 +1850,22 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req, binlen = populate_binsettings(binsettings, data); if(binlen <= 0) { failf(data, "nghttp2 unexpectedly failed on pack_settings_payload"); - Curl_dyn_free(req); + curlx_dyn_free(req); return CURLE_FAILED_INIT; } - result = Curl_base64url_encode((const char *)binsettings, (size_t)binlen, - &base64, &blen); + result = curlx_base64url_encode((const char *)binsettings, (size_t)binlen, + &base64, &blen); if(result) { - Curl_dyn_free(req); + curlx_dyn_free(req); return result; } - result = Curl_dyn_addf(req, - "Connection: Upgrade, HTTP2-Settings\r\n" - "Upgrade: %s\r\n" - "HTTP2-Settings: %s\r\n", - NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, base64); + result = curlx_dyn_addf(req, + "Connection: Upgrade, HTTP2-Settings\r\n" + "Upgrade: %s\r\n" + "HTTP2-Settings: %s\r\n", + NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, base64); free(base64); k->upgr101 = UPGR101_H2; @@ -1810,25 +1925,25 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, size_t i; *err = CURLE_OK; - Curl_dyn_init(&dbuf, DYN_TRAILERS); + curlx_dyn_init(&dbuf, DYN_TRAILERS); for(i = 0; i < Curl_dynhds_count(&stream->resp_trailers); ++i) { e = Curl_dynhds_getn(&stream->resp_trailers, i); if(!e) break; - Curl_dyn_reset(&dbuf); - *err = Curl_dyn_addf(&dbuf, "%.*s: %.*s\x0d\x0a", - (int)e->namelen, e->name, - (int)e->valuelen, e->value); + curlx_dyn_reset(&dbuf); + *err = curlx_dyn_addf(&dbuf, "%.*s: %.*s\x0d\x0a", + (int)e->namelen, e->name, + (int)e->valuelen, e->value); if(*err) break; - Curl_debug(data, CURLINFO_HEADER_IN, Curl_dyn_ptr(&dbuf), - Curl_dyn_len(&dbuf)); + Curl_debug(data, CURLINFO_HEADER_IN, curlx_dyn_ptr(&dbuf), + curlx_dyn_len(&dbuf)); *err = Curl_client_write(data, CLIENTWRITE_HEADER|CLIENTWRITE_TRAILER, - Curl_dyn_ptr(&dbuf), Curl_dyn_len(&dbuf)); + curlx_dyn_ptr(&dbuf), curlx_dyn_len(&dbuf)); if(*err) break; } - Curl_dyn_free(&dbuf); + curlx_dyn_free(&dbuf); if(*err) goto out; } @@ -1963,6 +2078,11 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, CURLcode result = CURLE_OK; ssize_t nread; + if(should_close_session(ctx)) { + CURL_TRC_CF(data, cf, "progress ingress, session is closed"); + return CURLE_HTTP2; + } + /* Process network input buffer fist */ if(!Curl_bufq_is_empty(&ctx->inbufq)) { CURL_TRC_CF(data, cf, "Process %zu bytes in connection buffer", @@ -2035,7 +2155,7 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, * a read() is called anyway. It is not clear what the calling sequence * is for such a case. */ failf(data, "http/2 recv on a transfer never opened " - "or already cleared, mid=%" FMT_OFF_T, data->mid); + "or already cleared, mid=%u", data->mid); *err = CURLE_HTTP2; return -1; } @@ -2442,7 +2562,7 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf, static CURLcode cf_h2_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_h2_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; @@ -2456,7 +2576,7 @@ static CURLcode cf_h2_connect(struct Curl_cfilter *cf, /* Connect the lower filters first */ if(!cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, blocking, done); + result = Curl_conn_cf_connect(cf->next, data, done); if(result || !*done) return result; } @@ -2537,6 +2657,7 @@ static CURLcode cf_h2_shutdown(struct Curl_cfilter *cf, CF_DATA_SAVE(save, cf, data); if(!ctx->sent_goaway) { + ctx->sent_goaway = TRUE; rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE, ctx->local_max_sid, 0, (const uint8_t *)"shutdown", @@ -2547,7 +2668,6 @@ static CURLcode cf_h2_shutdown(struct Curl_cfilter *cf, result = CURLE_SEND_ERROR; goto out; } - ctx->sent_goaway = TRUE; } /* GOAWAY submitted, process egress and ingress until nghttp2 is done. */ result = CURLE_OK; @@ -2580,7 +2700,10 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf, DEBUGASSERT(data); if(ctx && ctx->h2 && stream) { - CURLcode result = cf_h2_update_local_win(cf, data, stream, pause); + CURLcode result; + + stream->write_paused = pause; + result = cf_h2_update_local_win(cf, data, stream); if(result) return result; @@ -2684,7 +2807,7 @@ static CURLcode cf_h2_query(struct Curl_cfilter *cf, CF_DATA_SAVE(save, cf, data); if(nghttp2_session_check_request_allowed(ctx->h2) == 0) { /* the limit is what we have in use right now */ - effective_max = CONN_INUSE(cf->conn); + effective_max = CONN_ATTACHED(cf->conn); } else { effective_max = ctx->max_concurrent_streams; @@ -2795,8 +2918,9 @@ out: bool Curl_http2_may_switch(struct Curl_easy *data) { - if(Curl_conn_http_version(data) < 20 && - data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { + if(Curl_conn_http_version(data, data->conn) < 20 && + (data->state.http_neg.wanted & CURL_HTTP_V2x) && + data->state.http_neg.h2_prior_knowledge) { #ifndef CURL_DISABLE_PROXY if(data->conn->bits.httpproxy && !data->conn->bits.tunnel_proxy) { /* We do not support HTTP/2 proxies yet. Also it is debatable @@ -2815,7 +2939,7 @@ CURLcode Curl_http2_switch(struct Curl_easy *data) struct Curl_cfilter *cf; CURLcode result; - DEBUGASSERT(Curl_conn_http_version(data) < 20); + DEBUGASSERT(Curl_conn_http_version(data, data->conn) < 20); result = http2_cfilter_add(&cf, data, data->conn, FIRSTSOCKET, FALSE); if(result) @@ -2827,7 +2951,7 @@ CURLcode Curl_http2_switch(struct Curl_easy *data) if(cf->next) { bool done; - return Curl_conn_cf_connect(cf, data, FALSE, &done); + return Curl_conn_cf_connect(cf, data, &done); } return CURLE_OK; } @@ -2837,7 +2961,7 @@ CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data) struct Curl_cfilter *cf_h2; CURLcode result; - DEBUGASSERT(Curl_conn_http_version(data) < 20); + DEBUGASSERT(Curl_conn_http_version(data, data->conn) < 20); result = http2_cfilter_insert_after(cf, data, FALSE); if(result) @@ -2849,7 +2973,7 @@ CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data) if(cf_h2->next) { bool done; - return Curl_conn_cf_connect(cf_h2, data, FALSE, &done); + return Curl_conn_cf_connect(cf_h2, data, &done); } return CURLE_OK; } @@ -2862,7 +2986,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data, struct cf_h2_ctx *ctx; CURLcode result; - DEBUGASSERT(Curl_conn_http_version(data) < 20); + DEBUGASSERT(Curl_conn_http_version(data, conn) < 20); DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED); result = http2_cfilter_add(&cf, data, conn, sockindex, TRUE); @@ -2900,7 +3024,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data, if(cf->next) { bool done; - return Curl_conn_cf_connect(cf, data, FALSE, &done); + return Curl_conn_cf_connect(cf, data, &done); } return CURLE_OK; } @@ -2909,7 +3033,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data, CURLE_HTTP2_STREAM error! */ bool Curl_h2_http_1_1_error(struct Curl_easy *data) { - if(Curl_conn_http_version(data) == 20) { + if(Curl_conn_http_version(data, data->conn) == 20) { int err = Curl_conn_get_stream_error(data, data->conn, FIRSTSOCKET); return err == NGHTTP2_HTTP_1_1_REQUIRED; } diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.c b/Utilities/cmcurl/lib/http_aws_sigv4.c index c217d0d8e6..7425382359 100644 --- a/Utilities/cmcurl/lib/http_aws_sigv4.c +++ b/Utilities/cmcurl/lib/http_aws_sigv4.c @@ -9,7 +9,7 @@ * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * 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 @@ -35,7 +35,7 @@ #include "parsedate.h" #include "sendf.h" #include "escape.h" -#include "strparse.h" +#include "curlx/strparse.h" #include @@ -46,16 +46,16 @@ #include "slist.h" -#define HMAC_SHA256(k, kl, d, dl, o) \ - do { \ - result = Curl_hmacit(&Curl_HMAC_SHA256, \ - (unsigned char *)k, \ - kl, \ - (unsigned char *)d, \ - dl, o); \ - if(result) { \ - goto fail; \ - } \ +#define HMAC_SHA256(k, kl, d, dl, o) \ + do { \ + result = Curl_hmacit(&Curl_HMAC_SHA256, \ + (const unsigned char *)k, \ + kl, \ + (const unsigned char *)d, \ + dl, o); \ + if(result) { \ + goto fail; \ + } \ } while(0) #define TIMESTAMP_SIZE 17 @@ -63,6 +63,27 @@ /* hex-encoded with trailing null */ #define SHA256_HEX_LENGTH (2 * CURL_SHA256_DIGEST_LENGTH + 1) +#define MAX_QUERY_COMPONENTS 128 + +struct pair { + struct dynbuf key; + struct dynbuf value; +}; + +static void dyn_array_free(struct dynbuf *db, size_t num_elements); +static void pair_array_free(struct pair *pair_array, size_t num_elements); +static CURLcode split_to_dyn_array(const char *source, + struct dynbuf db[MAX_QUERY_COMPONENTS], + size_t *num_splits); +static bool is_reserved_char(const char c); +static CURLcode uri_encode_path(struct Curl_str *original_path, + struct dynbuf *new_path); +static CURLcode encode_query_component(char *component, size_t len, + struct dynbuf *db); +static CURLcode http_aws_decode_encode(const char *in, size_t in_len, + struct dynbuf *out); +static bool should_urlencode(struct Curl_str *service_name); + static void sha256_to_hex(char *dst, unsigned char *sha) { Curl_hexencode(sha, CURL_SHA256_DIGEST_LENGTH, @@ -83,7 +104,7 @@ static void trim_headers(struct curl_slist *head) { struct curl_slist *l; for(l = head; l; l = l->next) { - char *value; /* to read from */ + const char *value; /* to read from */ char *store; size_t colon = strcspn(l->data, ":"); Curl_strntolower(l->data, l->data, colon); @@ -92,15 +113,14 @@ static void trim_headers(struct curl_slist *head) if(!*value) continue; ++value; - store = value; + store = (char *)CURL_UNCONST(value); /* skip leading whitespace */ - while(*value && ISBLANK(*value)) - value++; + curlx_str_passblanks(&value); while(*value) { int space = 0; - while(*value && ISBLANK(*value)) { + while(ISBLANK(*value)) { value++; space++; } @@ -113,7 +133,7 @@ static void trim_headers(struct curl_slist *head) else *store++ = *value++; } - *store = 0; /* null terminate */ + *store = 0; /* null-terminate */ } } @@ -155,6 +175,57 @@ static int compare_header_names(const char *a, const char *b) return cmp; } +/* Merge duplicate header definitions by comma delimiting their values + in the order defined the headers are defined, expecting headers to + be alpha-sorted and use ':' at this point */ +static CURLcode merge_duplicate_headers(struct curl_slist *head) +{ + struct curl_slist *curr = head; + CURLcode result = CURLE_OK; + + while(curr) { + struct curl_slist *next = curr->next; + if(!next) + break; + + if(compare_header_names(curr->data, next->data) == 0) { + struct dynbuf buf; + char *colon_next; + char *val_next; + + curlx_dyn_init(&buf, CURL_MAX_HTTP_HEADER); + + result = curlx_dyn_add(&buf, curr->data); + if(result) + return result; + + colon_next = strchr(next->data, ':'); + DEBUGASSERT(colon_next); + val_next = colon_next + 1; + + result = curlx_dyn_addn(&buf, ",", 1); + if(result) + return result; + + result = curlx_dyn_add(&buf, val_next); + if(result) + return result; + + free(curr->data); + curr->data = curlx_dyn_ptr(&buf); + + curr->next = next->next; + free(next->data); + free(next); + } + else { + curr = curr->next; + } + } + + return CURLE_OK; +} + /* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */ static CURLcode make_headers(struct Curl_easy *data, const char *hostname, @@ -234,7 +305,7 @@ static CURLcode make_headers(struct Curl_easy *data, sep = strchr(l->data, ';'); if(!sep || (*sep == ':' && !*(sep + 1))) continue; - for(ptr = sep + 1; ISSPACE(*ptr); ++ptr) + for(ptr = sep + 1; ISBLANK(*ptr); ++ptr) ; if(!*ptr && ptr != sep + 1) /* a value of whitespace only */ continue; @@ -261,16 +332,15 @@ static CURLcode make_headers(struct Curl_easy *data, *date_header = aprintf("%s: %s\r\n", date_hdr_key, timestamp); } else { - char *value; - char *endp; + const char *value; + const char *endp; value = strchr(*date_header, ':'); if(!value) { *date_header = NULL; goto fail; } ++value; - while(ISBLANK(*value)) - ++value; + curlx_str_passblanks(&value); endp = value; while(*endp && ISALNUM(*endp)) ++endp; @@ -301,12 +371,16 @@ static CURLcode make_headers(struct Curl_easy *data, } } while(again); + ret = merge_duplicate_headers(head); + if(ret) + goto fail; + for(l = head; l; l = l->next) { char *tmp; - if(Curl_dyn_add(canonical_headers, l->data)) + if(curlx_dyn_add(canonical_headers, l->data)) goto fail; - if(Curl_dyn_add(canonical_headers, "\n")) + if(curlx_dyn_add(canonical_headers, "\n")) goto fail; tmp = strchr(l->data, ':'); @@ -314,10 +388,10 @@ static CURLcode make_headers(struct Curl_easy *data, *tmp = 0; if(l != head) { - if(Curl_dyn_add(signed_headers, ";")) + if(curlx_dyn_add(signed_headers, ";")) goto fail; } - if(Curl_dyn_add(signed_headers, l->data)) + if(curlx_dyn_add(signed_headers, l->data)) goto fail; } @@ -334,14 +408,13 @@ fail: SHA256_HEX_LENGTH) /* try to parse a payload hash from the content-sha256 header */ -static char *parse_content_sha_hdr(struct Curl_easy *data, - const char *provider1, - size_t plen, - size_t *value_len) -{ +static const char *parse_content_sha_hdr(struct Curl_easy *data, + const char *provider1, + size_t plen, + size_t *value_len) { char key[CONTENT_SHA256_KEY_LEN]; size_t key_len; - char *value; + const char *value; size_t len; key_len = msnprintf(key, sizeof(key), "x-%.*s-content-sha256", @@ -356,8 +429,7 @@ static char *parse_content_sha_hdr(struct Curl_easy *data, return NULL; ++value; - while(*value && ISBLANK(*value)) - ++value; + curlx_str_passblanks(&value); len = strlen(value); while(len > 0 && ISBLANK(value[len-1])) @@ -390,7 +462,8 @@ static CURLcode calc_payload_hash(struct Curl_easy *data, #define S3_UNSIGNED_PAYLOAD "UNSIGNED-PAYLOAD" static CURLcode calc_s3_payload_hash(struct Curl_easy *data, - Curl_HttpReq httpreq, char *provider1, + Curl_HttpReq httpreq, + const char *provider1, size_t plen, unsigned char *sha_hash, char *sha_hex, char *header) @@ -425,158 +498,181 @@ fail: return ret; } -struct pair { - const char *p; - size_t len; -}; - static int compare_func(const void *a, const void *b) { + const struct pair *aa = a; const struct pair *bb = b; + const size_t aa_key_len = curlx_dyn_len(&aa->key); + const size_t bb_key_len = curlx_dyn_len(&bb->key); + const size_t aa_value_len = curlx_dyn_len(&aa->value); + const size_t bb_value_len = curlx_dyn_len(&bb->value); + int compare; + /* If one element is empty, the other is always sorted higher */ - if(aa->len == 0 && bb->len == 0) + + /* Compare keys */ + if((aa_key_len == 0) && (bb_key_len == 0)) return 0; - if(aa->len == 0) + if(aa_key_len == 0) return -1; - if(bb->len == 0) + if(bb_key_len == 0) return 1; - return strncmp(aa->p, bb->p, aa->len < bb->len ? aa->len : bb->len); + compare = strcmp(curlx_dyn_ptr(&aa->key), curlx_dyn_ptr(&bb->key)); + if(compare) { + return compare; + } + + /* Compare values */ + if((aa_value_len == 0) && (bb_value_len == 0)) + return 0; + if(aa_value_len == 0) + return -1; + if(bb_value_len == 0) + return 1; + compare = strcmp(curlx_dyn_ptr(&aa->value), curlx_dyn_ptr(&bb->value)); + + return compare; + } -#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) +UNITTEST CURLcode canon_path(const char *q, size_t len, + struct dynbuf *new_path, + bool do_uri_encode) { 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]={'%'}; + struct Curl_str original_path; - 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; - } - } - } + curlx_str_assign(&original_path, q, len); + + /* Normalized path will be either the same or shorter than the original + * path, plus trailing slash */ + + if(do_uri_encode) + result = uri_encode_path(&original_path, new_path); + else + result = curlx_dyn_addn(new_path, q, len); + + if(!result) { + if(curlx_dyn_len(new_path) == 0) + result = curlx_dyn_add(new_path, "/"); } + return result; } - -static CURLcode canon_query(struct Curl_easy *data, - const char *query, struct dynbuf *dq) +UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq) { CURLcode result = CURLE_OK; - int entry = 0; - int i; - const char *p = query; - struct pair array[MAX_QUERYPAIRS]; - struct pair *ap = &array[0]; + + struct dynbuf query_array[MAX_QUERY_COMPONENTS]; + struct pair encoded_query_array[MAX_QUERY_COMPONENTS]; + size_t num_query_components; + size_t counted_query_components = 0; + size_t index; + if(!query) return result; - /* sort the name=value pairs first */ - do { - char *amp; - entry++; - ap->p = p; - amp = strchr(p, '&'); - if(amp) - ap->len = amp - p; /* excluding the ampersand */ + result = split_to_dyn_array(query, &query_array[0], + &num_query_components); + if(result) { + goto fail; + } + + /* Create list of pairs, each pair containing an encoded query + * component */ + + for(index = 0; index < num_query_components; index++) { + const char *in_key; + size_t in_key_len; + char *offset; + size_t query_part_len = curlx_dyn_len(&query_array[index]); + char *query_part = curlx_dyn_ptr(&query_array[index]); + + in_key = query_part; + + offset = strchr(query_part, '='); + /* If there is no equals, this key has no value */ + if(!offset) { + in_key_len = strlen(in_key); + } else { - ap->len = strlen(p); + in_key_len = offset - in_key; + } + + curlx_dyn_init(&encoded_query_array[index].key, query_part_len*3 + 1); + curlx_dyn_init(&encoded_query_array[index].value, query_part_len*3 + 1); + counted_query_components++; + + /* Decode/encode the key */ + result = http_aws_decode_encode(in_key, in_key_len, + &encoded_query_array[index].key); + if(result) { + goto fail; + } + + /* Decode/encode the value if it exists */ + if(offset && offset != (query_part + query_part_len - 1)) { + size_t in_value_len; + const char *in_value = offset + 1; + in_value_len = query_part + query_part_len - (offset + 1); + result = http_aws_decode_encode(in_value, in_value_len, + &encoded_query_array[index].value); + if(result) { + goto fail; + } + } + else { + /* If there is no value, the value is an empty string */ + curlx_dyn_init(&encoded_query_array[index].value, 2); + result = curlx_dyn_addn(&encoded_query_array[index].value, "", 1); + } + + if(result) { + goto fail; + } + } + + /* Sort the encoded query components by key and value */ + qsort(&encoded_query_array, num_query_components, + sizeof(struct pair), compare_func); + + /* Append the query components together to make a full query string */ + for(index = 0; index < num_query_components; index++) { + + if(index) + result = curlx_dyn_addn(dq, "&", 1); + if(!result) { + char *key_ptr = curlx_dyn_ptr(&encoded_query_array[index].key); + char *value_ptr = curlx_dyn_ptr(&encoded_query_array[index].value); + size_t vlen = curlx_dyn_len(&encoded_query_array[index].value); + if(value_ptr && vlen) { + result = curlx_dyn_addf(dq, "%s=%s", key_ptr, value_ptr); + } + else { + /* Empty value is always encoded to key= */ + result = curlx_dyn_addf(dq, "%s=", key_ptr); + } + } + if(result) break; - } - ap++; - p = amp + 1; - } while(entry < MAX_QUERYPAIRS); - if(entry == MAX_QUERYPAIRS) { - /* too many query pairs for us */ - failf(data, "aws-sigv4: too many query pairs in URL"); - return CURLE_URL_MALFORMAT; } - qsort(&array[0], entry, sizeof(struct pair), compare_func); - - ap = &array[0]; - for(i = 0; !result && (i < entry); i++, ap++) { - const char *q = ap->p; - bool found_equals = FALSE; - if(!ap->len) - continue; - 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(!result && i < entry - 1) { - /* insert ampersands between query pairs */ - result = Curl_dyn_addn(dq, "&", 1); - } - } +fail: + if(counted_query_components) + /* the encoded_query_array might not be initialized yet */ + pair_array_free(&encoded_query_array[0], counted_query_components); + dyn_array_free(&query_array[0], num_query_components); return result; } - -CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) +CURLcode Curl_output_aws_sigv4(struct Curl_easy *data) { CURLcode result = CURLE_OUT_OF_MEMORY; struct connectdata *conn = data->conn; - size_t len; - char *line; + const char *line; struct Curl_str provider0; struct Curl_str provider1; struct Curl_str region = { NULL, 0}; @@ -593,7 +689,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) char *date_header = NULL; Curl_HttpReq httpreq; const char *method = NULL; - char *payload_hash = NULL; + const char *payload_hash = NULL; size_t payload_hash_len = 0; unsigned char sha_hash[CURL_SHA256_DIGEST_LENGTH]; char sha_hex[SHA256_HEX_LENGTH]; @@ -608,8 +704,10 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) unsigned char sign1[CURL_SHA256_DIGEST_LENGTH] = {0}; char *auth_headers = NULL; - DEBUGASSERT(!proxy); - (void)proxy; + if(data->set.path_as_is) { + failf(data, "Cannot use sigv4 authentication with path-as-is flag"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } if(Curl_checkheaders(data, STRCONST("Authorization"))) { /* Authorization already present, Bailing out */ @@ -617,10 +715,10 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) } /* we init those buffers here, so goto fail will free initialized dynbuf */ - 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); + curlx_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER); + curlx_dyn_init(&canonical_path, CURL_MAX_HTTP_HEADER); /* * Parameters parsing @@ -629,86 +727,70 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) * AWS is the default because most of non-amazon providers * are still using aws:amz as a prefix. */ - line = data->set.str[STRING_AWS_SIGV4] ? - data->set.str[STRING_AWS_SIGV4] : (char *)"aws:amz"; + line = data->set.str[STRING_AWS_SIGV4]; + if(!line || !*line) + line = "aws:amz"; /* provider0[:provider1[:region[:service]]] No string can be longer than N bytes of non-whitespace */ - if(Curl_str_until(&line, &provider0, MAX_SIGV4_LEN, ':')) { + if(curlx_str_until(&line, &provider0, MAX_SIGV4_LEN, ':')) { failf(data, "first aws-sigv4 provider cannot be empty"); result = CURLE_BAD_FUNCTION_ARGUMENT; goto fail; } - if(Curl_str_single(&line, ':') || - Curl_str_until(&line, &provider1, MAX_SIGV4_LEN, ':')) { - provider1.str = provider0.str; - provider1.len = provider0.len; + if(curlx_str_single(&line, ':') || + curlx_str_until(&line, &provider1, MAX_SIGV4_LEN, ':')) { + provider1 = provider0; } - else if(Curl_str_single(&line, ':') || - Curl_str_until(&line, ®ion, MAX_SIGV4_LEN, ':') || - Curl_str_single(&line, ':') || - Curl_str_until(&line, &service, MAX_SIGV4_LEN, ':')) { + else if(curlx_str_single(&line, ':') || + curlx_str_until(&line, ®ion, MAX_SIGV4_LEN, ':') || + curlx_str_single(&line, ':') || + curlx_str_until(&line, &service, MAX_SIGV4_LEN, ':')) { /* nothing to do */ } - if(!service.len) { - char *hostdot = strchr(hostname, '.'); - if(!hostdot) { + if(!curlx_strlen(&service)) { + const char *p = hostname; + if(curlx_str_until(&p, &service, MAX_SIGV4_LEN, '.') || + curlx_str_single(&p, '.')) { failf(data, "aws-sigv4: service missing in parameters and hostname"); result = CURLE_URL_MALFORMAT; goto fail; } - len = hostdot - hostname; - if(len > MAX_SIGV4_LEN) { - failf(data, "aws-sigv4: service too long in hostname"); - result = CURLE_URL_MALFORMAT; - goto fail; - } - service.str = (char *)hostname; - service.len = len; infof(data, "aws_sigv4: picked service %.*s from host", - (int)service.len, service.str); + (int)curlx_strlen(&service), curlx_str(&service)); - if(!region.len) { - const char *reg = hostdot + 1; - const char *hostreg = strchr(reg, '.'); - if(!hostreg) { + if(!curlx_strlen(®ion)) { + if(curlx_str_until(&p, ®ion, MAX_SIGV4_LEN, '.') || + curlx_str_single(&p, '.')) { failf(data, "aws-sigv4: region missing in parameters and hostname"); result = CURLE_URL_MALFORMAT; goto fail; } - len = hostreg - reg; - if(len > MAX_SIGV4_LEN) { - failf(data, "aws-sigv4: region too long in hostname"); - result = CURLE_URL_MALFORMAT; - goto fail; - } - region.str = (char *)reg; - region.len = len; infof(data, "aws_sigv4: picked region %.*s from host", - (int)region.len, region.str); + (int)curlx_strlen(®ion), curlx_str(®ion)); } } Curl_http_method(data, conn, &method, &httpreq); - payload_hash = parse_content_sha_hdr(data, provider1.str, provider1.len, - &payload_hash_len); + payload_hash = + parse_content_sha_hdr(data, curlx_str(&provider1), + curlx_strlen(&provider1), &payload_hash_len); if(!payload_hash) { /* AWS S3 requires a x-amz-content-sha256 header, and supports special * values like UNSIGNED-PAYLOAD */ - bool sign_as_s3 = ((provider0.len == 3) && - strncasecompare(provider0.str, "aws", 3)) && - ((service.len == 2) && strncasecompare(service.str, "s3", 2)); + bool sign_as_s3 = curlx_str_casecompare(&provider0, "aws") && + curlx_str_casecompare(&service, "s3"); if(sign_as_s3) - result = calc_s3_payload_hash(data, httpreq, - provider1.str, provider1.len, - sha_hash, sha_hex, content_sha256_hdr); + result = calc_s3_payload_hash(data, httpreq, curlx_str(&provider1), + curlx_strlen(&provider1), sha_hash, + sha_hex, content_sha256_hdr); else result = calc_payload_hash(data, sha_hash, sha_hex); if(result) @@ -740,7 +822,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) } result = make_headers(data, hostname, timestamp, - provider1.str, provider1.len, + curlx_str(&provider1), curlx_strlen(&provider1), &date_header, content_sha256_hdr, &canonical_headers, &signed_headers); if(result) @@ -756,12 +838,13 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) memcpy(date, timestamp, sizeof(date)); date[sizeof(date) - 1] = 0; - result = canon_query(data, data->state.up.query, &canonical_query); + result = canon_query(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); + result = canon_path(data->state.up.path, strlen(data->state.up.path), + &canonical_path, + should_urlencode(&service)); if(result) goto fail; result = CURLE_OUT_OF_MEMORY; @@ -774,28 +857,30 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) "%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), + curlx_dyn_ptr(&canonical_path), + curlx_dyn_ptr(&canonical_query) ? + curlx_dyn_ptr(&canonical_query) : "", + curlx_dyn_ptr(&canonical_headers), + curlx_dyn_ptr(&signed_headers), (int)payload_hash_len, payload_hash); if(!canonical_request) goto fail; - DEBUGF(infof(data, "Canonical request: %s", canonical_request)); + infof(data, "aws_sigv4: Canonical request (enclosed in []) - [%s]", + canonical_request); - request_type = aprintf("%.*s4_request", (int)provider0.len, provider0.str); + request_type = aprintf("%.*s4_request", + (int)curlx_strlen(&provider0), curlx_str(&provider0)); if(!request_type) goto fail; /* provider0 is lowercased *after* aprintf() so that the buffer can be written to */ - Curl_strntolower(request_type, request_type, provider0.len); + Curl_strntolower(request_type, request_type, curlx_strlen(&provider0)); - credential_scope = aprintf("%s/%.*s/%.*s/%s", - date, (int)region.len, region.str, - (int)service.len, service.str, + credential_scope = aprintf("%s/%.*s/%.*s/%s", date, + (int)curlx_strlen(®ion), curlx_str(®ion), + (int)curlx_strlen(&service), curlx_str(&service), request_type); if(!credential_scope) goto fail; @@ -814,7 +899,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) "%s\n" /* RequestDateTime */ "%s\n" /* CredentialScope */ "%s", /* HashedCanonicalRequest in hex */ - (int)provider0.len, provider0.str, + (int)curlx_strlen(&provider0), curlx_str(&provider0), timestamp, credential_scope, sha_hex); @@ -822,24 +907,32 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) goto fail; /* make provider0 part done uppercase */ - Curl_strntoupper(str_to_sign, provider0.str, provider0.len); + Curl_strntoupper(str_to_sign, curlx_str(&provider0), + curlx_strlen(&provider0)); - secret = aprintf("%.*s4%s", (int)provider0.len, provider0.str, - data->state.aptr.passwd ? + infof(data, "aws_sigv4: String to sign (enclosed in []) - [%s]", + str_to_sign); + + secret = aprintf("%.*s4%s", (int)curlx_strlen(&provider0), + curlx_str(&provider0), data->state.aptr.passwd ? data->state.aptr.passwd : ""); if(!secret) goto fail; /* make provider0 part done uppercase */ - Curl_strntoupper(secret, provider0.str, provider0.len); + Curl_strntoupper(secret, curlx_str(&provider0), curlx_strlen(&provider0)); HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0); - HMAC_SHA256(sign0, sizeof(sign0), region.str, region.len, sign1); - HMAC_SHA256(sign1, sizeof(sign1), service.str, service.len, sign0); + HMAC_SHA256(sign0, sizeof(sign0), + curlx_str(®ion), curlx_strlen(®ion), sign1); + HMAC_SHA256(sign1, sizeof(sign1), + curlx_str(&service), curlx_strlen(&service), sign0); HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1); HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0); sha256_to_hex(sha_hex, sign0); + infof(data, "aws_sigv4: Signature - %s", sha_hex); + auth_headers = aprintf("Authorization: %.*s4-HMAC-SHA256 " "Credential=%s/%s, " "SignedHeaders=%s, " @@ -851,10 +944,10 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) */ "%s" "%s", /* optional sha256 header includes \r\n */ - (int)provider0.len, provider0.str, + (int)curlx_strlen(&provider0), curlx_str(&provider0), user, credential_scope, - Curl_dyn_ptr(&signed_headers), + curlx_dyn_ptr(&signed_headers), sha_hex, date_header ? date_header : "", content_sha256_hdr); @@ -863,18 +956,18 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) } /* provider 0 uppercase */ Curl_strntoupper(&auth_headers[sizeof("Authorization: ") - 1], - provider0.str, provider0.len); + curlx_str(&provider0), curlx_strlen(&provider0)); - Curl_safefree(data->state.aptr.userpwd); + free(data->state.aptr.userpwd); data->state.aptr.userpwd = auth_headers; data->state.authhost.done = TRUE; result = CURLE_OK; fail: - Curl_dyn_free(&canonical_query); - Curl_dyn_free(&canonical_path); - Curl_dyn_free(&canonical_headers); - Curl_dyn_free(&signed_headers); + curlx_dyn_free(&canonical_query); + curlx_dyn_free(&canonical_path); + curlx_dyn_free(&canonical_headers); + curlx_dyn_free(&signed_headers); free(canonical_request); free(request_type); free(credential_scope); @@ -884,4 +977,176 @@ fail: return result; } +/* +* Frees all allocated strings in a dynbuf pair array, and the dynbuf itself +*/ + +static void pair_array_free(struct pair *pair_array, size_t num_elements) +{ + size_t index; + + for(index = 0; index != num_elements; index++) { + curlx_dyn_free(&pair_array[index].key); + curlx_dyn_free(&pair_array[index].value); + } + +} + +/* +* Frees all allocated strings in a split dynbuf, and the dynbuf itself +*/ + +static void dyn_array_free(struct dynbuf *db, size_t num_elements) +{ + size_t index; + + for(index = 0; index < num_elements; index++) + curlx_dyn_free((&db[index])); +} + +/* +* Splits source string by SPLIT_BY, and creates an array of dynbuf in db. +* db is initialized by this function. +* Caller is responsible for freeing the array elements with dyn_array_free +*/ + +#define SPLIT_BY '&' + +static CURLcode split_to_dyn_array(const char *source, + struct dynbuf db[MAX_QUERY_COMPONENTS], + size_t *num_splits_out) +{ + CURLcode result = CURLE_OK; + size_t len = strlen(source); + size_t pos; /* Position in result buffer */ + size_t start = 0; /* Start of current segment */ + size_t segment_length = 0; + size_t index = 0; + size_t num_splits = 0; + + /* Split source_ptr on SPLIT_BY and store the segment offsets and length in + * array */ + for(pos = 0; pos < len; pos++) { + if(source[pos] == SPLIT_BY) { + if(segment_length) { + curlx_dyn_init(&db[index], segment_length + 1); + result = curlx_dyn_addn(&db[index], &source[start], + segment_length); + if(result) + goto fail; + + segment_length = 0; + index++; + if(++num_splits == MAX_QUERY_COMPONENTS) { + result = CURLE_TOO_LARGE; + goto fail; + } + } + start = pos + 1; + } + else { + segment_length++; + } + } + + if(segment_length) { + curlx_dyn_init(&db[index], segment_length + 1); + result = curlx_dyn_addn(&db[index], &source[start], segment_length); + if(!result) { + if(++num_splits == MAX_QUERY_COMPONENTS) + result = CURLE_TOO_LARGE; + } + } +fail: + *num_splits_out = num_splits; + return result; +} + + +static bool is_reserved_char(const char c) +{ + return (ISALNUM(c) || ISURLPUNTCS(c)); +} + +static CURLcode uri_encode_path(struct Curl_str *original_path, + struct dynbuf *new_path) +{ + const char *p = curlx_str(original_path); + size_t i; + + for(i = 0; i < curlx_strlen(original_path); i++) { + /* Do not encode slashes or unreserved chars from RFC 3986 */ + CURLcode result = CURLE_OK; + unsigned char c = p[i]; + if(is_reserved_char(c) || c == '/') + result = curlx_dyn_addn(new_path, &c, 1); + else + result = curlx_dyn_addf(new_path, "%%%02X", c); + if(result) + return result; + } + + return CURLE_OK; +} + + +static CURLcode encode_query_component(char *component, size_t len, + struct dynbuf *db) +{ + size_t i; + for(i = 0; i < len; i++) { + CURLcode result = CURLE_OK; + unsigned char this_char = component[i]; + + if(is_reserved_char(this_char)) + /* Escape unreserved chars from RFC 3986 */ + result = curlx_dyn_addn(db, &this_char, 1); + else if(this_char == '+') + /* Encode '+' as space */ + result = curlx_dyn_add(db, "%20"); + else + result = curlx_dyn_addf(db, "%%%02X", this_char); + if(result) + return result; + } + + return CURLE_OK; +} + +/* +* Populates a dynbuf containing url_encode(url_decode(in)) +*/ + +static CURLcode http_aws_decode_encode(const char *in, size_t in_len, + struct dynbuf *out) +{ + char *out_s; + size_t out_s_len; + CURLcode result = + Curl_urldecode(in, in_len, &out_s, &out_s_len, REJECT_NADA); + + if(!result) { + result = encode_query_component(out_s, out_s_len, out); + Curl_safefree(out_s); + } + return result; +} + +static bool should_urlencode(struct Curl_str *service_name) +{ + /* + * These services require unmodified (not additionally url encoded) URL + * paths. + * should_urlencode == true is equivalent to should_urlencode_uri_path + * from the AWS SDK. Urls are already normalized by the curl url parser + */ + + if(curlx_str_cmp(service_name, "s3") || + curlx_str_cmp(service_name, "s3-express") || + curlx_str_cmp(service_name, "s3-outposts")) { + return false; + } + return true; +} + #endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS) */ diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.h b/Utilities/cmcurl/lib/http_aws_sigv4.h index 57cc5706eb..9747c948a6 100644 --- a/Utilities/cmcurl/lib/http_aws_sigv4.h +++ b/Utilities/cmcurl/lib/http_aws_sigv4.h @@ -11,7 +11,7 @@ * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * 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 @@ -24,8 +24,18 @@ * ***************************************************************************/ #include "curl_setup.h" +#include "curlx/dynbuf.h" +#include "urldata.h" +#include "curlx/strparse.h" /* this is for creating aws_sigv4 header output */ -CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy); +CURLcode Curl_output_aws_sigv4(struct Curl_easy *data); + +#ifdef UNITTESTS +UNITTEST CURLcode canon_path(const char *q, size_t len, + struct dynbuf *new_path, + bool normalize); +UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq); +#endif #endif /* HEADER_CURL_HTTP_AWS_SIGV4_H */ diff --git a/Utilities/cmcurl/lib/http_chunks.c b/Utilities/cmcurl/lib/http_chunks.c index aea84be986..63e477c48a 100644 --- a/Utilities/cmcurl/lib/http_chunks.c +++ b/Utilities/cmcurl/lib/http_chunks.c @@ -30,12 +30,12 @@ #include "curl_printf.h" #include "curl_trc.h" #include "sendf.h" /* for the client write stuff */ -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "content_encoding.h" #include "http.h" #include "multiif.h" -#include "strtoofft.h" -#include "warnless.h" +#include "curlx/strparse.h" +#include "curlx/warnless.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -85,7 +85,7 @@ void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, 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); + curlx_dyn_init(&ch->trailer, DYN_H1_TRAILER); ch->ignore_body = ignore_body; } @@ -96,14 +96,14 @@ void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch, ch->hexindex = 0; /* start at 0 */ ch->state = CHUNK_HEX; /* we get hex first! */ ch->last_code = CHUNKE_OK; - Curl_dyn_reset(&ch->trailer); + curlx_dyn_reset(&ch->trailer); ch->ignore_body = ignore_body; } void Curl_httpchunk_free(struct Curl_easy *data, struct Curl_chunker *ch) { (void)data; - Curl_dyn_free(&ch->trailer); + curlx_dyn_free(&ch->trailer); } bool Curl_httpchunk_is_done(struct Curl_easy *data, struct Curl_chunker *ch) @@ -134,7 +134,7 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, 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); + result = Curl_client_write(data, CLIENTWRITE_BODY, buf, blen); if(result) { ch->state = CHUNK_FAILED; ch->last_code = CHUNKE_PASSTHRU_ERROR; @@ -158,6 +158,7 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, (*pconsumed)++; } else { + const char *p; if(0 == ch->hexindex) { /* This is illegal data, we received junk where we expected a hexadecimal digit. */ @@ -166,11 +167,11 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, ch->last_code = CHUNKE_ILLEGAL_HEX; return CURLE_RECV_ERROR; } - /* blen and buf are unmodified */ ch->hexbuffer[ch->hexindex] = 0; - if(curlx_strtoofft(ch->hexbuffer, NULL, 16, &ch->datasize)) { - failf(data, "chunk hex-length not valid: '%s'", ch->hexbuffer); + p = &ch->hexbuffer[0]; + if(curlx_str_hex(&p, &ch->datasize, CURL_OFF_T_MAX)) { + failf(data, "invalid chunk size: '%s'", ch->hexbuffer); ch->state = CHUNK_FAILED; ch->last_code = CHUNKE_ILLEGAL_HEX; return CURLE_RECV_ERROR; @@ -212,8 +213,7 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY, buf, piece); else - result = Curl_client_write(data, CLIENTWRITE_BODY, - (char *)buf, piece); + result = Curl_client_write(data, CLIENTWRITE_BODY, buf, piece); if(result) { ch->state = CHUNK_FAILED; ch->last_code = CHUNKE_PASSTHRU_ERROR; @@ -251,21 +251,20 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, case CHUNK_TRAILER: if((*buf == 0x0d) || (*buf == 0x0a)) { - char *tr = Curl_dyn_ptr(&ch->trailer); + char *tr = curlx_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(&ch->trailer, (char *)STRCONST("\x0d\x0a")); + result = curlx_dyn_addn(&ch->trailer, 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); + tr = curlx_dyn_ptr(&ch->trailer); if(!data->set.http_te_skip) { + size_t trlen = curlx_dyn_len(&ch->trailer); if(cw_next) result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_HEADER| @@ -282,7 +281,7 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, return result; } } - Curl_dyn_reset(&ch->trailer); + curlx_dyn_reset(&ch->trailer); ch->state = CHUNK_TRAILER_CR; if(*buf == 0x0a) /* already on the LF */ @@ -295,7 +294,7 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, } } else { - result = Curl_dyn_addn(&ch->trailer, buf, 1); + result = curlx_dyn_addn(&ch->trailer, buf, 1); if(result) { ch->state = CHUNK_FAILED; ch->last_code = CHUNKE_OUT_OF_MEMORY; diff --git a/Utilities/cmcurl/lib/http_chunks.h b/Utilities/cmcurl/lib/http_chunks.h index 34951ea0f4..b84e479fcd 100644 --- a/Utilities/cmcurl/lib/http_chunks.h +++ b/Utilities/cmcurl/lib/http_chunks.h @@ -26,7 +26,7 @@ #ifndef CURL_DISABLE_HTTP -#include "dynbuf.h" +#include "curlx/dynbuf.h" struct connectdata; @@ -49,7 +49,7 @@ typedef enum { POST_CR state. */ CHUNK_DATA, - /* POSTLF should get a CR and then a LF and nothing else, then move back to + /* POSTLF should get a CR and then an LF and nothing else, then move back to HEX as the CRLF combination marks the end of a chunk. A missing CR is no big deal. */ CHUNK_POSTLF, @@ -64,7 +64,7 @@ typedef enum { CHUNK_TRAILER, /* A trailer CR has been found - next state is CHUNK_TRAILER_POSTCR. - Next char must be a LF */ + Next char must be an LF */ CHUNK_TRAILER_CR, /* A trailer LF must be found now, otherwise CHUNKE_BAD_CHUNK will be diff --git a/Utilities/cmcurl/lib/http_digest.c b/Utilities/cmcurl/lib/http_digest.c index a3ba17a51f..651bb83403 100644 --- a/Utilities/cmcurl/lib/http_digest.c +++ b/Utilities/cmcurl/lib/http_digest.c @@ -30,6 +30,7 @@ #include "strcase.h" #include "vauth/vauth.h" #include "http_digest.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -62,8 +63,7 @@ CURLcode Curl_input_digest(struct Curl_easy *data, return CURLE_BAD_CONTENT_ENCODING; header += strlen("Digest"); - while(*header && ISBLANK(*header)) - header++; + curlx_str_passblanks(&header); return Curl_auth_decode_digest_http_message(header, digest); } @@ -145,15 +145,15 @@ CURLcode Curl_output_digest(struct Curl_easy *data, */ if(authp->iestyle) { - tmp = strchr((char *)uripath, '?'); + tmp = strchr((const char *)uripath, '?'); if(tmp) { - size_t urilen = tmp - (char *)uripath; + size_t urilen = tmp - (const char *)uripath; /* typecast is fine here since the value is always less than 32 bits */ path = (unsigned char *) aprintf("%.*s", (int)urilen, uripath); } } if(!tmp) - path = (unsigned char *) strdup((char *) uripath); + path = (unsigned char *) strdup((const char *) uripath); if(!path) return CURLE_OUT_OF_MEMORY; diff --git a/Utilities/cmcurl/lib/http_negotiate.c b/Utilities/cmcurl/lib/http_negotiate.c index f031d0abc8..5bec3b39ff 100644 --- a/Utilities/cmcurl/lib/http_negotiate.c +++ b/Utilities/cmcurl/lib/http_negotiate.c @@ -32,6 +32,7 @@ #include "http_negotiate.h" #include "vauth/vauth.h" #include "vtls/vtls.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -86,8 +87,7 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, /* Obtain the input token, if any */ header += strlen("Negotiate"); - while(*header && ISBLANK(*header)) - header++; + curlx_str_passblanks(&header); len = strlen(header); neg_ctx->havenegdata = len != 0; @@ -109,9 +109,10 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, 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) +#ifdef HAVE_GSSAPI +#ifdef USE_SSL + curlx_dyn_init(&neg_ctx->channel_binding_data, SSL_CB_MAX_SIZE + 1); if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - Curl_dyn_init(&neg_ctx->channel_binding_data, SSL_CB_MAX_SIZE + 1); result = Curl_ssl_get_channel_binding( data, FIRSTSOCKET, &neg_ctx->channel_binding_data); if(result) { @@ -119,14 +120,17 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, return result; } } -#endif +#else + curlx_dyn_init(&neg_ctx->channel_binding_data, 1); +#endif /* USE_SSL */ +#endif /* HAVE_GSSAPI */ /* 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); +#ifdef HAVE_GSSAPI + curlx_dyn_free(&neg_ctx->channel_binding_data); #endif if(result) @@ -203,12 +207,12 @@ CURLcode Curl_output_negotiate(struct Curl_easy *data, if(proxy) { #ifndef CURL_DISABLE_PROXY - Curl_safefree(data->state.aptr.proxyuserpwd); + free(data->state.aptr.proxyuserpwd); data->state.aptr.proxyuserpwd = userp; #endif } else { - Curl_safefree(data->state.aptr.userpwd); + free(data->state.aptr.userpwd); data->state.aptr.userpwd = userp; } diff --git a/Utilities/cmcurl/lib/http_ntlm.c b/Utilities/cmcurl/lib/http_ntlm.c index ab6f1dd921..e6a3b175a7 100644 --- a/Utilities/cmcurl/lib/http_ntlm.c +++ b/Utilities/cmcurl/lib/http_ntlm.c @@ -33,16 +33,15 @@ * https://www.innovation.ch/java/ntlm.html */ -#define DEBUG_ME 0 - #include "urldata.h" #include "sendf.h" #include "strcase.h" #include "http_ntlm.h" #include "curl_ntlm_core.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "vauth/vauth.h" #include "url.h" +#include "curlx/strparse.h" /* SSL backend-specific #if branches in this file must be kept in the order documented in curl_ntlm_core. */ @@ -55,12 +54,6 @@ #include "curl_memory.h" #include "memdebug.h" -#if DEBUG_ME -# define DEBUG_OUT(x) x -#else -# define DEBUG_OUT(x) Curl_nop_stmt -#endif - CURLcode Curl_input_ntlm(struct Curl_easy *data, bool proxy, /* if proxy or not */ const char *header) /* rest of the www-authenticate: @@ -78,14 +71,12 @@ CURLcode Curl_input_ntlm(struct Curl_easy *data, if(checkprefix("NTLM", header)) { header += strlen("NTLM"); - while(*header && ISSPACE(*header)) - header++; - + curlx_str_passblanks(&header); if(*header) { unsigned char *hdr; size_t hdrlen; - result = Curl_base64_decode(header, &hdr, &hdrlen); + result = curlx_base64_decode(header, &hdr, &hdrlen); if(!result) { struct bufref hdrbuf; @@ -214,7 +205,7 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) ntlm, &ntlmmsg); if(!result) { DEBUGASSERT(Curl_bufref_len(&ntlmmsg) != 0); - result = Curl_base64_encode((const char *) Curl_bufref_ptr(&ntlmmsg), + result = curlx_base64_encode((const char *) Curl_bufref_ptr(&ntlmmsg), Curl_bufref_len(&ntlmmsg), &base64, &len); if(!result) { free(*allocuserpwd); @@ -233,8 +224,8 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) result = Curl_auth_create_ntlm_type3_message(data, userp, passwdp, ntlm, &ntlmmsg); if(!result && Curl_bufref_len(&ntlmmsg)) { - result = Curl_base64_encode((const char *) Curl_bufref_ptr(&ntlmmsg), - Curl_bufref_len(&ntlmmsg), &base64, &len); + result = curlx_base64_encode((const char *) Curl_bufref_ptr(&ntlmmsg), + Curl_bufref_len(&ntlmmsg), &base64, &len); if(!result) { free(*allocuserpwd); *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", diff --git a/Utilities/cmcurl/lib/http_proxy.c b/Utilities/cmcurl/lib/http_proxy.c index 3a2e9f2b05..3df6329c06 100644 --- a/Utilities/cmcurl/lib/http_proxy.c +++ b/Utilities/cmcurl/lib/http_proxy.c @@ -38,11 +38,12 @@ #include "cf-h1-proxy.h" #include "cf-h2-proxy.h" #include "connect.h" -#include "curlx.h" +#include "strcase.h" #include "vtls/vtls.h" #include "transfer.h" #include "multiif.h" #include "vauth/vauth.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -60,7 +61,7 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data, struct dynhds *hds) { struct connectdata *conn = data->conn; - char *ptr; + const char *ptr; struct curl_slist *h[2]; struct curl_slist *headers; int numlists = 1; /* by default */ @@ -108,8 +109,7 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data, name = headers->data; namelen = ptr - headers->data; ptr++; /* pass the colon */ - while(*ptr && ISSPACE(*ptr)) - ptr++; + curlx_str_passblanks(&ptr); if(*ptr) { value = ptr; valuelen = strlen(value); @@ -131,8 +131,7 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data, name = headers->data; namelen = ptr - headers->data; ptr++; /* pass the semicolon */ - while(*ptr && ISSPACE(*ptr)) - ptr++; + curlx_str_passblanks(&ptr); if(!*ptr) { /* quirk #2, send an empty header */ value = ""; @@ -307,7 +306,7 @@ out: static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_proxy_ctx *ctx = cf->ctx; CURLcode result; @@ -319,7 +318,7 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "connect"); connect_sub: - result = cf->next->cft->do_connect(cf->next, data, blocking, done); + result = cf->next->cft->do_connect(cf->next, data, done); if(result || !*done) return result; diff --git a/Utilities/cmcurl/lib/httpsrr.c b/Utilities/cmcurl/lib/httpsrr.c index 9884b92580..df93ac34ac 100644 --- a/Utilities/cmcurl/lib/httpsrr.c +++ b/Utilities/cmcurl/lib/httpsrr.c @@ -31,122 +31,154 @@ #include "httpsrr.h" #include "connect.h" #include "sendf.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" -CURLcode Curl_httpsrr_decode_alpn(const unsigned char *cp, size_t len, - unsigned char *alpns) +#define MAX_ALPN_LENGTH 255 + +static CURLcode httpsrr_decode_alpn(const char *cp, size_t len, + unsigned char *alpns) { /* - * spec here is as per RFC 9460, section-7.1.1 - * encoding is a concatenated 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;-) + * The wire-format value for "alpn" consists of at least one alpn-id + * prefixed by its length as a single octet, and these length-value pairs + * are concatenated to form the SvcParamValue. These pairs MUST exactly fill + * the SvcParamValue; otherwise, the SvcParamValue is malformed. */ - struct dynbuf dval; int idnum = 0; - Curl_dyn_init(&dval, DYN_DOH_RESPONSE); while(len > 0) { size_t tlen = (size_t) *cp++; - size_t i; enum alpnid id; len--; if(tlen > len) - 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; - } - len -= tlen; + return CURLE_BAD_CONTENT_ENCODING; /* we only store ALPN ids we know about */ - id = Curl_alpn2alpnid(Curl_dyn_ptr(&dval), Curl_dyn_len(&dval)); + id = Curl_alpn2alpnid(cp, tlen); if(id != ALPN_none) { if(idnum == MAX_HTTPSRR_ALPNS) break; - alpns[idnum++] = (unsigned char)id; + if(idnum && memchr(alpns, id, idnum)) + /* this ALPN id is already stored */ + ; + else + alpns[idnum++] = (unsigned char)id; } - Curl_dyn_reset(&dval); + cp += tlen; + len -= tlen; } - Curl_dyn_free(&dval); if(idnum < MAX_HTTPSRR_ALPNS) alpns[idnum] = ALPN_none; /* terminate the list */ return CURLE_OK; -err: - Curl_dyn_free(&dval); - return CURLE_BAD_CONTENT_ENCODING; } +CURLcode Curl_httpsrr_set(struct Curl_easy *data, + struct Curl_https_rrinfo *hi, + uint16_t rrkey, const uint8_t *val, size_t vlen) +{ + CURLcode result = CURLE_OK; + switch(rrkey) { + case HTTPS_RR_CODE_MANDATORY: + CURL_TRC_DNS(data, "HTTPS RR MANDATORY left to implement"); + break; + case HTTPS_RR_CODE_ALPN: /* str_list */ + result = httpsrr_decode_alpn((const char *)val, vlen, hi->alpns); + CURL_TRC_DNS(data, "HTTPS RR ALPN: %u %u %u %u", + hi->alpns[0], hi->alpns[1], hi->alpns[2], hi->alpns[3]); + break; + case HTTPS_RR_CODE_NO_DEF_ALPN: + if(vlen) /* no data */ + return CURLE_BAD_FUNCTION_ARGUMENT; + hi->no_def_alpn = TRUE; + CURL_TRC_DNS(data, "HTTPS RR no-def-alpn"); + break; + case HTTPS_RR_CODE_IPV4: /* addr4 list */ + if(!vlen || (vlen & 3)) /* the size must be 4-byte aligned */ + return CURLE_BAD_FUNCTION_ARGUMENT; + hi->ipv4hints = Curl_memdup(val, vlen); + if(!hi->ipv4hints) + return CURLE_OUT_OF_MEMORY; + hi->ipv4hints_len = vlen; + CURL_TRC_DNS(data, "HTTPS RR IPv4"); + break; + case HTTPS_RR_CODE_ECH: + if(!vlen) + return CURLE_BAD_FUNCTION_ARGUMENT; + hi->echconfiglist = Curl_memdup(val, vlen); + if(!hi->echconfiglist) + return CURLE_OUT_OF_MEMORY; + hi->echconfiglist_len = vlen; + CURL_TRC_DNS(data, "HTTPS RR ECH"); + break; + case HTTPS_RR_CODE_IPV6: /* addr6 list */ + if(!vlen || (vlen & 15)) /* the size must be 16-byte aligned */ + return CURLE_BAD_FUNCTION_ARGUMENT; + hi->ipv6hints = Curl_memdup(val, vlen); + if(!hi->ipv6hints) + return CURLE_OUT_OF_MEMORY; + hi->ipv6hints_len = vlen; + CURL_TRC_DNS(data, "HTTPS RR IPv6"); + break; + case HTTPS_RR_CODE_PORT: + if(vlen != 2) + return CURLE_BAD_FUNCTION_ARGUMENT; + hi->port = (unsigned short)((val[0] << 8) | val[1]); + CURL_TRC_DNS(data, "HTTPS RR port %u", hi->port); + break; + default: + CURL_TRC_DNS(data, "HTTPS RR unknown code"); + break; + } + return result; +} + +struct Curl_https_rrinfo * +Curl_httpsrr_dup_move(struct Curl_https_rrinfo *rrinfo) +{ + struct Curl_https_rrinfo *dup = Curl_memdup(rrinfo, sizeof(*rrinfo)); + if(dup) + memset(rrinfo, 0, sizeof(*rrinfo)); + return dup; +} + +void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo) +{ + Curl_safefree(rrinfo->target); + Curl_safefree(rrinfo->echconfiglist); + Curl_safefree(rrinfo->ipv4hints); + Curl_safefree(rrinfo->ipv6hints); +} + + #ifdef USE_ARES -static void httpsrr_opt(struct Curl_easy *data, - const ares_dns_rr_t *rr, - ares_dns_rr_key_t key, size_t idx) +static CURLcode httpsrr_opt(struct Curl_easy *data, + const ares_dns_rr_t *rr, + ares_dns_rr_key_t key, size_t idx, + struct Curl_https_rrinfo *hinfo) { - size_t len = 0; const unsigned char *val = NULL; unsigned short code; - struct thread_data *res = data->state.async.tdata; - struct Curl_https_rrinfo *hi = &res->hinfo; - code = ares_dns_rr_get_opt(rr, key, idx, &val, &len); + size_t len = 0; - switch(code) { - case HTTPS_RR_CODE_ALPN: /* str_list */ - Curl_httpsrr_decode_alpn(val, len, hi->alpns); - infof(data, "HTTPS RR ALPN: %u %u %u %u", - hi->alpns[0], hi->alpns[1], hi->alpns[2], hi->alpns[3]); - break; - case HTTPS_RR_CODE_NO_DEF_ALPN: - infof(data, "HTTPS RR no-def-alpn"); - break; - case HTTPS_RR_CODE_IPV4: /* addr4 list */ - infof(data, "HTTPS RR IPv4"); - break; - case HTTPS_RR_CODE_ECH: - infof(data, "HTTPS RR ECH"); - break; - case HTTPS_RR_CODE_IPV6: /* addr6 list */ - infof(data, "HTTPS RR IPv6"); - break; - case HTTPS_RR_CODE_PORT: - infof(data, "HTTPS RR port"); - break; - default: - infof(data, "HTTPS RR unknown code"); - break; - } + code = ares_dns_rr_get_opt(rr, key, idx, &val, &len); + return Curl_httpsrr_set(data, hinfo, code, val, len); } -void Curl_dnsrec_done_cb(void *arg, ares_status_t status, - size_t timeouts, - const ares_dns_record_t *dnsrec) +CURLcode Curl_httpsrr_from_ares(struct Curl_easy *data, + const ares_dns_record_t *dnsrec, + struct Curl_https_rrinfo *hinfo) { - struct Curl_easy *data = arg; + CURLcode result = CURLE_OK; size_t i; -#ifdef CURLRES_ARES - struct thread_data *res = data->state.async.tdata; - - res->num_pending--; -#endif - (void)timeouts; - if((ARES_SUCCESS != status) || !dnsrec) - return; for(i = 0; i < ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); i++) { + const char *target; size_t opt; const ares_dns_rr_t *rr = ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i); @@ -154,12 +186,26 @@ void Curl_dnsrec_done_cb(void *arg, ares_status_t status, continue; /* When SvcPriority is 0, the SVCB record is in AliasMode. Otherwise, it is in ServiceMode */ - infof(data, "HTTPS RR priority: %u", - ares_dns_rr_get_u16(rr, ARES_RR_HTTPS_PRIORITY)); + target = ares_dns_rr_get_str(rr, ARES_RR_HTTPS_TARGET); + if(target && target[0]) { + hinfo->target = strdup(target); + if(!hinfo->target) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + CURL_TRC_DNS(data, "HTTPS RR target: %s", hinfo->target); + } + CURL_TRC_DNS(data, "HTTPS RR priority: %u", + ares_dns_rr_get_u16(rr, ARES_RR_HTTPS_PRIORITY)); for(opt = 0; opt < ares_dns_rr_get_opt_cnt(rr, ARES_RR_HTTPS_PARAMS); - opt++) - httpsrr_opt(data, rr, ARES_RR_HTTPS_PARAMS, opt); + opt++) { + result = httpsrr_opt(data, rr, ARES_RR_HTTPS_PARAMS, opt, hinfo); + if(result) + break; + } } +out: + return result; } #endif /* USE_ARES */ diff --git a/Utilities/cmcurl/lib/httpsrr.h b/Utilities/cmcurl/lib/httpsrr.h index ade2126f0f..83119dc6bd 100644 --- a/Utilities/cmcurl/lib/httpsrr.h +++ b/Utilities/cmcurl/lib/httpsrr.h @@ -35,6 +35,8 @@ #define CURL_MAXLEN_host_name 253 #define MAX_HTTPSRR_ALPNS 4 +struct Curl_easy; + struct Curl_https_rrinfo { /* * Fields from HTTPS RR. The only mandatory fields are priority and target. @@ -51,13 +53,22 @@ struct Curl_https_rrinfo { /* store parsed alpnid entries in the array, end with ALPN_none */ int port; /* -1 means not set */ uint16_t priority; - bool no_def_alpn; /* keytag = 2 */ + BIT(no_def_alpn); /* keytag = 2 */ }; -#endif + +CURLcode Curl_httpsrr_set(struct Curl_easy *data, + struct Curl_https_rrinfo *hi, + uint16_t rrkey, const uint8_t *val, size_t vlen); + +struct Curl_https_rrinfo * +Curl_httpsrr_dup_move(struct Curl_https_rrinfo *rrinfo); + +void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo); /* * Code points for DNS wire format SvcParams as per RFC 9460 */ +#define HTTPS_RR_CODE_MANDATORY 0x00 #define HTTPS_RR_CODE_ALPN 0x01 #define HTTPS_RR_CODE_NO_DEF_ALPN 0x02 #define HTTPS_RR_CODE_PORT 0x03 @@ -65,12 +76,11 @@ struct Curl_https_rrinfo { #define HTTPS_RR_CODE_ECH 0x05 #define HTTPS_RR_CODE_IPV6 0x06 -CURLcode Curl_httpsrr_decode_alpn(const unsigned char *cp, size_t len, - unsigned char *alpns); +#if defined(USE_ARES) +CURLcode Curl_httpsrr_from_ares(struct Curl_easy *data, + const ares_dns_record_t *dnsrec, + struct Curl_https_rrinfo *hinfo); +#endif /* USE_ARES */ +#endif /* USE_HTTPSRR */ -#if defined(USE_ARES) && defined(USE_HTTPSRR) -void Curl_dnsrec_done_cb(void *arg, ares_status_t status, - size_t timeouts, - const ares_dns_record_t *dnsrec); -#endif #endif /* HEADER_CURL_HTTPSRR_H */ diff --git a/Utilities/cmcurl/lib/idn.c b/Utilities/cmcurl/lib/idn.c index ed20cdc16b..798c9aaef1 100644 --- a/Utilities/cmcurl/lib/idn.c +++ b/Utilities/cmcurl/lib/idn.c @@ -30,8 +30,8 @@ #include "urldata.h" #include "idn.h" #include "sendf.h" -#include "curl_multibyte.h" -#include "warnless.h" +#include "curlx/multibyte.h" +#include "curlx/warnless.h" #ifdef USE_LIBIDN2 #include @@ -64,13 +64,14 @@ static CURLcode iconv_to_utf8(const char *in, size_t inlen, 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; + char *iconv_in = (char *)CURL_UNCONST(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) { + /* !checksrc! disable ERRNOVAR 1 */ if(errno == ENOMEM) return CURLE_OUT_OF_MEMORY; else @@ -80,6 +81,7 @@ static CURLcode iconv_to_utf8(const char *in, size_t inlen, return CURLE_OK; } else { + /* !checksrc! disable ERRNOVAR 1 */ if(errno == ENOMEM) return CURLE_OUT_OF_MEMORY; else diff --git a/Utilities/cmcurl/lib/if2ip.c b/Utilities/cmcurl/lib/if2ip.c index 55afd553d6..6da68efd50 100644 --- a/Utilities/cmcurl/lib/if2ip.c +++ b/Utilities/cmcurl/lib/if2ip.c @@ -67,7 +67,8 @@ unsigned int Curl_ipv6_scope(const struct sockaddr *sa) { if(sa->sa_family == AF_INET6) { - const struct sockaddr_in6 * sa6 = (const struct sockaddr_in6 *)(void *) sa; + const struct sockaddr_in6 * sa6 = + (const struct sockaddr_in6 *)(const void *) sa; const unsigned char *b = sa6->sin6_addr.s6_addr; unsigned short w = (unsigned short) ((b[0] << 8) | b[1]); @@ -92,7 +93,7 @@ unsigned int Curl_ipv6_scope(const struct sockaddr *sa) } #endif -#ifndef CURL_DISABLE_BINDLOCAL +#if !defined(CURL_DISABLE_BINDLOCAL) || !defined(CURL_DISABLE_FTP) #if defined(HAVE_GETIFADDRS) @@ -265,4 +266,4 @@ if2ip_result_t Curl_if2ip(int af, #endif -#endif /* CURL_DISABLE_BINDLOCAL */ +#endif /* CURL_DISABLE_BINDLOCAL && CURL_DISABLE_FTP */ diff --git a/Utilities/cmcurl/lib/imap.c b/Utilities/cmcurl/lib/imap.c index 49abaf4277..fc38a5b61e 100644 --- a/Utilities/cmcurl/lib/imap.c +++ b/Utilities/cmcurl/lib/imap.c @@ -36,6 +36,7 @@ ***************************************************************************/ #include "curl_setup.h" +#include "curlx/dynbuf.h" #ifndef CURL_DISABLE_IMAP @@ -64,7 +65,7 @@ #include "socks.h" #include "imap.h" #include "mime.h" -#include "strtoofft.h" +#include "curlx/strparse.h" #include "strcase.h" #include "vtls/vtls.h" #include "cfilters.h" @@ -74,7 +75,7 @@ #include "url.h" #include "bufref.h" #include "curl_sasl.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "curl_ctype.h" /* The last 3 #include files should be in this order */ @@ -82,8 +83,73 @@ #include "curl_memory.h" #include "memdebug.h" + +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_IMAP_EASY "meta:proto:imap:easy" +/* meta key for storing protocol meta at connection */ +#define CURL_META_IMAP_CONN "meta:proto:imap:conn" + +typedef enum { + IMAP_STOP, /* do nothing state, stops the state machine */ + IMAP_SERVERGREET, /* waiting for the initial greeting immediately after + a connect */ + IMAP_CAPABILITY, + IMAP_STARTTLS, + IMAP_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS + (multi mode only) */ + IMAP_AUTHENTICATE, + IMAP_LOGIN, + IMAP_LIST, + IMAP_SELECT, + IMAP_FETCH, + IMAP_FETCH_FINAL, + IMAP_APPEND, + IMAP_APPEND_FINAL, + IMAP_SEARCH, + IMAP_LOGOUT, + IMAP_LAST /* never used */ +} imapstate; + +/* imap_conn is used for struct connection-oriented data */ +struct imap_conn { + struct pingpong pp; + struct SASL sasl; /* SASL-related parameters */ + struct dynbuf dyn; /* for the IMAP commands */ + char *mailbox; /* The last selected mailbox */ + char *mailbox_uidvalidity; /* UIDVALIDITY parsed from select response */ + imapstate state; /* Always use imap.c:state() to change state! */ + char resptag[5]; /* Response tag to wait for */ + unsigned char preftype; /* Preferred authentication type */ + unsigned char cmdid; /* Last used command ID */ + BIT(ssldone); /* Is connect() over SSL done? */ + BIT(preauth); /* Is this connection PREAUTH? */ + BIT(tls_supported); /* StartTLS capability supported by server */ + BIT(login_disabled); /* LOGIN command disabled by server */ + BIT(ir_supported); /* Initial response supported by server */ +}; + +/* This IMAP struct is used in the Curl_easy. All IMAP data that is + connection-oriented must be in imap_conn to properly deal with the fact that + perhaps the Curl_easy is changed between the times the connection is + used. */ +struct IMAP { + curl_pp_transfer transfer; + char *mailbox; /* Mailbox to select */ + char *uidvalidity; /* UIDVALIDITY to check in select */ + char *uid; /* Message UID to fetch */ + char *mindex; /* Index in mail box of mail to fetch */ + char *section; /* Message SECTION to fetch */ + char *partial; /* Message PARTIAL to fetch */ + char *query; /* Query to search for */ + char *custom; /* Custom request */ + char *custom_params; /* Parameters for the custom request */ +}; + + /* Local API functions */ -static CURLcode imap_regular_transfer(struct Curl_easy *data, bool *done); +static CURLcode imap_regular_transfer(struct Curl_easy *data, + struct IMAP *imap, + bool *done); static CURLcode imap_do(struct Curl_easy *data, bool *done); static CURLcode imap_done(struct Curl_easy *data, CURLcode status, bool premature); @@ -97,11 +163,15 @@ 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, ...) - 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); +static CURLcode imap_sendf(struct Curl_easy *data, + struct imap_conn *imapc, + const char *fmt, ...) CURL_PRINTF(3, 4); +static CURLcode imap_parse_url_options(struct connectdata *conn, + struct imap_conn *imapc); +static CURLcode imap_parse_url_path(struct Curl_easy *data, + struct IMAP *imap); +static CURLcode imap_parse_custom_request(struct Curl_easy *data, + struct IMAP *imap); static CURLcode imap_perform_authenticate(struct Curl_easy *data, const char *mech, const struct bufref *initresp); @@ -111,6 +181,7 @@ static CURLcode imap_continue_authenticate(struct Curl_easy *data, static CURLcode imap_cancel_authenticate(struct Curl_easy *data, const char *mech); static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out); +static void imap_easy_reset(struct IMAP *imap); /* * IMAP protocol handler. @@ -192,6 +263,10 @@ static const struct SASLproto saslimap = { SASL_FLAG_BASE64 /* Configuration flags */ }; +struct ulbits { + int bit; + const char *flag; +}; /*********************************************************************** * @@ -242,14 +317,21 @@ static bool imap_matchresp(const char *line, size_t len, const char *cmd) * response which can be processed by the response handler. */ static bool imap_endofresp(struct Curl_easy *data, struct connectdata *conn, - char *line, size_t len, int *resp) + const char *line, size_t len, int *resp) { - struct IMAP *imap = data->req.p.imap; - struct imap_conn *imapc = &conn->proto.imapc; - const char *id = imapc->resptag; - size_t id_len = strlen(id); + struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); + struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); + const char *id; + size_t id_len; + + DEBUGASSERT(imapc); + DEBUGASSERT(imap); + if(!imapc || !imap) + return FALSE; /* Do we have a tagged command response? */ + id = imapc->resptag; + id_len = strlen(id); if(len >= id_len + 1 && !memcmp(id, line, id_len) && line[id_len] == ' ') { line += id_len + 1; len -= id_len + 1; @@ -275,17 +357,17 @@ static bool imap_endofresp(struct Curl_easy *data, struct connectdata *conn, case IMAP_LIST: if((!imap->custom && !imap_matchresp(line, len, "LIST")) || - (imap->custom && !imap_matchresp(line, len, imap->custom) && - (!strcasecompare(imap->custom, "STORE") || - !imap_matchresp(line, len, "FETCH")) && - !strcasecompare(imap->custom, "SELECT") && - !strcasecompare(imap->custom, "EXAMINE") && - !strcasecompare(imap->custom, "SEARCH") && - !strcasecompare(imap->custom, "EXPUNGE") && - !strcasecompare(imap->custom, "LSUB") && - !strcasecompare(imap->custom, "UID") && - !strcasecompare(imap->custom, "GETQUOTAROOT") && - !strcasecompare(imap->custom, "NOOP"))) + (imap->custom && !imap_matchresp(line, len, imap->custom) && + (!strcasecompare(imap->custom, "STORE") || + !imap_matchresp(line, len, "FETCH")) && + !strcasecompare(imap->custom, "SELECT") && + !strcasecompare(imap->custom, "EXAMINE") && + !strcasecompare(imap->custom, "SEARCH") && + !strcasecompare(imap->custom, "EXPUNGE") && + !strcasecompare(imap->custom, "LSUB") && + !strcasecompare(imap->custom, "UID") && + !strcasecompare(imap->custom, "GETQUOTAROOT") && + !strcasecompare(imap->custom, "NOOP"))) return FALSE; break; @@ -317,8 +399,8 @@ static bool imap_endofresp(struct Curl_easy *data, struct connectdata *conn, a space and optionally some text as per RFC-3501 for the AUTHENTICATE and APPEND commands and as outlined in Section 4. Examples of RFC-4959 but some email servers ignore this and only send a single + instead. */ - if(imap && !imap->custom && ((len == 3 && line[0] == '+') || - (len >= 2 && !memcmp("+ ", line, 2)))) { + if(!imap->custom && ((len == 3 && line[0] == '+') || + (len >= 2 && !memcmp("+ ", line, 2)))) { switch(imapc->state) { /* States which are interested in continuation responses */ case IMAP_AUTHENTICATE: @@ -346,9 +428,16 @@ 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 = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); - size_t len = data->conn->proto.imapc.pp.nfinal; + struct imap_conn *imapc = + Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN); + char *message; + size_t len; + if(!imapc) + return CURLE_FAILED_INIT; + + message = curlx_dyn_ptr(&imapc->pp.recvbuf); + len = imapc->pp.nfinal; if(len > 2) { /* Find the start of the message */ len -= 2; @@ -378,9 +467,10 @@ static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out) * * This is the ONLY way to change IMAP state! */ -static void imap_state(struct Curl_easy *data, imapstate newstate) +static void imap_state(struct Curl_easy *data, + struct imap_conn *imapc, + imapstate newstate) { - struct imap_conn *imapc = &data->conn->proto.imapc; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char * const names[]={ @@ -406,7 +496,7 @@ static void imap_state(struct Curl_easy *data, imapstate newstate) infof(data, "IMAP %p state change from %s to %s", (void *)imapc, names[imapc->state], names[newstate]); #endif - + (void)data; imapc->state = newstate; } @@ -418,19 +508,19 @@ static void imap_state(struct Curl_easy *data, imapstate newstate) * supported capabilities. */ static CURLcode imap_perform_capability(struct Curl_easy *data, - struct connectdata *conn) + struct imap_conn *imapc) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; + imapc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanisms yet */ imapc->sasl.authused = SASL_AUTH_NONE; /* Clear the auth. mechanism used */ imapc->tls_supported = FALSE; /* Clear the TLS capability */ /* Send the CAPABILITY command */ - result = imap_sendf(data, "CAPABILITY"); + result = imap_sendf(data, imapc, "CAPABILITY"); if(!result) - imap_state(data, IMAP_CAPABILITY); + imap_state(data, imapc, IMAP_CAPABILITY); return result; } @@ -441,13 +531,14 @@ static CURLcode imap_perform_capability(struct Curl_easy *data, * * Sends the STARTTLS command to start the upgrade to TLS. */ -static CURLcode imap_perform_starttls(struct Curl_easy *data) +static CURLcode imap_perform_starttls(struct Curl_easy *data, + struct imap_conn *imapc) { /* Send the STARTTLS command */ - CURLcode result = imap_sendf(data, "STARTTLS"); + CURLcode result = imap_sendf(data, imapc, "STARTTLS"); if(!result) - imap_state(data, IMAP_STARTTLS); + imap_state(data, imapc, IMAP_STARTTLS); return result; } @@ -459,11 +550,11 @@ static CURLcode imap_perform_starttls(struct Curl_easy *data) * Performs the upgrade to TLS. */ static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data, + struct imap_conn *imapc, struct connectdata *conn) { #ifdef USE_SSL /* Start the SSL connection */ - struct imap_conn *imapc = &conn->proto.imapc; CURLcode result; bool ssldone = FALSE; @@ -473,7 +564,6 @@ static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data, goto out; /* Change the connection handler */ conn->handler = &Curl_handler_imaps; - conn->bits.tls_upgraded = TRUE; } DEBUGASSERT(!imapc->ssldone); @@ -483,12 +573,13 @@ static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data, if(!result && ssldone) { imapc->ssldone = ssldone; /* perform CAPA now, changes imapc->state out of IMAP_UPGRADETLS */ - result = imap_perform_capability(data, conn); + result = imap_perform_capability(data, imapc); } out: return result; #else (void)data; + (void)imapc; (void)conn; return CURLE_NOT_BUILT_IN; #endif @@ -501,6 +592,7 @@ out: * Sends a clear text LOGIN command to authenticate with. */ static CURLcode imap_perform_login(struct Curl_easy *data, + struct imap_conn *imapc, struct connectdata *conn) { CURLcode result = CURLE_OK; @@ -510,7 +602,7 @@ static CURLcode imap_perform_login(struct Curl_easy *data, /* Check we have a username and password to authenticate with and end the connect phase if we do not */ if(!data->state.aptr.user) { - imap_state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return result; } @@ -520,14 +612,14 @@ static CURLcode imap_perform_login(struct Curl_easy *data, passwd = imap_atom(conn->passwd, FALSE); /* Send the LOGIN command */ - result = imap_sendf(data, "LOGIN %s %s", user ? user : "", + result = imap_sendf(data, imapc, "LOGIN %s %s", user ? user : "", passwd ? passwd : ""); free(user); free(passwd); if(!result) - imap_state(data, IMAP_LOGIN); + imap_state(data, imapc, IMAP_LOGIN); return result; } @@ -543,16 +635,20 @@ static CURLcode imap_perform_authenticate(struct Curl_easy *data, const char *mech, const struct bufref *initresp) { + struct imap_conn *imapc = + Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN); CURLcode result = CURLE_OK; const char *ir = (const char *) Curl_bufref_ptr(initresp); + if(!imapc) + return CURLE_FAILED_INIT; if(ir) { /* Send the AUTHENTICATE command with the initial response */ - result = imap_sendf(data, "AUTHENTICATE %s %s", mech, ir); + result = imap_sendf(data, imapc, "AUTHENTICATE %s %s", mech, ir); } else { /* Send the AUTHENTICATE command */ - result = imap_sendf(data, "AUTHENTICATE %s", mech); + result = imap_sendf(data, imapc, "AUTHENTICATE %s", mech); } return result; @@ -568,10 +664,12 @@ static CURLcode imap_continue_authenticate(struct Curl_easy *data, const char *mech, const struct bufref *resp) { - struct imap_conn *imapc = &data->conn->proto.imapc; + struct imap_conn *imapc = + Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN); (void)mech; - + if(!imapc) + return CURLE_FAILED_INIT; return Curl_pp_sendf(data, &imapc->pp, "%s", (const char *) Curl_bufref_ptr(resp)); } @@ -585,10 +683,12 @@ static CURLcode imap_continue_authenticate(struct Curl_easy *data, static CURLcode imap_cancel_authenticate(struct Curl_easy *data, const char *mech) { - struct imap_conn *imapc = &data->conn->proto.imapc; + struct imap_conn *imapc = + Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN); (void)mech; - + if(!imapc) + return CURLE_FAILED_INIT; return Curl_pp_sendf(data, &imapc->pp, "*"); } @@ -601,17 +701,16 @@ static CURLcode imap_cancel_authenticate(struct Curl_easy *data, * mechanism not be available between the client and server. */ static CURLcode imap_perform_authentication(struct Curl_easy *data, - struct connectdata *conn) + struct imap_conn *imapc) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; saslprogress progress; /* Check if already authenticated OR if there is enough data to authenticate 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); + imap_state(data, imapc, IMAP_STOP); return result; } @@ -620,15 +719,12 @@ static CURLcode imap_perform_authentication(struct Curl_easy *data, if(!result) { if(progress == SASL_INPROGRESS) - imap_state(data, IMAP_AUTHENTICATE); + imap_state(data, imapc, IMAP_AUTHENTICATE); else if(!imapc->login_disabled && (imapc->preftype & IMAP_TYPE_CLEARTEXT)) /* Perform clear text authentication */ - result = imap_perform_login(data, conn); - else { - /* Other mechanisms not supported */ - infof(data, "No known authentication mechanisms supported"); - result = CURLE_LOGIN_DENIED; - } + result = imap_perform_login(data, imapc, data->conn); + else + result = Curl_sasl_is_blocked(&imapc->sasl, data); } return result; @@ -640,14 +736,15 @@ static CURLcode imap_perform_authentication(struct Curl_easy *data, * * Sends a LIST command or an alternative custom request. */ -static CURLcode imap_perform_list(struct Curl_easy *data) +static CURLcode imap_perform_list(struct Curl_easy *data, + struct imap_conn *imapc, + struct IMAP *imap) { CURLcode result = CURLE_OK; - struct IMAP *imap = data->req.p.imap; if(imap->custom) /* Send the custom request */ - result = imap_sendf(data, "%s%s", imap->custom, + result = imap_sendf(data, imapc, "%s%s", imap->custom, imap->custom_params ? imap->custom_params : ""); else { /* Make sure the mailbox is in the correct atom format if necessary */ @@ -657,13 +754,13 @@ static CURLcode imap_perform_list(struct Curl_easy *data) return CURLE_OUT_OF_MEMORY; /* Send the LIST command */ - result = imap_sendf(data, "LIST \"%s\" *", mailbox); + result = imap_sendf(data, imapc, "LIST \"%s\" *", mailbox); free(mailbox); } if(!result) - imap_state(data, IMAP_LIST); + imap_state(data, imapc, IMAP_LIST); return result; } @@ -674,12 +771,11 @@ static CURLcode imap_perform_list(struct Curl_easy *data) * * Sends a SELECT command to ask the server to change the selected mailbox. */ -static CURLcode imap_perform_select(struct Curl_easy *data) +static CURLcode imap_perform_select(struct Curl_easy *data, + struct imap_conn *imapc, + struct IMAP *imap) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct IMAP *imap = data->req.p.imap; - struct imap_conn *imapc = &conn->proto.imapc; char *mailbox; /* Invalidate old information as we are switching mailboxes */ @@ -698,12 +794,12 @@ static CURLcode imap_perform_select(struct Curl_easy *data) return CURLE_OUT_OF_MEMORY; /* Send the SELECT command */ - result = imap_sendf(data, "SELECT %s", mailbox); + result = imap_sendf(data, imapc, "SELECT %s", mailbox); free(mailbox); if(!result) - imap_state(data, IMAP_SELECT); + imap_state(data, imapc, IMAP_SELECT); return result; } @@ -714,30 +810,31 @@ static CURLcode imap_perform_select(struct Curl_easy *data) * * Sends a FETCH command to initiate the download of a message. */ -static CURLcode imap_perform_fetch(struct Curl_easy *data) +static CURLcode imap_perform_fetch(struct Curl_easy *data, + struct imap_conn *imapc, + struct IMAP *imap) { CURLcode result = CURLE_OK; - struct IMAP *imap = data->req.p.imap; /* Check we have a UID */ if(imap->uid) { /* Send the FETCH command */ if(imap->partial) - result = imap_sendf(data, "UID FETCH %s BODY[%s]<%s>", + result = imap_sendf(data, imapc, "UID FETCH %s BODY[%s]<%s>", imap->uid, imap->section ? imap->section : "", imap->partial); else - result = imap_sendf(data, "UID FETCH %s BODY[%s]", + result = imap_sendf(data, imapc, "UID FETCH %s BODY[%s]", imap->uid, imap->section ? imap->section : ""); } else if(imap->mindex) { /* Send the FETCH command */ if(imap->partial) - result = imap_sendf(data, "FETCH %s BODY[%s]<%s>", + result = imap_sendf(data, imapc, "FETCH %s BODY[%s]<%s>", imap->mindex, imap->section ? imap->section : "", imap->partial); else - result = imap_sendf(data, "FETCH %s BODY[%s]", + result = imap_sendf(data, imapc, "FETCH %s BODY[%s]", imap->mindex, imap->section ? imap->section : ""); } else { @@ -745,7 +842,7 @@ static CURLcode imap_perform_fetch(struct Curl_easy *data) return CURLE_URL_MALFORMAT; } if(!result) - imap_state(data, IMAP_FETCH); + imap_state(data, imapc, IMAP_FETCH); return result; } @@ -756,11 +853,13 @@ static CURLcode imap_perform_fetch(struct Curl_easy *data) * * Sends an APPEND command to initiate the upload of a message. */ -static CURLcode imap_perform_append(struct Curl_easy *data) +static CURLcode imap_perform_append(struct Curl_easy *data, + struct imap_conn *imapc, + struct IMAP *imap) { CURLcode result = CURLE_OK; - struct IMAP *imap = data->req.p.imap; char *mailbox; + struct dynbuf flags; /* Check we have a mailbox */ if(!imap->mailbox) { @@ -809,14 +908,48 @@ static CURLcode imap_perform_append(struct Curl_easy *data) if(!mailbox) return CURLE_OUT_OF_MEMORY; - /* Send the APPEND command */ - result = imap_sendf(data, "APPEND %s (\\Seen) {%" FMT_OFF_T "}", - mailbox, data->state.infilesize); + /* Generate flags string and send the APPEND command */ + curlx_dyn_init(&flags, 100); + if(data->set.upload_flags) { + int i; + struct ulbits ulflag[] = { + {CURLULFLAG_ANSWERED, "Answered"}, + {CURLULFLAG_DELETED, "Deleted"}, + {CURLULFLAG_DRAFT, "Draft"}, + {CURLULFLAG_FLAGGED, "Flagged"}, + {CURLULFLAG_SEEN, "Seen"}, + {0, NULL} + }; + result = CURLE_OUT_OF_MEMORY; + if(curlx_dyn_add(&flags, " (")) { + goto cleanup; + } + + for(i = 0; ulflag[i].bit; i++) { + if(data->set.upload_flags & ulflag[i].bit) { + if((curlx_dyn_len(&flags) > 2 && curlx_dyn_add(&flags, " ")) || + curlx_dyn_add(&flags, "\\") || + curlx_dyn_add(&flags, ulflag[i].flag)) + goto cleanup; + } + } + + if(curlx_dyn_add(&flags, ")")) + goto cleanup; + } + else if(curlx_dyn_add(&flags, "")) + goto cleanup; + + result = imap_sendf(data, imapc, "APPEND %s%s {%" FMT_OFF_T "}", + mailbox, curlx_dyn_ptr(&flags), data->state.infilesize); + +cleanup: + curlx_dyn_free(&flags); free(mailbox); if(!result) - imap_state(data, IMAP_APPEND); + imap_state(data, imapc, IMAP_APPEND); return result; } @@ -827,10 +960,11 @@ static CURLcode imap_perform_append(struct Curl_easy *data) * * Sends a SEARCH command. */ -static CURLcode imap_perform_search(struct Curl_easy *data) +static CURLcode imap_perform_search(struct Curl_easy *data, + struct imap_conn *imapc, + struct IMAP *imap) { CURLcode result = CURLE_OK; - struct IMAP *imap = data->req.p.imap; /* Check we have a query string */ if(!imap->query) { @@ -839,10 +973,10 @@ static CURLcode imap_perform_search(struct Curl_easy *data) } /* Send the SEARCH command */ - result = imap_sendf(data, "SEARCH %s", imap->query); + result = imap_sendf(data, imapc, "SEARCH %s", imap->query); if(!result) - imap_state(data, IMAP_SEARCH); + imap_state(data, imapc, IMAP_SEARCH); return result; } @@ -853,28 +987,28 @@ static CURLcode imap_perform_search(struct Curl_easy *data) * * Performs the logout action prior to sclose() being called. */ -static CURLcode imap_perform_logout(struct Curl_easy *data) +static CURLcode imap_perform_logout(struct Curl_easy *data, + struct imap_conn *imapc) { /* Send the LOGOUT command */ - CURLcode result = imap_sendf(data, "LOGOUT"); + CURLcode result = imap_sendf(data, imapc, "LOGOUT"); if(!result) - imap_state(data, IMAP_LOGOUT); + imap_state(data, imapc, IMAP_LOGOUT); return result; } /* For the initial server greeting */ static CURLcode imap_state_servergreet_resp(struct Curl_easy *data, + struct imap_conn *imapc, int imapcode, imapstate instate) { - struct connectdata *conn = data->conn; (void)instate; /* no use for this yet */ if(imapcode == IMAP_RESP_PREAUTH) { /* PREAUTH */ - struct imap_conn *imapc = &conn->proto.imapc; imapc->preauth = TRUE; infof(data, "PREAUTH connection, already authenticated"); } @@ -883,22 +1017,22 @@ static CURLcode imap_state_servergreet_resp(struct Curl_easy *data, return CURLE_WEIRD_SERVER_REPLY; } - return imap_perform_capability(data, conn); + return imap_perform_capability(data, imapc); } /* For CAPABILITY responses */ static CURLcode imap_state_capability_resp(struct Curl_easy *data, + struct imap_conn *imapc, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct imap_conn *imapc = &conn->proto.imapc; - const char *line = Curl_dyn_ptr(&imapc->pp.recvbuf); + const char *line = curlx_dyn_ptr(&imapc->pp.recvbuf); (void)instate; /* no use for this yet */ - /* Do we have a untagged response? */ + /* Do we have an untagged response? */ if(imapcode == '*') { line += 2; @@ -954,33 +1088,33 @@ static CURLcode imap_state_capability_resp(struct Curl_easy *data, /* PREAUTH is not compatible with STARTTLS. */ if(imapcode == IMAP_RESP_OK && imapc->tls_supported && !imapc->preauth) { /* Switch to TLS connection now */ - result = imap_perform_starttls(data); + result = imap_perform_starttls(data, imapc); } else if(data->set.use_ssl <= CURLUSESSL_TRY) - result = imap_perform_authentication(data, conn); + result = imap_perform_authentication(data, imapc); else { failf(data, "STARTTLS not available."); result = CURLE_USE_SSL_FAILED; } } else - result = imap_perform_authentication(data, conn); + result = imap_perform_authentication(data, imapc); return result; } /* For STARTTLS responses */ static CURLcode imap_state_starttls_resp(struct Curl_easy *data, + struct imap_conn *imapc, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; (void)instate; /* no use for this yet */ /* Pipelining in response is forbidden. */ - if(data->conn->proto.imapc.pp.overflow) + if(imapc->pp.overflow) return CURLE_WEIRD_SERVER_REPLY; if(imapcode != IMAP_RESP_OK) { @@ -989,22 +1123,21 @@ static CURLcode imap_state_starttls_resp(struct Curl_easy *data, result = CURLE_USE_SSL_FAILED; } else - result = imap_perform_authentication(data, conn); + result = imap_perform_authentication(data, imapc); } else - imap_state(data, IMAP_UPGRADETLS); + imap_state(data, imapc, IMAP_UPGRADETLS); return result; } /* For SASL authentication responses */ static CURLcode imap_state_auth_resp(struct Curl_easy *data, - struct connectdata *conn, + struct imap_conn *imapc, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; saslprogress progress; (void)instate; /* no use for this yet */ @@ -1013,12 +1146,12 @@ static CURLcode imap_state_auth_resp(struct Curl_easy *data, if(!result) switch(progress) { case SASL_DONE: - imap_state(data, IMAP_STOP); /* Authenticated */ + imap_state(data, imapc, IMAP_STOP); /* Authenticated */ break; case SASL_IDLE: /* No mechanism left after cancellation */ if((!imapc->login_disabled) && (imapc->preftype & IMAP_TYPE_CLEARTEXT)) /* Perform clear text authentication */ - result = imap_perform_login(data, conn); + result = imap_perform_login(data, imapc, data->conn); else { failf(data, "Authentication cancelled"); result = CURLE_LOGIN_DENIED; @@ -1033,6 +1166,7 @@ static CURLcode imap_state_auth_resp(struct Curl_easy *data, /* For LOGIN responses */ static CURLcode imap_state_login_resp(struct Curl_easy *data, + struct imap_conn *imapc, int imapcode, imapstate instate) { @@ -1045,19 +1179,20 @@ static CURLcode imap_state_login_resp(struct Curl_easy *data, } else /* End of connect phase */ - imap_state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return result; } /* For LIST and SEARCH responses */ static CURLcode imap_state_listsearch_resp(struct Curl_easy *data, + struct imap_conn *imapc, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - char *line = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); - size_t len = data->conn->proto.imapc.pp.nfinal; + char *line = curlx_dyn_ptr(&imapc->pp.recvbuf); + size_t len = imapc->pp.nfinal; (void)instate; /* No use for this yet */ @@ -1067,20 +1202,20 @@ static CURLcode imap_state_listsearch_resp(struct Curl_easy *data, result = CURLE_QUOTE_ERROR; else /* End of DO phase */ - imap_state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return result; } /* For SELECT responses */ -static CURLcode imap_state_select_resp(struct Curl_easy *data, int imapcode, +static CURLcode imap_state_select_resp(struct Curl_easy *data, + struct imap_conn *imapc, + struct IMAP *imap, + int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct IMAP *imap = data->req.p.imap; - struct imap_conn *imapc = &conn->proto.imapc; - const char *line = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); + const char *line = curlx_dyn_ptr(&imapc->pp.recvbuf); (void)instate; /* no use for this yet */ @@ -1093,11 +1228,11 @@ static CURLcode imap_state_select_resp(struct Curl_easy *data, int imapcode, len++; if(len && (p[len] == ']')) { struct dynbuf uid; - Curl_dyn_init(&uid, 20); - if(Curl_dyn_addn(&uid, p, len)) + curlx_dyn_init(&uid, 20); + if(curlx_dyn_addn(&uid, p, len)) return CURLE_OUT_OF_MEMORY; - Curl_safefree(imapc->mailbox_uidvalidity); - imapc->mailbox_uidvalidity = Curl_dyn_ptr(&uid); + free(imapc->mailbox_uidvalidity); + imapc->mailbox_uidvalidity = curlx_dyn_ptr(&uid); } } } @@ -1116,11 +1251,11 @@ static CURLcode imap_state_select_resp(struct Curl_easy *data, int imapcode, return CURLE_OUT_OF_MEMORY; if(imap->custom) - result = imap_perform_list(data); + result = imap_perform_list(data, imapc, imap); else if(imap->query) - result = imap_perform_search(data); + result = imap_perform_search(data, imapc, imap); else - result = imap_perform_fetch(data); + result = imap_perform_fetch(data, imapc, imap); } } else { @@ -1133,14 +1268,14 @@ static CURLcode imap_state_select_resp(struct Curl_easy *data, int imapcode, /* For the (first line of the) FETCH responses */ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, - struct connectdata *conn, int imapcode, + struct imap_conn *imapc, + int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; struct pingpong *pp = &imapc->pp; - const char *ptr = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); - size_t len = data->conn->proto.imapc.pp.nfinal; + const char *ptr = curlx_dyn_ptr(&imapc->pp.recvbuf); + size_t len = imapc->pp.nfinal; bool parsed = FALSE; curl_off_t size = 0; @@ -1148,7 +1283,7 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, if(imapcode != '*') { Curl_pgrsSetDownloadSize(data, -1); - imap_state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return CURLE_REMOTE_FILE_NOT_FOUND; } @@ -1156,9 +1291,9 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, the continuation data contained within the curly brackets */ ptr = memchr(ptr, '{', len); if(ptr) { - char *endptr; - if(!curlx_strtoofft(ptr + 1, &endptr, 10, &size) && - (endptr - ptr > 1 && *endptr == '}')) + ptr++; + if(!curlx_str_number(&ptr, &size, CURL_OFF_T_MAX) && + !curlx_str_single(&ptr, '}')) parsed = TRUE; } @@ -1173,7 +1308,7 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, size_t chunk = pp->overflow; /* keep only the overflow */ - Curl_dyn_tail(&pp->recvbuf, chunk); + curlx_dyn_tail(&pp->recvbuf, chunk); pp->nfinal = 0; /* done */ if(chunk > (size_t)size) @@ -1182,11 +1317,11 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, if(!chunk) { /* no size, we are done with the data */ - imap_state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return CURLE_OK; } result = Curl_client_write(data, CLIENTWRITE_BODY, - Curl_dyn_ptr(&pp->recvbuf), chunk); + curlx_dyn_ptr(&pp->recvbuf), chunk); if(result) return result; @@ -1197,12 +1332,12 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, if(pp->overflow > chunk) { /* remember the remaining trailing overflow data */ pp->overflow -= chunk; - Curl_dyn_tail(&pp->recvbuf, pp->overflow); + curlx_dyn_tail(&pp->recvbuf, pp->overflow); } else { pp->overflow = 0; /* handled */ /* Free the cache */ - Curl_dyn_reset(&pp->recvbuf); + curlx_dyn_reset(&pp->recvbuf); } } @@ -1225,13 +1360,14 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, } /* End of DO phase */ - imap_state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return result; } /* For final FETCH responses performed after the download */ static CURLcode imap_state_fetch_final_resp(struct Curl_easy *data, + struct imap_conn *imapc, int imapcode, imapstate instate) { @@ -1243,13 +1379,15 @@ static CURLcode imap_state_fetch_final_resp(struct Curl_easy *data, result = CURLE_WEIRD_SERVER_REPLY; else /* End of DONE phase */ - imap_state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return result; } /* For APPEND responses */ -static CURLcode imap_state_append_resp(struct Curl_easy *data, int imapcode, +static CURLcode imap_state_append_resp(struct Curl_easy *data, + struct imap_conn *imapc, + int imapcode, imapstate instate) { CURLcode result = CURLE_OK; @@ -1266,7 +1404,7 @@ static CURLcode imap_state_append_resp(struct Curl_easy *data, int imapcode, Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); /* End of DO phase */ - imap_state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); } return result; @@ -1274,6 +1412,7 @@ static CURLcode imap_state_append_resp(struct Curl_easy *data, int imapcode, /* For final APPEND responses performed after the upload */ static CURLcode imap_state_append_final_resp(struct Curl_easy *data, + struct imap_conn *imapc, int imapcode, imapstate instate) { @@ -1285,25 +1424,29 @@ static CURLcode imap_state_append_final_resp(struct Curl_easy *data, result = CURLE_UPLOAD_FAILED; else /* End of DONE phase */ - imap_state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); return result; } -static CURLcode imap_statemachine(struct Curl_easy *data, - struct connectdata *conn) +static CURLcode imap_pp_statemachine(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result = CURLE_OK; int imapcode; - struct imap_conn *imapc = &conn->proto.imapc; - struct pingpong *pp = &imapc->pp; + struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); + struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); + struct pingpong *pp; size_t nread = 0; - (void)data; + (void)data; + if(!imapc || !imap) + return CURLE_FAILED_INIT; + pp = &imapc->pp; /* Busy upgrading the connection; right now all I/O is SSL/TLS, not IMAP */ upgrade_tls: if(imapc->state == IMAP_UPGRADETLS) { - result = imap_perform_upgrade_tls(data, conn); + result = imap_perform_upgrade_tls(data, imapc, conn); if(result || (imapc->state == IMAP_UPGRADETLS)) return result; } @@ -1328,15 +1471,16 @@ upgrade_tls: /* We have now received a full IMAP server response */ switch(imapc->state) { case IMAP_SERVERGREET: - result = imap_state_servergreet_resp(data, imapcode, imapc->state); + result = imap_state_servergreet_resp(data, imapc, + imapcode, imapc->state); break; case IMAP_CAPABILITY: - result = imap_state_capability_resp(data, imapcode, imapc->state); + result = imap_state_capability_resp(data, imapc, imapcode, imapc->state); break; case IMAP_STARTTLS: - result = imap_state_starttls_resp(data, imapcode, imapc->state); + result = imap_state_starttls_resp(data, imapc, imapcode, imapc->state); /* During UPGRADETLS, leave the read loop as we need to connect * (e.g. TLS handshake) before we continue sending/receiving. */ if(!result && (imapc->state == IMAP_UPGRADETLS)) @@ -1344,42 +1488,45 @@ upgrade_tls: break; case IMAP_AUTHENTICATE: - result = imap_state_auth_resp(data, conn, imapcode, imapc->state); + result = imap_state_auth_resp(data, imapc, imapcode, imapc->state); break; case IMAP_LOGIN: - result = imap_state_login_resp(data, imapcode, imapc->state); + result = imap_state_login_resp(data, imapc, imapcode, imapc->state); break; case IMAP_LIST: case IMAP_SEARCH: - result = imap_state_listsearch_resp(data, imapcode, imapc->state); + result = imap_state_listsearch_resp(data, imapc, imapcode, imapc->state); break; case IMAP_SELECT: - result = imap_state_select_resp(data, imapcode, imapc->state); + result = imap_state_select_resp(data, imapc, imap, + imapcode, imapc->state); break; case IMAP_FETCH: - result = imap_state_fetch_resp(data, conn, imapcode, imapc->state); + result = imap_state_fetch_resp(data, imapc, imapcode, imapc->state); break; case IMAP_FETCH_FINAL: - result = imap_state_fetch_final_resp(data, imapcode, imapc->state); + result = imap_state_fetch_final_resp(data, imapc, + imapcode, imapc->state); break; case IMAP_APPEND: - result = imap_state_append_resp(data, imapcode, imapc->state); + result = imap_state_append_resp(data, imapc, imapcode, imapc->state); break; case IMAP_APPEND_FINAL: - result = imap_state_append_final_resp(data, imapcode, imapc->state); + result = imap_state_append_final_resp(data, imapc, + imapcode, imapc->state); break; case IMAP_LOGOUT: default: /* internal error */ - imap_state(data, IMAP_STOP); + imap_state(data, imapc, IMAP_STOP); break; } } while(!result && imapc->state != IMAP_STOP && Curl_pp_moredata(pp)); @@ -1391,9 +1538,12 @@ upgrade_tls: static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct imap_conn *imapc = &conn->proto.imapc; + struct imap_conn *imapc = + Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN); + *done = FALSE; + if(!imapc) + return CURLE_FAILED_INIT; result = Curl_pp_statemach(data, &imapc->pp, FALSE, FALSE); *done = (imapc->state == IMAP_STOP); @@ -1401,11 +1551,10 @@ static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done) } static CURLcode imap_block_statemach(struct Curl_easy *data, - struct connectdata *conn, + struct imap_conn *imapc, bool disconnecting) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; while(imapc->state != IMAP_STOP && !result) result = Curl_pp_statemach(data, &imapc->pp, TRUE, disconnecting); @@ -1413,26 +1562,14 @@ static CURLcode imap_block_statemach(struct Curl_easy *data, return result; } -/* Allocate and initialize the struct IMAP for the current Curl_easy if - required */ -static CURLcode imap_init(struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct IMAP *imap; - - imap = data->req.p.imap = calloc(1, sizeof(struct IMAP)); - if(!imap) - result = CURLE_OUT_OF_MEMORY; - - return result; -} - /* For the IMAP "protocol connect" and "doing" phases only */ static int imap_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks) { - return Curl_pp_getsock(data, &conn->proto.imapc.pp, socks); + struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); + return imapc ? + Curl_pp_getsock(data, &imapc->pp, socks) : GETSOCK_BLANK; } /*********************************************************************** @@ -1447,32 +1584,24 @@ static int imap_getsock(struct Curl_easy *data, */ static CURLcode imap_connect(struct Curl_easy *data, bool *done) { + struct imap_conn *imapc = + Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN); CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct imap_conn *imapc = &conn->proto.imapc; - struct pingpong *pp = &imapc->pp; *done = FALSE; /* default to not done yet */ + if(!imapc) + return CURLE_FAILED_INIT; /* We always support persistent connections in IMAP */ - connkeep(conn, "IMAP default"); - - PINGPONG_SETUP(pp, imap_statemachine, imap_endofresp); - - /* Set the default preferred authentication type and mechanism */ - imapc->preftype = IMAP_TYPE_ANY; - Curl_sasl_init(&imapc->sasl, data, &saslimap); - - Curl_dyn_init(&imapc->dyn, DYN_IMAP_CMD); - Curl_pp_init(pp); + connkeep(data->conn, "IMAP default"); /* Parse the URL options */ - result = imap_parse_url_options(conn); + result = imap_parse_url_options(data->conn, imapc); if(result) return result; /* Start off waiting for the server greeting response */ - imap_state(data, IMAP_SERVERGREET); + imap_state(data, imapc, IMAP_SERVERGREET); /* Start off with an response id of '*' */ strcpy(imapc->resptag, "*"); @@ -1496,10 +1625,13 @@ static CURLcode imap_done(struct Curl_easy *data, CURLcode status, { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct IMAP *imap = data->req.p.imap; + struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); + struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); (void)premature; + if(!imapc) + return CURLE_FAILED_INIT; if(!imap) return CURLE_OK; @@ -1513,33 +1645,20 @@ static CURLcode imap_done(struct Curl_easy *data, CURLcode status, /* Handle responses after FETCH or APPEND transfer has finished */ if(!data->state.upload && !IS_MIME_POST(data)) - imap_state(data, IMAP_FETCH_FINAL); + imap_state(data, imapc, IMAP_FETCH_FINAL); else { /* End the APPEND command first by sending an empty line */ - result = Curl_pp_sendf(data, &conn->proto.imapc.pp, "%s", ""); + result = Curl_pp_sendf(data, &imapc->pp, "%s", ""); if(!result) - imap_state(data, IMAP_APPEND_FINAL); + imap_state(data, imapc, IMAP_APPEND_FINAL); } /* Run the state-machine */ if(!result) - result = imap_block_statemach(data, conn, FALSE); + result = imap_block_statemach(data, imapc, FALSE); } - /* Cleanup our per-request based variables */ - Curl_safefree(imap->mailbox); - Curl_safefree(imap->uidvalidity); - Curl_safefree(imap->uid); - Curl_safefree(imap->mindex); - Curl_safefree(imap->section); - Curl_safefree(imap->partial); - Curl_safefree(imap->query); - Curl_safefree(imap->custom); - Curl_safefree(imap->custom_params); - - /* Clear the transfer mode for the next request */ - imap->transfer = PPTRANSFER_BODY; - + imap_easy_reset(imap); return result; } @@ -1556,11 +1675,13 @@ static CURLcode imap_perform(struct Curl_easy *data, bool *connected, /* This is IMAP and no proxy */ CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct IMAP *imap = data->req.p.imap; - struct imap_conn *imapc = &conn->proto.imapc; + struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); + struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); bool selected = FALSE; DEBUGF(infof(data, "DO phase starts")); + if(!imapc || !imap) + return CURLE_FAILED_INIT; if(data->req.no_body) { /* Requested no body means no transfer */ @@ -1580,23 +1701,23 @@ static CURLcode imap_perform(struct Curl_easy *data, bool *connected, /* Start the first command in the DO phase */ if(data->state.upload || IS_MIME_POST(data)) /* APPEND can be executed directly */ - result = imap_perform_append(data); + result = imap_perform_append(data, imapc, imap); else if(imap->custom && (selected || !imap->mailbox)) /* Custom command using the same mailbox or no mailbox */ - result = imap_perform_list(data); + result = imap_perform_list(data, imapc, imap); else if(!imap->custom && selected && (imap->uid || imap->mindex)) /* FETCH from the same mailbox */ - result = imap_perform_fetch(data); + result = imap_perform_fetch(data, imapc, imap); else if(!imap->custom && selected && imap->query) /* SEARCH the current mailbox */ - result = imap_perform_search(data); + result = imap_perform_search(data, imapc, imap); else if(imap->mailbox && !selected && (imap->custom || imap->uid || imap->mindex || imap->query)) /* SELECT the mailbox */ - result = imap_perform_select(data); + result = imap_perform_select(data, imapc, imap); else /* LIST */ - result = imap_perform_list(data); + result = imap_perform_list(data, imapc, imap); if(result) return result; @@ -1623,20 +1744,23 @@ static CURLcode imap_perform(struct Curl_easy *data, bool *connected, */ static CURLcode imap_do(struct Curl_easy *data, bool *done) { + struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); CURLcode result = CURLE_OK; *done = FALSE; /* default to false */ + if(!imap) + return CURLE_FAILED_INIT; /* Parse the URL path */ - result = imap_parse_url_path(data); + result = imap_parse_url_path(data, imap); if(result) return result; /* Parse the custom request */ - result = imap_parse_custom_request(data); + result = imap_parse_custom_request(data, imap); if(result) return result; - result = imap_regular_transfer(data, done); + result = imap_regular_transfer(data, imap, done); return result; } @@ -1651,39 +1775,32 @@ static CURLcode imap_do(struct Curl_easy *data, bool *done) static CURLcode imap_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { - struct imap_conn *imapc = &conn->proto.imapc; + struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); + (void)data; + if(imapc) { + /* We cannot send quit unconditionally. If this connection is stale or + 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. */ - /* We cannot send quit unconditionally. If this connection is stale or - 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. */ + /* The IMAP session may or may not have been allocated/setup at this + point! */ + if(!dead_connection && conn->bits.protoconnstart) { + if(!imap_perform_logout(data, imapc)) + (void)imap_block_statemach(data, imapc, TRUE); /* ignore errors */ + } - /* The IMAP session may or may not have been allocated/setup at this - point! */ - if(!dead_connection && conn->bits.protoconnstart) { - if(!imap_perform_logout(data)) - (void)imap_block_statemach(data, conn, TRUE); /* ignore errors */ + /* Cleanup the SASL module */ + Curl_sasl_cleanup(conn, imapc->sasl.authused); } - - /* Disconnect from the server */ - Curl_pp_disconnect(&imapc->pp); - Curl_dyn_free(&imapc->dyn); - - /* Cleanup the SASL module */ - Curl_sasl_cleanup(conn, imapc->sasl.authused); - - /* Cleanup our connection based variables */ - Curl_safefree(imapc->mailbox); - Curl_safefree(imapc->mailbox_uidvalidity); - return CURLE_OK; } /* Call this when the DO phase has completed */ -static CURLcode imap_dophase_done(struct Curl_easy *data, bool connected) +static CURLcode imap_dophase_done(struct Curl_easy *data, + struct IMAP *imap, + bool connected) { - struct IMAP *imap = data->req.p.imap; - (void)connected; if(imap->transfer != PPTRANSFER_BODY) @@ -1696,12 +1813,17 @@ static CURLcode imap_dophase_done(struct Curl_easy *data, bool connected) /* Called from multi.c while DOing */ static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done) { - CURLcode result = imap_multi_statemach(data, dophase_done); + struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); + CURLcode result; + if(!imap) + return CURLE_FAILED_INIT; + + result = imap_multi_statemach(data, dophase_done); if(result) DEBUGF(infof(data, "DO phase failed")); else if(*dophase_done) { - result = imap_dophase_done(data, FALSE /* not connected */); + result = imap_dophase_done(data, imap, FALSE /* not connected */); DEBUGF(infof(data, "DO phase is complete")); } @@ -1719,6 +1841,7 @@ static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done) * remote host. */ static CURLcode imap_regular_transfer(struct Curl_easy *data, + struct IMAP *imap, bool *dophase_done) { CURLcode result = CURLE_OK; @@ -1738,21 +1861,75 @@ static CURLcode imap_regular_transfer(struct Curl_easy *data, /* Perform post DO phase operations if necessary */ if(!result && *dophase_done) - result = imap_dophase_done(data, connected); + result = imap_dophase_done(data, imap, connected); return result; } +static void imap_easy_reset(struct IMAP *imap) +{ + Curl_safefree(imap->mailbox); + Curl_safefree(imap->uidvalidity); + Curl_safefree(imap->uid); + Curl_safefree(imap->mindex); + Curl_safefree(imap->section); + Curl_safefree(imap->partial); + Curl_safefree(imap->query); + Curl_safefree(imap->custom); + Curl_safefree(imap->custom_params); + /* Clear the transfer mode for the next request */ + imap->transfer = PPTRANSFER_BODY; +} + +static void imap_easy_dtor(void *key, size_t klen, void *entry) +{ + struct IMAP *imap = entry; + (void)key; + (void)klen; + imap_easy_reset(imap); + free(imap); +} + +static void imap_conn_dtor(void *key, size_t klen, void *entry) +{ + struct imap_conn *imapc = entry; + (void)key; + (void)klen; + Curl_pp_disconnect(&imapc->pp); + curlx_dyn_free(&imapc->dyn); + Curl_safefree(imapc->mailbox); + Curl_safefree(imapc->mailbox_uidvalidity); + free(imapc); +} + static CURLcode imap_setup_connection(struct Curl_easy *data, struct connectdata *conn) { - /* Initialise the IMAP layer */ - CURLcode result = imap_init(data); - if(result) - return result; + struct imap_conn *imapc; + struct pingpong *pp; + struct IMAP *imap; - /* Clear the TLS upgraded flag */ - conn->bits.tls_upgraded = FALSE; + imapc = calloc(1, sizeof(*imapc)); + if(!imapc) + return CURLE_OUT_OF_MEMORY; + + pp = &imapc->pp; + PINGPONG_SETUP(pp, imap_pp_statemachine, imap_endofresp); + + /* Set the default preferred authentication type and mechanism */ + imapc->preftype = IMAP_TYPE_ANY; + Curl_sasl_init(&imapc->sasl, data, &saslimap); + + curlx_dyn_init(&imapc->dyn, DYN_IMAP_CMD); + Curl_pp_init(pp); + + if(Curl_conn_meta_set(conn, CURL_META_IMAP_CONN, imapc, imap_conn_dtor)) + return CURLE_OUT_OF_MEMORY; + + imap = calloc(1, sizeof(struct IMAP)); + if(!imap || + Curl_meta_set(data, CURL_META_IMAP_EASY, imap, imap_easy_dtor)) + return CURLE_OUT_OF_MEMORY; return CURLE_OK; } @@ -1765,10 +1942,11 @@ static CURLcode imap_setup_connection(struct Curl_easy *data, * * Designed to never block. */ -static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...) +static CURLcode imap_sendf(struct Curl_easy *data, + struct imap_conn *imapc, + const char *fmt, ...) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &data->conn->proto.imapc; DEBUGASSERT(fmt); @@ -1778,10 +1956,10 @@ static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...) ++imapc->cmdid); /* start with a blank buffer */ - Curl_dyn_reset(&imapc->dyn); + curlx_dyn_reset(&imapc->dyn); /* append tag + space + fmt */ - result = Curl_dyn_addf(&imapc->dyn, "%s %s", imapc->resptag, fmt); + result = curlx_dyn_addf(&imapc->dyn, "%s %s", imapc->resptag, fmt); if(!result) { va_list ap; va_start(ap, fmt); @@ -1789,7 +1967,7 @@ static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" #endif - result = Curl_pp_vsendf(data, &imapc->pp, Curl_dyn_ptr(&imapc->dyn), ap); + result = Curl_pp_vsendf(data, &imapc->pp, curlx_dyn_ptr(&imapc->dyn), ap); #ifdef __clang__ #pragma clang diagnostic pop #endif @@ -1823,24 +2001,24 @@ static char *imap_atom(const char *str, bool escape_only) /* nothing to escape, return a strdup */ return strdup(str); - Curl_dyn_init(&line, 2000); + curlx_dyn_init(&line, 2000); - if(!escape_only && Curl_dyn_addn(&line, "\"", 1)) + if(!escape_only && curlx_dyn_addn(&line, "\"", 1)) return NULL; while(*str) { if((*str == '\\' || *str == '"') && - Curl_dyn_addn(&line, "\\", 1)) + curlx_dyn_addn(&line, "\\", 1)) return NULL; - if(Curl_dyn_addn(&line, str, 1)) + if(curlx_dyn_addn(&line, str, 1)) return NULL; str++; } - if(!escape_only && Curl_dyn_addn(&line, "\"", 1)) + if(!escape_only && curlx_dyn_addn(&line, "\"", 1)) return NULL; - return Curl_dyn_ptr(&line); + return curlx_dyn_ptr(&line); } /*********************************************************************** @@ -1882,10 +2060,10 @@ static bool imap_is_bchar(char ch) * * Parse the URL login options. */ -static CURLcode imap_parse_url_options(struct connectdata *conn) +static CURLcode imap_parse_url_options(struct connectdata *conn, + struct imap_conn *imapc) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; const char *ptr = conn->options; bool prefer_login = FALSE; @@ -1946,11 +2124,11 @@ static CURLcode imap_parse_url_options(struct connectdata *conn) * Parse the URL path into separate path components. * */ -static CURLcode imap_parse_url_path(struct Curl_easy *data) +static CURLcode imap_parse_url_path(struct Curl_easy *data, + struct IMAP *imap) { /* The imap struct is already initialised in imap_connect() */ CURLcode result = CURLE_OK; - struct IMAP *imap = data->req.p.imap; const char *begin = &data->state.up.path[1]; /* skip leading slash */ const char *ptr = begin; @@ -2078,10 +2256,10 @@ static CURLcode imap_parse_url_path(struct Curl_easy *data) * * Parse the custom request. */ -static CURLcode imap_parse_custom_request(struct Curl_easy *data) +static CURLcode imap_parse_custom_request(struct Curl_easy *data, + struct IMAP *imap) { CURLcode result = CURLE_OK; - struct IMAP *imap = data->req.p.imap; const char *custom = data->set.str[STRING_CUSTOMREQUEST]; if(custom) { diff --git a/Utilities/cmcurl/lib/imap.h b/Utilities/cmcurl/lib/imap.h index 784ee97e55..f802ed5c0c 100644 --- a/Utilities/cmcurl/lib/imap.h +++ b/Utilities/cmcurl/lib/imap.h @@ -27,65 +27,6 @@ #include "pingpong.h" #include "curl_sasl.h" -/**************************************************************************** - * IMAP unique setup - ***************************************************************************/ -typedef enum { - IMAP_STOP, /* do nothing state, stops the state machine */ - IMAP_SERVERGREET, /* waiting for the initial greeting immediately after - a connect */ - IMAP_CAPABILITY, - IMAP_STARTTLS, - IMAP_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS - (multi mode only) */ - IMAP_AUTHENTICATE, - IMAP_LOGIN, - IMAP_LIST, - IMAP_SELECT, - IMAP_FETCH, - IMAP_FETCH_FINAL, - IMAP_APPEND, - IMAP_APPEND_FINAL, - IMAP_SEARCH, - IMAP_LOGOUT, - IMAP_LAST /* never used */ -} imapstate; - -/* This IMAP struct is used in the Curl_easy. All IMAP data that is - connection-oriented must be in imap_conn to properly deal with the fact that - perhaps the Curl_easy is changed between the times the connection is - used. */ -struct IMAP { - curl_pp_transfer transfer; - char *mailbox; /* Mailbox to select */ - char *uidvalidity; /* UIDVALIDITY to check in select */ - char *uid; /* Message UID to fetch */ - char *mindex; /* Index in mail box of mail to fetch */ - char *section; /* Message SECTION to fetch */ - char *partial; /* Message PARTIAL to fetch */ - char *query; /* Query to search for */ - char *custom; /* Custom request */ - char *custom_params; /* Parameters for the custom request */ -}; - -/* imap_conn is used for struct connection-oriented data in the connectdata - struct */ -struct imap_conn { - struct pingpong pp; - struct SASL sasl; /* SASL-related parameters */ - struct dynbuf dyn; /* for the IMAP commands */ - char *mailbox; /* The last selected mailbox */ - char *mailbox_uidvalidity; /* UIDVALIDITY parsed from select response */ - imapstate state; /* Always use imap.c:state() to change state! */ - char resptag[5]; /* Response tag to wait for */ - unsigned char preftype; /* Preferred authentication type */ - unsigned char cmdid; /* Last used command ID */ - BIT(ssldone); /* Is connect() over SSL done? */ - BIT(preauth); /* Is this connection PREAUTH? */ - BIT(tls_supported); /* StartTLS capability supported by server */ - BIT(login_disabled); /* LOGIN command disabled by server */ - BIT(ir_supported); /* Initial response supported by server */ -}; extern const struct Curl_handler Curl_handler_imap; extern const struct Curl_handler Curl_handler_imaps; diff --git a/Utilities/cmcurl/lib/inet_ntop.c b/Utilities/cmcurl/lib/inet_ntop.c index bce0ed7c56..bb2ec57872 100644 --- a/Utilities/cmcurl/lib/inet_ntop.c +++ b/Utilities/cmcurl/lib/inet_ntop.c @@ -38,7 +38,7 @@ #include "curl_printf.h" #define IN6ADDRSZ 16 -#define INADDRSZ 4 +/* #define INADDRSZ 4 */ #define INT16SZ 2 /* @@ -56,7 +56,7 @@ * Returns `dst' (as a const) * Note: * - uses no statics - * - takes a unsigned char* not an in_addr as input + * - takes an unsigned char* not an in_addr as input */ static char *inet_ntop4(const unsigned char *src, char *dst, size_t size) { @@ -74,7 +74,11 @@ static char *inet_ntop4(const unsigned char *src, char *dst, size_t size) len = strlen(tmp); if(len == 0 || len >= size) { - errno = ENOSPC; +#ifdef USE_WINSOCK + CURL_SETERRNO(WSAEINVAL); +#else + CURL_SETERRNO(ENOSPC); +#endif return NULL; } strcpy(dst, tmp); @@ -153,7 +157,6 @@ static char *inet_ntop6(const unsigned char *src, char *dst, size_t size) if(i == 6 && best.base == 0 && (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) { if(!inet_ntop4(src + 12, tp, sizeof(tmp) - (tp - tmp))) { - errno = ENOSPC; return NULL; } tp += strlen(tp); @@ -171,7 +174,11 @@ static char *inet_ntop6(const unsigned char *src, char *dst, size_t size) /* Check for overflow, copy, and we are done. */ if((size_t)(tp - tmp) > size) { - errno = ENOSPC; +#ifdef USE_WINSOCK + CURL_SETERRNO(WSAEINVAL); +#else + CURL_SETERRNO(ENOSPC); +#endif return NULL; } strcpy(dst, tmp); @@ -197,7 +204,7 @@ char *Curl_inet_ntop(int af, const void *src, char *buf, size_t size) case AF_INET6: return inet_ntop6((const unsigned char *)src, buf, size); default: - errno = EAFNOSUPPORT; + CURL_SETERRNO(SOCKEAFNOSUPPORT); return NULL; } } diff --git a/Utilities/cmcurl/lib/inet_ntop.h b/Utilities/cmcurl/lib/inet_ntop.h index 6bc7e27a79..9923daaed1 100644 --- a/Utilities/cmcurl/lib/inet_ntop.h +++ b/Utilities/cmcurl/lib/inet_ntop.h @@ -38,19 +38,13 @@ char *Curl_inet_ntop(int af, const void *addr, char *buf, size_t size); #ifdef HAVE_ARPA_INET_H #include #endif -#ifdef _WIN32 -#if defined(_MSC_VER) && (_MSC_VER <= 1900) -#define Curl_inet_ntop(af,addr,buf,size) inet_ntop(af, (void *)addr, buf, size) +#ifdef __AMIGA__ +#define Curl_inet_ntop(af,addr,buf,size) \ + (char *)inet_ntop(af, CURL_UNCONST(addr), (unsigned char *)buf, \ + (curl_socklen_t)(size)) #else -#define Curl_inet_ntop(af,addr,buf,size) inet_ntop(af, addr, buf, size) -#endif -#elif defined(__AMIGA__) -#define Curl_inet_ntop(af,addr,buf,size) \ - (char *)inet_ntop(af, (void *)addr, (unsigned char *)buf, \ - (curl_socklen_t)(size)) -#else -#define Curl_inet_ntop(af,addr,buf,size) \ - inet_ntop(af, addr, buf, (curl_socklen_t)(size)) +#define Curl_inet_ntop(af,addr,buf,size) \ + inet_ntop(af, addr, buf, (curl_socklen_t)(size)) #endif #endif diff --git a/Utilities/cmcurl/lib/krb5.c b/Utilities/cmcurl/lib/krb5.c index 4faa263499..9ebba4e3d1 100644 --- a/Utilities/cmcurl/lib/krb5.c +++ b/Utilities/cmcurl/lib/krb5.c @@ -46,15 +46,16 @@ #endif #include "urldata.h" +#include "url.h" #include "cfilters.h" #include "cf-socket.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "ftp.h" #include "curl_gssapi.h" #include "sendf.h" #include "transfer.h" #include "curl_krb5.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "strcase.h" #include "strdup.h" @@ -173,7 +174,7 @@ krb5_encode(void *app_data, const void *from, int length, int level, void **to) /* NOTE that the cast is safe, neither of the krb5, gnu gss and heimdal * libraries modify the input buffer in gss_wrap() */ - dec.value = (void *)from; + dec.value = CURL_UNCONST(from); dec.length = (size_t)length; maj = gss_wrap(&min, *context, level == PROT_PRIVATE, @@ -215,8 +216,12 @@ 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->curl_sa_addr; + (struct sockaddr_in *)CURL_UNCONST(&conn->remote_addr->curl_sa_addr); char *stringp; + struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); + + if(!ftpc) + return -2; if(getsockname(conn->sock[FIRSTSOCKET], (struct sockaddr *)&conn->local_addr, &l) < 0) @@ -242,8 +247,7 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) if(Curl_GetFTPResponse(data, &nread, NULL)) return -1; else { - struct pingpong *pp = &conn->proto.ftpc.pp; - char *line = Curl_dyn_ptr(&pp->recvbuf); + char *line = curlx_dyn_ptr(&ftpc->pp.recvbuf); if(line[0] != '3') return -1; } @@ -304,7 +308,7 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) if(output_buffer.length) { char *cmd; - result = Curl_base64_encode((char *)output_buffer.value, + result = curlx_base64_encode((char *)output_buffer.value, output_buffer.length, &p, &base64_sz); if(result) { infof(data, "base64-encoding: %s", curl_easy_strerror(result)); @@ -331,9 +335,8 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) break; } else { - struct pingpong *pp = &conn->proto.ftpc.pp; - size_t len = Curl_dyn_len(&pp->recvbuf); - p = Curl_dyn_ptr(&pp->recvbuf); + size_t len = curlx_dyn_len(&ftpc->pp.recvbuf); + p = curlx_dyn_ptr(&ftpc->pp.recvbuf); if((len < 4) || (p[0] != '2' && p[0] != '3')) { infof(data, "Server did not accept auth data"); ret = AUTH_ERROR; @@ -348,7 +351,7 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) if(p) { unsigned char *outptr; size_t outlen; - result = Curl_base64_decode(p + 5, &outptr, &outlen); + result = curlx_base64_decode(p + 5, &outptr, &outlen); if(result) { failf(data, "base64-decoding: %s", curl_easy_strerror(result)); ret = AUTH_CONTINUE; @@ -537,7 +540,7 @@ static CURLcode read_data(struct Curl_easy *data, int sockindex, if(len > CURL_MAX_INPUT_LENGTH) return CURLE_TOO_LARGE; - Curl_dyn_reset(&buf->buf); + curlx_dyn_reset(&buf->buf); } else return CURLE_RECV_ERROR; @@ -548,18 +551,18 @@ static CURLcode read_data(struct Curl_easy *data, int sockindex, result = socket_read(data, sockindex, buffer, (size_t)nread); if(result) return result; - result = Curl_dyn_addn(&buf->buf, buffer, nread); + result = curlx_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), + curlx_dyn_ptr(&buf->buf), len, conn->data_prot, conn); if(nread < 0) return CURLE_RECV_ERROR; - Curl_dyn_setlen(&buf->buf, nread); + curlx_dyn_setlen(&buf->buf, nread); buf->index = 0; return CURLE_OK; } @@ -567,10 +570,10 @@ static CURLcode read_data(struct Curl_easy *data, int sockindex, static size_t buffer_read(struct krb5buffer *buf, void *data, size_t len) { - size_t size = Curl_dyn_len(&buf->buf); + size_t size = curlx_dyn_len(&buf->buf); if(size - buf->index < len) len = size - buf->index; - memcpy(data, Curl_dyn_ptr(&buf->buf) + buf->index, len); + memcpy(data, curlx_dyn_ptr(&buf->buf) + buf->index, len); buf->index += len; return len; } @@ -605,7 +608,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(Curl_dyn_len(&conn->in_buffer.buf) == 0) { + if(curlx_dyn_len(&conn->in_buffer.buf) == 0) { if(bytes_read > 0) conn->in_buffer.eof_flag = 1; return bytes_read; @@ -645,7 +648,7 @@ static void do_sec_send(struct Curl_easy *data, struct connectdata *conn, return; /* error */ if(iscmd) { - error = Curl_base64_encode(buffer, curlx_sitouz(bytes), + error = curlx_base64_encode(buffer, curlx_sitouz(bytes), &cmd_buffer, &cmd_size); if(error) { free(buffer); @@ -723,7 +726,7 @@ int Curl_sec_read_msg(struct Curl_easy *data, struct connectdata *conn, DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); - error = Curl_base64_decode(buffer + 4, (unsigned char **)&buf, &decoded_sz); + error = curlx_base64_decode(buffer + 4, (unsigned char **)&buf, &decoded_sz); if(error || decoded_sz == 0) return -1; @@ -781,9 +784,12 @@ 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; + struct ftp_conn *ftpc = Curl_conn_meta_get(conn, CURL_META_FTP_CONN); char *line; + if(!ftpc) + return -2; + code = ftp_send_command(data, "PBSZ %u", buffer_size); if(code < 0) return -1; @@ -794,7 +800,7 @@ static int sec_set_protection_level(struct Curl_easy *data) } conn->buffer_size = buffer_size; - line = Curl_dyn_ptr(&pp->recvbuf); + line = curlx_dyn_ptr(&ftpc->pp.recvbuf); pbsz = strstr(line, "PBSZ="); if(pbsz) { /* stick to default value if the check fails */ @@ -855,7 +861,6 @@ 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); @@ -914,14 +919,21 @@ Curl_sec_login(struct Curl_easy *data, struct connectdata *conn) return choose_mech(data, conn); } +void +Curl_sec_conn_init(struct connectdata *conn) +{ + curlx_dyn_init(&conn->in_buffer.buf, CURL_MAX_INPUT_LENGTH); + conn->in_buffer.index = 0; + conn->in_buffer.eof_flag = 0; +} void -Curl_sec_end(struct connectdata *conn) +Curl_sec_conn_destroy(struct connectdata *conn) { if(conn->mech && conn->mech->end) conn->mech->end(conn->app_data); Curl_safefree(conn->app_data); - Curl_dyn_free(&conn->in_buffer.buf); + curlx_dyn_free(&conn->in_buffer.buf); conn->in_buffer.index = 0; conn->in_buffer.eof_flag = 0; conn->sec_complete = 0; diff --git a/Utilities/cmcurl/lib/ldap.c b/Utilities/cmcurl/lib/ldap.c index 77bd9fba67..c1be2f4bda 100644 --- a/Utilities/cmcurl/lib/ldap.c +++ b/Utilities/cmcurl/lib/ldap.c @@ -89,10 +89,10 @@ #include "progress.h" #include "transfer.h" #include "strcase.h" -#include "strtok.h" +#include "curlx/strparse.h" #include "curl_ldap.h" -#include "curl_multibyte.h" -#include "curl_base64.h" +#include "curlx/multibyte.h" +#include "curlx/base64.h" #include "connect.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -285,8 +285,8 @@ static int ldap_win_bind(struct Curl_easy *data, LDAP *server, PTCHAR inpass = NULL; if(user && passwd && (data->set.httpauth & CURLAUTH_BASIC)) { - inuser = curlx_convert_UTF8_to_tchar((char *) user); - inpass = curlx_convert_UTF8_to_tchar((char *) passwd); + inuser = curlx_convert_UTF8_to_tchar(user); + inpass = curlx_convert_UTF8_to_tchar(passwd); rc = (int)ldap_simple_bind_s(server, inuser, inpass); @@ -543,7 +543,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) #endif name_len = strlen(name); - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"DN: ", 4); + result = Curl_client_write(data, CLIENTWRITE_BODY, "DN: ", 4); if(result) { FREE_ON_WINLDAP(name); ldap_memfree(dn); @@ -557,7 +557,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1); + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); if(result) { FREE_ON_WINLDAP(name); ldap_memfree(dn); @@ -593,7 +593,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) vals = ldap_get_values_len(server, entryIterator, attribute); if(vals) { for(i = 0; (vals[i] != NULL); i++) { - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\t", 1); + result = Curl_client_write(data, CLIENTWRITE_BODY, "\t", 1); if(result) { ldap_value_free_len(vals); FREE_ON_WINLDAP(attr); @@ -615,7 +615,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) goto quit; } - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)": ", 2); + result = Curl_client_write(data, CLIENTWRITE_BODY, ": ", 2); if(result) { ldap_value_free_len(vals); FREE_ON_WINLDAP(attr); @@ -629,8 +629,8 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) if((attr_len > 7) && (strcmp(";binary", attr + (attr_len - 7)) == 0)) { /* Binary attribute, encode to base64. */ - result = Curl_base64_encode(vals[i]->bv_val, vals[i]->bv_len, - &val_b64, &val_b64_sz); + result = curlx_base64_encode(vals[i]->bv_val, vals[i]->bv_len, + &val_b64, &val_b64_sz); if(result) { ldap_value_free_len(vals); FREE_ON_WINLDAP(attr); @@ -670,7 +670,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) } } - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1); + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); if(result) { ldap_value_free_len(vals); FREE_ON_WINLDAP(attr); @@ -690,7 +690,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) FREE_ON_WINLDAP(attr); ldap_memfree(attribute); - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1); + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); if(result) goto quit; } @@ -728,7 +728,9 @@ static void _ldap_trace(const char *fmt, ...) if(do_trace == -1) { const char *env = getenv("CURL_TRACE"); - do_trace = (env && strtol(env, NULL, 10) > 0); + curl_off_t e = 0; + if(!curlx_str_number(&env, &e, INT_MAX)) + do_trace = e > 0; } if(!do_trace) return; @@ -759,36 +761,17 @@ static int str2scope(const char *p) return -1; } -/* - * Split 'str' into strings separated by commas. - * Note: out[] points into 'str'. - */ -static bool split_str(char *str, char ***out, size_t *count) +/* number of entries in the attributes list */ +static size_t num_entries(const char *s) { - char **res; - char *lasts; - char *s; - size_t i; size_t items = 1; - s = strchr(str, ','); + s = strchr(s, ','); while(s) { items++; - s = strchr(++s, ','); + s = strchr(s + 1, ','); } - - res = calloc(items, sizeof(char *)); - if(!res) - return FALSE; - - for(i = 0, s = Curl_strtok_r(str, ",", &lasts); s && i < items; - s = Curl_strtok_r(NULL, ",", &lasts), i++) - res[i] = s; - - *out = res; - *count = items; - - return TRUE; + return items; } /* @@ -882,15 +865,8 @@ static int _ldap_url_parse2(struct Curl_easy *data, *q++ = '\0'; if(*p) { - char **attributes; - size_t count = 0; - - /* Split the string into an array of attributes */ - if(!split_str(p, &attributes, &count)) { - rc = LDAP_NO_MEMORY; - - goto quit; - } + size_t count = num_entries(p); /* at least one */ + const char *atp = p; /* Allocate our array (+1 for the NULL entry) */ #if defined(USE_WIN32_LDAP) @@ -899,27 +875,25 @@ static int _ldap_url_parse2(struct Curl_easy *data, ludp->lud_attrs = calloc(count + 1, sizeof(char *)); #endif if(!ludp->lud_attrs) { - free(attributes); - rc = LDAP_NO_MEMORY; - goto quit; } for(i = 0; i < count; i++) { char *unescaped; CURLcode result; + struct Curl_str out; - LDAP_TRACE(("attr[%zu] '%s'\n", i, attributes[i])); + if(curlx_str_until(&atp, &out, 1024, ',')) + break; + + LDAP_TRACE(("attr[%zu] '%.*s'\n", i, (int)out.len, out.str)); /* Unescape the attribute */ - result = Curl_urldecode(attributes[i], 0, &unescaped, NULL, + result = Curl_urldecode(out.str, out.len, &unescaped, NULL, REJECT_ZERO); if(result) { - free(attributes); - rc = LDAP_NO_MEMORY; - goto quit; } @@ -931,10 +905,7 @@ static int _ldap_url_parse2(struct Curl_easy *data, free(unescaped); if(!ludp->lud_attrs[i]) { - free(attributes); - rc = LDAP_NO_MEMORY; - goto quit; } #else @@ -942,9 +913,9 @@ static int _ldap_url_parse2(struct Curl_easy *data, #endif ludp->lud_attrs_dups++; + if(curlx_str_single(&atp, ',')) + break; } - - free(attributes); } p = q; diff --git a/Utilities/cmcurl/lib/llist.c b/Utilities/cmcurl/lib/llist.c index a2c199bc62..82934425cf 100644 --- a/Utilities/cmcurl/lib/llist.c +++ b/Utilities/cmcurl/lib/llist.c @@ -32,12 +32,11 @@ /* this must be the last include file */ #include "memdebug.h" +#ifdef DEBUGBUILD #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) { @@ -86,7 +85,7 @@ Curl_llist_insert_next(struct Curl_llist *list, #ifdef DEBUGBUILD ne->_init = NODEINIT; #endif - ne->_ptr = (void *) p; + ne->_ptr = CURL_UNCONST(p); ne->_list = list; if(list->_size == 0) { list->_head = ne; diff --git a/Utilities/cmcurl/lib/md4.c b/Utilities/cmcurl/lib/md4.c index 8a3c884415..a77085a6b2 100644 --- a/Utilities/cmcurl/lib/md4.c +++ b/Utilities/cmcurl/lib/md4.c @@ -30,7 +30,7 @@ #include "strdup.h" #include "curl_md4.h" -#include "warnless.h" +#include "curlx/warnless.h" #ifdef USE_OPENSSL #include @@ -170,7 +170,12 @@ static int MD4_Init(MD4_CTX *ctx) static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) { - CryptHashData(ctx->hHash, (BYTE *)data, (unsigned int) size, 0); +#ifdef __MINGW32CE__ + CryptHashData(ctx->hHash, (BYTE *)CURL_UNCONST(data), + (unsigned int) size, 0); +#else + CryptHashData(ctx->hHash, (const BYTE *)data, (unsigned int) size, 0); +#endif } static void MD4_Final(unsigned char *result, MD4_CTX *ctx) @@ -308,16 +313,16 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx); */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) #define MD4_SET(n) \ - (*(MD4_u32plus *)(void *)&ptr[(n) * 4]) + (*(const MD4_u32plus *)(const void *)&ptr[(n) * 4]) #define MD4_GET(n) \ MD4_SET(n) #else #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)) + (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 MD4_GET(n) \ (ctx->block[(n)]) #endif diff --git a/Utilities/cmcurl/lib/md5.c b/Utilities/cmcurl/lib/md5.c index 1cf1231810..6a273c56c0 100644 --- a/Utilities/cmcurl/lib/md5.c +++ b/Utilities/cmcurl/lib/md5.c @@ -32,7 +32,7 @@ #include "curl_md5.h" #include "curl_hmac.h" -#include "warnless.h" +#include "curlx/warnless.h" #ifdef USE_MBEDTLS #include @@ -252,7 +252,11 @@ static void my_md5_update(void *in, unsigned int inputLen) { my_md5_ctx *ctx = in; - CryptHashData(ctx->hHash, (unsigned char *)input, inputLen, 0); +#ifdef __MINGW32CE__ + CryptHashData(ctx->hHash, (BYTE *)CURL_UNCONST(input), inputLen, 0); +#else + CryptHashData(ctx->hHash, (const BYTE *)input, inputLen, 0); +#endif } static void my_md5_final(unsigned char *digest, void *in) @@ -356,7 +360,7 @@ static void my_md5_final(unsigned char *result, void *ctx); */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) #define MD5_SET(n) \ - (*(MD5_u32plus *)(void *)&ptr[(n) * 4]) + (*(const MD5_u32plus *)(const void *)&ptr[(n) * 4]) #define MD5_GET(n) \ MD5_SET(n) #else diff --git a/Utilities/cmcurl/lib/memdebug.c b/Utilities/cmcurl/lib/memdebug.c index 9c284ede51..b351726b32 100644 --- a/Utilities/cmcurl/lib/memdebug.c +++ b/Utilities/cmcurl/lib/memdebug.c @@ -117,20 +117,19 @@ static bool countcheck(const char *func, int line, const char *source) fprintf(stderr, "LIMIT %s:%d %s reached memlimit\n", source, line, func); fflush(curl_dbg_logfile); /* because it might crash now */ - errno = ENOMEM; + /* !checksrc! disable ERRNOVAR 1 */ + CURL_SETERRNO(ENOMEM); return TRUE; /* RETURN ERROR! */ } else memsize--; /* countdown */ - - } return FALSE; /* allow this */ } -ALLOC_FUNC void *curl_dbg_malloc(size_t wantedsize, - int line, const char *source) +ALLOC_FUNC +void *curl_dbg_malloc(size_t wantedsize, int line, const char *source) { struct memdebug *mem; size_t size; @@ -156,8 +155,9 @@ ALLOC_FUNC void *curl_dbg_malloc(size_t wantedsize, return mem ? mem->mem : NULL; } -ALLOC_FUNC void *curl_dbg_calloc(size_t wanted_elements, size_t wanted_size, - int line, const char *source) +ALLOC_FUNC +void *curl_dbg_calloc(size_t wanted_elements, size_t wanted_size, + int line, const char *source) { struct memdebug *mem; size_t size, user_size; @@ -184,8 +184,8 @@ ALLOC_FUNC void *curl_dbg_calloc(size_t wanted_elements, size_t wanted_size, return mem ? mem->mem : NULL; } -ALLOC_FUNC char *curl_dbg_strdup(const char *str, - int line, const char *source) +ALLOC_FUNC +char *curl_dbg_strdup(const char *str, int line, const char *source) { char *mem; size_t len; @@ -209,8 +209,8 @@ ALLOC_FUNC char *curl_dbg_strdup(const char *str, } #if defined(_WIN32) && defined(UNICODE) -ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, - int line, const char *source) +ALLOC_FUNC +wchar_t *curl_dbg_wcsdup(const wchar_t *str, int line, const char *source) { wchar_t *mem; size_t wsiz, bsiz; @@ -229,7 +229,7 @@ ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, if(source) curl_dbg_log("MEM %s:%d wcsdup(%p) (%zu) = %p\n", - source, line, (void *)str, bsiz, (void *)mem); + source, line, (const void *)str, bsiz, (void *)mem); return mem; } @@ -238,7 +238,7 @@ ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, /* We provide a realloc() that accepts a NULL as pointer, which then performs a malloc(). In order to work with ares. */ void *curl_dbg_realloc(void *ptr, size_t wantedsize, - int line, const char *source) + int line, const char *source) { struct memdebug *mem = NULL; @@ -378,6 +378,24 @@ curl_socket_t curl_dbg_accept(curl_socket_t s, void *saddr, void *saddrlen, return sockfd; } +#ifdef HAVE_ACCEPT4 +curl_socket_t curl_dbg_accept4(curl_socket_t s, void *saddr, void *saddrlen, + int flags, + int line, const char *source) +{ + struct sockaddr *addr = (struct sockaddr *)saddr; + curl_socklen_t *addrlen = (curl_socklen_t *)saddrlen; + + curl_socket_t sockfd = accept4(s, addr, addrlen, flags); + + if(source && (sockfd != CURL_SOCKET_BAD)) + curl_dbg_log("FD %s:%d accept() = %" FMT_SOCKET_T "\n", + source, line, sockfd); + + return sockfd; +} +#endif + /* separate function to allow libcurl to mark a "faked" close */ void curl_dbg_mark_sclose(curl_socket_t sockfd, int line, const char *source) { @@ -394,8 +412,9 @@ int curl_dbg_sclose(curl_socket_t sockfd, int line, const char *source) return res; } -ALLOC_FUNC FILE *curl_dbg_fopen(const char *file, const char *mode, - int line, const char *source) +ALLOC_FUNC +FILE *curl_dbg_fopen(const char *file, const char *mode, + int line, const char *source) { FILE *res = fopen(file, mode); @@ -406,8 +425,9 @@ ALLOC_FUNC FILE *curl_dbg_fopen(const char *file, const char *mode, return res; } -ALLOC_FUNC FILE *curl_dbg_fdopen(int filedes, const char *mode, - int line, const char *source) +ALLOC_FUNC +FILE *curl_dbg_fdopen(int filedes, const char *mode, + int line, const char *source) { FILE *res = fdopen(filedes, mode); if(source) @@ -431,33 +451,25 @@ int curl_dbg_fclose(FILE *file, int line, const char *source) return res; } -#define LOGLINE_BUFSIZE 1024 - /* this does the writing to the memory tracking log file */ void curl_dbg_log(const char *format, ...) { - char *buf; + char buf[1024]; int nchars; va_list ap; if(!curl_dbg_logfile) return; - buf = (Curl_cmalloc)(LOGLINE_BUFSIZE); - if(!buf) - return; - va_start(ap, format); - nchars = mvsnprintf(buf, LOGLINE_BUFSIZE, format, ap); + nchars = mvsnprintf(buf, sizeof(buf), format, ap); va_end(ap); - if(nchars > LOGLINE_BUFSIZE - 1) - nchars = LOGLINE_BUFSIZE - 1; + if(nchars > (int)sizeof(buf) - 1) + nchars = (int)sizeof(buf) - 1; if(nchars > 0) fwrite(buf, 1, (size_t)nchars, curl_dbg_logfile); - - (Curl_cfree)(buf); } #endif /* CURLDEBUG */ diff --git a/Utilities/cmcurl/lib/memdebug.h b/Utilities/cmcurl/lib/memdebug.h index 80f3374e52..11b250dea2 100644 --- a/Utilities/cmcurl/lib/memdebug.h +++ b/Utilities/cmcurl/lib/memdebug.h @@ -33,12 +33,21 @@ #include #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))) +#ifdef __clang__ +# define ALLOC_FUNC __attribute__((__malloc__)) +# if __clang_major__ >= 4 +# define ALLOC_SIZE(s) __attribute__((__alloc_size__(s))) +# define ALLOC_SIZE2(n, s) __attribute__((__alloc_size__(n, s))) +# else +# define ALLOC_SIZE(s) +# define ALLOC_SIZE2(n, s) +# endif +#elif 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))) #elif defined(_MSC_VER) -# define ALLOC_FUNC __declspec(restrict) +# define ALLOC_FUNC __declspec(restrict) # define ALLOC_SIZE(s) # define ALLOC_SIZE2(n, s) #else @@ -49,25 +58,25 @@ #define CURL_MT_LOGFNAME_BUFSIZE 512 +/* Avoid redundant redeclaration warnings with modern compilers, when including + this header multiple times. */ +#ifndef HEADER_CURL_MEMDEBUG_H_EXTERNS +#define HEADER_CURL_MEMDEBUG_H_EXTERNS extern FILE *curl_dbg_logfile; /* memory functions */ -CURL_EXTERN ALLOC_FUNC ALLOC_SIZE(1) void *curl_dbg_malloc(size_t size, - int line, - const char *source); -CURL_EXTERN ALLOC_FUNC ALLOC_SIZE2(1, 2) void *curl_dbg_calloc(size_t elements, - size_t size, int line, const char *source); -CURL_EXTERN ALLOC_SIZE(2) void *curl_dbg_realloc(void *ptr, - size_t size, - int line, - const char *source); 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); +CURL_EXTERN ALLOC_FUNC ALLOC_SIZE(1) + void *curl_dbg_malloc(size_t size, int line, const char *source); +CURL_EXTERN ALLOC_FUNC ALLOC_SIZE2(1, 2) + void *curl_dbg_calloc(size_t n, size_t size, int line, const char *source); +CURL_EXTERN ALLOC_SIZE(2) + void *curl_dbg_realloc(void *ptr, size_t size, 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) -CURL_EXTERN ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, - int line, - const char *source); +CURL_EXTERN ALLOC_FUNC + wchar_t *curl_dbg_wcsdup(const wchar_t *str, int line, const char *source); #endif CURL_EXTERN void curl_dbg_memdebug(const char *logname); @@ -83,6 +92,11 @@ CURL_EXTERN int curl_dbg_sclose(curl_socket_t sockfd, int line, const char *source); CURL_EXTERN curl_socket_t curl_dbg_accept(curl_socket_t s, void *a, void *alen, int line, const char *source); +#ifdef HAVE_ACCEPT4 +CURL_EXTERN curl_socket_t curl_dbg_accept4(curl_socket_t s, void *saddr, + void *saddrlen, int flags, + int line, const char *source); +#endif #ifdef HAVE_SOCKETPAIR CURL_EXTERN int curl_dbg_socketpair(int domain, int type, int protocol, curl_socket_t socket_vector[2], @@ -102,12 +116,15 @@ CURL_EXTERN RECV_TYPE_RETV curl_dbg_recv(RECV_TYPE_ARG1 sockfd, const char *source); /* FILE functions */ -CURL_EXTERN ALLOC_FUNC FILE *curl_dbg_fopen(const char *file, const char *mode, - int line, const char *source); -CURL_EXTERN ALLOC_FUNC FILE *curl_dbg_fdopen(int filedes, const char *mode, - int line, const char *source); - CURL_EXTERN int curl_dbg_fclose(FILE *file, int line, const char *source); +CURL_EXTERN ALLOC_FUNC + FILE *curl_dbg_fopen(const char *file, const char *mode, + int line, const char *source); +CURL_EXTERN ALLOC_FUNC + FILE *curl_dbg_fdopen(int filedes, const char *mode, + int line, const char *source); + +#endif /* HEADER_CURL_MEMDEBUG_H_EXTERNS */ #ifndef MEMDEBUG_NODEFINES @@ -147,34 +164,17 @@ CURL_EXTERN int curl_dbg_fclose(FILE *file, int line, const char *source); #undef accept /* for those with accept as a macro */ #define accept(sock,addr,len)\ curl_dbg_accept(sock, addr, len, __LINE__, __FILE__) +#ifdef HAVE_ACCEPT4 +#undef accept4 /* for those with accept4 as a macro */ +#define accept4(sock,addr,len,flags)\ + curl_dbg_accept4(sock, addr, len, flags, __LINE__, __FILE__) +#endif #ifdef HAVE_SOCKETPAIR #define socketpair(domain,type,protocol,socket_vector)\ 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 - our macro as for other platforms. Instead, we redefine the new name they - define getaddrinfo to become! */ -#define ogetaddrinfo(host,serv,hint,res) \ - curl_dbg_getaddrinfo(host, serv, hint, res, __LINE__, __FILE__) -#else -#undef getaddrinfo -#define getaddrinfo(host,serv,hint,res) \ - curl_dbg_getaddrinfo(host, serv, hint, res, __LINE__, __FILE__) -#endif -#endif /* HAVE_GETADDRINFO */ - -#ifdef HAVE_FREEADDRINFO -#undef freeaddrinfo -#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 #define sclose(sockfd) curl_dbg_sclose(sockfd,__LINE__,__FILE__) diff --git a/Utilities/cmcurl/lib/mime.c b/Utilities/cmcurl/lib/mime.c index a90d170579..c90c34898d 100644 --- a/Utilities/cmcurl/lib/mime.c +++ b/Utilities/cmcurl/lib/mime.c @@ -29,10 +29,11 @@ struct Curl_easy; #include "mime.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "urldata.h" #include "sendf.h" #include "strdup.h" +#include "curlx/base64.h" #if !defined(CURL_DISABLE_MIME) && (!defined(CURL_DISABLE_HTTP) || \ !defined(CURL_DISABLE_SMTP) || \ @@ -45,7 +46,7 @@ struct Curl_easy; #include "rand.h" #include "slist.h" #include "strcase.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -87,10 +88,6 @@ static const struct mime_encoder encoders[] = { {ZERO_NULL, ZERO_NULL, ZERO_NULL} }; -/* Base64 encoding table */ -static const char base64enc[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - /* Quoted-printable character class table. * * We cannot rely on ctype functions since quoted-printable input data @@ -318,19 +315,19 @@ static char *escape_string(struct Curl_easy *data, if(strategy == MIMESTRATEGY_MAIL || (data && (data->set.mime_formescape))) table = mimetable; - Curl_dyn_init(&db, CURL_MAX_INPUT_LENGTH); + curlx_dyn_init(&db, CURL_MAX_INPUT_LENGTH); - for(result = Curl_dyn_addn(&db, STRCONST("")); !result && *src; src++) { + for(result = curlx_dyn_addn(&db, STRCONST("")); !result && *src; src++) { for(p = table; *p && **p != *src; p++) ; if(*p) - result = Curl_dyn_add(&db, *p + 1); + result = curlx_dyn_add(&db, *p + 1); else - result = Curl_dyn_addn(&db, src, 1); + result = curlx_dyn_addn(&db, src, 1); } - return Curl_dyn_ptr(&db); + return curlx_dyn_ptr(&db); } /* Check if header matches. */ @@ -472,10 +469,10 @@ static size_t encoder_base64_read(char *buffer, size_t size, bool ateof, i = st->buf[st->bufbeg++] & 0xFF; i = (i << 8) | (st->buf[st->bufbeg++] & 0xFF); i = (i << 8) | (st->buf[st->bufbeg++] & 0xFF); - *ptr++ = base64enc[(i >> 18) & 0x3F]; - *ptr++ = base64enc[(i >> 12) & 0x3F]; - *ptr++ = base64enc[(i >> 6) & 0x3F]; - *ptr++ = base64enc[i & 0x3F]; + *ptr++ = Curl_base64encdec[(i >> 18) & 0x3F]; + *ptr++ = Curl_base64encdec[(i >> 12) & 0x3F]; + *ptr++ = Curl_base64encdec[(i >> 6) & 0x3F]; + *ptr++ = Curl_base64encdec[i & 0x3F]; cursize += 4; st->pos += 4; size -= 4; @@ -499,10 +496,10 @@ static size_t encoder_base64_read(char *buffer, size_t size, bool ateof, i = (st->buf[st->bufbeg + 1] & 0xFF) << 8; i |= (st->buf[st->bufbeg] & 0xFF) << 16; - ptr[0] = base64enc[(i >> 18) & 0x3F]; - ptr[1] = base64enc[(i >> 12) & 0x3F]; + ptr[0] = Curl_base64encdec[(i >> 18) & 0x3F]; + ptr[1] = Curl_base64encdec[(i >> 12) & 0x3F]; if(++st->bufbeg != st->bufend) { - ptr[2] = base64enc[(i >> 6) & 0x3F]; + ptr[2] = Curl_base64encdec[(i >> 6) & 0x3F]; st->bufbeg++; } cursize += 4; @@ -1742,7 +1739,7 @@ const char *Curl_mime_contenttype(const char *filename) const char *nameend = filename + len1; unsigned int i; - for(i = 0; i < sizeof(ctts) / sizeof(ctts[0]); i++) { + for(i = 0; i < CURL_ARRAYSIZE(ctts); i++) { size_t len2 = strlen(ctts[i].extension); if(len1 >= len2 && strcasecompare(nameend - len2, ctts[i].extension)) diff --git a/Utilities/cmcurl/lib/mprintf.c b/Utilities/cmcurl/lib/mprintf.c index 8bc9054407..176f8a3e4b 100644 --- a/Utilities/cmcurl/lib/mprintf.c +++ b/Utilities/cmcurl/lib/mprintf.c @@ -23,17 +23,14 @@ */ #include "curl_setup.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "curl_printf.h" +#include "curlx/strparse.h" #include "curl_memory.h" /* The last #include file should be: */ #include "memdebug.h" -/* - * If SIZEOF_SIZE_T has not been defined, default to the size of long. - */ - #ifdef HAVE_LONGLONG # define LONG_LONG_TYPE long long # define HAVE_LONG_LONG_TYPE @@ -67,17 +64,16 @@ #endif /* Lower-case digits. */ -static const char lower_digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; +const unsigned char Curl_ldigits[] = "0123456789abcdef"; /* Upper-case digits. */ -static const char upper_digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const unsigned char Curl_udigits[] = "0123456789ABCDEF"; -#define OUTCHAR(x) \ - do { \ - if(!stream((unsigned char)x, userp)) \ - done++; \ - else \ - return done; /* return on failure */ \ +#define OUTCHAR(x) \ + do { \ + if(stream((unsigned char)x, userp)) \ + return TRUE; \ + (*donep)++; \ } while(0) /* Data type to read from the arglist */ @@ -134,7 +130,7 @@ enum { struct va_input { FormatType type; /* FormatType */ union { - char *str; + const char *str; void *ptr; mp_intmax_t nums; /* signed */ mp_uintmax_t numu; /* unsigned */ @@ -150,7 +146,7 @@ struct outsegment { int precision; /* precision OR precision parameter number */ unsigned int flags; unsigned int input; /* input argument array index */ - char *start; /* format string start to output */ + const char *start; /* format string start to output */ size_t outlen; /* number of bytes from the format string to output */ }; @@ -169,26 +165,19 @@ struct asprintf { returns -1 if no valid number was provided. */ -static int dollarstring(char *input, char **end) +static int dollarstring(const char *p, const char **end) { - if(ISDIGIT(*input)) { - int number = 0; - do { - if(number < MAX_PARAMETERS) { - number *= 10; - number += *input - '0'; - } - input++; - } while(ISDIGIT(*input)); - - if(number && (number <= MAX_PARAMETERS) && ('$' == *input)) { - *end = ++input; - return number - 1; - } - } - return -1; + curl_off_t num; + if(curlx_str_number(&p, &num, MAX_PARAMETERS) || + curlx_str_single(&p, '$') || !num) + return -1; + *end = p; + return (int)num - 1; } +#define is_arg_used(x,y) ((x)[(y)/8] & (1 << ((y)&7))) +#define mark_arg_used(x,y) ((x)[y/8] |= (unsigned char)(1 << ((y)&7))) + /* * Parse the format string. * @@ -216,13 +205,8 @@ static int parsefmt(const char *format, int *opieces, int *ipieces, va_list arglist) { - char *fmt = (char *)format; + const char *fmt = format; int param_num = 0; - int param; - int width; - int precision; - unsigned int flags; - FormatType type; int max_param = -1; int i; int ocount = 0; @@ -230,7 +214,7 @@ static int parsefmt(const char *format, size_t outlen = 0; struct outsegment *optr; int use_dollar = DOLLAR_UNKNOWN; - char *start = fmt; + const char *start = fmt; /* clear, set a bit for each used input */ memset(usedinput, 0, sizeof(usedinput)); @@ -239,6 +223,11 @@ static int parsefmt(const char *format, if(*fmt == '%') { struct va_input *iptr; bool loopit = TRUE; + FormatType type; + unsigned int flags = 0; + int width = 0; + int precision = 0; + int param = -1; fmt++; outlen = (size_t)(fmt - start - 1); if(*fmt == '%') { @@ -258,9 +247,6 @@ static int parsefmt(const char *format, continue; /* while */ } - flags = 0; - width = precision = 0; - if(use_dollar != DOLLAR_NOPE) { param = dollarstring(fmt, &fmt); if(param < 0) { @@ -275,8 +261,6 @@ static int parsefmt(const char *format, else use_dollar = DOLLAR_USE; } - else - param = -1; /* Handle the flags */ while(loopit) { @@ -311,20 +295,15 @@ static int parsefmt(const char *format, precision = -1; } else { - bool is_neg = FALSE; + bool is_neg; + curl_off_t num; flags |= FLAGS_PREC; - precision = 0; - if('-' == *fmt) { - is_neg = TRUE; + is_neg = ('-' == *fmt); + if(is_neg) fmt++; - } - while(ISDIGIT(*fmt)) { - int n = *fmt - '0'; - if(precision > (INT_MAX - n) / 10) - return PFMT_PREC; - precision = precision * 10 + n; - fmt++; - } + if(curlx_str_number(&fmt, &num, INT_MAX)) + return PFMT_PREC; + precision = (int)num; if(is_neg) precision = -precision; } @@ -337,7 +316,7 @@ static int parsefmt(const char *format, case 'h': flags |= FLAGS_SHORT; break; -#if defined(_WIN32) || defined(_WIN32_WCE) +#ifdef _WIN32 case 'I': /* Non-ANSI integer extensions I32 I64 */ if((fmt[0] == '3') && (fmt[1] == '2')) { @@ -356,7 +335,7 @@ static int parsefmt(const char *format, #endif } break; -#endif /* _WIN32 || _WIN32_WCE */ +#endif /* _WIN32 */ case 'l': if(flags & FLAGS_LONG) flags |= FLAGS_LONGLONG; @@ -390,18 +369,15 @@ static int parsefmt(const char *format, flags |= FLAGS_PAD_NIL; FALLTHROUGH(); case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': + case '5': case '6': case '7': case '8': case '9': { + curl_off_t num; flags |= FLAGS_WIDTH; - width = 0; fmt--; - do { - int n = *fmt - '0'; - if(width > (INT_MAX - n) / 10) - return PFMT_WIDTH; - width = width * 10 + n; - fmt++; - } while(ISDIGIT(*fmt)); + if(curlx_str_number(&fmt, &num, INT_MAX)) + return PFMT_WIDTH; + width = (int)num; break; + } case '*': /* read width from argument list */ flags |= FLAGS_WIDTHPARAM; if(use_dollar == DOLLAR_USE) { @@ -511,9 +487,8 @@ static int parsefmt(const char *format, if(width < 0) width = param_num++; else { - /* if this identifies a parameter already used, this - is illegal */ - if(usedinput[width/8] & (1 << (width&7))) + /* if this identifies a parameter already used, this is illegal */ + if(is_arg_used(usedinput, width)) return PFMT_WIDTHARG; } if(width >= MAX_PARAMETERS) @@ -523,16 +498,15 @@ static int parsefmt(const char *format, in[width].type = FORMAT_WIDTH; /* mark as used */ - usedinput[width/8] |= (unsigned char)(1 << (width&7)); + mark_arg_used(usedinput, width); } if(flags & FLAGS_PRECPARAM) { if(precision < 0) precision = param_num++; else { - /* if this identifies a parameter already used, this - is illegal */ - if(usedinput[precision/8] & (1 << (precision&7))) + /* if this identifies a parameter already used, this is illegal */ + if(is_arg_used(usedinput, precision)) return PFMT_PRECARG; } if(precision >= MAX_PARAMETERS) @@ -541,7 +515,7 @@ static int parsefmt(const char *format, max_param = precision; in[precision].type = FORMAT_PRECISION; - usedinput[precision/8] |= (unsigned char)(1 << (precision&7)); + mark_arg_used(usedinput, precision); } /* Handle the specifier */ @@ -556,7 +530,7 @@ static int parsefmt(const char *format, iptr->type = type; /* mark this input as used */ - usedinput[param/8] |= (unsigned char)(1 << (param&7)); + mark_arg_used(usedinput, param); fmt++; optr = &out[ocount++]; @@ -589,14 +563,14 @@ static int parsefmt(const char *format, /* 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)))) + if(!is_arg_used(usedinput, i)) /* bad input */ return PFMT_INPUTGAP; /* based on the type, read the correct argument */ switch(iptr->type) { case FORMAT_STRING: - iptr->val.str = va_arg(arglist, char *); + iptr->val.str = va_arg(arglist, const char *); break; case FORMAT_INTPTR: @@ -645,6 +619,333 @@ static int parsefmt(const char *format, return PFMT_OK; } +struct mproperty { + int width; /* Width of a field. */ + int prec; /* Precision of a field. */ + unsigned int flags; +}; + +static bool out_double(void *userp, + int (*stream)(unsigned char, void *), + struct mproperty *p, + double dnum, + char *work, int *donep) +{ + char formatbuf[32]="%"; + char *fptr = &formatbuf[1]; + size_t left = sizeof(formatbuf)-strlen(formatbuf); + int flags = p->flags; + int width = p->width; + int prec = p->prec; + + 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 >= BUFFSIZE) + width = BUFFSIZE - 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 */ + int maxprec = BUFFSIZE - 1; + double val = dnum; + int len; + if(prec > maxprec) + prec = maxprec - 1; + if(width > 0 && prec <= width) + maxprec -= width; + while(val >= 10.0) { + val /= 10; + maxprec--; + } + + if(prec > maxprec) + prec = 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(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 */ + +#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 */ +#ifdef HAVE_SNPRINTF + /* !checksrc! disable LONGLINE */ + /* NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) */ + (snprintf)(work, BUFFSIZE, formatbuf, dnum); +#ifdef _WIN32 + /* Old versions of the Windows CRT do not terminate the snprintf output + buffer if it reaches the max size so we do that here. */ + work[BUFFSIZE - 1] = 0; +#endif +#else + (sprintf)(work, formatbuf, dnum); +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + DEBUGASSERT(strlen(work) < BUFFSIZE); + while(*work) { + if(stream(*work++, userp)) + return TRUE; + (*donep)++; + } + return 0; +} + +static bool out_number(void *userp, + int (*stream)(unsigned char, void *), + struct mproperty *p, + mp_uintmax_t num, + mp_intmax_t nums, + char *work, int *donep) +{ + const unsigned char *digits = Curl_ldigits; + int flags = p->flags; + int width = p->width; + int prec = p->prec; + bool is_alt = flags & FLAGS_ALT; + bool is_neg = FALSE; + int base = 10; + + /* 'workend' points to the final buffer byte position, but with an extra + byte as margin to avoid the (FALSE?) warning Coverity gives us + otherwise */ + char *workend = &work[BUFFSIZE - 2]; + char *w; + + if(flags & FLAGS_CHAR) { + /* Character. */ + if(!(flags & FLAGS_LEFT)) + while(--width > 0) + OUTCHAR(' '); + OUTCHAR((char) num); + if(flags & FLAGS_LEFT) + while(--width > 0) + OUTCHAR(' '); + return FALSE; + } + if(flags & FLAGS_OCTAL) + /* Octal unsigned integer */ + base = 8; + + else if(flags & FLAGS_HEX) { + /* Hexadecimal unsigned integer */ + digits = (flags & FLAGS_UPPER) ? Curl_udigits : Curl_ldigits; + base = 16; + } + else if(flags & FLAGS_UNSIGNED) + /* Decimal unsigned integer */ + ; + + else { + /* Decimal integer. */ + is_neg = (nums < 0); + if(is_neg) { + /* signed_num might fail to hold absolute negative minimum by 1 */ + mp_intmax_t signed_num; /* Used to convert negative in positive. */ + signed_num = nums + (mp_intmax_t)1; + signed_num = -signed_num; + num = (mp_uintmax_t)signed_num; + num += (mp_uintmax_t)1; + } + } + + /* Supply a default precision if none was given. */ + if(prec == -1) + prec = 1; + + /* Put the number in WORK. */ + w = workend; + DEBUGASSERT(base <= 16); + 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 -= (int)(workend - w); + prec -= (int)(workend - w); + + if(is_alt && base == 8 && prec <= 0) { + *w-- = '0'; + --width; + } + + if(prec > 0) { + width -= prec; + while(prec-- > 0 && w >= work) + *w-- = '0'; + } + + if(is_alt && base == 16) + width -= 2; + + if(is_neg || (flags & FLAGS_SHOWSIGN) || (flags & FLAGS_SPACE)) + --width; + + if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_PAD_NIL)) + while(width-- > 0) + OUTCHAR(' '); + + if(is_neg) + OUTCHAR('-'); + else if(flags & FLAGS_SHOWSIGN) + OUTCHAR('+'); + else if(flags & FLAGS_SPACE) + OUTCHAR(' '); + + if(is_alt && base == 16) { + OUTCHAR('0'); + if(flags & FLAGS_UPPER) + OUTCHAR('X'); + else + OUTCHAR('x'); + } + + if(!(flags & FLAGS_LEFT) && (flags & FLAGS_PAD_NIL)) + while(width-- > 0) + OUTCHAR('0'); + + /* Write the number. */ + while(++w <= workend) { + OUTCHAR(*w); + } + + if(flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); + + return FALSE; +} + +static const char nilstr[] = "(nil)"; + +static bool out_string(void *userp, + int (*stream)(unsigned char, void *), + struct mproperty *p, + const char *str, + int *donep) +{ + int flags = p->flags; + int width = p->width; + int prec = p->prec; + size_t len; + + 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 { + str = ""; + len = 0; + } + } + else if(prec != -1) + len = (size_t)prec; + else if(*str == '\0') + len = 0; + else + len = strlen(str); + + width -= (len > INT_MAX) ? INT_MAX : (int)len; + + if(flags & FLAGS_ALT) + OUTCHAR('"'); + + if(!(flags & FLAGS_LEFT)) + while(width-- > 0) + OUTCHAR(' '); + + for(; len && *str; len--) + OUTCHAR(*str++); + if(flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); + + if(flags & FLAGS_ALT) + OUTCHAR('"'); + + return FALSE; +} + +static bool out_pointer(void *userp, + int (*stream)(unsigned char, void *), + struct mproperty *p, + const char *ptr, + char *work, + int *donep) +{ + /* Generic pointer. */ + if(ptr) { + size_t num = (size_t) ptr; + + /* If the pointer is not NULL, write it as a %#x spec. */ + p->flags |= FLAGS_HEX|FLAGS_ALT; + if(out_number(userp, stream, p, num, 0, work, donep)) + return TRUE; + } + else { + /* Write "(nil)" for a nil pointer. */ + const char *point; + int width = p->width; + int flags = p->flags; + + width -= (int)(sizeof(nilstr) - 1); + if(flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); + for(point = nilstr; *point; ++point) + OUTCHAR(*point); + if(!(flags & FLAGS_LEFT)) + while(width-- > 0) + OUTCHAR(' '); + } + return FALSE; +} + /* * formatf() - the general printf function. * @@ -669,8 +970,6 @@ static int formatf( const char *format, /* %-formatted string */ va_list ap_save) /* list of parameters */ { - 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 */ @@ -680,368 +979,102 @@ static int formatf( struct va_input input[MAX_PARAMETERS]; char work[BUFFSIZE + 2]; - /* 'workend' points to the final buffer byte position, but with an extra - byte as margin to avoid the (FALSE?) warning Coverity gives us - otherwise */ - char *workend = &work[BUFFSIZE - 2]; - /* Parse the format string */ if(parsefmt(format, output, input, &ocount, &icount, ap_save)) return 0; 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; + struct va_input *iptr = &input[optr->input]; + struct mproperty p; size_t outlen = optr->outlen; - unsigned int flags = optr->flags; if(outlen) { - char *str = optr->start; - for(; outlen && *str; outlen--) - OUTCHAR(*str++); + const char *str = optr->start; + for(; outlen && *str; outlen--) { + if(stream(*str++, userp)) + return done; + done++; + } if(optr->flags & FLAGS_SUBSTR) /* this is just a substring */ continue; } + p.flags = optr->flags; + /* pick up the specified width */ - if(flags & FLAGS_WIDTHPARAM) { - width = (int)input[optr->width].val.nums; - if(width < 0) { + if(p.flags & FLAGS_WIDTHPARAM) { + p.width = (int)input[optr->width].val.nums; + if(p.width < 0) { /* "A negative field width is taken as a '-' flag followed by a positive field width." */ - if(width == INT_MIN) - width = INT_MAX; + if(p.width == INT_MIN) + p.width = INT_MAX; else - width = -width; - flags |= FLAGS_LEFT; - flags &= ~(unsigned int)FLAGS_PAD_NIL; + p.width = -p.width; + p.flags |= FLAGS_LEFT; + p.flags &= ~(unsigned int)FLAGS_PAD_NIL; } } else - width = optr->width; + p.width = optr->width; /* pick up the specified precision */ - if(flags & FLAGS_PRECPARAM) { - prec = (int)input[optr->precision].val.nums; - if(prec < 0) + if(p.flags & FLAGS_PRECPARAM) { + p.prec = (int)input[optr->precision].val.nums; + if(p.prec < 0) /* "A negative precision is taken as if the precision were omitted." */ - prec = -1; + p.prec = -1; } - else if(flags & FLAGS_PREC) - prec = optr->precision; + else if(p.flags & FLAGS_PREC) + p.prec = optr->precision; else - prec = -1; - - is_alt = (flags & FLAGS_ALT) ? 1 : 0; - iptr = &input[optr->input]; + p.prec = -1; switch(iptr->type) { case FORMAT_INTU: case FORMAT_LONGU: case FORMAT_LONGLONGU: - flags |= FLAGS_UNSIGNED; - FALLTHROUGH(); + p.flags |= FLAGS_UNSIGNED; + if(out_number(userp, stream, &p, iptr->val.numu, 0, work, &done)) + return done; + break; + case FORMAT_INT: case FORMAT_LONG: case FORMAT_LONGLONG: - num = iptr->val.numu; - if(flags & FLAGS_CHAR) { - /* Character. */ - if(!(flags & FLAGS_LEFT)) - while(--width > 0) - OUTCHAR(' '); - OUTCHAR((char) num); - if(flags & FLAGS_LEFT) - while(--width > 0) - OUTCHAR(' '); - break; - } - if(flags & FLAGS_OCTAL) { - /* Octal unsigned integer */ - base = 8; - is_neg = FALSE; - } - else if(flags & FLAGS_HEX) { - /* Hexadecimal unsigned integer */ - digits = (flags & FLAGS_UPPER) ? upper_digits : lower_digits; - base = 16; - is_neg = FALSE; - } - else if(flags & FLAGS_UNSIGNED) { - /* Decimal unsigned integer */ - base = 10; - is_neg = FALSE; - } - else { - /* Decimal integer. */ - base = 10; - - 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; - } - } -number: - /* Supply a default precision if none was given. */ - if(prec == -1) - prec = 1; - - /* Put the number in WORK. */ - w = workend; - 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 -= (int)(workend - w); - prec -= (int)(workend - w); - - if(is_alt && base == 8 && prec <= 0) { - *w-- = '0'; - --width; - } - - if(prec > 0) { - width -= prec; - while(prec-- > 0 && w >= work) - *w-- = '0'; - } - - if(is_alt && base == 16) - width -= 2; - - if(is_neg || (flags & FLAGS_SHOWSIGN) || (flags & FLAGS_SPACE)) - --width; - - if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_PAD_NIL)) - while(width-- > 0) - OUTCHAR(' '); - - if(is_neg) - OUTCHAR('-'); - else if(flags & FLAGS_SHOWSIGN) - OUTCHAR('+'); - else if(flags & FLAGS_SPACE) - OUTCHAR(' '); - - if(is_alt && base == 16) { - OUTCHAR('0'); - if(flags & FLAGS_UPPER) - OUTCHAR('X'); - else - OUTCHAR('x'); - } - - if(!(flags & FLAGS_LEFT) && (flags & FLAGS_PAD_NIL)) - while(width-- > 0) - OUTCHAR('0'); - - /* Write the number. */ - while(++w <= workend) { - OUTCHAR(*w); - } - - if(flags & FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); + if(out_number(userp, stream, &p, iptr->val.numu, + iptr->val.nums, work, &done)) + return done; break; - 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 { - str = ""; - len = 0; - } - } - else if(prec != -1) - len = (size_t)prec; - else if(*str == '\0') - len = 0; - else - len = strlen(str); - - width -= (len > INT_MAX) ? INT_MAX : (int)len; - - if(flags & FLAGS_ALT) - OUTCHAR('"'); - - if(!(flags & FLAGS_LEFT)) - while(width-- > 0) - OUTCHAR(' '); - - for(; len && *str; len--) - OUTCHAR(*str++); - if(flags & FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); - - if(flags & FLAGS_ALT) - OUTCHAR('"'); + case FORMAT_STRING: + if(out_string(userp, stream, &p, iptr->val.str, &done)) + return done; break; - } case FORMAT_PTR: - /* Generic pointer. */ - 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; - } - else { - /* Write "(nil)" for a nil pointer. */ - const char *point; - - 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(' '); - } + if(out_pointer(userp, stream, &p, iptr->val.ptr, work, &done)) + return done; break; - 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 >= BUFFSIZE) - width = BUFFSIZE - 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 */ - int maxprec = BUFFSIZE - 1; - double val = iptr->val.dnum; - if(prec > maxprec) - prec = maxprec - 1; - if(width > 0 && prec <= width) - maxprec -= width; - while(val >= 10.0) { - val /= 10; - maxprec--; - } - - if(prec > maxprec) - prec = 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(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 */ - -#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 */ -#ifdef HAVE_SNPRINTF - (snprintf)(work, BUFFSIZE, formatbuf, iptr->val.dnum); /* NOLINT */ -#ifdef _WIN32 - /* Old versions of the Windows CRT do not terminate the snprintf output - buffer if it reaches the max size so we do that here. */ - work[BUFFSIZE - 1] = 0; -#endif -#else - (sprintf)(work, formatbuf, iptr->val.dnum); -#endif -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - DEBUGASSERT(strlen(work) < BUFFSIZE); - for(fptr = work; *fptr; fptr++) - OUTCHAR(*fptr); + case FORMAT_DOUBLE: + if(out_double(userp, stream, &p, iptr->val.dnum, work, &done)) + return done; break; - } case FORMAT_INTPTR: /* Answer the count of characters written. */ #ifdef HAVE_LONG_LONG_TYPE - if(flags & FLAGS_LONGLONG) + if(p.flags & FLAGS_LONGLONG) *(LONG_LONG_TYPE *) iptr->val.ptr = (LONG_LONG_TYPE)done; else #endif - if(flags & FLAGS_LONG) + if(p.flags & FLAGS_LONG) *(long *) iptr->val.ptr = (long)done; - else if(!(flags & FLAGS_SHORT)) + else if(!(p.flags & FLAGS_SHORT)) *(int *) iptr->val.ptr = (int)done; else *(short *) iptr->val.ptr = (short)done; @@ -1106,7 +1139,7 @@ int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...) static int alloc_addbyter(unsigned char outc, void *f) { struct asprintf *infop = f; - CURLcode result = Curl_dyn_addn(infop->b, &outc, 1); + CURLcode result = curlx_dyn_addn(infop->b, &outc, 1); if(result) { infop->merr = result == CURLE_TOO_LARGE ? MERR_TOO_LARGE : MERR_MEM; return 1 ; /* fail */ @@ -1115,7 +1148,7 @@ static int alloc_addbyter(unsigned char outc, void *f) } /* appends the formatted string, returns MERR error code */ -int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save) +int curlx_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save) { struct asprintf info; info.b = dyn; @@ -1123,7 +1156,7 @@ int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save) (void)formatf(&info, alloc_addbyter, format, ap_save); if(info.merr) { - Curl_dyn_free(info.b); + curlx_dyn_free(info.b); return info.merr; } return 0; @@ -1134,16 +1167,16 @@ char *curl_mvaprintf(const char *format, va_list ap_save) struct asprintf info; struct dynbuf dyn; info.b = &dyn; - Curl_dyn_init(info.b, DYN_APRINTF); + curlx_dyn_init(info.b, DYN_APRINTF); info.merr = MERR_OK; (void)formatf(&info, alloc_addbyter, format, ap_save); if(info.merr) { - Curl_dyn_free(info.b); + curlx_dyn_free(info.b); return NULL; } - if(Curl_dyn_len(info.b)) - return Curl_dyn_ptr(info.b); + if(curlx_dyn_len(info.b)) + return curlx_dyn_ptr(info.b); return strdup(""); } @@ -1189,7 +1222,6 @@ int curl_mprintf(const char *format, ...) int retcode; va_list ap_save; /* argument pointer */ va_start(ap_save, format); - retcode = formatf(stdout, fputc_wrapper, format, ap_save); va_end(ap_save); return retcode; diff --git a/Utilities/cmcurl/lib/mqtt.c b/Utilities/cmcurl/lib/mqtt.c index fe242c7784..77e7334613 100644 --- a/Utilities/cmcurl/lib/mqtt.c +++ b/Utilities/cmcurl/lib/mqtt.c @@ -37,7 +37,7 @@ #include "strdup.h" #include "url.h" #include "escape.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "curl_printf.h" #include "curl_memory.h" #include "multiif.h" @@ -46,17 +46,59 @@ /* The last #include file should be: */ #include "memdebug.h" -#define MQTT_MSG_CONNECT 0x10 -#define MQTT_MSG_CONNACK 0x20 -#define MQTT_MSG_PUBLISH 0x30 -#define MQTT_MSG_SUBSCRIBE 0x82 -#define MQTT_MSG_SUBACK 0x90 +/* first byte is command. + second byte is for flags. */ +#define MQTT_MSG_CONNECT 0x10 +/* #define MQTT_MSG_CONNACK 0x20 */ +#define MQTT_MSG_PUBLISH 0x30 +#define MQTT_MSG_SUBSCRIBE 0x82 +#define MQTT_MSG_SUBACK 0x90 #define MQTT_MSG_DISCONNECT 0xe0 +#define MQTT_MSG_PINGREQ 0xC0 +#define MQTT_MSG_PINGRESP 0xD0 #define MQTT_CONNACK_LEN 2 #define MQTT_SUBACK_LEN 3 #define MQTT_CLIENTID_LEN 12 /* "curl0123abcd" */ +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_MQTT_EASY "meta:proto:mqtt:easy" +/* meta key for storing protocol meta at connection */ +#define CURL_META_MQTT_CONN "meta:proto:mqtt:conn" + +enum mqttstate { + MQTT_FIRST, /* 0 */ + MQTT_REMAINING_LENGTH, /* 1 */ + MQTT_CONNACK, /* 2 */ + MQTT_SUBACK, /* 3 */ + MQTT_SUBACK_COMING, /* 4 - the SUBACK remainder */ + MQTT_PUBWAIT, /* 5 - wait for publish */ + MQTT_PUB_REMAIN, /* 6 - wait for the remainder of the publish */ + + MQTT_NOSTATE /* 7 - never used an actual state */ +}; + +struct mqtt_conn { + enum mqttstate state; + enum mqttstate nextstate; /* switch to this after remaining length is + done */ + unsigned int packetid; +}; + +/* protocol-specific transfer-related data */ +struct MQTT { + struct dynbuf sendbuf; + /* when receiving */ + struct dynbuf recvbuf; + size_t npacket; /* byte counter */ + size_t remaining_length; + unsigned char pkt_hd[4]; /* for decoding the arriving packet length */ + struct curltime lastTime; /* last time we sent or received data */ + unsigned char firstbyte; + BIT(pingsent); /* 1 while we wait for ping response */ +}; + + /* * Forward declarations. */ @@ -99,45 +141,72 @@ const struct Curl_handler Curl_handler_mqtt = { PROTOPT_NONE /* flags */ }; +static void mqtt_easy_dtor(void *key, size_t klen, void *entry) +{ + struct MQTT *mq = entry; + (void)key; + (void)klen; + curlx_dyn_free(&mq->sendbuf); + curlx_dyn_free(&mq->recvbuf); + free(mq); +} + +static void mqtt_conn_dtor(void *key, size_t klen, void *entry) +{ + (void)key; + (void)klen; + free(entry); +} + static CURLcode mqtt_setup_conn(struct Curl_easy *data, struct connectdata *conn) { - /* allocate the HTTP-specific struct for the Curl_easy, only to survive - during this request */ + /* setup MQTT specific meta data at easy handle and connection */ + struct mqtt_conn *mqtt; struct MQTT *mq; - (void)conn; - DEBUGASSERT(data->req.p.mqtt == NULL); + + mqtt = calloc(1, sizeof(*mqtt)); + if(!mqtt || + Curl_conn_meta_set(conn, CURL_META_MQTT_CONN, mqtt, mqtt_conn_dtor)) + return CURLE_OUT_OF_MEMORY; 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; + curlx_dyn_init(&mq->recvbuf, DYN_MQTT_RECV); + curlx_dyn_init(&mq->sendbuf, DYN_MQTT_SEND); + if(Curl_meta_set(data, CURL_META_MQTT_EASY, mq, mqtt_easy_dtor)) + return CURLE_OUT_OF_MEMORY; return CURLE_OK; } static CURLcode mqtt_send(struct Curl_easy *data, - char *buf, size_t len) + const char *buf, size_t len) { - CURLcode result = CURLE_OK; - struct MQTT *mq = data->req.p.mqtt; size_t n; + CURLcode result; + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); + + if(!mq) + return CURLE_FAILED_INIT; + result = Curl_xfer_send(data, buf, len, FALSE, &n); if(result) return result; + mq->lastTime = curlx_now(); Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)n); if(len != n) { size_t nsend = len - n; - char *sendleftovers = Curl_memdup(&buf[n], nsend); - if(!sendleftovers) - return CURLE_OUT_OF_MEMORY; - mq->sendleftovers = sendleftovers; - mq->nsend = nsend; - } - else { - mq->sendleftovers = NULL; - mq->nsend = 0; + if(curlx_dyn_len(&mq->sendbuf)) { + DEBUGASSERT(curlx_dyn_len(&mq->sendbuf) >= nsend); + result = curlx_dyn_tail(&mq->sendbuf, nsend); /* keep this much */ + } + else { + result = curlx_dyn_addn(&mq->sendbuf, &buf[n], nsend); + } } + else + curlx_dyn_reset(&mq->sendbuf); return result; } @@ -257,7 +326,6 @@ static CURLcode mqtt_connect(struct Curl_easy *data) int remain_pos = 0; char remain[4] = {0}; size_t packetlen = 0; - size_t payloadlen = 0; size_t start_user = 0; size_t start_pwd = 0; char client_id[MQTT_CLIENTID_LEN + 1] = "curl"; @@ -272,14 +340,11 @@ static CURLcode mqtt_connect(struct Curl_easy *data) const char *passwd = data->state.aptr.passwd ? data->state.aptr.passwd : ""; const size_t plen = strlen(passwd); - - payloadlen = ulen + plen + MQTT_CLIENTID_LEN + 2; - /* The plus 2 are for the MSB and LSB describing the length of the string to - * be added on the payload. Refer to spec 1.5.2 and 1.5.4 */ - if(ulen) - payloadlen += 2; - if(plen) - payloadlen += 2; + const size_t payloadlen = ulen + plen + MQTT_CLIENTID_LEN + 2 + + /* The plus 2s below are for the MSB and LSB describing the length of the + string to be added on the payload. Refer to spec 1.5.2 and 1.5.4 */ + (ulen ? 2 : 0) + + (plen ? 2 : 0); /* getting how much occupy the remain length */ remain_pos = mqtt_encode_len(remain, payloadlen + 10); @@ -288,12 +353,11 @@ static CURLcode mqtt_connect(struct Curl_easy *data) packetlen = payloadlen + 10 + remain_pos + 1; /* allocating packet */ - if(packetlen > 268435455) + if(packetlen > 0xFFFFFFF) return CURLE_WEIRD_SERVER_REPLY; - packet = malloc(packetlen); + packet = calloc(1, packetlen); if(!packet) return CURLE_OUT_OF_MEMORY; - memset(packet, 0, packetlen); /* set initial values for the CONNECT packet */ pos = init_connpack(packet, remain, remain_pos); @@ -309,9 +373,9 @@ static CURLcode mqtt_connect(struct Curl_easy *data) } infof(data, "Using client id '%s'", client_id); - /* position where starts the user payload */ + /* position where the user payload starts */ start_user = pos + 3 + MQTT_CLIENTID_LEN; - /* position where starts the password payload */ + /* position where the password payload starts */ start_pwd = start_user + ulen; /* if username was provided, add it to the packet */ if(ulen) { @@ -320,7 +384,7 @@ static CURLcode mqtt_connect(struct Curl_easy *data) rc = add_user(username, ulen, (unsigned char *)packet, start_user, remain_pos); if(rc) { - failf(data, "Username is too large: [%zu]", ulen); + failf(data, "Username too long: [%zu]", ulen); result = CURLE_WEIRD_SERVER_REPLY; goto end; } @@ -330,7 +394,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: [%zu]", plen); + failf(data, "Password too long: [%zu]", plen); result = CURLE_WEIRD_SERVER_REPLY; goto end; } @@ -349,20 +413,19 @@ end: static CURLcode mqtt_disconnect(struct Curl_easy *data) { - CURLcode result = CURLE_OK; - 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; + return mqtt_send(data, "\xe0\x00", 2); } 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); + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); + size_t rlen; CURLcode result; + if(!mq) + return CURLE_FAILED_INIT; + rlen = curlx_dyn_len(&mq->recvbuf); + if(rlen < nbytes) { unsigned char readbuf[1024]; ssize_t nread; @@ -372,42 +435,49 @@ static CURLcode mqtt_recv_atleast(struct Curl_easy *data, size_t nbytes) if(result) return result; DEBUGASSERT(nread >= 0); - if(Curl_dyn_addn(&mq->recvbuf, readbuf, (size_t)nread)) + if(curlx_dyn_addn(&mq->recvbuf, readbuf, (size_t)nread)) return CURLE_OUT_OF_MEMORY; - rlen = Curl_dyn_len(&mq->recvbuf); + rlen = curlx_dyn_len(&mq->recvbuf); } return (rlen >= nbytes) ? CURLE_OK : CURLE_AGAIN; } 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); + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); + DEBUGASSERT(mq); + if(mq) { + size_t rlen = curlx_dyn_len(&mq->recvbuf); + if(rlen <= nbytes) + curlx_dyn_reset(&mq->recvbuf); + else + curlx_dyn_tail(&mq->recvbuf, rlen - nbytes); + } } static CURLcode mqtt_verify_connack(struct Curl_easy *data) { - struct MQTT *mq = data->req.p.mqtt; + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); CURLcode result; char *ptr; + DEBUGASSERT(mq); + if(!mq) + return CURLE_FAILED_INIT; + result = mqtt_recv_atleast(data, MQTT_CONNACK_LEN); if(result) goto fail; /* verify CONNACK */ - DEBUGASSERT(Curl_dyn_len(&mq->recvbuf) >= MQTT_CONNACK_LEN); - ptr = Curl_dyn_ptr(&mq->recvbuf); + DEBUGASSERT(curlx_dyn_len(&mq->recvbuf) >= MQTT_CONNACK_LEN); + ptr = curlx_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, ptr[0], ptr[1]); - Curl_dyn_reset(&mq->recvbuf); + curlx_dyn_reset(&mq->recvbuf); result = CURLE_WEIRD_SERVER_REPLY; goto fail; } @@ -444,12 +514,16 @@ static CURLcode mqtt_subscribe(struct Curl_easy *data) char encodedsize[4]; size_t n; struct connectdata *conn = data->conn; + struct mqtt_conn *mqtt = Curl_conn_meta_get(conn, CURL_META_MQTT_CONN); + + if(!mqtt) + return CURLE_FAILED_INIT; result = mqtt_get_topic(data, &topic, &topiclen); if(result) goto fail; - conn->proto.mqtt.packetid++; + mqtt->packetid++; packetlen = topiclen + 5; /* packetid + topic (has a two byte length field) + 2 bytes topic length + QoS byte */ @@ -464,14 +538,14 @@ static CURLcode mqtt_subscribe(struct Curl_easy *data) packet[0] = MQTT_MSG_SUBSCRIBE; memcpy(&packet[1], encodedsize, n); - packet[1 + n] = (conn->proto.mqtt.packetid >> 8) & 0xff; - packet[2 + n] = conn->proto.mqtt.packetid & 0xff; + packet[1 + n] = (mqtt->packetid >> 8) & 0xff; + packet[2 + n] = mqtt->packetid & 0xff; packet[3 + n] = (topiclen >> 8) & 0xff; packet[4 + n ] = topiclen & 0xff; memcpy(&packet[5 + n], topic, topiclen); packet[5 + n + topiclen] = 0; /* QoS zero */ - result = mqtt_send(data, (char *)packet, packetlen); + result = mqtt_send(data, (const char *)packet, packetlen); fail: free(topic); @@ -484,25 +558,28 @@ fail: */ static CURLcode mqtt_verify_suback(struct Curl_easy *data) { - struct MQTT *mq = data->req.p.mqtt; + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); struct connectdata *conn = data->conn; - struct mqtt_conn *mqtt = &conn->proto.mqtt; + struct mqtt_conn *mqtt = Curl_conn_meta_get(conn, CURL_META_MQTT_CONN); CURLcode result; char *ptr; + if(!mqtt || !mq) + return CURLE_FAILED_INIT; + result = mqtt_recv_atleast(data, MQTT_SUBACK_LEN); if(result) goto fail; /* verify SUBACK */ - DEBUGASSERT(Curl_dyn_len(&mq->recvbuf) >= MQTT_SUBACK_LEN); - ptr = Curl_dyn_ptr(&mq->recvbuf); + DEBUGASSERT(curlx_dyn_len(&mq->recvbuf) >= MQTT_SUBACK_LEN); + ptr = curlx_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); + curlx_dyn_reset(&mq->recvbuf); result = CURLE_WEIRD_SERVER_REPLY; goto fail; } @@ -558,7 +635,7 @@ static CURLcode mqtt_publish(struct Curl_easy *data) i += topiclen; memcpy(&pkt[i], payload, payloadlen); i += payloadlen; - result = mqtt_send(data, (char *)pkt, i); + result = mqtt_send(data, (const char *)pkt, i); fail: free(pkt); @@ -606,7 +683,10 @@ static void mqstate(struct Curl_easy *data, enum mqttstate nextstate) /* used if state == FIRST */ { struct connectdata *conn = data->conn; - struct mqtt_conn *mqtt = &conn->proto.mqtt; + struct mqtt_conn *mqtt = Curl_conn_meta_get(conn, CURL_META_MQTT_CONN); + DEBUGASSERT(mqtt); + if(!mqtt) + return; #ifdef DEBUGBUILD infof(data, "%s (from %s) (next is %s)", statenames[state], @@ -625,10 +705,14 @@ static CURLcode mqtt_read_publish(struct Curl_easy *data, bool *done) struct connectdata *conn = data->conn; ssize_t nread; size_t remlen; - struct mqtt_conn *mqtt = &conn->proto.mqtt; - struct MQTT *mq = data->req.p.mqtt; + struct mqtt_conn *mqtt = Curl_conn_meta_get(conn, CURL_META_MQTT_CONN); + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); unsigned char packet; + DEBUGASSERT(mqtt); + if(!mqtt || !mq) + return CURLE_FAILED_INIT; + switch(mqtt->state) { MQTT_SUBACK_COMING: case MQTT_SUBACK_COMING: @@ -692,6 +776,9 @@ MQTT_SUBACK_COMING: goto end; } + /* we received something */ + mq->lastTime = curlx_now(); + /* if QoS is set, message contains packet id */ result = Curl_client_write(data, CLIENTWRITE_BODY, buffer, nread); if(result) @@ -714,9 +801,15 @@ end: static CURLcode mqtt_do(struct Curl_easy *data, bool *done) { + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); CURLcode result = CURLE_OK; *done = FALSE; /* unconditionally */ + if(!mq) + return CURLE_FAILED_INIT; + mq->lastTime = curlx_now(); + mq->pingsent = FALSE; + result = mqtt_connect(data); if(result) { failf(data, "Error %d sending MQTT CONNECT request", result); @@ -729,34 +822,73 @@ static CURLcode mqtt_do(struct Curl_easy *data, bool *done) static CURLcode mqtt_done(struct Curl_easy *data, CURLcode status, bool premature) { - struct MQTT *mq = data->req.p.mqtt; + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); (void)status; (void)premature; - Curl_safefree(mq->sendleftovers); - Curl_dyn_free(&mq->recvbuf); + if(mq) { + curlx_dyn_free(&mq->sendbuf); + curlx_dyn_free(&mq->recvbuf); + } return CURLE_OK; } +/* we ping regularly to avoid being disconnected by the server */ +static CURLcode mqtt_ping(struct Curl_easy *data) +{ + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct mqtt_conn *mqtt = Curl_conn_meta_get(conn, CURL_META_MQTT_CONN); + + if(!mqtt || !mq) + return CURLE_FAILED_INIT; + + if(mqtt->state == MQTT_FIRST && + !mq->pingsent && + data->set.upkeep_interval_ms > 0) { + struct curltime t = curlx_now(); + timediff_t diff = curlx_timediff(t, mq->lastTime); + + if(diff > data->set.upkeep_interval_ms) { + /* 0xC0 is PINGREQ, and 0x00 is remaining length */ + unsigned char packet[2] = { 0xC0, 0x00 }; + size_t packetlen = sizeof(packet); + + result = mqtt_send(data, (char *)packet, packetlen); + if(!result) { + mq->pingsent = TRUE; + } + infof(data, "mqtt_ping: sent ping request."); + } + } + return result; +} + static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) { + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct mqtt_conn *mqtt = &conn->proto.mqtt; - struct MQTT *mq = data->req.p.mqtt; ssize_t nread; unsigned char recvbyte; + struct mqtt_conn *mqtt = Curl_conn_meta_get(data->conn, CURL_META_MQTT_CONN); + + if(!mqtt || !mq) + return CURLE_FAILED_INIT; *done = FALSE; - if(mq->nsend) { + if(curlx_dyn_len(&mq->sendbuf)) { /* send the remainder of an outgoing packet */ - char *ptr = mq->sendleftovers; - result = mqtt_send(data, mq->sendleftovers, mq->nsend); - free(ptr); + result = mqtt_send(data, curlx_dyn_ptr(&mq->sendbuf), + curlx_dyn_len(&mq->sendbuf)); if(result) return result; } + result = mqtt_ping(data); + if(result) + return result; + infof(data, "mqtt_doing: state [%d]", (int) mqtt->state); switch(mqtt->state) { case MQTT_FIRST: @@ -770,7 +902,11 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) result = CURLE_RECV_ERROR; break; } - Curl_debug(data, CURLINFO_HEADER_IN, (char *)&mq->firstbyte, 1); + Curl_debug(data, CURLINFO_HEADER_IN, (const char *)&mq->firstbyte, 1); + + /* we received something */ + mq->lastTime = curlx_now(); + /* remember the first byte */ mq->npacket = 0; mqstate(data, MQTT_REMAINING_LENGTH, MQTT_NOSTATE); @@ -780,7 +916,7 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) result = Curl_xfer_recv(data, (char *)&recvbyte, 1, &nread); if(result || !nread) break; - Curl_debug(data, CURLINFO_HEADER_IN, (char *)&recvbyte, 1); + Curl_debug(data, CURLINFO_HEADER_IN, (const char *)&recvbyte, 1); mq->pkt_hd[mq->npacket++] = recvbyte; } while((recvbyte & 0x80) && (mq->npacket < 4)); if(!result && nread && (recvbyte & 0x80)) @@ -801,6 +937,13 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) infof(data, "Got DISCONNECT"); *done = TRUE; } + + /* ping response */ + if(mq->firstbyte == MQTT_MSG_PINGRESP) { + infof(data, "Received ping response."); + mq->pingsent = FALSE; + mqstate(data, MQTT_FIRST, MQTT_PUBWAIT); + } break; case MQTT_CONNACK: result = mqtt_verify_connack(data); diff --git a/Utilities/cmcurl/lib/mqtt.h b/Utilities/cmcurl/lib/mqtt.h index 99ab12a98a..8fb8a33c02 100644 --- a/Utilities/cmcurl/lib/mqtt.h +++ b/Utilities/cmcurl/lib/mqtt.h @@ -28,36 +28,4 @@ extern const struct Curl_handler Curl_handler_mqtt; #endif -enum mqttstate { - MQTT_FIRST, /* 0 */ - MQTT_REMAINING_LENGTH, /* 1 */ - MQTT_CONNACK, /* 2 */ - MQTT_SUBACK, /* 3 */ - MQTT_SUBACK_COMING, /* 4 - the SUBACK remainder */ - MQTT_PUBWAIT, /* 5 - wait for publish */ - MQTT_PUB_REMAIN, /* 6 - wait for the remainder of the publish */ - - MQTT_NOSTATE /* 7 - never used an actual state */ -}; - -struct mqtt_conn { - enum mqttstate state; - enum mqttstate nextstate; /* switch to this after remaining length is - done */ - unsigned int packetid; -}; - -/* protocol-specific transfer-related data */ -struct MQTT { - char *sendleftovers; - size_t nsend; /* size of sendleftovers */ - - /* when receiving */ - 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/Utilities/cmcurl/lib/multi.c b/Utilities/cmcurl/lib/multi.c index 2b05e94a04..64eb1ae854 100644 --- a/Utilities/cmcurl/lib/multi.c +++ b/Utilities/cmcurl/lib/multi.c @@ -36,11 +36,12 @@ #include "share.h" #include "psl.h" #include "multiif.h" +#include "multi_ev.h" #include "sendf.h" -#include "timeval.h" +#include "curlx/timeval.h" #include "http.h" #include "select.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "speedcheck.h" #include "conncache.h" #include "multihandle.h" @@ -57,6 +58,9 @@ #include "curl_memory.h" #include "memdebug.h" +/* initial multi->xfers table size for a full multi */ +#define CURL_XFER_TABLE_SIZE 512 + /* 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 @@ -94,8 +98,6 @@ 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); @@ -104,30 +106,8 @@ static CURLMcode multi_timeout(struct Curl_multi *multi, 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); - -#if defined( DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) -static const char * const multi_statename[]={ - "INIT", - "PENDING", - "SETUP", - "CONNECT", - "RESOLVING", - "CONNECTING", - "TUNNELING", - "PROTOCONNECT", - "PROTOCONNECTING", - "DO", - "DOING", - "DOING_MORE", - "DID", - "PERFORMING", - "RATELIMITING", - "DONE", - "COMPLETED", - "MSGSENT", -}; +#ifdef DEBUGBUILD +static void multi_xfer_tbl_dump(struct Curl_multi *multi); #endif /* function pointer called once when switching TO a state */ @@ -179,31 +159,25 @@ static void mstate(struct Curl_easy *data, CURLMstate state NULL /* MSGSENT */ }; -#if defined(DEBUGBUILD) && defined(CURL_DISABLE_VERBOSE_STRINGS) - (void) lineno; -#endif - if(oldstate == state) /* do not bother when the new state is the same as the old state */ return; - data->mstate = state; - -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - if(data->mstate >= MSTATE_PENDING && - data->mstate < MSTATE_COMPLETED) { - infof(data, - "STATE: %s => %s handle %p; line %d", - multi_statename[oldstate], multi_statename[data->mstate], - (void *)data, lineno); - } +#ifdef DEBUGBUILD + CURL_TRC_M(data, "-> [%s] (line %d)", CURL_MSTATE_NAME(state), lineno); +#else + CURL_TRC_M(data, "-> [%s]", CURL_MSTATE_NAME(state)); #endif + data->mstate = state; + if(state == MSTATE_COMPLETED) { - /* 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) { + /* changing to COMPLETED means it is in process and needs to go */ + DEBUGASSERT(Curl_uint_bset_contains(&data->multi->process, data->mid)); + Curl_uint_bset_remove(&data->multi->process, data->mid); + Curl_uint_bset_remove(&data->multi->pending, data->mid); /* to be sure */ + + if(Curl_uint_bset_empty(&data->multi->process)) { /* free the transfer buffer when we have no more active transfers */ multi_xfer_bufs_free(data->multi); } @@ -220,163 +194,6 @@ static void mstate(struct Curl_easy *data, CURLMstate state #define multistate(x,y) mstate(x,y, __LINE__) #endif -/* - * We add one of these structs to the sockhash for each socket - */ - -struct Curl_sh_entry { - struct Curl_hash transfers; /* hash of transfers using this socket */ - unsigned int action; /* what combined action READ/WRITE this socket waits - for */ - unsigned int users; /* number of transfers using this */ - void *socketp; /* settable by users with curl_multi_assign() */ - unsigned int readers; /* this many transfers want to read */ - unsigned int writers; /* this many transfers want to write */ -}; - -/* look up a given socket in the socket hash, skip invalid sockets */ -static struct Curl_sh_entry *sh_getentry(struct Curl_hash *sh, - curl_socket_t s) -{ - if(s != CURL_SOCKET_BAD) { - /* only look for proper sockets */ - return Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t)); - } - return NULL; -} - -#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) -{ - 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)k2_len; - return !memcmp(k1, k2, k1_len); -} - -static void trhash_dtor(void *nada) -{ - (void)nada; -} - -/* - * The sockhash has its own separate subhash in each entry that need to be - * safely destroyed first. - */ -static void sockhash_destroy(struct Curl_hash *h) -{ - struct Curl_hash_iterator iter; - struct Curl_hash_element *he; - - DEBUGASSERT(h); - Curl_hash_start_iterate(h, &iter); - he = Curl_hash_next_element(&iter); - while(he) { - struct Curl_sh_entry *sh = (struct Curl_sh_entry *)he->ptr; - Curl_hash_destroy(&sh->transfers); - he = Curl_hash_next_element(&iter); - } - Curl_hash_destroy(h); -} - - -/* make sure this socket is present in the hash for this handle */ -static struct Curl_sh_entry *sh_addentry(struct Curl_hash *sh, - curl_socket_t s) -{ - struct Curl_sh_entry *there = sh_getentry(sh, s); - struct Curl_sh_entry *check; - - if(there) { - /* it is present, return fine */ - return there; - } - - /* not present, add it */ - check = calloc(1, sizeof(struct Curl_sh_entry)); - if(!check) - return NULL; /* major failure */ - - Curl_hash_init(&check->transfers, TRHASH_SIZE, trhash, trhash_compare, - trhash_dtor); - - /* make/add new hash entry */ - if(!Curl_hash_add(sh, (char *)&s, sizeof(curl_socket_t), check)) { - Curl_hash_destroy(&check->transfers); - free(check); - return NULL; /* major failure */ - } - - return check; /* things are good in sockhash land */ -} - - -/* delete the given socket + handle from the hash */ -static void sh_delentry(struct Curl_sh_entry *entry, - struct Curl_hash *sh, curl_socket_t s) -{ - Curl_hash_destroy(&entry->transfers); - - /* We remove the hash entry. This will end up in a call to - sh_freeentry(). */ - Curl_hash_delete(sh, (char *)&s, sizeof(curl_socket_t)); -} - -/* - * free a sockhash entry - */ -static void sh_freeentry(void *freethis) -{ - struct Curl_sh_entry *p = (struct Curl_sh_entry *) freethis; - - free(p); -} - -static size_t fd_key_compare(void *k1, size_t k1_len, void *k2, size_t k2_len) -{ - (void) k1_len; (void) k2_len; - - return (*((curl_socket_t *) k1)) == (*((curl_socket_t *) k2)); -} - -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 % (curl_socket_t)slots_num); -} - -/* - * sh_init() creates a new socket hash and returns the handle for it. - * - * Quote from README.multi_socket: - * - * "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. 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, 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 */ @@ -400,7 +217,8 @@ static void multi_addmsg(struct Curl_multi *multi, struct Curl_message *msg) Curl_llist_append(&multi->msglist, msg, &msg->list); } -struct Curl_multi *Curl_multi_handle(size_t hashsize, /* socket hash */ +struct Curl_multi *Curl_multi_handle(unsigned int xfer_table_size, + size_t ev_hashsize, /* event hash */ size_t chashsize, /* connection hash */ size_t dnssize, /* dns hash */ size_t sesssize) /* TLS session cache */ @@ -412,29 +230,47 @@ struct Curl_multi *Curl_multi_handle(size_t hashsize, /* socket hash */ multi->magic = CURL_MULTI_HANDLE; - Curl_init_dnscache(&multi->hostcache, dnssize); - - sh_init(&multi->sockhash, hashsize); - + Curl_dnscache_init(&multi->dnscache, dnssize); + Curl_multi_ev_init(multi, ev_hashsize); + Curl_uint_tbl_init(&multi->xfers, NULL); + Curl_uint_bset_init(&multi->process); + Curl_uint_bset_init(&multi->pending); + Curl_uint_bset_init(&multi->msgsent); 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; - - if(Curl_ssl_scache_create(sesssize, 2, &multi->ssl_scache)) - goto error; - + Curl_hash_str, curlx_str_key_compare, ph_freeentry); 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; multi->max_concurrent_streams = 100; multi->last_timeout_ms = -1; + if(Curl_uint_bset_resize(&multi->process, xfer_table_size) || + Curl_uint_bset_resize(&multi->pending, xfer_table_size) || + Curl_uint_bset_resize(&multi->msgsent, xfer_table_size) || + Curl_uint_tbl_resize(&multi->xfers, xfer_table_size)) + goto error; + + multi->admin = curl_easy_init(); + if(!multi->admin) + goto error; + /* Initialize admin handle to operate inside this multi */ + multi->admin->multi = multi; + multi->admin->state.internal = TRUE; + Curl_llist_init(&multi->admin->state.timeoutlist, NULL); +#ifdef DEBUGBUILD + if(getenv("CURL_DEBUG")) + multi->admin->set.verbose = TRUE; +#endif + Curl_uint_tbl_add(&multi->xfers, multi->admin, &multi->admin->mid); + + if(Curl_cshutdn_init(&multi->cshutdn, multi)) + goto error; + + Curl_cpool_init(&multi->cpool, multi->admin, NULL, chashsize); + + if(Curl_ssl_scache_create(sesssize, 2, &multi->ssl_scache)) + goto error; + #ifdef USE_WINSOCK multi->wsa_event = WSACreateEvent(); if(multi->wsa_event == WSA_INVALID_EVENT) @@ -452,18 +288,30 @@ struct Curl_multi *Curl_multi_handle(size_t hashsize, /* socket hash */ error: - sockhash_destroy(&multi->sockhash); + Curl_multi_ev_cleanup(multi); Curl_hash_destroy(&multi->proto_hash); - Curl_hash_destroy(&multi->hostcache); + Curl_dnscache_destroy(&multi->dnscache); Curl_cpool_destroy(&multi->cpool); + Curl_cshutdn_destroy(&multi->cshutdn, multi->admin); Curl_ssl_scache_destroy(multi->ssl_scache); + if(multi->admin) { + multi->admin->multi = NULL; + Curl_close(&multi->admin); + } + + Curl_uint_bset_destroy(&multi->process); + Curl_uint_bset_destroy(&multi->pending); + Curl_uint_bset_destroy(&multi->msgsent); + Curl_uint_tbl_destroy(&multi->xfers); + free(multi); return NULL; } CURLM *curl_multi_init(void) { - return Curl_multi_handle(CURL_SOCKET_HASH_TABLE_SIZE, + return Curl_multi_handle(CURL_XFER_TABLE_SIZE, + CURL_SOCKET_HASH_TABLE_SIZE, CURL_CONNECTION_HASH_SIZE, CURL_DNS_HASH_SIZE, CURL_TLS_SESSION_SIZE); @@ -483,6 +331,44 @@ static void multi_warn_debug(struct Curl_multi *multi, struct Curl_easy *data) #define multi_warn_debug(x,y) Curl_nop_stmt #endif + +static CURLMcode multi_xfers_add(struct Curl_multi *multi, + struct Curl_easy *data) +{ + /* We want `multi->xfers` to have "sufficient" free rows, so that we do + * have to reuse the `mid` from a just removed easy right away. + * Since uint_tbl and uint_bset is quite memory efficient, + * regard less than 25% free as insufficient. + * (for low capacities, e.g. multi_easy, 4 or less). */ + unsigned int capacity = Curl_uint_tbl_capacity(&multi->xfers); + unsigned int unused = capacity - Curl_uint_tbl_count(&multi->xfers); + unsigned int min_unused = CURLMAX(capacity >> 2, 4); + + if(unused <= min_unused) { + /* make it a 64 multiple, since our bitsets frow by that and + * small (easy_multi) grows to at least 64 on first resize. */ + unsigned int newsize = (((capacity + min_unused) + 63) / 64) * 64; + DEBUGASSERT(newsize > capacity); + /* Grow the bitsets first. Should one fail, we do not need + * to downsize the already resized ones. The sets continue + * to work properly when larger than the table, but not + * the other way around. */ + if(Curl_uint_bset_resize(&multi->process, newsize) || + Curl_uint_bset_resize(&multi->pending, newsize) || + Curl_uint_bset_resize(&multi->msgsent, newsize) || + Curl_uint_tbl_resize(&multi->xfers, newsize)) + return CURLM_OUT_OF_MEMORY; + CURL_TRC_M(data, "increased xfer table size to %u", newsize); + } + /* Insert the easy into the table now that MUST have room for it */ + if(!Curl_uint_tbl_add(&multi->xfers, data, &data->mid)) { + DEBUGASSERT(0); + return CURLM_OUT_OF_MEMORY; + } + return CURLM_OK; +} + + CURLMcode curl_multi_add_handle(CURLM *m, CURL *d) { CURLMcode rc; @@ -507,10 +393,15 @@ CURLMcode curl_multi_add_handle(CURLM *m, CURL *d) if(multi->dead) { /* a "dead" handle cannot get added transfers while any existing easy handles are still alive - but if there are none alive anymore, it is - fine to start over and unmark the "deadness" of this handle */ - if(multi->num_alive) + fine to start over and unmark the "deadness" of this handle. + This means only the admin handle MUST be present. */ + if((Curl_uint_tbl_count(&multi->xfers) != 1) || + !Curl_uint_tbl_contains(&multi->xfers, 0)) return CURLM_ABORTED_BY_CALLBACK; multi->dead = FALSE; + Curl_uint_bset_clear(&multi->process); + Curl_uint_bset_clear(&multi->pending); + Curl_uint_bset_clear(&multi->msgsent); } if(data->multi_easy) { @@ -520,6 +411,10 @@ CURLMcode curl_multi_add_handle(CURLM *m, CURL *d) data->multi_easy = NULL; } + /* Insert the easy into the multi->xfers table, assigning it a `mid`. */ + if(multi_xfers_add(multi, data)) + return CURLM_OUT_OF_MEMORY; + /* Initialize timeout list for this handle */ Curl_llist_init(&data->state.timeoutlist, NULL); @@ -549,20 +444,14 @@ CURLMcode curl_multi_add_handle(CURLM *m, CURL *d) rc = Curl_update_timer(multi); if(rc) { data->multi = NULL; /* not anymore */ + Curl_uint_tbl_remove(&multi->xfers, data->mid); + data->mid = UINT_MAX; return rc; } /* set the easy handle */ multistate(data, MSTATE_INIT); - /* for multi interface connections, we share DNS cache automatically if the - easy handle's one is currently not set. */ - if(!data->dns.hostcache || - (data->dns.hostcachetype == HCACHE_NONE)) { - data->dns.hostcache = &multi->hostcache; - data->dns.hostcachetype = HCACHE_MULTI; - } - #ifdef USE_LIBPSL /* Do the same for PSL. */ if(data->share && (data->share->specifier & (1 << CURL_LOCK_DATA_PSL))) @@ -571,23 +460,25 @@ CURLMcode curl_multi_add_handle(CURLM *m, CURL *d) data->psl = &multi->psl; #endif - /* add the easy handle to the process list */ - Curl_llist_append(&multi->process, data, &data->multi_queue); - - /* increase the node-counter */ - multi->num_easy++; - - /* increase the alive-counter */ - multi->num_alive++; - - /* the identifier inside the multi instance */ - data->mid = multi->next_easy_mid++; - if(multi->next_easy_mid <= 0) - multi->next_easy_mid = 0; + /* add the easy handle to the process set */ + Curl_uint_bset_add(&multi->process, data->mid); + ++multi->xfers_alive; Curl_cpool_xfer_init(data); multi_warn_debug(multi, data); + /* The admin handle only ever has default timeouts set. To improve the + state somewhat we clone the timeouts from each added handle so that the + admin handle always has the same timeouts as the most recently added + easy handle. */ + multi->admin->set.timeout = data->set.timeout; + multi->admin->set.server_response_timeout = + data->set.server_response_timeout; + multi->admin->set.no_signal = data->set.no_signal; + + CURL_TRC_M(data, "added to multi, mid=%u, running=%u, total=%u", + data->mid, Curl_multi_xfers_running(multi), + Curl_uint_tbl_count(&multi->xfers)); return CURLM_OK; } @@ -619,20 +510,21 @@ static void multi_done_locked(struct connectdata *conn, Curl_detach_connection(data); + CURL_TRC_M(data, "multi_done_locked, in use=%u", + Curl_uint_spbset_count(&conn->xfers_attached)); 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))); + CURL_TRC_M(data, "Connection still in use %u, no more multi_done now!", + Curl_uint_spbset_count(&conn->xfers_attached)); 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); + Curl_resolv_unlink(data, &data->state.dns[0]); /* done with this */ + Curl_resolv_unlink(data, &data->state.dns[1]); + Curl_dnscache_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 @@ -660,14 +552,14 @@ static void multi_done_locked(struct connectdata *conn, #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))); + CURL_TRC_M(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); + Curl_conn_terminate(data, conn, mdctx->premature); } else { /* the connection is no longer in use by any transfer */ @@ -703,21 +595,15 @@ static CURLcode multi_done(struct Curl_easy *data, 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", - multi_statename[data->mstate], - (int)status, (int)premature, data->state.done)); -#else - DEBUGF(infof(data, "multi_done: status: %d prem: %d done: %d", - (int)status, (int)premature, data->state.done)); -#endif + CURL_TRC_M(data, "multi_done: status: %d prem: %d done: %d", + (int)status, (int)premature, data->state.done); if(data->state.done) /* Stop if multi_done() has already been called */ return CURLE_OK; - /* Stop the resolver and free its own resources (but not dns_entry yet). */ - Curl_resolver_kill(data); + /* Shut down any ongoing async resolver operation. */ + Curl_async_shutdown(data); /* Cleanup possible redirect junk */ Curl_safefree(data->req.newurl); @@ -738,7 +624,7 @@ static CURLcode multi_done(struct Curl_easy *data, } /* this calls the protocol-specific function pointer previously set */ - if(conn->handler->done) + if(conn->handler->done && (data->mstate >= MSTATE_PROTOCONNECT)) result = conn->handler->done(data, status, premature); else result = status; @@ -792,6 +678,7 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d) struct Curl_llist_node *e; CURLMcode rc; bool removed_timer = FALSE; + unsigned int mid; /* First, make some basic checks that the CURLM handle is a good handle */ if(!GOOD_MULTI_HANDLE(multi)) @@ -809,7 +696,11 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d) if(data->multi != multi) return CURLM_BAD_EASY_HANDLE; - if(!multi->num_easy) { + if(data->mid == UINT_MAX) { + DEBUGASSERT(0); + return CURLM_INTERNAL_ERROR; + } + if(Curl_uint_tbl_get(&multi->xfers, data->mid) != data) { DEBUGASSERT(0); return CURLM_INTERNAL_ERROR; } @@ -821,12 +712,6 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d) /* 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. */ - if(premature) { - /* this handle is "alive" so we need to count down the total number of - alive connections when this is removed */ - multi->num_alive--; - } - if(data->conn && data->mstate > MSTATE_DO && data->mstate < MSTATE_COMPLETED) { @@ -849,30 +734,20 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d) called. Do it after multi_done() in case that sets another time! */ removed_timer = Curl_expire_clear(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 - multi_done() call above */ - data->dns.hostcache = NULL; - data->dns.hostcachetype = HCACHE_NONE; - } + /* If in `msgsent`, it was deducted from `multi->xfers_alive` already. */ + if(!Curl_uint_bset_contains(&multi->msgsent, data->mid)) + --multi->xfers_alive; Curl_wildcard_dtor(&data->wildcard); - /* change state without using multistate(), only to make singlesocket() do - what we want */ data->mstate = MSTATE_COMPLETED; - /* This ignores the return code even in case of problems because there is - nothing more to do about that, here */ - (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 */ Curl_detach_connection(data); + /* Tell event handling that this transfer is definitely going away */ + Curl_multi_ev_xfer_done(multi, data); + if(data->set.connect_only && !data->multi_easy) { /* This removes a handle that was part the multi interface that used CONNECT_ONLY, that connection is now left alive but since this handle @@ -886,7 +761,7 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d) curl_socket_t s; s = Curl_getconnectinfo(data, &c); if((s != CURL_SOCKET_BAD) && c) { - Curl_cpool_disconnect(data, c, TRUE); + Curl_conn_terminate(data, c, TRUE); } } @@ -914,12 +789,19 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d) } } - data->multi = NULL; /* clear the association to this multi handle */ - data->mid = -1; + /* clear the association to this multi handle */ + mid = data->mid; + DEBUGASSERT(Curl_uint_tbl_contains(&multi->xfers, mid)); + Curl_uint_tbl_remove(&multi->xfers, mid); + Curl_uint_bset_remove(&multi->process, mid); + Curl_uint_bset_remove(&multi->pending, mid); + Curl_uint_bset_remove(&multi->msgsent, mid); + data->multi = NULL; + data->mid = UINT_MAX; + data->master_mid = UINT_MAX; /* NOTE NOTE NOTE We do not touch the easy handle here! */ - multi->num_easy--; /* one less to care about now */ process_pending_handles(multi); if(removed_timer) { @@ -927,6 +809,10 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d) if(rc) return rc; } + + CURL_TRC_M(data, "removed from multi, mid=%u, running=%u, total=%u", + mid, Curl_multi_xfers_running(multi), + Curl_uint_tbl_count(&multi->xfers)); return CURLM_OK; } @@ -946,7 +832,9 @@ void Curl_detach_connection(struct Curl_easy *data) { struct connectdata *conn = data->conn; if(conn) { - Curl_node_remove(&data->conn_queue); + Curl_uint_spbset_remove(&conn->xfers_attached, data->mid); + if(Curl_uint_spbset_empty(&conn->xfers_attached)) + conn->attached_multi = NULL; } data->conn = NULL; } @@ -963,7 +851,12 @@ void Curl_attach_connection(struct Curl_easy *data, DEBUGASSERT(!data->conn); DEBUGASSERT(conn); data->conn = conn; - Curl_llist_append(&conn->easyq, data, &data->conn_queue); + Curl_uint_spbset_add(&conn->xfers_attached, data->mid); + /* all attached transfers must be from the same multi */ + if(!conn->attached_multi) + conn->attached_multi = data->multi; + DEBUGASSERT(conn->attached_multi == data->multi); + if(conn->handler && conn->handler->attach) conn->handler->attach(data, conn); } @@ -1068,13 +961,15 @@ static int perform_getsock(struct Curl_easy *data, curl_socket_t *sock) /* 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) +void Curl_multi_getsock(struct Curl_easy *data, + struct easy_pollset *ps, + const char *caller) { bool expect_sockets = TRUE; - /* The no connection case can happen when this is called from - curl_multi_remove_handle() => singlesocket() => multi_getsock(). - */ + + /* If the transfer has no connection, this is fine. Happens when + called via curl_multi_remove_handle() => Curl_multi_ev_assess() => + Curl_multi_getsock(). */ Curl_pollset_reset(data, ps); if(!data->conn) return; @@ -1098,30 +993,30 @@ static void multi_getsock(struct Curl_easy *data, case MSTATE_CONNECTING: case MSTATE_TUNNELING: Curl_pollset_add_socks(data, ps, connecting_getsock); - Curl_conn_adjust_pollset(data, ps); + Curl_conn_adjust_pollset(data, data->conn, ps); break; case MSTATE_PROTOCONNECT: case MSTATE_PROTOCONNECTING: Curl_pollset_add_socks(data, ps, protocol_getsock); - Curl_conn_adjust_pollset(data, ps); + Curl_conn_adjust_pollset(data, data->conn, ps); break; case MSTATE_DO: case MSTATE_DOING: Curl_pollset_add_socks(data, ps, doing_getsock); - Curl_conn_adjust_pollset(data, ps); + Curl_conn_adjust_pollset(data, data->conn, ps); break; case MSTATE_DOING_MORE: Curl_pollset_add_socks(data, ps, domore_getsock); - Curl_conn_adjust_pollset(data, ps); + Curl_conn_adjust_pollset(data, data->conn, ps); break; case MSTATE_DID: /* same as PERFORMING in regard to polling */ case MSTATE_PERFORMING: Curl_pollset_add_socks(data, ps, perform_getsock); - Curl_conn_adjust_pollset(data, ps); + Curl_conn_adjust_pollset(data, data->conn, ps); break; case MSTATE_RATELIMITING: @@ -1143,6 +1038,35 @@ static void multi_getsock(struct Curl_easy *data, break; } + switch(ps->num) { + case 0: + CURL_TRC_M(data, "%s pollset[], timeouts=%zu, paused %d/%d (r/w)", + caller, Curl_llist_count(&data->state.timeoutlist), + Curl_creader_is_paused(data), Curl_cwriter_is_paused(data)); + break; + case 1: + CURL_TRC_M(data, "%s pollset[fd=%" FMT_SOCKET_T " %s%s], timeouts=%zu", + caller, ps->sockets[0], + (ps->actions[0] & CURL_POLL_IN) ? "IN" : "", + (ps->actions[0] & CURL_POLL_OUT) ? "OUT" : "", + Curl_llist_count(&data->state.timeoutlist)); + break; + case 2: + CURL_TRC_M(data, "%s pollset[fd=%" FMT_SOCKET_T " %s%s, " + "fd=%" FMT_SOCKET_T " %s%s], timeouts=%zu", + caller, ps->sockets[0], + (ps->actions[0] & CURL_POLL_IN) ? "IN" : "", + (ps->actions[0] & CURL_POLL_OUT) ? "OUT" : "", + ps->sockets[1], + (ps->actions[1] & CURL_POLL_IN) ? "IN" : "", + (ps->actions[1] & CURL_POLL_OUT) ? "OUT" : "", + Curl_llist_count(&data->state.timeoutlist)); + break; + default: + CURL_TRC_M(data, "%s pollset[fds=%u], timeouts=%zu", + caller, ps->num, Curl_llist_count(&data->state.timeoutlist)); + break; + } if(expect_sockets && !ps->num && !Curl_llist_count(&data->state.timeoutlist) && !Curl_cwriter_is_paused(data) && !Curl_creader_is_paused(data) && @@ -1164,9 +1088,8 @@ CURLMcode curl_multi_fdset(CURLM *m, Some easy handles may not have connected to the remote host yet, and then we must make sure that is done. */ int this_max_fd = -1; - struct Curl_llist_node *e; struct Curl_multi *multi = m; - unsigned int i; + unsigned int i, mid; (void)exc_fd_set; /* not used */ if(!GOOD_MULTI_HANDLE(multi)) @@ -1175,32 +1098,41 @@ CURLMcode curl_multi_fdset(CURLM *m, if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) { - struct Curl_easy *data = Curl_node_elem(e); + if(Curl_uint_bset_first(&multi->process, &mid)) { + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + struct easy_pollset ps; - 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 */ + if(!data) { + DEBUGASSERT(0); continue; + } + + Curl_multi_getsock(data, &ps, "curl_multi_fdset"); + for(i = 0; i < ps.num; i++) { + if(!FDSET_SOCK(ps.sockets[i])) + /* pretend it does not exist */ + continue; #if defined(__DJGPP__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Warith-conversion" #endif - 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(ps.actions[i] & CURL_POLL_IN) + FD_SET(ps.sockets[i], read_fd_set); + if(ps.actions[i] & CURL_POLL_OUT) + FD_SET(ps.sockets[i], write_fd_set); #if defined(__DJGPP__) #pragma GCC diagnostic pop #endif - if((int)data->last_poll.sockets[i] > this_max_fd) - this_max_fd = (int)data->last_poll.sockets[i]; + if((int)ps.sockets[i] > this_max_fd) + this_max_fd = (int)ps.sockets[i]; + } } + while(Curl_uint_bset_next(&multi->process, mid, &mid)); } - Curl_cpool_setfds(&multi->cpool, read_fd_set, write_fd_set, &this_max_fd); + Curl_cshutdn_setfds(&multi->cshutdn, multi->admin, + read_fd_set, write_fd_set, &this_max_fd); *max_fd = this_max_fd; @@ -1214,9 +1146,8 @@ CURLMcode curl_multi_waitfds(CURLM *m, { struct Curl_waitfds cwfds; CURLMcode result = CURLM_OK; - struct Curl_llist_node *e; struct Curl_multi *multi = m; - unsigned int need = 0; + unsigned int need = 0, mid; if(!ufds && (size || !fd_count)) return CURLM_BAD_FUNCTION_ARGUMENT; @@ -1228,13 +1159,22 @@ CURLMcode curl_multi_waitfds(CURLM *m, 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); - need += Curl_waitfds_add_ps(&cwfds, &data->last_poll); + if(Curl_uint_bset_first(&multi->process, &mid)) { + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + struct easy_pollset ps; + if(!data) { + DEBUGASSERT(0); + Curl_uint_bset_remove(&multi->process, mid); + continue; + } + Curl_multi_getsock(data, &ps, "curl_multi_waitfds"); + need += Curl_waitfds_add_ps(&cwfds, &ps); + } + while(Curl_uint_bset_next(&multi->process, mid, &mid)); } - need += Curl_cpool_add_waitfds(&multi->cpool, &cwfds); + need += Curl_cshutdn_add_waitfds(&multi->cshutdn, multi->admin, &cwfds); if(need != cwfds.n && ufds) { result = CURLM_OUT_OF_MEMORY; @@ -1279,7 +1219,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, 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; + unsigned int mid; #ifdef USE_WINSOCK WSANETWORKEVENTS wsa_events; @@ -1301,17 +1241,25 @@ static CURLMcode multi_wait(struct Curl_multi *multi, Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK); /* 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(Curl_uint_bset_first(&multi->process, &mid)) { + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + struct easy_pollset ps; + if(!data) { + DEBUGASSERT(0); + Curl_uint_bset_remove(&multi->process, mid); + continue; + } + Curl_multi_getsock(data, &ps, "multi_wait"); + if(Curl_pollfds_add_ps(&cpfds, &ps)) { + result = CURLM_OUT_OF_MEMORY; + goto out; + } } + while(Curl_uint_bset_next(&multi->process, mid, &mid)); } - if(Curl_cpool_add_pollfds(&multi->cpool, &cpfds)) { + if(Curl_cshutdn_add_pollfds(&multi->cshutdn, multi->admin, &cpfds)) { result = CURLM_OUT_OF_MEMORY; goto out; } @@ -1440,23 +1388,14 @@ static CURLMcode multi_wait(struct Curl_multi *multi, #ifdef USE_WINSOCK /* Count up all our own sockets that had activity, and remove them from the event. */ - 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); - } + for(i = 0; i < curl_nfds; ++i) { + wsa_events.lNetworkEvents = 0; + if(WSAEnumNetworkEvents(cpfds.pfds[i].fd, NULL, &wsa_events) == 0) { + if(ret && !pollrc && wsa_events.lNetworkEvents) + retcode++; } + WSAEventSelect(cpfds.pfds[i].fd, multi->wsa_event, 0); } - WSAResetEvent(multi->wsa_event); #else #ifdef ENABLE_WAKEUP @@ -1471,7 +1410,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, when there is no more data, breaking the loop. */ nread = wakeup_read(multi->wakeup_pair[0], buf, sizeof(buf)); if(nread <= 0) { - if(nread < 0 && EINTR == SOCKERRNO) + if(nread < 0 && SOCKEINTR == SOCKERRNO) continue; break; } @@ -1572,11 +1511,11 @@ CURLMcode curl_multi_wakeup(CURLM *m) int err = SOCKERRNO; int return_success; #ifdef USE_WINSOCK - return_success = WSAEWOULDBLOCK == err; + return_success = SOCKEWOULDBLOCK == err; #else - if(EINTR == err) + if(SOCKEINTR == err) continue; - return_success = EWOULDBLOCK == err || EAGAIN == err; + return_success = SOCKEWOULDBLOCK == err || EAGAIN == err; #endif if(!return_success) return CURLM_WAKEUP_FAILURE; @@ -1629,10 +1568,15 @@ CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, rc = curl_multi_add_handle(multi, data); if(!rc) { struct SingleRequest *k = &data->req; + CURLcode result; /* pass in NULL for 'conn' here since we do not want to init the connection, only this transfer */ - Curl_init_do(data, NULL); + result = Curl_init_do(data, NULL); + if(result) { + curl_multi_remove_handle(multi, data); + return CURLM_INTERNAL_ERROR; + } /* take this handle to the perform state right away */ multistate(data, MSTATE_PERFORMING); @@ -1697,22 +1641,22 @@ static bool multi_handle_timeout(struct Curl_easy *data, since = data->progress.t_startop; if(data->mstate == MSTATE_RESOLVING) failf(data, "Resolving timed out after %" FMT_TIMEDIFF_T - " milliseconds", Curl_timediff(*now, since)); + " milliseconds", curlx_timediff(*now, since)); else if(data->mstate == MSTATE_CONNECTING) failf(data, "Connection timed out after %" FMT_TIMEDIFF_T - " milliseconds", Curl_timediff(*now, since)); + " milliseconds", curlx_timediff(*now, since)); else { struct SingleRequest *k = &data->req; if(k->size != -1) { 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); + curlx_timediff(*now, since), k->bytecount, k->size); } else { failf(data, "Operation timed out after %" FMT_TIMEDIFF_T " milliseconds with %" FMT_OFF_T " bytes received", - Curl_timediff(*now, since), k->bytecount); + curlx_timediff(*now, since), k->bytecount); } } *result = CURLE_OPERATION_TIMEDOUT; @@ -1910,6 +1854,7 @@ static CURLMcode state_performing(struct Curl_easy *data, data->req.done = TRUE; } } +#ifndef CURL_DISABLE_HTTP else if((CURLE_HTTP2_STREAM == result) && Curl_h2_http_1_1_error(data)) { CURLcode ret = Curl_retry_request(data, &newurl); @@ -1917,7 +1862,8 @@ static CURLMcode state_performing(struct Curl_easy *data, 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; + data->state.http_neg.wanted = CURL_HTTP_V1x; + data->state.http_neg.allowed = CURL_HTTP_V1x; /* clear the error message bit too as we ignore the one we got */ data->state.errorbuf = FALSE; if(!newurl) @@ -1932,6 +1878,7 @@ static CURLMcode state_performing(struct Curl_easy *data, else result = ret; } +#endif if(result) { /* @@ -2207,43 +2154,18 @@ static CURLMcode state_resolving(struct Curl_multi *multi, CURLcode *resultp) { struct Curl_dns_entry *dns = NULL; - struct connectdata *conn = data->conn; - const char *hostname; - CURLcode result = CURLE_OK; + CURLcode result; 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); - + result = Curl_resolv_check(data, &dns); + CURL_TRC_DNS(data, "Curl_resolv_check() -> %d, %s", + result, dns ? "found" : "missing"); /* 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); + rc = Curl_multi_ev_assess_xfer(multi, data); if(rc) return rc; @@ -2251,7 +2173,7 @@ static CURLMcode state_resolving(struct Curl_multi *multi, bool connected; /* Perform the next step in the connection phase, and then move on to the WAITCONNECT state */ - result = Curl_once_resolved(data, &connected); + result = Curl_once_resolved(data, dns, &connected); if(result) /* if Curl_once_resolved() returns failure, the connection struct is @@ -2291,10 +2213,9 @@ static CURLMcode state_connect(struct Curl_multi *multi, /* 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); + /* move from process to pending set */ + Curl_uint_bset_remove(&multi->process, data->mid); + Curl_uint_bset_add(&multi->pending, data->mid); *resultp = CURLE_OK; return rc; } @@ -2362,7 +2283,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, rc = CURLM_OK; if(multi_ischanged(multi, TRUE)) { - DEBUGF(infof(data, "multi changed, check CONNECT_PEND queue")); + CURL_TRC_M(data, "multi changed, check CONNECT_PEND queue"); process_pending_handles(multi); /* multiplexed */ } @@ -2618,7 +2539,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, case MSTATE_PENDING: case MSTATE_MSGSENT: /* handles in these states should NOT be in this list */ - DEBUGASSERT(0); break; default: @@ -2663,7 +2583,7 @@ statemachine_end: We do not have to do this in every case block above where a failure is detected */ Curl_detach_connection(data); - Curl_cpool_disconnect(data, conn, dead_connection); + Curl_conn_terminate(data, conn, dead_connection); } } else if(data->mstate == MSTATE_CONNECT) { @@ -2690,9 +2610,22 @@ statemachine_end: } if(MSTATE_COMPLETED == data->mstate) { - if(data->set.fmultidone) { - /* signal via callback instead */ - data->set.fmultidone(data, result); + if(data->master_mid != UINT_MAX) { + /* A sub transfer, not for msgsent to application */ + struct Curl_easy *mdata; + + CURL_TRC_M(data, "sub xfer done for master %u", data->master_mid); + mdata = Curl_multi_get_easy(multi, data->master_mid); + if(mdata) { + if(mdata->sub_xfer_done) + mdata->sub_xfer_done(mdata, data, result); + else + CURL_TRC_M(data, "master easy %u without sub_xfer_done callback.", + data->master_mid); + } + else { + CURL_TRC_M(data, "master easy %u already gone.", data->master_mid); + } } else { /* now fill in the Curl_message with this info */ @@ -2707,10 +2640,11 @@ statemachine_end: } multistate(data, MSTATE_MSGSENT); - /* 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); + /* remove from the other sets, add to msgsent */ + Curl_uint_bset_remove(&multi->process, data->mid); + Curl_uint_bset_remove(&multi->pending, data->mid); + Curl_uint_bset_add(&multi->msgsent, data->mid); + --multi->xfers_alive; return CURLM_OK; } } while((rc == CURLM_CALL_MULTI_PERFORM) || multi_ischanged(multi, FALSE)); @@ -2724,10 +2658,9 @@ CURLMcode curl_multi_perform(CURLM *m, int *running_handles) { CURLMcode returncode = CURLM_OK; struct Curl_tree *t = NULL; - struct curltime now = Curl_now(); - struct Curl_llist_node *e; - struct Curl_llist_node *n = NULL; + struct curltime now = curlx_now(); struct Curl_multi *multi = m; + unsigned int mid; SIGPIPE_VARIABLE(pipe_st); if(!GOOD_MULTI_HANDLE(multi)) @@ -2737,27 +2670,30 @@ CURLMcode curl_multi_perform(CURLM *m, int *running_handles) return CURLM_RECURSIVE_API_CALL; sigpipe_init(&pipe_st); - for(e = Curl_llist_head(&multi->process); e; e = n) { - struct Curl_easy *data = Curl_node_elem(e); - CURLMcode result; - /* Do the loop and only alter the signal ignore state if the next handle - has a different NO_SIGNAL state than the previous */ - - /* 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; + if(Curl_uint_bset_first(&multi->process, &mid)) { + CURL_TRC_M(multi->admin, "multi_perform(running=%u)", + Curl_multi_xfers_running(multi)); + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + CURLMcode result; + if(!data) { + DEBUGASSERT(0); + Curl_uint_bset_remove(&multi->process, mid); + continue; + } + if(data != multi->admin) { + /* admin handle is processed below */ + sigpipe_apply(data, &pipe_st); + result = multi_runsingle(multi, &now, data); + if(result) + returncode = result; + } } + while(Curl_uint_bset_next(&multi->process, mid, &mid)); } - sigpipe_apply(multi->cpool.idata, &pipe_st); - Curl_cpool_multi_perform(multi); + sigpipe_apply(multi->admin, &pipe_st); + Curl_cshutdn_perform(&multi->cshutdn, multi->admin, CURL_SOCKET_TIMEOUT); sigpipe_restore(&pipe_st); if(multi_ischanged(m, TRUE)) @@ -2790,8 +2726,10 @@ CURLMcode curl_multi_perform(CURLM *m, int *running_handles) } } while(t); - if(running_handles) - *running_handles = (int)multi->num_alive; + if(running_handles) { + unsigned int running = Curl_multi_xfers_running(multi); + *running_handles = (running < INT_MAX) ? (int)running : INT_MAX; + } if(CURLM_OK >= returncode) returncode = Curl_update_timer(multi); @@ -2799,69 +2737,66 @@ CURLMcode curl_multi_perform(CURLM *m, int *running_handles) return returncode; } -/* 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_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(CURLM *m) { struct Curl_multi *multi = m; if(GOOD_MULTI_HANDLE(multi)) { - struct Curl_llist_node *e; - struct Curl_llist_node *n; + void *entry; + unsigned int mid; if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - /* 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, + * close internal ones. admin handle is special */ + if(Curl_uint_tbl_first(&multi->xfers, &mid, &entry)) { + do { + struct Curl_easy *data = entry; + if(!GOOD_EASY_HANDLE(data)) + return CURLM_BAD_HANDLE; - /* First remove all remaining easy handles */ - for(e = Curl_llist_head(&multi->process); e; e = n) { - struct Curl_easy *data = Curl_node_elem(e); +#ifdef DEBUGBUILD + if(mid != data->mid) { + CURL_TRC_M(data, "multi_cleanup: still present with mid=%u, " + "but unexpected data->mid=%u\n", mid, data->mid); + DEBUGASSERT(0); + } +#endif - if(!GOOD_EASY_HANDLE(data)) - return CURLM_BAD_HANDLE; + if(data == multi->admin) + continue; - 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); - if(data->dns.hostcachetype == HCACHE_MULTI) { - /* clear out the usage of the shared DNS cache */ - Curl_hostcache_clean(data, data->dns.hostcache); - data->dns.hostcache = NULL; - data->dns.hostcachetype = HCACHE_NONE; - } + if(!data->state.done && data->conn) + /* if DONE was never called for this handle */ + (void)multi_done(data, CURLE_OK, TRUE); - data->multi = NULL; /* clear the association */ + data->multi = NULL; /* clear the association */ + Curl_uint_tbl_remove(&multi->xfers, mid); + data->mid = UINT_MAX; #ifdef USE_LIBPSL - if(data->psl == &multi->psl) - data->psl = NULL; + if(data->psl == &multi->psl) + data->psl = NULL; #endif + if(data->state.internal) + Curl_close(&data); + } + while(Curl_uint_tbl_next(&multi->xfers, mid, &mid, &entry)); } Curl_cpool_destroy(&multi->cpool); + Curl_cshutdn_destroy(&multi->cshutdn, multi->admin); + if(multi->admin) { + CURL_TRC_M(multi->admin, "multi_cleanup, closing admin handle, done"); + multi->admin->multi = NULL; + Curl_uint_tbl_remove(&multi->xfers, multi->admin->mid); + Curl_close(&multi->admin); + } multi->magic = 0; /* not good anymore */ - sockhash_destroy(&multi->sockhash); + Curl_multi_ev_cleanup(multi); Curl_hash_destroy(&multi->proto_hash); - Curl_hash_destroy(&multi->hostcache); + Curl_dnscache_destroy(&multi->dnscache); Curl_psl_destroy(&multi->psl); Curl_ssl_scache_destroy(multi->ssl_scache); @@ -2877,6 +2812,16 @@ CURLMcode curl_multi_cleanup(CURLM *m) #endif multi_xfer_bufs_free(multi); +#ifdef DEBUGBUILD + if(Curl_uint_tbl_count(&multi->xfers)) { + multi_xfer_tbl_dump(multi); + DEBUGASSERT(0); + } +#endif + Curl_uint_bset_destroy(&multi->process); + Curl_uint_bset_destroy(&multi->pending); + Curl_uint_bset_destroy(&multi->msgsent); + Curl_uint_tbl_destroy(&multi->xfers); free(multi); return CURLM_OK; @@ -2922,233 +2867,14 @@ CURLMsg *curl_multi_info_read(CURLM *m, int *msgs_in_queue) return NULL; } -/* - * singlesocket() checks what sockets we deal with and their "action state" - * and if we have a different state in any of those sockets from last time we - * call the callback accordingly. - */ -static CURLMcode singlesocket(struct Curl_multi *multi, - struct Curl_easy *data) -{ - 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 rc; - - /* 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 < ps->num; i++) { - unsigned char cur_action = ps->actions[i]; - unsigned char last_action = 0; - int comboaction; - - s = ps->sockets[i]; - - /* get it from the hash */ - entry = sh_getentry(&multi->sockhash, s); - if(entry) { - /* check if new for this transfer */ - 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 did not have before, add it to the hash! */ - entry = sh_addentry(&multi->sockhash, s); - if(!entry) - /* fatal */ - return CURLM_OUT_OF_MEMORY; - } - if(last_action && (last_action != cur_action)) { - /* Socket was used already, but different action now */ - if(last_action & CURL_POLL_IN) { - DEBUGASSERT(entry->readers); - entry->readers--; - } - if(last_action & CURL_POLL_OUT) { - DEBUGASSERT(entry->writers); - entry->writers--; - } - if(cur_action & CURL_POLL_IN) { - entry->readers++; - } - if(cur_action & CURL_POLL_OUT) - entry->writers++; - } - 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(cur_action & CURL_POLL_IN) - entry->readers++; - 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)) { - Curl_hash_destroy(&entry->transfers); - return CURLM_OUT_OF_MEMORY; - } - } - - 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(last_action && ((int)entry->action == comboaction)) - /* same, continue */ - continue; - - if(multi->socket_cb) { - 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; - return CURLM_ABORTED_BY_CALLBACK; - } - } - - /* store the current action state */ - entry->action = (unsigned int)comboaction; - } - - /* 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 = last_ps->sockets[i]; - for(j = 0; j < ps->num; j++) { - if(s == ps->sockets[j]) { - /* this is still supervised */ - stillused = TRUE; - break; - } - } - if(stillused) - continue; - - entry = sh_getentry(&multi->sockhash, s); - /* if this is NULL here, the socket has been closed and notified so - already by Curl_multi_closed() */ - if(entry) { - 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) - 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 */ - if(Curl_hash_delete(&entry->transfers, (char *)&data, - sizeof(struct Curl_easy *))) { - DEBUGASSERT(NULL); - } - } - } - } /* for loop over num */ - - return CURLM_OK; -} - -CURLcode Curl_updatesocket(struct Curl_easy *data) -{ - if(singlesocket(data->multi, data)) - return CURLE_ABORTED_BY_CALLBACK; - return CURLE_OK; -} - - -/* - * 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 - * 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. - */ - -void Curl_multi_closed(struct Curl_easy *data, curl_socket_t s) +void Curl_multi_will_close(struct Curl_easy *data, curl_socket_t s) { if(data) { - /* 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) { - set_in_callback(multi, TRUE); - rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, - multi->socket_userp, entry->socketp); - set_in_callback(multi, FALSE); - } - - /* now remove it from the socket hash */ - sh_delentry(entry, &multi->sockhash, s); - if(rc == -1) - /* This just marks the multi handle as "dead" without returning an - error code primarily because this function is used from many - places where propagating an error back is tricky. */ - multi->dead = TRUE; - } + CURL_TRC_M(data, "Curl_multi_will_close fd=%" FMT_SOCKET_T, s); + Curl_multi_ev_socket_done(multi, data, s); } } } @@ -3179,7 +2905,7 @@ static CURLMcode add_next_timeout(struct curltime 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); + timediff_t diff = curlx_timediff_us(node->time, now); if(diff <= 0) /* remove outdated entry */ Curl_node_remove(e); @@ -3240,7 +2966,7 @@ static CURLMcode multi_run_expired(struct multi_run_ctx *mrc) continue; (void)add_next_timeout(mrc->now, multi, data); - if(data == multi->cpool.idata) { + if(data == multi->admin) { mrc->run_cpool = TRUE; continue; } @@ -3250,9 +2976,8 @@ static CURLMcode multi_run_expired(struct multi_run_ctx *mrc) 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); + /* reassess event handling of data */ + result = Curl_multi_ev_assess_xfer(multi, data); if(result) goto out; } @@ -3268,66 +2993,28 @@ static CURLMcode multi_socket(struct Curl_multi *multi, int *running_handles) { CURLMcode result = CURLM_OK; - struct Curl_easy *data = NULL; struct multi_run_ctx mrc; (void)ev_bitmask; memset(&mrc, 0, sizeof(mrc)); mrc.multi = multi; - mrc.now = Curl_now(); + mrc.now = curlx_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) { - for(e = Curl_llist_head(&multi->process); e && !result; - e = Curl_node_next(e)) { - result = singlesocket(multi, Curl_node_elem(e)); - } + /* Reassess event status of all active transfers */ + result = Curl_multi_ev_assess_xfer_bset(multi, &multi->process); } 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 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; - - /* the socket can be shared by many transfers, iterate */ - Curl_hash_start_iterate(&entry->transfers, &iter); - for(he = Curl_hash_next_element(&iter); he; - he = Curl_hash_next_element(&iter)) { - data = (struct Curl_easy *)he->ptr; - DEBUGASSERT(data); - DEBUGASSERT(data->magic == CURLEASY_MAGIC_NUMBER); - - 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); - } - } - } + Curl_multi_ev_expire_xfers(multi, s, &mrc.now, &mrc.run_cpool); } else { /* Asked to run due to time-out. Clear the 'last_expire_ts' variable to @@ -3336,6 +3023,7 @@ static CURLMcode multi_socket(struct Curl_multi *multi, handles the case when the application asks libcurl to run the timeout prematurely. */ memset(&multi->last_expire_ts, 0, sizeof(multi->last_expire_ts)); + mrc.run_cpool = TRUE; } result = multi_run_expired(&mrc); @@ -3348,22 +3036,24 @@ static CURLMcode multi_socket(struct Curl_multi *multi, * 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(); + mrc.now = curlx_now(); result = multi_run_expired(&mrc); } out: if(mrc.run_cpool) { - sigpipe_apply(multi->cpool.idata, &mrc.pipe_st); - Curl_cpool_multi_perform(multi); + sigpipe_apply(multi->admin, &mrc.pipe_st); + Curl_cshutdn_perform(&multi->cshutdn, multi->admin, s); } sigpipe_restore(&mrc.pipe_st); if(multi_ischanged(multi, TRUE)) process_pending_handles(multi); - if(running_handles) - *running_handles = (int)multi->num_alive; + if(running_handles) { + unsigned int running = Curl_multi_xfers_running(multi); + *running_handles = (running < INT_MAX) ? (int)running : INT_MAX; + } if(CURLM_OK >= result) result = Curl_update_timer(multi); @@ -3488,20 +3178,20 @@ static CURLMcode multi_timeout(struct Curl_multi *multi, if(multi->timetree) { /* we have a tree of expire times */ - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); /* splay the lowest to the bottom */ multi->timetree = Curl_splay(tv_zero, multi->timetree); - /* this will not return NULL from a non-emtpy tree, but some compilers + /* this will not return NULL from a non-empty 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) { + curlx_timediff_us(multi->timetree->key, now) > 0) { /* some time left before expiration */ - timediff_t diff = Curl_timediff_ceil(multi->timetree->key, now); + timediff_t diff = curlx_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; @@ -3574,7 +3264,7 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) #endif set_value = TRUE; } - else if(Curl_timediff_us(multi->last_expire_ts, expire_ts)) { + else if(curlx_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. */ @@ -3658,7 +3348,7 @@ multi_addtimeout(struct Curl_easy *data, /* find the correct spot in the list */ 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); + timediff_t diff = curlx_timediff(check->time, node->time); if(diff > 0) break; prev = e; @@ -3672,9 +3362,9 @@ multi_addtimeout(struct Curl_easy *data, return CURLM_OK; } -static void expire_ex(struct Curl_easy *data, - const struct curltime *nowp, - timediff_t milli, expire_id id) +void Curl_expire_ex(struct Curl_easy *data, + const struct curltime *nowp, + timediff_t milli, expire_id id) { struct Curl_multi *multi = data->multi; struct curltime *curr_expire = &data->state.expiretime; @@ -3707,7 +3397,7 @@ static void expire_ex(struct Curl_easy *data, /* 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, *curr_expire); + timediff_t diff = curlx_timediff(set, *curr_expire); int rc; if(diff > 0) { @@ -3745,8 +3435,8 @@ static void expire_ex(struct Curl_easy *data, */ void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id) { - struct curltime now = Curl_now(); - expire_ex(data, &now, milli, id); + struct curltime now = curlx_now(); + Curl_expire_ex(data, &now, milli, id); } /* @@ -3790,9 +3480,7 @@ bool Curl_expire_clear(struct Curl_easy *data) /* clear the timeout list too */ Curl_llist_destroy(list, NULL); -#ifdef DEBUGBUILD - infof(data, "Expire cleared"); -#endif + CURL_TRC_M(data, "Expire cleared"); nowp->tv_sec = 0; nowp->tv_usec = 0; return TRUE; @@ -3803,19 +3491,11 @@ bool Curl_expire_clear(struct Curl_easy *data) 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); - - if(!there) - return CURLM_BAD_SOCKET; - - there->socketp = hashp; - - return CURLM_OK; + return Curl_multi_ev_assign(multi, s, hashp); } static void move_pending_to_connect(struct Curl_multi *multi, @@ -3823,11 +3503,9 @@ static void move_pending_to_connect(struct Curl_multi *multi, { DEBUGASSERT(data->mstate == MSTATE_PENDING); - /* Remove this node from the pending list */ - Curl_node_remove(&data->multi_queue); - - /* put it into the process list */ - Curl_llist_append(&multi->process, data, &data->multi_queue); + /* Remove this node from the pending set, add into process set */ + Curl_uint_bset_remove(&multi->pending, data->mid); + Curl_uint_bset_add(&multi->process, data->mid); multistate(data, MSTATE_CONNECT); @@ -3851,10 +3529,15 @@ static void move_pending_to_connect(struct Curl_multi *multi, */ static void process_pending_handles(struct Curl_multi *multi) { - struct Curl_llist_node *e = Curl_llist_head(&multi->pending); - if(e) { - struct Curl_easy *data = Curl_node_elem(e); - move_pending_to_connect(multi, data); + unsigned int mid; + if(Curl_uint_bset_first(&multi->pending, &mid)) { + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + DEBUGASSERT(data); + if(data) + move_pending_to_connect(multi, data); + } + while(Curl_uint_bset_next(&multi->pending, mid, &mid)); } } @@ -3878,15 +3561,20 @@ unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) CURL **curl_multi_get_handles(CURLM *m) { struct Curl_multi *multi = m; - CURL **a = malloc(sizeof(struct Curl_easy *) * (multi->num_easy + 1)); + void *entry; + unsigned int count = Curl_uint_tbl_count(&multi->xfers); + CURL **a = malloc(sizeof(struct Curl_easy *) * (count + 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; + unsigned int i = 0, mid; + + if(Curl_uint_tbl_first(&multi->xfers, &mid, &entry)) { + do { + struct Curl_easy *data = entry; + DEBUGASSERT(i < count); + if(!data->state.internal) + a[i++] = data; + } + while(Curl_uint_tbl_next(&multi->xfers, mid, &mid, &entry)); } a[i] = NULL; /* last entry is a NULL */ } @@ -4058,31 +3746,53 @@ static void multi_xfer_bufs_free(struct Curl_multi *multi) multi->xfer_sockbuf_borrowed = FALSE; } -struct Curl_easy *Curl_multi_get_handle(struct Curl_multi *multi, - curl_off_t mid) +struct Curl_easy *Curl_multi_get_easy(struct Curl_multi *multi, + unsigned int 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; - } - } + struct Curl_easy *data = mid ? Curl_uint_tbl_get(&multi->xfers, mid) : NULL; + if(data && GOOD_EASY_HANDLE(data)) + return data; + CURL_TRC_M(multi->admin, "invalid easy handle in xfer table for mid=%u", + mid); + Curl_uint_tbl_remove(&multi->xfers, mid); return NULL; } + +unsigned int Curl_multi_xfers_running(struct Curl_multi *multi) +{ + return multi->xfers_alive; +} + +#ifdef DEBUGBUILD +static void multi_xfer_dump(struct Curl_multi *multi, unsigned int mid, + void *entry) +{ + struct Curl_easy *data = entry; + + (void)multi; + if(!data) { + fprintf(stderr, "mid=%u, entry=NULL, bug in xfer table?\n", mid); + } + else { + fprintf(stderr, "mid=%u, magic=%s, p=%p, id=%" FMT_OFF_T ", url=%s\n", + mid, (data->magic == CURLEASY_MAGIC_NUMBER) ? "GOOD" : "BAD!", + (void *)data, data->id, data->state.url); + } +} + +static void multi_xfer_tbl_dump(struct Curl_multi *multi) +{ + unsigned int mid; + void *entry; + fprintf(stderr, "=== multi xfer table (count=%u, capacity=%u\n", + Curl_uint_tbl_count(&multi->xfers), + Curl_uint_tbl_capacity(&multi->xfers)); + if(Curl_uint_tbl_first(&multi->xfers, &mid, &entry)) { + multi_xfer_dump(multi, mid, entry); + while(Curl_uint_tbl_next(&multi->xfers, mid, &mid, &entry)) + multi_xfer_dump(multi, mid, entry); + } + fprintf(stderr, "===\n"); + fflush(stderr); +} +#endif /* DEBUGBUILD */ diff --git a/Utilities/cmcurl/lib/multi_ev.c b/Utilities/cmcurl/lib/multi_ev.c new file mode 100644 index 0000000000..21d2867619 --- /dev/null +++ b/Utilities/cmcurl/lib/multi_ev.c @@ -0,0 +1,637 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "url.h" +#include "cfilters.h" +#include "curl_trc.h" +#include "multiif.h" +#include "curlx/timeval.h" +#include "multi_ev.h" +#include "select.h" +#include "uint-bset.h" +#include "uint-spbset.h" +#include "uint-table.h" +#include "curlx/warnless.h" +#include "multihandle.h" +#include "socks.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +static void mev_in_callback(struct Curl_multi *multi, bool value) +{ + multi->in_callback = value; +} + +#define CURL_MEV_CONN_HASH_SIZE 3 + +/* Information about a socket for which we inform the libcurl application + * what to supervise (CURL_POLL_IN/CURL_POLL_OUT/CURL_POLL_REMOVE) + */ +struct mev_sh_entry { + struct uint_spbset xfers; /* bitset of transfers `mid`s on this socket */ + struct connectdata *conn; /* connection using this socket or NULL */ + void *user_data; /* libcurl app data via curl_multi_assign() */ + unsigned int action; /* CURL_POLL_IN/CURL_POLL_OUT we last told the + * libcurl application to watch out for */ + unsigned int readers; /* this many transfers want to read */ + unsigned int writers; /* this many transfers want to write */ +}; + +static size_t mev_sh_entry_hash(void *key, size_t key_length, size_t slots_num) +{ + curl_socket_t fd = *((curl_socket_t *) key); + (void) key_length; + return (fd % (curl_socket_t)slots_num); +} + +static size_t mev_sh_entry_compare(void *k1, size_t k1_len, + void *k2, size_t k2_len) +{ + (void) k1_len; (void) k2_len; + return (*((curl_socket_t *) k1)) == (*((curl_socket_t *) k2)); +} + +/* sockhash entry destructor callback */ +static void mev_sh_entry_dtor(void *freethis) +{ + struct mev_sh_entry *entry = (struct mev_sh_entry *)freethis; + Curl_uint_spbset_destroy(&entry->xfers); + free(entry); +} + +/* look up a given socket in the socket hash, skip invalid sockets */ +static struct mev_sh_entry * +mev_sh_entry_get(struct Curl_hash *sh, curl_socket_t s) +{ + if(s != CURL_SOCKET_BAD) { + /* only look for proper sockets */ + return Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t)); + } + return NULL; +} + +/* make sure this socket is present in the hash for this handle */ +static struct mev_sh_entry * +mev_sh_entry_add(struct Curl_hash *sh, curl_socket_t s) +{ + struct mev_sh_entry *there = mev_sh_entry_get(sh, s); + struct mev_sh_entry *check; + + if(there) { + /* it is present, return fine */ + return there; + } + + /* not present, add it */ + check = calloc(1, sizeof(struct mev_sh_entry)); + if(!check) + return NULL; /* major failure */ + + Curl_uint_spbset_init(&check->xfers); + + /* make/add new hash entry */ + if(!Curl_hash_add(sh, (char *)&s, sizeof(curl_socket_t), check)) { + mev_sh_entry_dtor(check); + return NULL; /* major failure */ + } + + return check; /* things are good in sockhash land */ +} + +/* delete the given socket entry from the hash */ +static void mev_sh_entry_kill(struct Curl_multi *multi, curl_socket_t s) +{ + Curl_hash_delete(&multi->ev.sh_entries, (char *)&s, sizeof(curl_socket_t)); +} + +static size_t mev_sh_entry_user_count(struct mev_sh_entry *e) +{ + return Curl_uint_spbset_count(&e->xfers) + (e->conn ? 1 : 0); +} + +static bool mev_sh_entry_xfer_known(struct mev_sh_entry *e, + struct Curl_easy *data) +{ + return Curl_uint_spbset_contains(&e->xfers, data->mid); +} + +static bool mev_sh_entry_conn_known(struct mev_sh_entry *e, + struct connectdata *conn) +{ + return (e->conn == conn); +} + +static bool mev_sh_entry_xfer_add(struct mev_sh_entry *e, + struct Curl_easy *data) +{ + /* detect weird values */ + DEBUGASSERT(mev_sh_entry_user_count(e) < 100000); + return Curl_uint_spbset_add(&e->xfers, data->mid); +} + +static bool mev_sh_entry_conn_add(struct mev_sh_entry *e, + struct connectdata *conn) +{ + /* detect weird values */ + DEBUGASSERT(mev_sh_entry_user_count(e) < 100000); + DEBUGASSERT(!e->conn); + if(e->conn) + return FALSE; + e->conn = conn; + return TRUE; +} + + +static bool mev_sh_entry_xfer_remove(struct mev_sh_entry *e, + struct Curl_easy *data) +{ + bool present = Curl_uint_spbset_contains(&e->xfers, data->mid); + if(present) + Curl_uint_spbset_remove(&e->xfers, data->mid); + return present; +} + +static bool mev_sh_entry_conn_remove(struct mev_sh_entry *e, + struct connectdata *conn) +{ + DEBUGASSERT(e->conn == conn); + if(e->conn == conn) { + e->conn = NULL; + return TRUE; + } + return FALSE; +} + +/* Purge any information about socket `s`. + * Let the socket callback know as well when necessary */ +static CURLMcode mev_forget_socket(struct Curl_multi *multi, + struct Curl_easy *data, + curl_socket_t s, + const char *cause) +{ + struct mev_sh_entry *entry = mev_sh_entry_get(&multi->ev.sh_entries, s); + int rc = 0; + + if(!entry) /* we never knew or already forgot about this socket */ + return CURLM_OK; + + /* We managed this socket before, tell the socket callback to forget it. */ + if(multi->socket_cb) { + CURL_TRC_M(data, "ev %s, call(fd=%" FMT_SOCKET_T ", ev=REMOVE)", + cause, s); + mev_in_callback(multi, TRUE); + rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, + multi->socket_userp, entry->user_data); + mev_in_callback(multi, FALSE); + } + + mev_sh_entry_kill(multi, s); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + return CURLM_OK; +} + +static CURLMcode mev_sh_entry_update(struct Curl_multi *multi, + struct Curl_easy *data, + struct mev_sh_entry *entry, + curl_socket_t s, + unsigned char last_action, + unsigned char cur_action) +{ + int rc, comboaction; + + /* we should only be called when the callback exists */ + DEBUGASSERT(multi->socket_cb); + if(!multi->socket_cb) + return CURLM_OK; + + /* Transfer `data` goes from `last_action` to `cur_action` on socket `s` + * with `multi->ev.sh_entries` entry `entry`. Update `entry` and trigger + * `multi->socket_cb` on change, if the callback is set. */ + if(last_action == cur_action) /* nothing from `data` changed */ + return CURLM_OK; + + if(last_action & CURL_POLL_IN) { + DEBUGASSERT(entry->readers); + if(!(cur_action & CURL_POLL_IN)) + entry->readers--; + } + else if(cur_action & CURL_POLL_IN) + entry->readers++; + + if(last_action & CURL_POLL_OUT) { + DEBUGASSERT(entry->writers); + if(!(cur_action & CURL_POLL_OUT)) + entry->writers--; + } + else if(cur_action & CURL_POLL_OUT) + entry->writers++; + + DEBUGASSERT(entry->readers <= mev_sh_entry_user_count(entry)); + DEBUGASSERT(entry->writers <= mev_sh_entry_user_count(entry)); + DEBUGASSERT(entry->writers + entry->readers); + + CURL_TRC_M(data, "ev update fd=%" FMT_SOCKET_T ", action '%s%s' -> '%s%s'" + " (%d/%d r/w)", s, + (last_action & CURL_POLL_IN) ? "IN" : "", + (last_action & CURL_POLL_OUT) ? "OUT" : "", + (cur_action & CURL_POLL_IN) ? "IN" : "", + (cur_action & CURL_POLL_OUT) ? "OUT" : "", + entry->readers, entry->writers); + + comboaction = (entry->writers ? CURL_POLL_OUT : 0) | + (entry->readers ? CURL_POLL_IN : 0); + if(((int)entry->action == comboaction)) /* nothing for socket changed */ + return CURLM_OK; + + CURL_TRC_M(data, "ev update call(fd=%" FMT_SOCKET_T ", ev=%s%s)", + s, (comboaction & CURL_POLL_IN) ? "IN" : "", + (comboaction & CURL_POLL_OUT) ? "OUT" : ""); + mev_in_callback(multi, TRUE); + rc = multi->socket_cb(data, s, comboaction, multi->socket_userp, + entry->user_data); + + mev_in_callback(multi, FALSE); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + entry->action = (unsigned int)comboaction; + return CURLM_OK; +} + +static CURLMcode mev_pollset_diff(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn, + struct easy_pollset *ps, + struct easy_pollset *prev_ps) +{ + struct mev_sh_entry *entry; + curl_socket_t s; + unsigned int i, j; + CURLMcode mresult; + + /* The transfer `data` reports in `ps` the sockets it is interested + * in and which combination of CURL_POLL_IN/CURL_POLL_OUT it wants + * to have monitored for events. + * There can be more than 1 transfer interested in the same socket + * and 1 transfer might be interested in more than 1 socket. + * `prev_ps` is the pollset copy from the previous call here. On + * the 1st call it will be empty. + */ + DEBUGASSERT(ps); + DEBUGASSERT(prev_ps); + + /* Handle changes to sockets the transfer is interested in. */ + for(i = 0; i < ps->num; i++) { + unsigned char last_action; + bool first_time = FALSE; /* data/conn appears first time on socket */ + + s = ps->sockets[i]; + /* Have we handled this socket before? */ + entry = mev_sh_entry_get(&multi->ev.sh_entries, s); + if(!entry) { + /* new socket, add new entry */ + first_time = TRUE; + entry = mev_sh_entry_add(&multi->ev.sh_entries, s); + if(!entry) /* fatal */ + return CURLM_OUT_OF_MEMORY; + CURL_TRC_M(data, "ev new entry fd=%" FMT_SOCKET_T, s); + } + else if(conn) { + first_time = !mev_sh_entry_conn_known(entry, conn); + } + else { + first_time = !mev_sh_entry_xfer_known(entry, data); + } + + /* What was the previous action the transfer had regarding this socket? + * If the transfer is new to the socket, disregard the information + * in `last_poll`, because the socket might have been destroyed and + * reopened. We'd have cleared the sh_entry for that, but the socket + * might still be mentioned in the hashed pollsets. */ + last_action = 0; + if(first_time) { + if(conn) { + if(!mev_sh_entry_conn_add(entry, conn)) + return CURLM_OUT_OF_MEMORY; + } + else { + if(!mev_sh_entry_xfer_add(entry, data)) + return CURLM_OUT_OF_MEMORY; + } + CURL_TRC_M(data, "ev entry fd=%" FMT_SOCKET_T ", added %s #%" FMT_OFF_T + ", total=%u/%d (xfer/conn)", s, + conn ? "connection" : "transfer", + conn ? conn->connection_id : data->mid, + Curl_uint_spbset_count(&entry->xfers), + entry->conn ? 1 : 0); + } + else { + for(j = 0; j < prev_ps->num; j++) { + if(s == prev_ps->sockets[j]) { + last_action = prev_ps->actions[j]; + break; + } + } + } + /* track readers/writers changes and report to socket callback */ + mresult = mev_sh_entry_update(multi, data, entry, s, + last_action, ps->actions[i]); + if(mresult) + return mresult; + } + + /* Handle changes to sockets the transfer is NO LONGER interested in. */ + for(i = 0; i < prev_ps->num; i++) { + bool stillused = FALSE; + + s = prev_ps->sockets[i]; + for(j = 0; j < ps->num; j++) { + if(s == ps->sockets[j]) { + /* socket is still supervised */ + stillused = TRUE; + break; + } + } + if(stillused) + continue; + + entry = mev_sh_entry_get(&multi->ev.sh_entries, s); + /* if entry does not exist, we were either never told about it or + * have already cleaned up this socket via Curl_multi_ev_socket_done(). + * In other words: this is perfectly normal */ + if(!entry) + continue; + + if(conn && !mev_sh_entry_conn_remove(entry, conn)) { + /* `conn` says in `prev_ps` that it had been using a socket, + * but `conn` has not been registered for it. + * This should not happen if our book-keeping is correct? */ + CURL_TRC_M(data, "ev entry fd=%" FMT_SOCKET_T ", conn lost " + "interest but is not registered", s); + DEBUGASSERT(NULL); + continue; + } + + if(!conn && !mev_sh_entry_xfer_remove(entry, data)) { + /* `data` says in `prev_ps` that it had been using a socket, + * but `data` has not been registered for it. + * This should not happen if our book-keeping is correct? */ + CURL_TRC_M(data, "ev entry fd=%" FMT_SOCKET_T ", transfer lost " + "interest but is not registered", s); + DEBUGASSERT(NULL); + continue; + } + + if(mev_sh_entry_user_count(entry)) { + /* track readers/writers changes and report to socket callback */ + mresult = mev_sh_entry_update(multi, data, entry, s, + prev_ps->actions[i], 0); + if(mresult) + return mresult; + CURL_TRC_M(data, "ev entry fd=%" FMT_SOCKET_T ", removed transfer, " + "total=%u/%d (xfer/conn)", s, + Curl_uint_spbset_count(&entry->xfers), + entry->conn ? 1 : 0); + } + else { + mresult = mev_forget_socket(multi, data, s, "last user gone"); + if(mresult) + return mresult; + } + } /* for loop over num */ + + /* Remember for next time */ + memcpy(prev_ps, ps, sizeof(*prev_ps)); + return CURLM_OK; +} + +static void mev_pollset_dtor(void *key, size_t klen, void *entry) +{ + (void)key; + (void)klen; + free(entry); +} + +static struct easy_pollset* +mev_add_new_conn_pollset(struct connectdata *conn) +{ + struct easy_pollset *ps; + + ps = calloc(1, sizeof(*ps)); + if(!ps) + return NULL; + if(Curl_conn_meta_set(conn, CURL_META_MEV_POLLSET, ps, mev_pollset_dtor)) + return NULL; + return ps; +} + +static struct easy_pollset* +mev_add_new_xfer_pollset(struct Curl_easy *data) +{ + struct easy_pollset *ps; + + ps = calloc(1, sizeof(*ps)); + if(!ps) + return NULL; + if(Curl_meta_set(data, CURL_META_MEV_POLLSET, ps, mev_pollset_dtor)) + return NULL; + return ps; +} + +static struct easy_pollset * +mev_get_last_pollset(struct Curl_easy *data, + struct connectdata *conn) +{ + if(data) { + if(conn) + return Curl_conn_meta_get(conn, CURL_META_MEV_POLLSET); + return Curl_meta_get(data, CURL_META_MEV_POLLSET); + } + return NULL; +} + +static void mev_init_cur_pollset(struct easy_pollset *ps, + struct Curl_easy *data, + struct connectdata *conn) +{ + memset(ps, 0, sizeof(*ps)); + if(conn) + Curl_conn_adjust_pollset(data, conn, ps); + else if(data) + Curl_multi_getsock(data, ps, "ev assess"); +} + +static CURLMcode mev_assess(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn) +{ + if(multi && multi->socket_cb) { + struct easy_pollset ps, *last_ps; + + mev_init_cur_pollset(&ps, data, conn); + last_ps = mev_get_last_pollset(data, conn); + + if(!last_ps && ps.num) { + if(conn) + last_ps = mev_add_new_conn_pollset(conn); + else + last_ps = mev_add_new_xfer_pollset(data); + if(!last_ps) + return CURLM_OUT_OF_MEMORY; + } + + if(last_ps) + return mev_pollset_diff(multi, data, conn, &ps, last_ps); + else + DEBUGASSERT(!ps.num); + } + return CURLM_OK; +} + +CURLMcode Curl_multi_ev_assess_xfer(struct Curl_multi *multi, + struct Curl_easy *data) +{ + return mev_assess(multi, data, NULL); +} + +CURLMcode Curl_multi_ev_assess_conn(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn) +{ + return mev_assess(multi, data, conn); +} + +CURLMcode Curl_multi_ev_assess_xfer_bset(struct Curl_multi *multi, + struct uint_bset *set) +{ + unsigned int mid; + CURLMcode result = CURLM_OK; + + if(multi && multi->socket_cb && Curl_uint_bset_first(set, &mid)) { + do { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); + if(data) + result = Curl_multi_ev_assess_xfer(multi, data); + } + while(!result && Curl_uint_bset_next(set, mid, &mid)); + } + return result; +} + + +CURLMcode Curl_multi_ev_assign(struct Curl_multi *multi, + curl_socket_t s, + void *user_data) +{ + struct mev_sh_entry *e = mev_sh_entry_get(&multi->ev.sh_entries, s); + if(!e) + return CURLM_BAD_SOCKET; + e->user_data = user_data; + return CURLM_OK; +} + +void Curl_multi_ev_expire_xfers(struct Curl_multi *multi, + curl_socket_t s, + const struct curltime *nowp, + bool *run_cpool) +{ + struct mev_sh_entry *entry; + + DEBUGASSERT(s != CURL_SOCKET_TIMEOUT); + entry = mev_sh_entry_get(&multi->ev.sh_entries, s); + + /* 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. */ + if(entry) { + struct Curl_easy *data; + unsigned int mid; + + if(Curl_uint_spbset_first(&entry->xfers, &mid)) { + do { + data = Curl_multi_get_easy(multi, mid); + if(data) { + /* Expire with out current now, so we will get it below when + * asking the splaytree for expired transfers. */ + Curl_expire_ex(data, nowp, 0, EXPIRE_RUN_NOW); + } + } + while(Curl_uint_spbset_next(&entry->xfers, mid, &mid)); + } + + if(entry->conn) + *run_cpool = TRUE; + } +} + +void Curl_multi_ev_socket_done(struct Curl_multi *multi, + struct Curl_easy *data, curl_socket_t s) +{ + mev_forget_socket(multi, data, s, "socket done"); +} + +void Curl_multi_ev_xfer_done(struct Curl_multi *multi, + struct Curl_easy *data) +{ + DEBUGASSERT(!data->conn); /* transfer should have been detached */ + if(data != multi->admin) { + (void)mev_assess(multi, data, NULL); + Curl_meta_remove(data, CURL_META_MEV_POLLSET); + } +} + +void Curl_multi_ev_conn_done(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn) +{ + (void)mev_assess(multi, data, conn); + Curl_conn_meta_remove(conn, CURL_META_MEV_POLLSET); +} + +#define CURL_MEV_PS_HASH_SLOTS (991) /* nice prime */ + +void Curl_multi_ev_init(struct Curl_multi *multi, size_t hashsize) +{ + Curl_hash_init(&multi->ev.sh_entries, hashsize, mev_sh_entry_hash, + mev_sh_entry_compare, mev_sh_entry_dtor); +} + +void Curl_multi_ev_cleanup(struct Curl_multi *multi) +{ + Curl_hash_destroy(&multi->ev.sh_entries); +} diff --git a/Utilities/cmcurl/lib/multi_ev.h b/Utilities/cmcurl/lib/multi_ev.h new file mode 100644 index 0000000000..06be842fb9 --- /dev/null +++ b/Utilities/cmcurl/lib/multi_ev.h @@ -0,0 +1,83 @@ +#ifndef HEADER_CURL_MULTI_EV_H +#define HEADER_CURL_MULTI_EV_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 "hash.h" + +struct Curl_easy; +struct Curl_multi; +struct easy_pollset; +struct uint_bset; + +/* meta key for event pollset at easy handle or connection */ +#define CURL_META_MEV_POLLSET "meta:mev:ps" + +struct curl_multi_ev { + struct Curl_hash sh_entries; +}; + +/* Setup/teardown of multi event book-keeping. */ +void Curl_multi_ev_init(struct Curl_multi *multi, size_t hashsize); +void Curl_multi_ev_cleanup(struct Curl_multi *multi); + +/* Assign a 'user_data' to be passed to the socket callback when + * invoked with the given socket. This will fail if this socket + * is not active, e.g. the application has not been told to monitor it. */ +CURLMcode Curl_multi_ev_assign(struct Curl_multi *multi, curl_socket_t s, + void *user_data); + +/* Assess the transfer by getting its current pollset, compute + * any changes to the last one and inform the application's socket + * callback if things have changed. */ +CURLMcode Curl_multi_ev_assess_xfer(struct Curl_multi *multi, + struct Curl_easy *data); +/* Assess all easy handles on the list */ +CURLMcode Curl_multi_ev_assess_xfer_bset(struct Curl_multi *multi, + struct uint_bset *set); +/* Assess the connection by getting its current pollset */ +CURLMcode Curl_multi_ev_assess_conn(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn); + +/* Expire all transfers tied to the given socket */ +void Curl_multi_ev_expire_xfers(struct Curl_multi *multi, + curl_socket_t s, + const struct curltime *nowp, + bool *run_cpool); + +/* Socket will be closed, forget anything we know about it. */ +void Curl_multi_ev_socket_done(struct Curl_multi *multi, + struct Curl_easy *data, curl_socket_t s); + +/* Transfer is removed from the multi */ +void Curl_multi_ev_xfer_done(struct Curl_multi *multi, + struct Curl_easy *data); + +/* Connection is being destroyed */ +void Curl_multi_ev_conn_done(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn); + +#endif /* HEADER_CURL_MULTI_EV_H */ diff --git a/Utilities/cmcurl/lib/multihandle.h b/Utilities/cmcurl/lib/multihandle.h index c0a3d09608..04db02f0b1 100644 --- a/Utilities/cmcurl/lib/multihandle.h +++ b/Utilities/cmcurl/lib/multihandle.h @@ -27,10 +27,17 @@ #include "llist.h" #include "hash.h" #include "conncache.h" +#include "cshutdn.h" +#include "hostip.h" +#include "multi_ev.h" #include "psl.h" #include "socketpair.h" +#include "uint-bset.h" +#include "uint-spbset.h" +#include "uint-table.h" struct connectdata; +struct Curl_easy; struct Curl_message { struct Curl_llist_node list; @@ -38,9 +45,9 @@ struct Curl_message { struct CURLMsg extmsg; }; -/* NOTE: if you add a state here, add the name to the statename[] array as - well! -*/ +/* NOTE: if you add a state here, add the name to the statenames[] array + * in curl_trc.c as well! + */ typedef enum { MSTATE_INIT, /* 0 - start in this state */ MSTATE_PENDING, /* 1 - no connections, waiting for one */ @@ -86,17 +93,18 @@ struct Curl_multi { this multi handle with an easy handle. Set this to CURL_MULTI_HANDLE. */ unsigned int magic; - 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 */ + unsigned int xfers_alive; /* amount of added transfers that have + not yet reached COMPLETE state */ + struct uint_tbl xfers; /* transfers added to this multi */ + /* Each transfer's mid may be present in at most one of these */ + struct uint_bset process; /* transfer being processed */ + struct uint_bset pending; /* transfers in waiting (conn limit etc.) */ + struct uint_bset msgsent; /* transfers done with message for application */ struct Curl_llist msglist; /* a list of messages from completed transfers */ - /* 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 */ + struct Curl_easy *admin; /* internal easy handle for admin operations. + gets assigned `mid` 0 on multi init */ /* callback function and user data pointer for the *socket() API */ curl_socket_callback socket_cb; @@ -106,7 +114,7 @@ struct Curl_multi { curl_push_callback push_cb; void *push_userp; - struct Curl_hash hostcache; /* Hostname cache */ + struct Curl_dnscache dnscache; /* DNS cache */ struct Curl_ssl_scache *ssl_scache; /* TLS session pool */ #ifdef USE_LIBPSL @@ -128,10 +136,9 @@ struct Curl_multi { 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; + /* multi event related things */ + struct curl_multi_ev ev; + /* `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 @@ -140,8 +147,8 @@ struct Curl_multi { * the multi handle is cleaned up (see Curl_hash_add2()).*/ struct Curl_hash proto_hash; - /* Shared connection cache (bundles)*/ - struct cpool cpool; + struct cshutdn cshutdn; /* connection shutdown handling */ + struct cpool cpool; /* connection pool (bundles) */ long max_host_connections; /* if >0, a fixed limit of the maximum number of connections per host */ diff --git a/Utilities/cmcurl/lib/multiif.h b/Utilities/cmcurl/lib/multiif.h index 89ede92c03..eae634ab40 100644 --- a/Utilities/cmcurl/lib/multiif.h +++ b/Utilities/cmcurl/lib/multiif.h @@ -28,8 +28,10 @@ * Prototypes for library-wide functions provided by multi.c */ -CURLcode Curl_updatesocket(struct Curl_easy *data); void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id); +void Curl_expire_ex(struct Curl_easy *data, + const struct curltime *nowp, + timediff_t milli, expire_id id); 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; @@ -45,7 +47,8 @@ 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(size_t hashsize, +struct Curl_multi *Curl_multi_handle(unsigned int xfer_table_size, + size_t hashsize, size_t chashsize, size_t dnssize, size_t sesssize); @@ -64,26 +67,13 @@ struct Curl_multi *Curl_multi_handle(size_t hashsize, /* mask for checking if read and/or write is set for index x */ #define GETSOCK_MASK_RW(x) (GETSOCK_READSOCK(x)|GETSOCK_WRITESOCK(x)) -/* - * 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 - * 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. +/** + * Let the multi handle know that the socket is about to be closed. + * The multi will then remove anything it knows about the socket, so + * when the OS is using this socket (number) again subsequently, + * the internal book keeping will not get confused. */ - -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); +void Curl_multi_will_close(struct Curl_easy *data, curl_socket_t s); /* * Add a handle and move it into PERFORM state at once. For pushed streams. @@ -96,6 +86,10 @@ 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); +void Curl_multi_getsock(struct Curl_easy *data, + struct easy_pollset *ps, + const char *caller); + /** * Borrow the transfer buffer from the multi, suitable * for the given transfer `data`. The buffer may only be used in one @@ -170,9 +164,13 @@ CURLcode Curl_multi_xfer_sockbuf_borrow(struct Curl_easy *data, 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. + * Get the easy handle for the given mid. + * Returns NULL if not found. */ -struct Curl_easy *Curl_multi_get_handle(struct Curl_multi *multi, - curl_off_t id); +struct Curl_easy *Curl_multi_get_easy(struct Curl_multi *multi, + unsigned int mid); + +/* Get the # of transfers current in process/pending. */ +unsigned int Curl_multi_xfers_running(struct Curl_multi *multi); #endif /* HEADER_CURL_MULTIIF_H */ diff --git a/Utilities/cmcurl/lib/netrc.c b/Utilities/cmcurl/lib/netrc.c index ba6708991d..7df3f17fc3 100644 --- a/Utilities/cmcurl/lib/netrc.c +++ b/Utilities/cmcurl/lib/netrc.c @@ -26,15 +26,20 @@ #ifndef CURL_DISABLE_NETRC #ifdef HAVE_PWD_H +#ifdef __AMIGA__ #undef __NO_NET_API /* required for AmigaOS to declare getpwuid() */ +#endif #include +#ifdef __AMIGA__ #define __NO_NET_API #endif +#endif #include #include "netrc.h" #include "strcase.h" #include "curl_get_line.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -73,19 +78,18 @@ static NETRCcode file2memory(const char *filename, struct dynbuf *filebuf) NETRCcode ret = NETRC_FILE_MISSING; /* if it cannot open the file */ FILE *file = fopen(filename, FOPEN_READTEXT); struct dynbuf linebuf; - Curl_dyn_init(&linebuf, MAX_NETRC_LINE); + curlx_dyn_init(&linebuf, MAX_NETRC_LINE); if(file) { ret = NETRC_OK; while(Curl_get_line(&linebuf, file)) { CURLcode result; - const char *line = Curl_dyn_ptr(&linebuf); + const char *line = curlx_dyn_ptr(&linebuf); /* skip comments on load */ - while(ISBLANK(*line)) - line++; + curlx_str_passblanks(&line); if(*line == '#') continue; - result = Curl_dyn_add(filebuf, line); + result = curlx_dyn_add(filebuf, line); if(result) { ret = curl2netrc(result); goto done; @@ -93,7 +97,7 @@ static NETRCcode file2memory(const char *filename, struct dynbuf *filebuf) } } done: - Curl_dyn_free(&linebuf); + curlx_dyn_free(&linebuf); if(file) fclose(file); return ret; @@ -122,7 +126,7 @@ static NETRCcode parsenetrc(struct store_netrc *store, struct dynbuf token; struct dynbuf *filebuf = &store->filebuf; DEBUGASSERT(!*passwordp); - Curl_dyn_init(&token, MAX_NETRC_TOKEN); + curlx_dyn_init(&token, MAX_NETRC_TOKEN); if(!store->loaded) { NETRCcode ret = file2memory(netrcfile, filebuf); @@ -131,16 +135,15 @@ static NETRCcode parsenetrc(struct store_netrc *store, store->loaded = TRUE; } - netrcbuffer = Curl_dyn_ptr(filebuf); + netrcbuffer = curlx_dyn_ptr(filebuf); while(!done) { - char *tok = netrcbuffer; + const char *tok = netrcbuffer; while(tok && !done) { - char *tok_end; + const char *tok_end; bool quoted; - Curl_dyn_reset(&token); - while(ISBLANK(*tok)) - tok++; + curlx_dyn_reset(&token); + curlx_str_passblanks(&tok); /* tok is first non-space letter */ if(state == MACDEF) { if((*tok == '\n') || (*tok == '\r')) @@ -158,7 +161,7 @@ static NETRCcode parsenetrc(struct store_netrc *store, if(!quoted) { size_t len = 0; CURLcode result; - while(!ISSPACE(*tok_end)) { + while(*tok_end > ' ') { tok_end++; len++; } @@ -166,7 +169,7 @@ static NETRCcode parsenetrc(struct store_netrc *store, retcode = NETRC_SYNTAX_ERROR; goto out; } - result = Curl_dyn_addn(&token, tok, len); + result = curlx_dyn_addn(&token, tok, len); if(result) { retcode = curl2netrc(result); goto out; @@ -203,7 +206,7 @@ static NETRCcode parsenetrc(struct store_netrc *store, endquote = TRUE; break; } - result = Curl_dyn_addn(&token, &s, 1); + result = curlx_dyn_addn(&token, &s, 1); if(result) { retcode = curl2netrc(result); goto out; @@ -217,7 +220,12 @@ static NETRCcode parsenetrc(struct store_netrc *store, } } - tok = Curl_dyn_ptr(&token); + if(curlx_dyn_len(&token)) + tok = curlx_dyn_ptr(&token); + else + /* since tok might actually be NULL for no content, set it to blank + to avoid having to deal with it being NULL */ + tok = ""; switch(state) { case NOTHING: @@ -329,7 +337,7 @@ static NETRCcode parsenetrc(struct store_netrc *store, } /* while !done */ out: - Curl_dyn_free(&token); + curlx_dyn_free(&token); if(!retcode) { if(!password && our_login) { /* success without a password, set a blank one */ @@ -348,7 +356,7 @@ out: *passwordp = password; } else { - Curl_dyn_free(filebuf); + curlx_dyn_free(filebuf); if(!specific_login) free(login); free(password); @@ -453,12 +461,12 @@ NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host, void Curl_netrc_init(struct store_netrc *s) { - Curl_dyn_init(&s->filebuf, MAX_NETRC_FILE); + curlx_dyn_init(&s->filebuf, MAX_NETRC_FILE); s->loaded = FALSE; } void Curl_netrc_cleanup(struct store_netrc *s) { - Curl_dyn_free(&s->filebuf); + curlx_dyn_free(&s->filebuf); s->loaded = FALSE; } #endif diff --git a/Utilities/cmcurl/lib/netrc.h b/Utilities/cmcurl/lib/netrc.h index ac0f88622b..ef3bde5212 100644 --- a/Utilities/cmcurl/lib/netrc.h +++ b/Utilities/cmcurl/lib/netrc.h @@ -26,7 +26,7 @@ #include "curl_setup.h" #ifndef CURL_DISABLE_NETRC -#include "dynbuf.h" +#include "curlx/dynbuf.h" struct store_netrc { struct dynbuf filebuf; diff --git a/Utilities/cmcurl/lib/noproxy.c b/Utilities/cmcurl/lib/noproxy.c index 78cc06fa01..f70f57eb5a 100644 --- a/Utilities/cmcurl/lib/noproxy.c +++ b/Utilities/cmcurl/lib/noproxy.c @@ -26,9 +26,10 @@ #ifndef CURL_DISABLE_PROXY -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "strcase.h" #include "noproxy.h" +#include "curlx/strparse.h" #ifdef HAVE_NETINET_IN_H #include @@ -53,9 +54,9 @@ UNITTEST bool Curl_cidr4_match(const char *ipv4, /* 1.2.3.4 address */ /* strange input */ return FALSE; - if(1 != Curl_inet_pton(AF_INET, ipv4, &address)) + if(1 != curlx_inet_pton(AF_INET, ipv4, &address)) return FALSE; - if(1 != Curl_inet_pton(AF_INET, network, &check)) + if(1 != curlx_inet_pton(AF_INET, network, &check)) return FALSE; if(bits && (bits != 32)) { @@ -91,9 +92,9 @@ UNITTEST bool Curl_cidr6_match(const char *ipv6, rest = bits & 0x07; if((bytes > 16) || ((bytes == 16) && rest)) return FALSE; - if(1 != Curl_inet_pton(AF_INET6, ipv6, address)) + if(1 != curlx_inet_pton(AF_INET6, ipv6, address)) return FALSE; - if(1 != Curl_inet_pton(AF_INET6, network, check)) + if(1 != curlx_inet_pton(AF_INET6, network, check)) return FALSE; if(bytes && memcmp(address, check, bytes)) return FALSE; @@ -162,7 +163,7 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy) else { unsigned int address; namelen = strlen(name); - if(1 == Curl_inet_pton(AF_INET, name, &address)) + if(1 == curlx_inet_pton(AF_INET, name, &address)) type = TYPE_IPV4; else { /* ignore trailing dots in the hostname */ @@ -177,8 +178,7 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy) bool match = FALSE; /* pass blanks */ - while(*p && ISBLANK(*p)) - p++; + curlx_str_passblanks(&p); token = p; /* pass over the pattern */ @@ -234,7 +234,7 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy) /* 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 */ + *slash = 0; /* null-terminate there */ } if(type == TYPE_IPV6) match = Curl_cidr6_match(name, check, bits); @@ -247,8 +247,7 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy) return TRUE; } /* if(tokenlen) */ /* pass blanks after pattern */ - while(ISBLANK(*p)) - p++; + curlx_str_passblanks(&p); /* if not a comma, this ends the loop */ if(*p != ',') break; diff --git a/Utilities/cmcurl/lib/openldap.c b/Utilities/cmcurl/lib/openldap.c index 22e5bdd279..6343c40c09 100644 --- a/Utilities/cmcurl/lib/openldap.c +++ b/Utilities/cmcurl/lib/openldap.c @@ -41,12 +41,13 @@ #include #include "urldata.h" +#include "url.h" #include #include "sendf.h" #include "vtls/vtls.h" #include "transfer.h" #include "curl_ldap.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "cfilters.h" #include "connect.h" #include "curl_sasl.h" @@ -202,15 +203,20 @@ struct ldapreqinfo { int nument; }; +/* meta key for storing ldapconninfo at easy handle */ +#define CURL_META_LDAP_EASY "meta:proto:ldap:easy" +/* meta key for storing ldapconninfo at connection */ +#define CURL_META_LDAP_CONN "meta:proto:ldap:conn" + + /* * oldap_state() * * This is the ONLY way to change LDAP state! */ -static void oldap_state(struct Curl_easy *data, ldapstate newstate) +static void oldap_state(struct Curl_easy *data, struct ldapconninfo *li, + ldapstate newstate) { - struct ldapconninfo *ldapc = data->conn->proto.ldapc; - #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char * const names[] = { @@ -225,12 +231,12 @@ static void oldap_state(struct Curl_easy *data, ldapstate newstate) /* LAST */ }; - if(ldapc->state != newstate) + if(li->state != newstate) infof(data, "LDAP %p state change from %s to %s", - (void *)ldapc, names[ldapc->state], names[newstate]); + (void *)li, names[li->state], names[newstate]); #endif - - ldapc->state = newstate; + (void)data; + li->state = newstate; } /* Map some particular LDAP error codes to CURLcode values. */ @@ -277,7 +283,7 @@ static CURLcode oldap_url_parse(struct Curl_easy *data, LDAPURLDesc **ludp) 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])) + if((size_t) rc < CURL_ARRAYSIZE(url_errs)) msg = url_errs[rc]; failf(data, "LDAP local: %s", msg); } @@ -288,9 +294,13 @@ static CURLcode oldap_url_parse(struct Curl_easy *data, LDAPURLDesc **ludp) static CURLcode oldap_parse_login_options(struct connectdata *conn) { CURLcode result = CURLE_OK; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); const char *ptr = conn->options; + DEBUGASSERT(li); + if(!li) + return CURLE_FAILED_INIT; + while(!result && ptr && *ptr) { const char *key = ptr; const char *value; @@ -334,7 +344,12 @@ static CURLcode oldap_setup_connection(struct Curl_easy *data, */ static CURLcode oldap_get_message(struct Curl_easy *data, struct bufref *out) { - struct berval *servercred = data->conn->proto.ldapc->servercred; + struct ldapconninfo *li = + Curl_conn_meta_get(data->conn, CURL_META_LDAP_CONN); + struct berval *servercred = li ? li->servercred : NULL; + DEBUGASSERT(li); + if(!li) + return CURLE_FAILED_INIT; if(!servercred || !servercred->bv_val) return CURLE_WEIRD_SERVER_REPLY; @@ -349,12 +364,15 @@ static CURLcode oldap_perform_auth(struct Curl_easy *data, const char *mech, const struct bufref *initresp) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); struct berval cred; struct berval *pcred = &cred; int rc; - cred.bv_val = (char *) Curl_bufref_ptr(initresp); + DEBUGASSERT(li); + if(!li) + return CURLE_FAILED_INIT; + cred.bv_val = (char *)CURL_UNCONST(Curl_bufref_ptr(initresp)); cred.bv_len = Curl_bufref_len(initresp); if(!cred.bv_val) pcred = NULL; @@ -371,12 +389,14 @@ static CURLcode oldap_continue_auth(struct Curl_easy *data, const char *mech, const struct bufref *resp) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); struct berval cred; struct berval *pcred = &cred; int rc; - cred.bv_val = (char *) Curl_bufref_ptr(resp); + if(!li) + return CURLE_FAILED_INIT; + cred.bv_val = (char *)CURL_UNCONST(Curl_bufref_ptr(resp)); cred.bv_len = Curl_bufref_len(resp); if(!cred.bv_val) pcred = NULL; @@ -391,11 +411,15 @@ 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; - int rc = ldap_sasl_bind(li->ld, NULL, LDAP_SASL_NULL, NULL, NULL, NULL, - &li->msgid); + struct ldapconninfo *li = + Curl_conn_meta_get(data->conn, CURL_META_LDAP_CONN); + int rc; (void)mech; + if(!li) + return CURLE_FAILED_INIT; + rc = ldap_sasl_bind(li->ld, NULL, LDAP_SASL_NULL, NULL, NULL, NULL, + &li->msgid); if(rc != LDAP_SUCCESS) return oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); return CURLE_OK; @@ -405,11 +429,13 @@ static CURLcode oldap_cancel_auth(struct Curl_easy *data, const char *mech) static CURLcode oldap_perform_bind(struct Curl_easy *data, ldapstate newstate) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); char *binddn = NULL; struct berval passwd; int rc; + if(!li) + return CURLE_FAILED_INIT; passwd.bv_val = NULL; passwd.bv_len = 0; @@ -425,39 +451,47 @@ static CURLcode oldap_perform_bind(struct Curl_easy *data, ldapstate newstate) return oldap_map_error(rc, data->state.aptr.user ? CURLE_LOGIN_DENIED : CURLE_LDAP_CANNOT_BIND); - oldap_state(data, newstate); + oldap_state(data, li, newstate); return CURLE_OK; } /* Query the supported SASL authentication mechanisms. */ static CURLcode oldap_perform_mechs(struct Curl_easy *data) { - struct ldapconninfo *li = data->conn->proto.ldapc; + struct ldapconninfo *li = + Curl_conn_meta_get(data->conn, CURL_META_LDAP_CONN); int rc; static const char * const supportedSASLMechanisms[] = { "supportedSASLMechanisms", NULL }; + if(!li) + return CURLE_FAILED_INIT; rc = ldap_search_ext(li->ld, "", LDAP_SCOPE_BASE, "(objectclass=*)", - (char **) supportedSASLMechanisms, 0, + (char **)CURL_UNCONST(supportedSASLMechanisms), 0, NULL, NULL, NULL, 0, &li->msgid); if(rc != LDAP_SUCCESS) return oldap_map_error(rc, CURLE_LOGIN_DENIED); - oldap_state(data, OLDAP_MECHS); + oldap_state(data, li, OLDAP_MECHS); return CURLE_OK; } /* Starts SASL bind. */ static CURLcode oldap_perform_sasl(struct Curl_easy *data) { + struct ldapconninfo *li = + Curl_conn_meta_get(data->conn, CURL_META_LDAP_CONN); saslprogress progress = SASL_IDLE; - struct ldapconninfo *li = data->conn->proto.ldapc; - CURLcode result = Curl_sasl_start(&li->sasl, data, TRUE, &progress); + CURLcode result; - oldap_state(data, OLDAP_SASL); + if(!li) + return CURLE_FAILED_INIT; + result = Curl_sasl_start(&li->sasl, data, TRUE, &progress); + + oldap_state(data, li, OLDAP_SASL); if(!result && progress != SASL_INPROGRESS) - result = CURLE_LOGIN_DENIED; + result = Curl_sasl_is_blocked(&li->sasl, data); return result; } @@ -466,17 +500,22 @@ static Sockbuf_IO ldapsb_tls; static bool ssl_installed(struct connectdata *conn) { - return conn->proto.ldapc->recv != NULL; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); + return li && li->recv != NULL; } static CURLcode oldap_ssl_connect(struct Curl_easy *data, ldapstate newstate) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); bool ssldone = FALSE; - CURLcode result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); + CURLcode result; + + if(!li) + return CURLE_FAILED_INIT; + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); if(!result) { - oldap_state(data, newstate); + oldap_state(data, li, newstate); if(ssldone) { Sockbuf *sb; @@ -495,72 +534,98 @@ static CURLcode oldap_ssl_connect(struct Curl_easy *data, ldapstate newstate) /* Send the STARTTLS request */ static CURLcode oldap_perform_starttls(struct Curl_easy *data) { - struct ldapconninfo *li = data->conn->proto.ldapc; - int rc = ldap_start_tls(li->ld, NULL, NULL, &li->msgid); + struct ldapconninfo *li = + Curl_conn_meta_get(data->conn, CURL_META_LDAP_CONN); + int rc; + if(!li) + return CURLE_FAILED_INIT; + rc = ldap_start_tls(li->ld, NULL, NULL, &li->msgid); if(rc != LDAP_SUCCESS) return oldap_map_error(rc, CURLE_USE_SSL_FAILED); - oldap_state(data, OLDAP_STARTTLS); + oldap_state(data, li, OLDAP_STARTTLS); return CURLE_OK; } #endif +static void oldap_easy_dtor(void *key, size_t klen, void *entry) +{ + struct ldapreqinfo *lr = entry; + (void)key; + (void)klen; + free(lr); +} + +static void oldap_conn_dtor(void *key, size_t klen, void *entry) +{ + struct ldapconninfo *li = entry; + (void)key; + (void)klen; + if(li->ld) { + ldap_unbind_ext(li->ld, NULL, NULL); + li->ld = NULL; + } + free(li); +} + static CURLcode oldap_connect(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; struct ldapconninfo *li; static const int version = LDAP_VERSION3; + char *hosturl = NULL; + CURLcode result; int rc; - char *hosturl; #ifdef CURL_OPENLDAP_DEBUG static int do_trace = -1; #endif (void)done; - 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; + if(!li) { + result = CURLE_OUT_OF_MEMORY; + goto out; } + result = Curl_conn_meta_set(conn, CURL_META_LDAP_CONN, li, oldap_conn_dtor); + if(result) + goto out; + + li->proto = ldap_pvt_url_scheme2proto(data->state.up.scheme); + + /* Initialize the SASL storage */ + Curl_sasl_init(&li->sasl, data, &saslldap); + + result = oldap_parse_login_options(conn); + if(result) + goto out; + 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; + if(!hosturl) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } rc = ldap_init_fd(conn->sock[FIRSTSOCKET], li->proto, hosturl, &li->ld); if(rc) { failf(data, "LDAP local: Cannot connect to %s, %s", hosturl, ldap_err2string(rc)); - free(hosturl); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto out; } - free(hosturl); - #ifdef CURL_OPENLDAP_DEBUG if(do_trace < 0) { const char *env = getenv("CURL_OPENLDAP_TRACE"); - do_trace = (env && strtol(env, NULL, 10) > 0); + curl_off_t e = 0; + if(!curlx_str_number(&env, &e, INT_MAX)) + do_trace = e > 0; } if(do_trace) ldap_set_option(li->ld, LDAP_OPT_DEBUG_LEVEL, &do_trace); @@ -573,23 +638,30 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done) ldap_set_option(li->ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); #ifdef USE_SSL - if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) - return oldap_ssl_connect(data, OLDAP_SSL); + if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + result = oldap_ssl_connect(data, OLDAP_SSL); + goto out; + } if(data->set.use_ssl) { - CURLcode result = oldap_perform_starttls(data); - + result = oldap_perform_starttls(data); if(!result || data->set.use_ssl != CURLUSESSL_TRY) - return result; + goto out; } #endif - if(li->sasl.prefmech != SASL_AUTH_NONE) - return oldap_perform_mechs(data); + if(li->sasl.prefmech != SASL_AUTH_NONE) { + result = oldap_perform_mechs(data); + goto out; + } /* Force bind even if anonymous bind is not needed in protocol version 3 to detect missing version 3 support. */ - return oldap_perform_bind(data, OLDAP_BIND); + result = oldap_perform_bind(data, OLDAP_BIND); + +out: + free(hosturl); + return result; } /* Handle the supported SASL mechanisms query response */ @@ -597,12 +669,14 @@ static CURLcode oldap_state_mechs_resp(struct Curl_easy *data, LDAPMessage *msg, int code) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); int rc; BerElement *ber = NULL; CURLcode result = CURLE_OK; struct berval bv, *bvals; + if(!li) + return CURLE_FAILED_INIT; switch(ldap_msgtype(msg)) { case LDAP_RES_SEARCH_ENTRY: /* Got a list of supported SASL mechanisms. */ @@ -662,11 +736,13 @@ static CURLcode oldap_state_sasl_resp(struct Curl_easy *data, LDAPMessage *msg, int code) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); CURLcode result = CURLE_OK; saslprogress progress; int rc; + if(!li) + return CURLE_FAILED_INIT; li->servercred = NULL; rc = ldap_parse_sasl_bind_result(li->ld, msg, &li->servercred, 0); if(rc != LDAP_SUCCESS) { @@ -676,7 +752,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) - oldap_state(data, OLDAP_STOP); + oldap_state(data, li, OLDAP_STOP); } if(li->servercred) @@ -689,11 +765,14 @@ static CURLcode oldap_state_bind_resp(struct Curl_easy *data, LDAPMessage *msg, int code) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); CURLcode result = CURLE_OK; struct berval *bv = NULL; int rc; + if(!li) + return CURLE_FAILED_INIT; + if(code != LDAP_SUCCESS) return oldap_map_error(code, CURLE_LDAP_CANNOT_BIND); @@ -704,7 +783,7 @@ static CURLcode oldap_state_bind_resp(struct Curl_easy *data, LDAPMessage *msg, result = oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); } else - oldap_state(data, OLDAP_STOP); + oldap_state(data, li, OLDAP_STOP); if(bv) ber_bvfree(bv); @@ -715,12 +794,15 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); LDAPMessage *msg = NULL; struct timeval tv = {0, 0}; int code = LDAP_SUCCESS; int rc; + if(!li) + return CURLE_FAILED_INIT; + if(li->state != OLDAP_SSL && li->state != OLDAP_TLS) { /* Get response to last command. */ rc = ldap_result(li->ld, li->msgid, LDAP_MSG_ONE, &tv, &msg); @@ -795,14 +877,13 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done) if(result) result = oldap_map_error(code, CURLE_USE_SSL_FAILED); else if(ssl_installed(conn)) { - conn->bits.tls_upgraded = TRUE; if(li->sasl.prefmech != SASL_AUTH_NONE) result = oldap_perform_mechs(data); else if(data->state.aptr.user) result = oldap_perform_bind(data, OLDAP_BIND); else { /* Version 3 supported: no bind required */ - oldap_state(data, OLDAP_STOP); + oldap_state(data, li, OLDAP_STOP); result = CURLE_OK; } } @@ -842,7 +923,7 @@ static CURLcode oldap_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); (void) dead_connection; #ifndef USE_SSL (void)data; @@ -861,8 +942,6 @@ static CURLcode oldap_disconnect(struct Curl_easy *data, li->ld = NULL; } Curl_sasl_cleanup(conn, li->sasl.authused); - conn->proto.ldapc = NULL; - free(li); } return CURLE_OK; } @@ -870,50 +949,55 @@ static CURLcode oldap_disconnect(struct Curl_easy *data, static CURLcode oldap_do(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); struct ldapreqinfo *lr; CURLcode result; int rc; LDAPURLDesc *lud; int msgid; + if(!li) + return CURLE_FAILED_INIT; connkeep(conn, "OpenLDAP do"); infof(data, "LDAP local: %s", data->state.url); result = oldap_url_parse(data, &lud); - if(!result) { + if(result) + goto out; + #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); - } + 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); - ldap_free_urldesc(lud); - if(rc != LDAP_SUCCESS) { - failf(data, "LDAP local: ldap_search_ext %s", ldap_err2string(rc)); - result = CURLE_LDAP_SEARCH_FAILED; - } - else { - lr = calloc(1, sizeof(struct ldapreqinfo)); - if(!lr) { - ldap_abandon_ext(li->ld, msgid, NULL, NULL); - result = CURLE_OUT_OF_MEMORY; - } - else { - lr->msgid = msgid; - data->req.p.ldap = lr; - Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); - *done = TRUE; - } - } + rc = ldap_search_ext(li->ld, lud->lud_dn, lud->lud_scope, + lud->lud_filter, lud->lud_attrs, 0, + NULL, NULL, NULL, 0, &msgid); + ldap_free_urldesc(lud); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ldap_search_ext %s", ldap_err2string(rc)); + result = CURLE_LDAP_SEARCH_FAILED; + goto out; } + + lr = calloc(1, sizeof(struct ldapreqinfo)); + if(!lr || + Curl_meta_set(data, CURL_META_LDAP_EASY, lr, oldap_easy_dtor)) { + ldap_abandon_ext(li->ld, msgid, NULL, NULL); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + lr->msgid = msgid; + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); + *done = TRUE; + +out: return result; } @@ -921,7 +1005,7 @@ static CURLcode oldap_done(struct Curl_easy *data, CURLcode res, bool premature) { struct connectdata *conn = data->conn; - struct ldapreqinfo *lr = data->req.p.ldap; + struct ldapreqinfo *lr = Curl_meta_get(data, CURL_META_LDAP_EASY); (void)res; (void)premature; @@ -929,12 +1013,13 @@ static CURLcode oldap_done(struct Curl_easy *data, CURLcode res, if(lr) { /* if there was a search in progress, abandon it */ if(lr->msgid) { - struct ldapconninfo *li = conn->proto.ldapc; - ldap_abandon_ext(li->ld, lr->msgid, NULL, NULL); + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); + if(li && li->ld) { + ldap_abandon_ext(li->ld, lr->msgid, NULL, NULL); + } lr->msgid = 0; } - data->req.p.ldap = NULL; - free(lr); + Curl_meta_remove(data, CURL_META_LDAP_EASY); } return CURLE_OK; @@ -952,13 +1037,13 @@ static CURLcode client_write(struct Curl_easy *data, separator, drop the latter. */ if(!len && plen && prefix[plen - 1] == ' ') plen--; - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *) prefix, plen); + result = Curl_client_write(data, CLIENTWRITE_BODY, prefix, plen); } if(!result && value) { - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *) value, len); + result = Curl_client_write(data, CLIENTWRITE_BODY, value, len); } if(!result && suffix) { - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *) suffix, slen); + result = Curl_client_write(data, CLIENTWRITE_BODY, suffix, slen); } return result; } @@ -967,8 +1052,8 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, size_t len, CURLcode *err) { struct connectdata *conn = data->conn; - struct ldapconninfo *li = conn->proto.ldapc; - struct ldapreqinfo *lr = data->req.p.ldap; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); + struct ldapreqinfo *lr = Curl_meta_get(data, CURL_META_LDAP_EASY); int rc; LDAPMessage *msg = NULL; BerElement *ber = NULL; @@ -982,6 +1067,10 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, (void)len; (void)buf; (void)sockindex; + if(!li || !lr) { + *err = CURLE_FAILED_INIT; + return -1; + } rc = ldap_result(li->ld, lr->msgid, LDAP_MSG_ONE, &tv, &msg); if(rc < 0) { @@ -1088,8 +1177,8 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, /* Binary value, encode to base64. */ if(bvals[i].bv_len) - result = Curl_base64_encode(bvals[i].bv_val, bvals[i].bv_len, - &val_b64, &val_b64_sz); + result = curlx_base64_encode(bvals[i].bv_val, bvals[i].bv_len, + &val_b64, &val_b64_sz); if(!result) result = client_write(data, STRCONST(": "), val_b64, val_b64_sz, STRCONST("\n")); @@ -1167,18 +1256,21 @@ ldapsb_tls_read(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) if(data) { struct connectdata *conn = data->conn; if(conn) { - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); CURLcode err = CURLE_RECV_ERROR; + if(!li) { + SET_SOCKERRNO(SOCKEINVAL); + return -1; + } ret = (li->recv)(data, FIRSTSOCKET, buf, len, &err); if(ret < 0 && err == CURLE_AGAIN) { - SET_SOCKERRNO(EWOULDBLOCK); + SET_SOCKERRNO(SOCKEWOULDBLOCK); } } } return ret; } - static ber_slen_t ldapsb_tls_write(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) { @@ -1187,11 +1279,16 @@ ldapsb_tls_write(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) if(data) { struct connectdata *conn = data->conn; if(conn) { - struct ldapconninfo *li = conn->proto.ldapc; + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); CURLcode err = CURLE_SEND_ERROR; + + if(!li) { + SET_SOCKERRNO(SOCKEINVAL); + return -1; + } ret = (li->send)(data, FIRSTSOCKET, buf, len, FALSE, &err); if(ret < 0 && err == CURLE_AGAIN) { - SET_SOCKERRNO(EWOULDBLOCK); + SET_SOCKERRNO(SOCKEWOULDBLOCK); } } } diff --git a/Utilities/cmcurl/lib/parsedate.c b/Utilities/cmcurl/lib/parsedate.c index 65b231ad5c..7e0c69106c 100644 --- a/Utilities/cmcurl/lib/parsedate.c +++ b/Utilities/cmcurl/lib/parsedate.c @@ -81,8 +81,9 @@ #include #include "strcase.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "parsedate.h" +#include "curlx/strparse.h" /* * parsedate() @@ -100,10 +101,12 @@ static int parsedate(const char *date, time_t *output); #define PARSEDATE_OK 0 #define PARSEDATE_FAIL -1 #define PARSEDATE_LATER 1 +#if defined(HAVE_TIME_T_UNSIGNED) || (SIZEOF_TIME_T < 5) #define PARSEDATE_SOONER 2 +#endif #if !defined(CURL_DISABLE_PARSEDATE) || !defined(CURL_DISABLE_FTP) || \ - !defined(CURL_DISABLE_FILE) + !defined(CURL_DISABLE_FILE) || defined(USE_GNUTLS) /* These names are also used by FTP and FILE code */ const char * const Curl_wkday[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; @@ -253,7 +256,7 @@ static int checktz(const char *check, size_t len) if(len > 4) /* longer than any valid timezone */ return -1; - for(i = 0; i < sizeof(tz)/sizeof(tz[0]); i++) { + for(i = 0; i < CURL_ARRAYSIZE(tz); i++) { size_t ilen = strlen(what->name); if((ilen == len) && strncasecompare(check, what->name, len)) @@ -336,7 +339,7 @@ match: *h = hh; *m = mm; *s = ss; - *endp = (char *)p; + *endp = (char *)CURL_UNCONST(p); return TRUE; } @@ -409,7 +412,7 @@ static int parsedate(const char *date, time_t *output) } else if(ISDIGIT(*date)) { /* a digit */ - int val; + unsigned int val; char *end; if((secnum == -1) && match_time(date, &hournum, &minnum, &secnum, &end)) { @@ -417,29 +420,18 @@ static int parsedate(const char *date, time_t *output) date = end; } else { - long lval; - int error; - int old_errno; - - old_errno = errno; - errno = 0; - lval = strtol(date, &end, 10); - error = errno; - if(errno != old_errno) - errno = old_errno; - - if(error) + curl_off_t lval; + int num_digits = 0; + const char *p = date; + if(curlx_str_number(&p, &lval, 99999999)) return PARSEDATE_FAIL; -#if LONG_MAX != INT_MAX - if((lval > (long)INT_MAX) || (lval < (long)INT_MIN)) - return PARSEDATE_FAIL; -#endif - - val = curlx_sltosi(lval); + /* we know num_digits cannot be larger than 8 */ + num_digits = (int)(p - date); + val = (unsigned int)lval; if((tzoff == -1) && - ((end - date) == 4) && + (num_digits == 4) && (val <= 1400) && (indate < date) && ((date[-1] == '+' || date[-1] == '-'))) { @@ -459,10 +451,10 @@ static int parsedate(const char *date, time_t *output) tzoff = date[-1]=='+' ? -tzoff : tzoff; } - if(((end - date) == 8) && - (yearnum == -1) && - (monnum == -1) && - (mdaynum == -1)) { + else if((num_digits == 8) && + (yearnum == -1) && + (monnum == -1) && + (mdaynum == -1)) { /* 8 digits, no year, month or day yet. This is YYYYMMDD */ found = TRUE; yearnum = val/10000; @@ -494,7 +486,7 @@ static int parsedate(const char *date, time_t *output) if(!found) return PARSEDATE_FAIL; - date = end; + date = p; } } diff --git a/Utilities/cmcurl/lib/pingpong.c b/Utilities/cmcurl/lib/pingpong.c index bae6dd273f..c5513f6050 100644 --- a/Utilities/cmcurl/lib/pingpong.c +++ b/Utilities/cmcurl/lib/pingpong.c @@ -29,6 +29,7 @@ #include "urldata.h" #include "cfilters.h" +#include "connect.h" #include "sendf.h" #include "select.h" #include "progress.h" @@ -50,10 +51,10 @@ timediff_t Curl_pp_state_timeout(struct Curl_easy *data, struct pingpong *pp, bool disconnecting) { - 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; + struct curltime now = curlx_now(); /* if CURLOPT_SERVER_RESPONSE_TIMEOUT is set, use that to determine remaining time, or use pp->response because SERVER_RESPONSE_TIMEOUT is @@ -62,18 +63,20 @@ timediff_t Curl_pp_state_timeout(struct Curl_easy *data, /* Without a requested timeout, we only wait 'response_time' seconds for the full response to arrive before we bail out */ - timeout_ms = response_time - - Curl_timediff(Curl_now(), pp->response); /* spent time */ + timeout_ms = response_time - curlx_timediff(now, pp->response); if(data->set.timeout && !disconnecting) { - /* if timeout is requested, find out how much remaining time we have */ - timediff_t timeout2_ms = data->set.timeout - /* timeout time */ - Curl_timediff(Curl_now(), conn->now); /* spent time */ - + /* if timeout is requested, find out how much overall remains */ + timediff_t timeout2_ms = Curl_timeleft(data, &now, FALSE); /* pick the lowest number */ timeout_ms = CURLMIN(timeout_ms, timeout2_ms); } + if(disconnecting) { + timediff_t total_left_ms = Curl_timeleft(data, NULL, FALSE); + timeout_ms = CURLMIN(timeout_ms, CURLMAX(total_left_ms, 0)); + } + return timeout_ms; } @@ -96,6 +99,7 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, return CURLE_OPERATION_TIMEDOUT; /* already too little time */ } + DEBUGF(infof(data, "pp_statematch, timeout=%" FMT_TIMEDIFF_T, timeout_ms)); if(block) { interval_ms = 1000; /* use 1 second timeout intervals */ if(timeout_ms < interval_ms) @@ -123,7 +127,7 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(data, Curl_now()); + result = Curl_speedcheck(data, curlx_now()); if(result) return result; @@ -135,6 +139,8 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, } else if(rc) result = pp->statemachine(data, data->conn); + else if(disconnecting) + return CURLE_OPERATION_TIMEDOUT; return result; } @@ -142,11 +148,13 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, /* initialize stuff to prepare for reading a fresh new response */ void Curl_pp_init(struct pingpong *pp) { + DEBUGASSERT(!pp->initialised); pp->nread_resp = 0; - pp->response = Curl_now(); /* start response time-out now! */ + pp->response = curlx_now(); /* start response time-out now! */ pp->pending_resp = TRUE; - Curl_dyn_init(&pp->sendbuf, DYN_PINGPPONG_CMD); - Curl_dyn_init(&pp->recvbuf, DYN_PINGPPONG_CMD); + curlx_dyn_init(&pp->sendbuf, DYN_PINGPPONG_CMD); + curlx_dyn_init(&pp->recvbuf, DYN_PINGPPONG_CMD); + pp->initialised = TRUE; } /*********************************************************************** @@ -182,19 +190,19 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, /* cannot send without a connection! */ return CURLE_SEND_ERROR; - Curl_dyn_reset(&pp->sendbuf); - result = Curl_dyn_vaddf(&pp->sendbuf, fmt, args); + curlx_dyn_reset(&pp->sendbuf); + result = curlx_dyn_vaddf(&pp->sendbuf, fmt, args); if(result) return result; /* append CRLF */ - result = Curl_dyn_addn(&pp->sendbuf, "\r\n", 2); + result = curlx_dyn_addn(&pp->sendbuf, "\r\n", 2); if(result) return result; pp->pending_resp = TRUE; - write_len = Curl_dyn_len(&pp->sendbuf); - s = Curl_dyn_ptr(&pp->sendbuf); + write_len = curlx_dyn_len(&pp->sendbuf); + s = curlx_dyn_ptr(&pp->sendbuf); #ifdef HAVE_GSSAPI conn->data_prot = PROT_CMD; @@ -223,7 +231,7 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, else { pp->sendthis = NULL; pp->sendleft = pp->sendsize = 0; - pp->response = Curl_now(); + pp->response = curlx_now(); } return CURLE_OK; @@ -297,10 +305,10 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data, 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); + size_t full = curlx_dyn_len(&pp->recvbuf); /* trim off the "final" leading part */ - Curl_dyn_tail(&pp->recvbuf, full - pp->nfinal); + curlx_dyn_tail(&pp->recvbuf, full - pp->nfinal); pp->nfinal = 0; /* now gone */ } @@ -318,7 +326,7 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data, return CURLE_RECV_ERROR; } - result = Curl_dyn_addn(&pp->recvbuf, buffer, gotbytes); + result = curlx_dyn_addn(&pp->recvbuf, buffer, gotbytes); if(result) return result; @@ -328,8 +336,8 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data, } do { - char *line = Curl_dyn_ptr(&pp->recvbuf); - char *nl = memchr(line, '\n', Curl_dyn_len(&pp->recvbuf)); + char *line = curlx_dyn_ptr(&pp->recvbuf); + char *nl = memchr(line, '\n', curlx_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 */ @@ -355,8 +363,8 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data, 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; + if(curlx_dyn_len(&pp->recvbuf) > length) + pp->overflow = curlx_dyn_len(&pp->recvbuf) - length; else pp->overflow = 0; *size = pp->nread_resp; /* size of the response */ @@ -364,11 +372,11 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data, gotbytes = 0; /* force break out of outer loop */ break; } - if(Curl_dyn_len(&pp->recvbuf) > length) + if(curlx_dyn_len(&pp->recvbuf) > length) /* keep the remaining piece */ - Curl_dyn_tail((&pp->recvbuf), Curl_dyn_len(&pp->recvbuf) - length); + curlx_dyn_tail((&pp->recvbuf), curlx_dyn_len(&pp->recvbuf) - length); else - Curl_dyn_reset(&pp->recvbuf); + curlx_dyn_reset(&pp->recvbuf); } else { /* without a newline, there is no overflow */ @@ -434,21 +442,24 @@ CURLcode Curl_pp_flushsend(struct Curl_easy *data, else { pp->sendthis = NULL; pp->sendleft = pp->sendsize = 0; - pp->response = Curl_now(); + pp->response = curlx_now(); } return CURLE_OK; } CURLcode Curl_pp_disconnect(struct pingpong *pp) { - Curl_dyn_free(&pp->sendbuf); - Curl_dyn_free(&pp->recvbuf); + if(pp->initialised) { + curlx_dyn_free(&pp->sendbuf); + curlx_dyn_free(&pp->recvbuf); + memset(pp, 0, sizeof(*pp)); + } return CURLE_OK; } bool Curl_pp_moredata(struct pingpong *pp) { - return !pp->sendleft && Curl_dyn_len(&pp->recvbuf) > pp->nfinal; + return !pp->sendleft && curlx_dyn_len(&pp->recvbuf) > pp->nfinal; } #endif diff --git a/Utilities/cmcurl/lib/pingpong.h b/Utilities/cmcurl/lib/pingpong.h index 72239ff059..0665b83659 100644 --- a/Utilities/cmcurl/lib/pingpong.h +++ b/Utilities/cmcurl/lib/pingpong.h @@ -48,9 +48,6 @@ typedef enum { */ struct pingpong { size_t nread_resp; /* number of bytes currently read of a server response */ - 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; /* 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 */ @@ -69,14 +66,18 @@ struct pingpong { CURLcode (*statemachine)(struct Curl_easy *data, struct connectdata *conn); bool (*endofresp)(struct Curl_easy *data, struct connectdata *conn, - char *ptr, size_t len, int *code); + const char *ptr, size_t len, int *code); + BIT(initialised); + BIT(pending_resp); /* set TRUE when a server response is pending or in + progress, and is cleared once the last response is + read */ }; #define PINGPONG_SETUP(pp,s,e) \ do { \ - pp->response_time = RESP_TIMEOUT; \ - pp->statemachine = s; \ - pp->endofresp = e; \ + (pp)->response_time = RESP_TIMEOUT; \ + (pp)->statemachine = s; \ + (pp)->endofresp = e; \ } while(0) /* diff --git a/Utilities/cmcurl/lib/pop3.c b/Utilities/cmcurl/lib/pop3.c index 07c7dbac40..aa8c2d834a 100644 --- a/Utilities/cmcurl/lib/pop3.c +++ b/Utilities/cmcurl/lib/pop3.c @@ -64,8 +64,8 @@ #include "escape.h" #include "http.h" /* for HTTP proxy tunnel stuff */ #include "socks.h" +#include "pingpong.h" #include "pop3.h" -#include "strtoofft.h" #include "strcase.h" #include "vtls/vtls.h" #include "cfilters.h" @@ -76,13 +76,75 @@ #include "bufref.h" #include "curl_sasl.h" #include "curl_md5.h" -#include "warnless.h" +#include "curlx/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" +/* Authentication type flags */ +#define POP3_TYPE_CLEARTEXT (1 << 0) +#define POP3_TYPE_APOP (1 << 1) +#define POP3_TYPE_SASL (1 << 2) + +/* Authentication type values */ +#define POP3_TYPE_NONE 0 +#define POP3_TYPE_ANY (POP3_TYPE_CLEARTEXT|POP3_TYPE_APOP|POP3_TYPE_SASL) + +/* This is the 5-bytes End-Of-Body marker for POP3 */ +#define POP3_EOB "\x0d\x0a\x2e\x0d\x0a" +#define POP3_EOB_LEN 5 + +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_POP3_EASY "meta:proto:pop3:easy" +/* meta key for storing protocol meta at connection */ +#define CURL_META_POP3_CONN "meta:proto:pop3:conn" + +/* + * POP3 easy handle state + */ +struct POP3 { + curl_pp_transfer transfer; + char *id; /* Message ID */ + char *custom; /* Custom Request */ +}; + +/* + * POP3 connection state + */ +typedef enum { + POP3_STOP, /* do nothing state, stops the state machine */ + POP3_SERVERGREET, /* waiting for the initial greeting immediately after + a connect */ + POP3_CAPA, + POP3_STARTTLS, + POP3_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS + (multi mode only) */ + POP3_AUTH, + POP3_APOP, + POP3_USER, + POP3_PASS, + POP3_COMMAND, + POP3_QUIT, + POP3_LAST /* never used */ +} pop3state; + +struct pop3_conn { + struct pingpong pp; + pop3state state; /* Always use pop3.c:state() to change state! */ + size_t eob; /* Number of bytes of the EOB (End Of Body) that + have been received so far */ + size_t strip; /* Number of bytes from the start to ignore as + non-body */ + struct SASL sasl; /* SASL-related storage */ + char *apoptimestamp; /* APOP timestamp from the server greeting */ + unsigned char authtypes; /* Accepted authentication types */ + unsigned char preftype; /* Preferred authentication type */ + BIT(ssldone); /* Is connect() over SSL done? */ + BIT(tls_supported); /* StartTLS capability supported by server */ +}; + /* Local API functions */ static CURLcode pop3_regular_transfer(struct Curl_easy *data, bool *done); static CURLcode pop3_do(struct Curl_easy *data, bool *done); @@ -245,10 +307,13 @@ static bool pop3_is_multiline(const char *cmdline) * types and allowed SASL mechanisms. */ static bool pop3_endofresp(struct Curl_easy *data, struct connectdata *conn, - char *line, size_t len, int *resp) + const char *line, size_t len, int *resp) { - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); (void)data; + DEBUGASSERT(pop3c); + if(!pop3c) /* internal error */ + return TRUE; /* Do we have an error response? */ if(len >= 4 && !memcmp("-ERR", line, 4)) { @@ -295,9 +360,15 @@ 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 = Curl_dyn_ptr(&data->conn->proto.pop3c.pp.recvbuf); - size_t len = data->conn->proto.pop3c.pp.nfinal; + struct pop3_conn *pop3c = + Curl_conn_meta_get(data->conn, CURL_META_POP3_CONN); + char *message; + size_t len; + if(!pop3c) + return CURLE_FAILED_INIT; + message = curlx_dyn_ptr(&pop3c->pp.recvbuf); + len = pop3c->pp.nfinal; if(len > 2) { /* Find the start of the message */ len -= 2; @@ -329,30 +400,33 @@ static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out) */ static void pop3_state(struct Curl_easy *data, pop3state newstate) { - struct pop3_conn *pop3c = &data->conn->proto.pop3c; + struct pop3_conn *pop3c = + Curl_conn_meta_get(data->conn, CURL_META_POP3_CONN); + if(pop3c) { #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - /* for debug purposes */ - static const char * const names[] = { - "STOP", - "SERVERGREET", - "CAPA", - "STARTTLS", - "UPGRADETLS", - "AUTH", - "APOP", - "USER", - "PASS", - "COMMAND", - "QUIT", - /* LAST */ - }; + /* for debug purposes */ + static const char * const names[] = { + "STOP", + "SERVERGREET", + "CAPA", + "STARTTLS", + "UPGRADETLS", + "AUTH", + "APOP", + "USER", + "PASS", + "COMMAND", + "QUIT", + /* LAST */ + }; - if(pop3c->state != newstate) - infof(data, "POP3 %p state change from %s to %s", - (void *)pop3c, names[pop3c->state], names[newstate]); + if(pop3c->state != newstate) + infof(data, "POP3 %p state change from %s to %s", + (void *)pop3c, names[pop3c->state], names[newstate]); #endif - pop3c->state = newstate; + pop3c->state = newstate; + } } /*********************************************************************** @@ -365,8 +439,11 @@ static void pop3_state(struct Curl_easy *data, pop3state newstate) static CURLcode pop3_perform_capa(struct Curl_easy *data, struct connectdata *conn) { + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; + + if(!pop3c) + return CURLE_FAILED_INIT; pop3c->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanisms yet */ pop3c->sasl.authused = SASL_AUTH_NONE; /* Clear the auth. mechanism used */ @@ -390,9 +467,14 @@ static CURLcode pop3_perform_capa(struct Curl_easy *data, static CURLcode pop3_perform_starttls(struct Curl_easy *data, struct connectdata *conn) { - /* Send the STLS command */ - CURLcode result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", "STLS"); + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + CURLcode result; + if(!pop3c) + return CURLE_FAILED_INIT; + + /* Send the STLS command */ + result = Curl_pp_sendf(data, &pop3c->pp, "%s", "STLS"); if(!result) pop3_state(data, POP3_STARTTLS); @@ -410,17 +492,19 @@ static CURLcode pop3_perform_upgrade_tls(struct Curl_easy *data, { #ifdef USE_SSL /* Start the SSL connection */ - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result; bool ssldone = FALSE; + if(!pop3c) + return CURLE_FAILED_INIT; + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); if(result) goto out; /* Change the connection handler */ conn->handler = &Curl_handler_pop3s; - conn->bits.tls_upgraded = TRUE; } DEBUGASSERT(!pop3c->ssldone); @@ -450,8 +534,12 @@ out: static CURLcode pop3_perform_user(struct Curl_easy *data, struct connectdata *conn) { + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; + if(!pop3c) + return CURLE_FAILED_INIT; + /* Check we have a username and password to authenticate with and end the connect phase if we do not */ if(!data->state.aptr.user) { @@ -461,7 +549,7 @@ static CURLcode pop3_perform_user(struct Curl_easy *data, } /* Send the USER command */ - result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "USER %s", + result = Curl_pp_sendf(data, &pop3c->pp, "USER %s", conn->user ? conn->user : ""); if(!result) pop3_state(data, POP3_USER); @@ -479,13 +567,16 @@ static CURLcode pop3_perform_user(struct Curl_easy *data, static CURLcode pop3_perform_apop(struct Curl_easy *data, struct connectdata *conn) { + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; size_t i; struct MD5_context *ctxt; unsigned char digest[MD5_DIGEST_LEN]; char secret[2 * MD5_DIGEST_LEN + 1]; + if(!pop3c) + return CURLE_FAILED_INIT; + /* Check we have a username and password to authenticate with and end the connect phase if we do not */ if(!data->state.aptr.user) { @@ -532,10 +623,14 @@ static CURLcode pop3_perform_auth(struct Curl_easy *data, const char *mech, const struct bufref *initresp) { + struct pop3_conn *pop3c = + Curl_conn_meta_get(data->conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &data->conn->proto.pop3c; const char *ir = (const char *) Curl_bufref_ptr(initresp); + if(!pop3c) + return CURLE_FAILED_INIT; + if(ir) { /* AUTH ... */ /* Send the AUTH command with the initial response */ result = Curl_pp_sendf(data, &pop3c->pp, "AUTH %s %s", mech, ir); @@ -558,9 +653,12 @@ static CURLcode pop3_continue_auth(struct Curl_easy *data, const char *mech, const struct bufref *resp) { - struct pop3_conn *pop3c = &data->conn->proto.pop3c; + struct pop3_conn *pop3c = + Curl_conn_meta_get(data->conn, CURL_META_POP3_CONN); (void)mech; + if(!pop3c) + return CURLE_FAILED_INIT; return Curl_pp_sendf(data, &pop3c->pp, "%s", (const char *) Curl_bufref_ptr(resp)); @@ -574,9 +672,12 @@ static CURLcode pop3_continue_auth(struct Curl_easy *data, */ static CURLcode pop3_cancel_auth(struct Curl_easy *data, const char *mech) { - struct pop3_conn *pop3c = &data->conn->proto.pop3c; + struct pop3_conn *pop3c = + Curl_conn_meta_get(data->conn, CURL_META_POP3_CONN); (void)mech; + if(!pop3c) + return CURLE_FAILED_INIT; return Curl_pp_sendf(data, &pop3c->pp, "*"); } @@ -592,10 +693,13 @@ static CURLcode pop3_cancel_auth(struct Curl_easy *data, const char *mech) static CURLcode pop3_perform_authentication(struct Curl_easy *data, struct connectdata *conn) { + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; saslprogress progress = SASL_IDLE; + if(!pop3c) + return CURLE_FAILED_INIT; + /* Check we have enough data to authenticate with and end the connect phase if we do not */ if(!Curl_sasl_can_authenticate(&pop3c->sasl, data)) { @@ -622,11 +726,8 @@ static CURLcode pop3_perform_authentication(struct Curl_easy *data, if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_CLEARTEXT) /* Perform clear text authentication */ result = pop3_perform_user(data, conn); - else { - /* Other mechanisms not supported */ - infof(data, "No known authentication mechanisms supported"); - result = CURLE_LOGIN_DENIED; - } + else + result = Curl_sasl_is_blocked(&pop3c->sasl, data); } return result; @@ -642,9 +743,13 @@ static CURLcode pop3_perform_command(struct Curl_easy *data) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct POP3 *pop3 = data->req.p.pop3; + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); const char *command = NULL; + if(!pop3 || !pop3c) + return CURLE_FAILED_INIT; + /* Calculate the default command */ if(pop3->id[0] == '\0' || data->set.list_only) { command = "LIST"; @@ -661,10 +766,9 @@ static CURLcode pop3_perform_command(struct Curl_easy *data) /* Send the command */ if(pop3->id[0] != '\0') - result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s %s", - command, pop3->id); + result = Curl_pp_sendf(data, &pop3c->pp, "%s %s", command, pop3->id); else - result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", command); + result = Curl_pp_sendf(data, &pop3c->pp, "%s", command); if(!result) { pop3_state(data, POP3_COMMAND); @@ -683,9 +787,14 @@ static CURLcode pop3_perform_command(struct Curl_easy *data) static CURLcode pop3_perform_quit(struct Curl_easy *data, struct connectdata *conn) { - /* Send the QUIT command */ - CURLcode result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", "QUIT"); + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + CURLcode result; + if(!pop3c) + return CURLE_FAILED_INIT; + + /* Send the QUIT command */ + result = Curl_pp_sendf(data, &pop3c->pp, "%s", "QUIT"); if(!result) pop3_state(data, POP3_QUIT); @@ -699,11 +808,16 @@ 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 = Curl_dyn_ptr(&data->conn->proto.pop3c.pp.recvbuf); - size_t len = data->conn->proto.pop3c.pp.nfinal; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + const char *line; + size_t len; (void)instate; /* no use for this yet */ + if(!pop3c) + return CURLE_FAILED_INIT; + + line = curlx_dyn_ptr(&pop3c->pp.recvbuf); + len = pop3c->pp.nfinal; if(pop3code != '+') { failf(data, "Got unexpected pop3-server response"); @@ -749,13 +863,18 @@ 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 = Curl_dyn_ptr(&data->conn->proto.pop3c.pp.recvbuf); - size_t len = data->conn->proto.pop3c.pp.nfinal; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + const char *line; + size_t len; (void)instate; /* no use for this yet */ + if(!pop3c) + return CURLE_FAILED_INIT; - /* Do we have a untagged continuation response? */ + line = curlx_dyn_ptr(&pop3c->pp.recvbuf); + len = pop3c->pp.nfinal; + + /* Do we have an untagged continuation response? */ if(pop3code == '*') { /* Does the server support the STLS capability? */ if(len >= 4 && !memcmp(line, "STLS", 4)) @@ -834,11 +953,15 @@ static CURLcode pop3_state_starttls_resp(struct Curl_easy *data, int pop3code, pop3state instate) { + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; (void)instate; /* no use for this yet */ + if(!pop3c) + return CURLE_FAILED_INIT; + /* Pipelining in response is forbidden. */ - if(data->conn->proto.pop3c.pp.overflow) + if(pop3c->pp.overflow) return CURLE_WEIRD_SERVER_REPLY; if(pop3code != '+') { @@ -862,10 +985,12 @@ static CURLcode pop3_state_auth_resp(struct Curl_easy *data, { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); saslprogress progress; (void)instate; /* no use for this yet */ + if(!pop3c) + return CURLE_FAILED_INIT; result = Curl_sasl_continue(&pop3c->sasl, data, pop3code, &progress); if(!result) @@ -921,15 +1046,19 @@ static CURLcode pop3_state_user_resp(struct Curl_easy *data, int pop3code, { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); (void)instate; /* no use for this yet */ + if(!pop3c) + return CURLE_FAILED_INIT; + if(pop3code != '+') { failf(data, "Access denied. %c", pop3code); result = CURLE_LOGIN_DENIED; } else /* Send the PASS command */ - result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "PASS %s", + result = Curl_pp_sendf(data, &pop3c->pp, "PASS %s", conn->passwd ? conn->passwd : ""); if(!result) pop3_state(data, POP3_PASS); @@ -962,12 +1091,15 @@ static CURLcode pop3_state_command_resp(struct Curl_easy *data, { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct POP3 *pop3 = data->req.p.pop3; - struct pop3_conn *pop3c = &conn->proto.pop3c; - struct pingpong *pp = &pop3c->pp; + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + struct pingpong *pp; (void)instate; /* no use for this yet */ + if(!pop3 || !pop3c) + return CURLE_FAILED_INIT; + pp = &pop3c->pp; if(pop3code != '+') { pop3_state(data, POP3_STOP); return CURLE_WEIRD_SERVER_REPLY; @@ -993,18 +1125,18 @@ static CURLcode pop3_state_command_resp(struct Curl_easy *data, the body */ /* keep only the overflow */ - Curl_dyn_tail(&pp->recvbuf, pp->overflow); + curlx_dyn_tail(&pp->recvbuf, pp->overflow); pp->nfinal = 0; /* done */ if(!data->req.no_body) { - result = pop3_write(data, Curl_dyn_ptr(&pp->recvbuf), - Curl_dyn_len(&pp->recvbuf), FALSE); + result = pop3_write(data, curlx_dyn_ptr(&pp->recvbuf), + curlx_dyn_len(&pp->recvbuf), FALSE); if(result) return result; } /* reset the buffer */ - Curl_dyn_reset(&pp->recvbuf); + curlx_dyn_reset(&pp->recvbuf); pp->overflow = 0; } } @@ -1020,13 +1152,17 @@ static CURLcode pop3_state_command_resp(struct Curl_easy *data, static CURLcode pop3_statemachine(struct Curl_easy *data, struct connectdata *conn) { + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; int pop3code; - struct pop3_conn *pop3c = &conn->proto.pop3c; - struct pingpong *pp = &pop3c->pp; + struct pingpong *pp; size_t nread = 0; (void)data; + if(!pop3c) + return CURLE_FAILED_INIT; + + pp = &pop3c->pp; /* Busy upgrading the connection; right now all I/O is SSL/TLS, not POP3 */ upgrade_tls: if(pop3c->state == POP3_UPGRADETLS) { @@ -1107,8 +1243,10 @@ static CURLcode pop3_multi_statemach(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + if(!pop3c) + return CURLE_FAILED_INIT; result = Curl_pp_statemach(data, &pop3c->pp, FALSE, FALSE); *done = (pop3c->state == POP3_STOP); @@ -1120,7 +1258,10 @@ static CURLcode pop3_block_statemach(struct Curl_easy *data, bool disconnecting) { CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + + if(!pop3c) + return CURLE_FAILED_INIT; while(pop3c->state != POP3_STOP && !result) result = Curl_pp_statemach(data, &pop3c->pp, TRUE, disconnecting); @@ -1128,25 +1269,14 @@ static CURLcode pop3_block_statemach(struct Curl_easy *data, return result; } -/* Allocate and initialize the POP3 struct for the current Curl_easy if - required */ -static CURLcode pop3_init(struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct POP3 *pop3; - - pop3 = data->req.p.pop3 = calloc(1, sizeof(struct POP3)); - if(!pop3) - result = CURLE_OUT_OF_MEMORY; - - return result; -} - /* For the POP3 "protocol connect" and "doing" phases only */ static int pop3_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks) { - return Curl_pp_getsock(data, &conn->proto.pop3c.pp, socks); + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + if(pop3c) + return Curl_pp_getsock(data, &pop3c->pp, socks); + return GETSOCK_BLANK; } /*********************************************************************** @@ -1163,10 +1293,12 @@ static CURLcode pop3_connect(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct pop3_conn *pop3c = &conn->proto.pop3c; - struct pingpong *pp = &pop3c->pp; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + struct pingpong *pp = pop3c ? &pop3c->pp : NULL; *done = FALSE; /* default to not done yet */ + if(!pop3c) + return CURLE_FAILED_INIT; /* We always support persistent connections in POP3 */ connkeep(conn, "POP3 default"); @@ -1206,7 +1338,7 @@ static CURLcode pop3_done(struct Curl_easy *data, CURLcode status, bool premature) { CURLcode result = CURLE_OK; - struct POP3 *pop3 = data->req.p.pop3; + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); (void)premature; @@ -1240,7 +1372,10 @@ static CURLcode pop3_perform(struct Curl_easy *data, bool *connected, { /* This is POP3 and no proxy */ CURLcode result = CURLE_OK; - struct POP3 *pop3 = data->req.p.pop3; + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); + + if(!pop3) + return CURLE_FAILED_INIT; DEBUGF(infof(data, "DO phase starts")); @@ -1305,9 +1440,12 @@ static CURLcode pop3_do(struct Curl_easy *data, bool *done) static CURLcode pop3_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); (void)data; + if(!pop3c) + return CURLE_FAILED_INIT; + /* We cannot send quit unconditionally. If this connection is stale or 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. */ @@ -1388,16 +1526,42 @@ static CURLcode pop3_regular_transfer(struct Curl_easy *data, return result; } +static void pop3_easy_dtor(void *key, size_t klen, void *entry) +{ + struct POP3 *pop3 = entry; + (void)key; + (void)klen; + DEBUGASSERT(pop3); + /* Cleanup our per-request based variables */ + Curl_safefree(pop3->id); + Curl_safefree(pop3->custom); + free(pop3); +} + +static void pop3_conn_dtor(void *key, size_t klen, void *entry) +{ + struct pop3_conn *pop3c = entry; + (void)key; + (void)klen; + DEBUGASSERT(pop3c); + Curl_pp_disconnect(&pop3c->pp); + Curl_safefree(pop3c->apoptimestamp); + free(pop3c); +} + static CURLcode pop3_setup_connection(struct Curl_easy *data, struct connectdata *conn) { - /* Initialise the POP3 layer */ - CURLcode result = pop3_init(data); - if(result) - return result; + struct pop3_conn *pop3c; + struct POP3 *pop3 = calloc(1, sizeof(*pop3)); + if(!pop3 || + Curl_meta_set(data, CURL_META_POP3_EASY, pop3, pop3_easy_dtor)) + return CURLE_OUT_OF_MEMORY; - /* Clear the TLS upgraded flag */ - conn->bits.tls_upgraded = FALSE; + pop3c = calloc(1, sizeof(*pop3c)); + if(!pop3c || + Curl_conn_meta_set(conn, CURL_META_POP3_CONN, pop3c, pop3_conn_dtor)) + return CURLE_OUT_OF_MEMORY; return CURLE_OK; } @@ -1410,10 +1574,13 @@ static CURLcode pop3_setup_connection(struct Curl_easy *data, */ static CURLcode pop3_parse_url_options(struct connectdata *conn) { + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; const char *ptr = conn->options; + if(!pop3c) + return CURLE_FAILED_INIT; + while(!result && ptr && *ptr) { const char *key = ptr; const char *value; @@ -1468,9 +1635,11 @@ static CURLcode pop3_parse_url_options(struct connectdata *conn) static CURLcode pop3_parse_url_path(struct Curl_easy *data) { /* The POP3 struct is already initialised in pop3_connect() */ - struct POP3 *pop3 = data->req.p.pop3; + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); const char *path = &data->state.up.path[1]; /* skip leading path */ + if(!pop3) + return CURLE_FAILED_INIT; /* URL decode the path for the message ID */ return Curl_urldecode(path, 0, &pop3->id, NULL, REJECT_CTRL); } @@ -1484,9 +1653,11 @@ static CURLcode pop3_parse_url_path(struct Curl_easy *data) static CURLcode pop3_parse_custom_request(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct POP3 *pop3 = data->req.p.pop3; + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); const char *custom = data->set.str[STRING_CUSTOMREQUEST]; + if(!pop3) + return CURLE_FAILED_INIT; /* URL decode the custom request */ if(custom) result = Curl_urldecode(custom, 0, &pop3->custom, NULL, REJECT_CTRL); @@ -1508,12 +1679,15 @@ static CURLcode pop3_write(struct Curl_easy *data, const char *str, CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; struct connectdata *conn = data->conn; - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); bool strip_dot = FALSE; size_t last = 0; size_t i; (void)is_eos; + if(!pop3c) + return CURLE_FAILED_INIT; + /* 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 the eob so the server will have prefixed it with an extra dot which we @@ -1588,11 +1762,11 @@ static CURLcode pop3_write(struct Curl_easy *data, const char *str, /* If the partial match was the CRLF and dot then only write the CRLF as the server would have inserted the dot */ if(strip_dot && prev - 1 > 0) { - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)POP3_EOB, + result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, prev - 1); } else if(!strip_dot) { - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)POP3_EOB, + result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, prev); } else { @@ -1612,7 +1786,7 @@ static CURLcode pop3_write(struct Curl_easy *data, const char *str, /* We have a full match so the transfer is done, however we must transfer the CRLF at the start of the EOB as this is considered to be part of the message as per RFC-1939, sect. 3 */ - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)POP3_EOB, 2); + result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, 2); k->keepon &= ~KEEP_RECV; pop3c->eob = 0; diff --git a/Utilities/cmcurl/lib/pop3.h b/Utilities/cmcurl/lib/pop3.h index 3d08dafa19..485e7c2c49 100644 --- a/Utilities/cmcurl/lib/pop3.h +++ b/Utilities/cmcurl/lib/pop3.h @@ -24,70 +24,7 @@ * ***************************************************************************/ -#include "pingpong.h" -#include "curl_sasl.h" - -/**************************************************************************** - * POP3 unique setup - ***************************************************************************/ -typedef enum { - POP3_STOP, /* do nothing state, stops the state machine */ - POP3_SERVERGREET, /* waiting for the initial greeting immediately after - a connect */ - POP3_CAPA, - POP3_STARTTLS, - POP3_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS - (multi mode only) */ - POP3_AUTH, - POP3_APOP, - POP3_USER, - POP3_PASS, - POP3_COMMAND, - POP3_QUIT, - POP3_LAST /* never used */ -} pop3state; - -/* This POP3 struct is used in the Curl_easy. All POP3 data that is - connection-oriented must be in pop3_conn to properly deal with the fact that - perhaps the Curl_easy is changed between the times the connection is - used. */ -struct POP3 { - curl_pp_transfer transfer; - char *id; /* Message ID */ - char *custom; /* Custom Request */ -}; - -/* pop3_conn is used for struct connection-oriented data in the connectdata - struct */ -struct pop3_conn { - struct pingpong pp; - pop3state state; /* Always use pop3.c:state() to change state! */ - size_t eob; /* Number of bytes of the EOB (End Of Body) that - have been received so far */ - size_t strip; /* Number of bytes from the start to ignore as - non-body */ - struct SASL sasl; /* SASL-related storage */ - char *apoptimestamp; /* APOP timestamp from the server greeting */ - unsigned char authtypes; /* Accepted authentication types */ - unsigned char preftype; /* Preferred authentication type */ - BIT(ssldone); /* Is connect() over SSL done? */ - BIT(tls_supported); /* StartTLS capability supported by server */ -}; - extern const struct Curl_handler Curl_handler_pop3; extern const struct Curl_handler Curl_handler_pop3s; -/* Authentication type flags */ -#define POP3_TYPE_CLEARTEXT (1 << 0) -#define POP3_TYPE_APOP (1 << 1) -#define POP3_TYPE_SASL (1 << 2) - -/* Authentication type values */ -#define POP3_TYPE_NONE 0 -#define POP3_TYPE_ANY (POP3_TYPE_CLEARTEXT|POP3_TYPE_APOP|POP3_TYPE_SASL) - -/* This is the 5-bytes End-Of-Body marker for POP3 */ -#define POP3_EOB "\x0d\x0a\x2e\x0d\x0a" -#define POP3_EOB_LEN 5 - #endif /* HEADER_CURL_POP3_H */ diff --git a/Utilities/cmcurl/lib/progress.c b/Utilities/cmcurl/lib/progress.c index 82cbeb3770..8e6d98f0d9 100644 --- a/Utilities/cmcurl/lib/progress.c +++ b/Utilities/cmcurl/lib/progress.c @@ -28,7 +28,7 @@ #include "sendf.h" #include "multiif.h" #include "progress.h" -#include "timeval.h" +#include "curlx/timeval.h" #include "curl_printf.h" /* check rate limits within this many recent milliseconds, at minimum. */ @@ -136,8 +136,7 @@ int Curl_pgrsDone(struct Curl_easy *data) if(rc) return rc; - if(!(data->progress.flags & PGRS_HIDE) && - !data->progress.callback) + if(!data->progress.hide && !data->progress.callback) /* only output if we do not use a progress callback and we are not * hidden */ fprintf(data->set.err, "\n"); @@ -181,7 +180,7 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, case TIMER_POSTQUEUE: /* Queue time is accumulative from all involved redirects */ data->progress.t_postqueue += - Curl_timediff_us(timestamp, data->progress.t_startqueue); + curlx_timediff_us(timestamp, data->progress.t_startqueue); break; case TIMER_STARTACCEPT: data->progress.t_acceptdata = timestamp; @@ -217,13 +216,13 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, delta = &data->progress.t_posttransfer; break; case TIMER_REDIRECT: - data->progress.t_redirect = Curl_timediff_us(timestamp, + data->progress.t_redirect = curlx_timediff_us(timestamp, data->progress.start); data->progress.t_startqueue = timestamp; break; } if(delta) { - timediff_t us = Curl_timediff_us(timestamp, data->progress.t_startsingle); + timediff_t us = curlx_timediff_us(timestamp, data->progress.t_startsingle); if(us < 1) us = 1; /* make sure at least one microsecond passed */ *delta += us; @@ -239,7 +238,7 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, */ struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer) { - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); Curl_pgrsTimeWas(data, timer, now); return now; @@ -247,18 +246,20 @@ struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer) 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.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); + struct Progress *p = &data->progress; + p->speeder_c = 0; /* reset the progress meter display */ + p->start = curlx_now(); + p->is_t_startransfer_set = FALSE; + p->ul.limit.start = p->start; + p->dl.limit.start = p->start; + p->ul.limit.start_size = 0; + p->dl.limit.start_size = 0; + p->dl.cur_size = 0; + p->ul.cur_size = 0; + /* the sizes are unknown at start */ + p->dl_size_known = FALSE; + p->ul_size_known = FALSE; + Curl_ratelimit(data, p->start); } /* @@ -308,7 +309,7 @@ timediff_t Curl_pgrsLimitWaitTime(struct pgrs_dir *d, * 'actual' is the time in milliseconds it took to actually download the * last 'size' bytes. */ - actual = Curl_timediff_ceil(now, d->limit.start); + actual = curlx_timediff_ceil(now, d->limit.start); if(actual < minimum) { /* if it downloaded the data faster than the limit, make it wait the difference */ @@ -334,14 +335,14 @@ void Curl_ratelimit(struct Curl_easy *data, struct curltime now) { /* 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(curlx_timediff(now, data->progress.dl.limit.start) >= MIN_RATE_LIMIT_PERIOD) { 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(curlx_timediff(now, data->progress.ul.limit.start) >= MIN_RATE_LIMIT_PERIOD) { data->progress.ul.limit.start = now; data->progress.ul.limit.start_size = data->progress.ul.cur_size; @@ -361,11 +362,11 @@ void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size) { if(size >= 0) { data->progress.dl.total_size = size; - data->progress.flags |= PGRS_DL_SIZE_KNOWN; + data->progress.dl_size_known = TRUE; } else { data->progress.dl.total_size = 0; - data->progress.flags &= ~PGRS_DL_SIZE_KNOWN; + data->progress.dl_size_known = FALSE; } } @@ -373,11 +374,11 @@ void Curl_pgrsSetUploadSize(struct Curl_easy *data, curl_off_t size) { if(size >= 0) { data->progress.ul.total_size = size; - data->progress.flags |= PGRS_UL_SIZE_KNOWN; + data->progress.ul_size_known = TRUE; } else { data->progress.ul.total_size = 0; - data->progress.flags &= ~PGRS_UL_SIZE_KNOWN; + data->progress.ul_size_known = FALSE; } } @@ -407,7 +408,7 @@ static bool progress_calc(struct Curl_easy *data, struct curltime now) struct Progress * const p = &data->progress; /* The time spent so far (from the start) in microseconds */ - p->timespent = Curl_timediff_us(now, p->start); + p->timespent = curlx_timediff_us(now, p->start); p->dl.speed = trspeed(p->dl.cur_size, p->timespent); p->ul.speed = trspeed(p->ul.cur_size, p->timespent); @@ -447,7 +448,7 @@ static bool progress_calc(struct Curl_easy *data, struct curltime now) 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]); + span_ms = curlx_timediff(now, p->speeder_time[checkindex]); if(0 == span_ms) span_ms = 1; /* at least one millisecond MUST have passed */ @@ -509,12 +510,13 @@ static void progress_meter(struct Curl_easy *data) struct pgrs_estimate total_estm; curl_off_t total_cur_size; curl_off_t total_expected_size; + curl_off_t dl_size; char time_left[10]; char time_total[10]; char time_spent[10]; curl_off_t cur_secs = (curl_off_t)p->timespent/1000000; /* seconds */ - if(!(p->flags & PGRS_HEADERS_OUT)) { + if(!p->headers_out) { if(data->state.resume_from) { fprintf(data->set.err, "** Resuming transfer from byte position %" FMT_OFF_T "\n", @@ -525,12 +527,12 @@ static void progress_meter(struct Curl_easy *data) "Time Time Time Current\n" " Dload Upload " "Total Spent Left Speed\n"); - p->flags |= PGRS_HEADERS_OUT; /* headers are shown */ + p->headers_out = TRUE; /* headers are shown */ } /* 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); + pgrs_estimates(&p->ul, (bool)p->ul_size_known, &ul_estm); + pgrs_estimates(&p->dl, (bool)p->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); @@ -541,8 +543,16 @@ static void progress_meter(struct Curl_easy *data) /* Get the total amount of data expected to get transferred */ 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); + p->ul_size_known ? p->ul.total_size : p->ul.cur_size; + + dl_size = + p->dl_size_known ? p->dl.total_size : p->dl.cur_size; + + /* integer overflow check */ + if((CURL_OFF_T_MAX - total_expected_size) < dl_size) + total_expected_size = CURL_OFF_T_MAX; /* capped */ + else + total_expected_size += dl_size; /* We have transferred this much so far */ total_cur_size = p->dl.cur_size + p->ul.cur_size; @@ -584,7 +594,7 @@ static void progress_meter(struct Curl_easy *data) */ static int pgrsupdate(struct Curl_easy *data, bool showprogress) { - if(!(data->progress.flags & PGRS_HIDE)) { + if(!data->progress.hide) { if(data->set.fxferinfo) { int result; /* There is a callback set, call that */ @@ -627,7 +637,7 @@ static int pgrsupdate(struct Curl_easy *data, bool showprogress) int Curl_pgrsUpdate(struct Curl_easy *data) { - struct curltime now = Curl_now(); /* what time is it */ + struct curltime now = curlx_now(); /* what time is it */ bool showprogress = progress_calc(data, now); return pgrsupdate(data, showprogress); } @@ -637,6 +647,6 @@ int Curl_pgrsUpdate(struct Curl_easy *data) */ void Curl_pgrsUpdate_nometer(struct Curl_easy *data) { - struct curltime now = Curl_now(); /* what time is it */ + struct curltime now = curlx_now(); /* what time is it */ (void)progress_calc(data, now); } diff --git a/Utilities/cmcurl/lib/progress.h b/Utilities/cmcurl/lib/progress.h index 326271ef1e..bbe135cdbc 100644 --- a/Utilities/cmcurl/lib/progress.h +++ b/Utilities/cmcurl/lib/progress.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "timeval.h" +#include "curlx/timeval.h" typedef enum { @@ -71,9 +71,4 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, 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) -#define PGRS_HEADERS_OUT (1<<7) /* set when the headers have been written */ - #endif /* HEADER_CURL_PROGRESS_H */ diff --git a/Utilities/cmcurl/lib/psl.c b/Utilities/cmcurl/lib/psl.c index 0b88b05b4c..a488a46e93 100644 --- a/Utilities/cmcurl/lib/psl.c +++ b/Utilities/cmcurl/lib/psl.c @@ -40,7 +40,7 @@ void Curl_psl_destroy(struct PslCache *pslcache) { if(pslcache->psl) { if(pslcache->dynamic) - psl_free((psl_ctx_t *) pslcache->psl); + psl_free((psl_ctx_t *)CURL_UNCONST(pslcache->psl)); pslcache->psl = NULL; pslcache->dynamic = FALSE; } @@ -48,7 +48,7 @@ void Curl_psl_destroy(struct PslCache *pslcache) static time_t now_seconds(void) { - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); return now.tv_sec; } diff --git a/Utilities/cmcurl/lib/psl.h b/Utilities/cmcurl/lib/psl.h index dd5bee21fb..dc11469a52 100644 --- a/Utilities/cmcurl/lib/psl.h +++ b/Utilities/cmcurl/lib/psl.h @@ -34,7 +34,7 @@ struct Curl_easy; struct PslCache { const psl_ctx_t *psl; /* The PSL. */ time_t expires; /* Time this PSL life expires. */ - bool dynamic; /* PSL should be released when no longer needed. */ + BIT(dynamic); /* PSL should be released when no longer needed. */ }; const psl_ctx_t *Curl_psl_use(struct Curl_easy *easy); diff --git a/Utilities/cmcurl/lib/rand.c b/Utilities/cmcurl/lib/rand.c index 8d55e260a4..c0368dd763 100644 --- a/Utilities/cmcurl/lib/rand.c +++ b/Utilities/cmcurl/lib/rand.c @@ -37,7 +37,7 @@ #include "urldata.h" #include "vtls/vtls.h" #include "sendf.h" -#include "timeval.h" +#include "curlx/timeval.h" #include "rand.h" #include "escape.h" @@ -130,7 +130,7 @@ static CURLcode weak_random(struct Curl_easy *data, static bool seeded = FALSE; unsigned int rnd; if(!seeded) { - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); randseed += (unsigned int)now.tv_usec + (unsigned int)now.tv_sec; randseed = randseed * 1103515245 + 12345; randseed = randseed * 1103515245 + 12345; @@ -191,12 +191,11 @@ static CURLcode randit(struct Curl_easy *data, unsigned int *rnd, * Curl_rand() stores 'num' number of random unsigned characters in the buffer * '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" - * random. + * If libcurl is built without TLS support or arc4random, this function will + * use "weak" random. * - * When built *with* TLS support and a backend that offers strong random, it - * will return error if it cannot provide strong random values. + * When built *with* TLS support, it will return error if it cannot provide + * strong random values. * * NOTE: 'data' may be passed in as NULL when coming from external API without * easy handle! diff --git a/Utilities/cmcurl/lib/rename.c b/Utilities/cmcurl/lib/rename.c index 8715a4306d..d3a46e0e6a 100644 --- a/Utilities/cmcurl/lib/rename.c +++ b/Utilities/cmcurl/lib/rename.c @@ -29,8 +29,8 @@ #if (!defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_COOKIES)) || \ !defined(CURL_DISABLE_ALTSVC) -#include "curl_multibyte.h" -#include "timeval.h" +#include "curlx/multibyte.h" +#include "curlx/timeval.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -40,14 +40,14 @@ /* return 0 on success, 1 on error */ int Curl_rename(const char *oldpath, const char *newpath) { -#ifdef _WIN32 +#if defined(_WIN32) && !defined(UNDER_CE) /* 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; - struct curltime start = Curl_now(); - TCHAR *tchar_oldpath = curlx_convert_UTF8_to_tchar((char *)oldpath); - TCHAR *tchar_newpath = curlx_convert_UTF8_to_tchar((char *)newpath); + struct curltime start = curlx_now(); + TCHAR *tchar_oldpath = curlx_convert_UTF8_to_tchar(oldpath); + TCHAR *tchar_newpath = curlx_convert_UTF8_to_tchar(newpath); for(;;) { timediff_t diff; if(MoveFileEx(tchar_oldpath, tchar_newpath, MOVEFILE_REPLACE_EXISTING)) { @@ -55,7 +55,7 @@ int Curl_rename(const char *oldpath, const char *newpath) curlx_unicodefree(tchar_newpath); break; } - diff = Curl_timediff(Curl_now(), start); + diff = curlx_timediff(curlx_now(), start); if(diff < 0 || diff > max_wait_ms) { curlx_unicodefree(tchar_oldpath); curlx_unicodefree(tchar_newpath); diff --git a/Utilities/cmcurl/lib/request.c b/Utilities/cmcurl/lib/request.c index d5f04e9f1d..f937a7f4bf 100644 --- a/Utilities/cmcurl/lib/request.c +++ b/Utilities/cmcurl/lib/request.c @@ -26,7 +26,7 @@ #include "urldata.h" #include "cfilters.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "doh.h" #include "multiif.h" #include "progress.h" @@ -34,6 +34,7 @@ #include "sendf.h" #include "transfer.h" #include "url.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -68,6 +69,8 @@ CURLcode Curl_req_soft_reset(struct SingleRequest *req, req->deductheadercount = 0; req->httpversion_sent = 0; req->httpversion = 0; + req->sendbuf_hds_len = 0; + result = Curl_client_start(data); if(result) return result; @@ -92,7 +95,7 @@ CURLcode Curl_req_soft_reset(struct SingleRequest *req, CURLcode Curl_req_start(struct SingleRequest *req, struct Curl_easy *data) { - req->start = Curl_now(); + req->start = curlx_now(); return Curl_req_soft_reset(req, data); } @@ -115,9 +118,6 @@ 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) @@ -140,6 +140,7 @@ void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data) req->httpcode = 0; req->keepon = 0; req->upgr101 = UPGR101_INIT; + req->sendbuf_hds_len = 0; req->timeofdoc = 0; req->location = NULL; req->newurl = NULL; @@ -167,17 +168,10 @@ void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data) 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, @@ -194,11 +188,13 @@ static CURLcode xfer_send(struct Curl_easy *data, /* 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; + if(body_len) { + const char *p = getenv("CURL_SMALLREQSEND"); + if(p) { + curl_off_t body_small; + if(!curlx_str_number(&p, &body_small, body_len)) + blen = hds_len + (size_t)body_small; + } } } #endif @@ -222,11 +218,11 @@ static CURLcode xfer_send(struct Curl_easy *data, data->req.eos_sent = TRUE; if(*pnwritten) { if(hds_len) - Curl_debug(data, CURLINFO_HEADER_OUT, (char *)buf, + Curl_debug(data, CURLINFO_HEADER_OUT, 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); + Curl_debug(data, CURLINFO_DATA_OUT, buf + hds_len, body_len); data->req.writebytecount += body_len; Curl_pgrsSetUploadCounter(data, data->req.writebytecount); } @@ -385,8 +381,8 @@ CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *req, return CURLE_FAILED_INIT; data->req.httpversion_sent = httpversion; - buf = Curl_dyn_ptr(req); - blen = Curl_dyn_len(req); + buf = curlx_dyn_ptr(req); + blen = curlx_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; diff --git a/Utilities/cmcurl/lib/request.h b/Utilities/cmcurl/lib/request.h index 4c77be962f..74d9f53439 100644 --- a/Utilities/cmcurl/lib/request.h +++ b/Utilities/cmcurl/lib/request.h @@ -32,9 +32,6 @@ /* forward declarations */ struct UserDefined; -#ifndef CURL_DISABLE_DOH -struct doh_probes; -#endif enum expect100 { EXP100_SEND_DATA, /* enough waiting, just send the body now */ @@ -102,24 +99,6 @@ struct SingleRequest { 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 diff --git a/Utilities/cmcurl/lib/rtsp.c b/Utilities/cmcurl/lib/rtsp.c index 4325240ee6..ac44bec422 100644 --- a/Utilities/cmcurl/lib/rtsp.c +++ b/Utilities/cmcurl/lib/rtsp.c @@ -40,11 +40,42 @@ #include "connect.h" #include "cfilters.h" #include "strdup.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" + +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_RTSP_EASY "meta:proto:rtsp:easy" +/* meta key for storing protocol meta at connection */ +#define CURL_META_RTSP_CONN "meta:proto:rtsp:conn" + +typedef enum { + RTP_PARSE_SKIP, + RTP_PARSE_CHANNEL, + RTP_PARSE_LEN, + RTP_PARSE_DATA +} rtp_parse_st; + +/* RTSP Connection data + * Currently, only used for tracking incomplete RTP data reads */ +struct rtsp_conn { + struct dynbuf buf; + int rtp_channel; + size_t rtp_len; + rtp_parse_st state; + BIT(in_header); +}; + +/* RTSP transfer data */ +struct RTSP { + long CSeq_sent; /* CSeq of this request */ + long CSeq_recv; /* CSeq received */ +}; + + #define RTP_PKT_LENGTH(p) ((((unsigned int)((unsigned char)((p)[2]))) << 8) | \ ((unsigned int)((unsigned char)((p)[3])))) @@ -52,8 +83,6 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done); static CURLcode rtsp_done(struct Curl_easy *data, CURLcode, bool premature); static CURLcode rtsp_connect(struct Curl_easy *data, bool *done); -static CURLcode rtsp_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead); static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks); @@ -112,7 +141,7 @@ const struct Curl_handler Curl_handler_rtsp = { rtsp_getsock_do, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ - rtsp_disconnect, /* disconnect */ + ZERO_NULL, /* disconnect */ rtsp_rtp_write_resp, /* write_resp */ ZERO_NULL, /* write_resp_hd */ rtsp_conncheck, /* connection_check */ @@ -126,17 +155,41 @@ const struct Curl_handler Curl_handler_rtsp = { #define MAX_RTP_BUFFERSIZE 1000000 /* arbitrary */ +static void rtsp_easy_dtor(void *key, size_t klen, void *entry) +{ + struct RTSP *rtsp = entry; + (void)key; + (void)klen; + free(rtsp); +} + +static void rtsp_conn_dtor(void *key, size_t klen, void *entry) +{ + struct rtsp_conn *rtspc = entry; + (void)key; + (void)klen; + curlx_dyn_free(&rtspc->buf); + free(rtspc); +} + static CURLcode rtsp_setup_connection(struct Curl_easy *data, struct connectdata *conn) { + struct rtsp_conn *rtspc; struct RTSP *rtsp; - (void)conn; - data->req.p.rtsp = rtsp = calloc(1, sizeof(struct RTSP)); - if(!rtsp) + rtspc = calloc(1, sizeof(*rtspc)); + if(!rtspc) + return CURLE_OUT_OF_MEMORY; + curlx_dyn_init(&rtspc->buf, MAX_RTP_BUFFERSIZE); + if(Curl_conn_meta_set(conn, CURL_META_RTSP_CONN, rtspc, rtsp_conn_dtor)) + return CURLE_OUT_OF_MEMORY; + + rtsp = calloc(1, sizeof(struct RTSP)); + if(!rtsp || + Curl_meta_set(data, CURL_META_RTSP_EASY, rtsp, rtsp_easy_dtor)) return CURLE_OUT_OF_MEMORY; - Curl_dyn_init(&conn->proto.rtspc.buf, MAX_RTP_BUFFERSIZE); return CURLE_OK; } @@ -163,8 +216,13 @@ static unsigned int rtsp_conncheck(struct Curl_easy *data, static CURLcode rtsp_connect(struct Curl_easy *data, bool *done) { + struct rtsp_conn *rtspc = + Curl_conn_meta_get(data->conn, CURL_META_RTSP_CONN); CURLcode httpStatus; + if(!rtspc) + return CURLE_FAILED_INIT; + httpStatus = Curl_http_connect(data, done); /* Initialize the CSeq if not already done */ @@ -173,34 +231,29 @@ static CURLcode rtsp_connect(struct Curl_easy *data, bool *done) if(data->state.rtsp_next_server_CSeq == 0) data->state.rtsp_next_server_CSeq = 1; - data->conn->proto.rtspc.rtp_channel = -1; + rtspc->rtp_channel = -1; return httpStatus; } -static CURLcode rtsp_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead) -{ - (void) dead; - (void) data; - Curl_dyn_free(&conn->proto.rtspc.buf); - return CURLE_OK; -} - - static CURLcode rtsp_done(struct Curl_easy *data, CURLcode status, bool premature) { - struct RTSP *rtsp = data->req.p.rtsp; + struct rtsp_conn *rtspc = + Curl_conn_meta_get(data->conn, CURL_META_RTSP_CONN); + struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY); CURLcode httpStatus; + if(!rtspc || !rtsp) + return CURLE_FAILED_INIT; + /* Bypass HTTP empty-reply checks on receive */ if(data->set.rtspreq == RTSPREQ_RECEIVE) premature = TRUE; httpStatus = Curl_http_done(data, status, premature); - if(rtsp && !status && !httpStatus) { + if(!status && !httpStatus) { /* Check the sequence numbers */ long CSeq_sent = rtsp->CSeq_sent; long CSeq_recv = rtsp->CSeq_recv; @@ -210,8 +263,7 @@ static CURLcode rtsp_done(struct Curl_easy *data, CSeq_sent, CSeq_recv); return CURLE_RTSP_CSEQ_ERROR; } - if(data->set.rtspreq == RTSPREQ_RECEIVE && - (data->conn->proto.rtspc.rtp_channel == -1)) { + if(data->set.rtspreq == RTSPREQ_RECEIVE && (rtspc->rtp_channel == -1)) { infof(data, "Got an RTP Receive with a CSeq of %ld", CSeq_recv); } if(data->set.rtspreq == RTSPREQ_RECEIVE && @@ -229,7 +281,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; Curl_RtspReq rtspreq = data->set.rtspreq; - struct RTSP *rtsp = data->req.p.rtsp; + struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY); struct dynbuf req_buffer; unsigned char httpversion = 11; /* RTSP is close to HTTP/1.1, sort of... */ @@ -246,8 +298,11 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) const char *p_userpwd = NULL; *done = TRUE; + if(!rtsp) + return CURLE_FAILED_INIT; + /* Initialize a dynamic send buffer */ - Curl_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER); + curlx_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER); rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq; rtsp->CSeq_recv = 0; @@ -345,8 +400,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) if(rtspreq == RTSPREQ_SETUP && !p_transport) { /* New Transport: setting? */ if(data->set.str[STRING_RTSP_TRANSPORT]) { - Curl_safefree(data->state.aptr.rtsp_transport); - + free(data->state.aptr.rtsp_transport); data->state.aptr.rtsp_transport = aprintf("Transport: %s\r\n", data->set.str[STRING_RTSP_TRANSPORT]); @@ -372,7 +426,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) /* Accept-Encoding header */ if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && data->set.str[STRING_ENCODING]) { - Curl_safefree(data->state.aptr.accept_encoding); + free(data->state.aptr.accept_encoding); data->state.aptr.accept_encoding = aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); @@ -426,7 +480,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) /* Check to see if there is a range set in the custom headers */ if(!Curl_checkheaders(data, STRCONST("Range")) && data->state.range) { - Curl_safefree(data->state.aptr.rangeline); + free(data->state.aptr.rangeline); data->state.aptr.rangeline = aprintf("Range: %s\r\n", data->state.range); p_range = data->state.aptr.rangeline; } @@ -447,10 +501,10 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) } 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); + curlx_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) goto out; @@ -459,7 +513,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) * to make comparison easier */ if(p_session_id) { - result = Curl_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id); + result = curlx_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id); if(result) goto out; } @@ -467,24 +521,24 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) /* * Shared HTTP-like options */ - result = Curl_dyn_addf(&req_buffer, - "%s" /* transport */ - "%s" /* accept */ - "%s" /* accept-encoding */ - "%s" /* range */ - "%s" /* referrer */ - "%s" /* user-agent */ - "%s" /* proxyuserpwd */ - "%s" /* userpwd */ - , - p_transport ? p_transport : "", - p_accept ? p_accept : "", - p_accept_encoding ? p_accept_encoding : "", - p_range ? p_range : "", - p_referrer ? p_referrer : "", - p_uagent ? p_uagent : "", - p_proxyuserpwd ? p_proxyuserpwd : "", - p_userpwd ? p_userpwd : ""); + result = curlx_dyn_addf(&req_buffer, + "%s" /* transport */ + "%s" /* accept */ + "%s" /* accept-encoding */ + "%s" /* range */ + "%s" /* referrer */ + "%s" /* user-agent */ + "%s" /* proxyuserpwd */ + "%s" /* userpwd */ + , + p_transport ? p_transport : "", + p_accept ? p_accept : "", + p_accept_encoding ? p_accept_encoding : "", + p_range ? p_range : "", + p_referrer ? p_referrer : "", + p_uagent ? p_uagent : "", + p_proxyuserpwd ? p_proxyuserpwd : "", + p_userpwd ? p_userpwd : ""); /* * Free userpwd now --- cannot reuse this for Negotiate and possibly NTLM @@ -540,8 +594,8 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) * actually set a custom Content-Length in the headers */ if(!Curl_checkheaders(data, STRCONST("Content-Length"))) { result = - Curl_dyn_addf(&req_buffer, "Content-Length: %" FMT_OFF_T"\r\n", - req_clen); + curlx_dyn_addf(&req_buffer, "Content-Length: %" FMT_OFF_T"\r\n", + req_clen); if(result) goto out; } @@ -549,9 +603,9 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) if(rtspreq == RTSPREQ_SET_PARAMETER || rtspreq == RTSPREQ_GET_PARAMETER) { if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { - result = Curl_dyn_addn(&req_buffer, - STRCONST("Content-Type: " - "text/parameters\r\n")); + result = curlx_dyn_addn(&req_buffer, + STRCONST("Content-Type: " + "text/parameters\r\n")); if(result) goto out; } @@ -559,9 +613,9 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) if(rtspreq == RTSPREQ_ANNOUNCE) { if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { - result = Curl_dyn_addn(&req_buffer, - STRCONST("Content-Type: " - "application/sdp\r\n")); + result = curlx_dyn_addn(&req_buffer, + STRCONST("Content-Type: " + "application/sdp\r\n")); if(result) goto out; } @@ -580,7 +634,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) } /* Finish the request buffer */ - result = Curl_dyn_addn(&req_buffer, STRCONST("\r\n")); + result = curlx_dyn_addn(&req_buffer, STRCONST("\r\n")); if(result) goto out; @@ -604,7 +658,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) result = CURLE_ABORTED_BY_CALLBACK; } out: - Curl_dyn_free(&req_buffer); + curlx_dyn_free(&req_buffer); return result; } @@ -612,10 +666,10 @@ out: * write any BODY bytes missing to the client, ignore the rest. */ static CURLcode rtp_write_body_junk(struct Curl_easy *data, + struct rtsp_conn *rtspc, const char *buf, size_t blen) { - struct rtsp_conn *rtspc = &(data->conn->proto.rtspc); curl_off_t body_remain; bool in_body; @@ -627,17 +681,17 @@ static CURLcode rtp_write_body_junk(struct Curl_easy *data, 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); + return Curl_client_write(data, CLIENTWRITE_BODY, buf, blen); } 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, + 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; @@ -649,7 +703,7 @@ static CURLcode rtsp_filter_rtp(struct Curl_easy *data, switch(rtspc->state) { case RTP_PARSE_SKIP: { - DEBUGASSERT(Curl_dyn_len(&rtspc->buf) == 0); + DEBUGASSERT(curlx_dyn_len(&rtspc->buf) == 0); while(blen && buf[0] != '$') { if(!in_body && buf[0] == 'R' && data->set.rtspreq != RTSPREQ_RECEIVE) { @@ -674,13 +728,12 @@ static CURLcode rtsp_filter_rtp(struct Curl_easy *data, /* 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); + result = rtp_write_body_junk(data, rtspc, buf - skip_len, skip_len); skip_len = 0; if(result) goto out; } - if(Curl_dyn_addn(&rtspc->buf, buf, 1)) { + if(curlx_dyn_addn(&rtspc->buf, buf, 1)) { result = CURLE_OUT_OF_MEMORY; goto out; } @@ -695,7 +748,7 @@ static CURLcode rtsp_filter_rtp(struct Curl_easy *data, 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); + DEBUGASSERT(curlx_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; @@ -706,7 +759,8 @@ static CURLcode rtsp_filter_rtp(struct Curl_easy *data, /* 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); + result = rtp_write_body_junk(data, rtspc, + curlx_dyn_ptr(&rtspc->buf), 1); if(result) goto out; } @@ -714,12 +768,12 @@ static CURLcode rtsp_filter_rtp(struct Curl_easy *data, /* count the '$' as skip and continue */ skip_len = 1; } - Curl_dyn_free(&rtspc->buf); + curlx_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)) { + if(curlx_dyn_addn(&rtspc->buf, buf, 1)) { result = CURLE_OUT_OF_MEMORY; goto out; } @@ -731,10 +785,10 @@ static CURLcode rtsp_filter_rtp(struct Curl_easy *data, } case RTP_PARSE_LEN: { - size_t rtp_len = Curl_dyn_len(&rtspc->buf); + size_t rtp_len = curlx_dyn_len(&rtspc->buf); const char *rtp_buf; DEBUGASSERT(rtp_len >= 2 && rtp_len < 4); - if(Curl_dyn_addn(&rtspc->buf, buf, 1)) { + if(curlx_dyn_addn(&rtspc->buf, buf, 1)) { result = CURLE_OUT_OF_MEMORY; goto out; } @@ -743,19 +797,19 @@ static CURLcode rtsp_filter_rtp(struct Curl_easy *data, --blen; if(rtp_len == 2) break; - rtp_buf = Curl_dyn_ptr(&rtspc->buf); + rtp_buf = curlx_dyn_ptr(&rtspc->buf); rtspc->rtp_len = RTP_PKT_LENGTH(rtp_buf) + 4; rtspc->state = RTP_PARSE_DATA; break; } case RTP_PARSE_DATA: { - size_t rtp_len = Curl_dyn_len(&rtspc->buf); + size_t rtp_len = curlx_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)) { + if(curlx_dyn_addn(&rtspc->buf, buf, needed)) { result = CURLE_OUT_OF_MEMORY; goto out; } @@ -765,15 +819,15 @@ static CURLcode rtsp_filter_rtp(struct Curl_easy *data, /* 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), + result = rtp_client_write(data, curlx_dyn_ptr(&rtspc->buf), rtspc->rtp_len); - Curl_dyn_free(&rtspc->buf); + curlx_dyn_free(&rtspc->buf); rtspc->state = RTP_PARSE_SKIP; if(result) goto out; } else { - if(Curl_dyn_addn(&rtspc->buf, buf, blen)) { + if(curlx_dyn_addn(&rtspc->buf, buf, blen)) { result = CURLE_OUT_OF_MEMORY; goto out; } @@ -791,7 +845,7 @@ static CURLcode rtsp_filter_rtp(struct Curl_easy *data, } out: if(!result && skip_len) - result = rtp_write_body_junk(data, (char *)(buf - skip_len), skip_len); + result = rtp_write_body_junk(data, rtspc, buf - skip_len, skip_len); return result; } @@ -800,10 +854,14 @@ static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, size_t blen, bool is_eos) { - struct rtsp_conn *rtspc = &(data->conn->proto.rtspc); + struct rtsp_conn *rtspc = + Curl_conn_meta_get(data->conn, CURL_META_RTSP_CONN); CURLcode result = CURLE_OK; size_t consumed = 0; + if(!rtspc) + return CURLE_FAILED_INIT; + if(!data->req.header) rtspc->in_header = FALSE; if(!blen) { @@ -815,7 +873,7 @@ static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, /* If header parsing is not ongoing, extract RTP messages */ if(!rtspc->in_header) { - result = rtsp_filter_rtp(data, buf, blen, &consumed); + result = rtsp_filter_rtp(data, rtspc, buf, blen, &consumed); if(result) goto out; buf += consumed; @@ -847,7 +905,7 @@ static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, data->req.size = 0; data->req.download_done = TRUE; } - result = rtsp_filter_rtp(data, buf, blen, &consumed); + result = rtsp_filter_rtp(data, rtspc, buf, blen, &consumed); if(result) goto out; blen -= consumed; @@ -865,8 +923,7 @@ static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, data->req.size)); if(!result && (is_eos || blen)) { result = Curl_client_write(data, CLIENTWRITE_BODY| - (is_eos ? CLIENTWRITE_EOS : 0), - (char *)buf, blen); + (is_eos ? CLIENTWRITE_EOS : 0), buf, blen); } out: @@ -906,7 +963,7 @@ CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len) } Curl_set_in_callback(data, TRUE); - wrote = writeit((char *)ptr, 1, len, user_ptr); + wrote = writeit((char *)CURL_UNCONST(ptr), 1, len, user_ptr); Curl_set_in_callback(data, FALSE); if(CURL_WRITEFUNC_PAUSE == wrote) { @@ -925,21 +982,18 @@ CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len) CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header) { if(checkprefix("CSeq:", header)) { - long CSeq = 0; - char *endp; + curl_off_t CSeq = 0; + struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY); const char *p = &header[5]; - while(ISBLANK(*p)) - p++; - CSeq = strtol(p, &endp, 10); - if(p != endp) { - struct RTSP *rtsp = data->req.p.rtsp; - rtsp->CSeq_recv = CSeq; /* mark the request */ - data->state.rtsp_CSeq_recv = CSeq; /* update the handle */ - } - else { + if(!rtsp) + return CURLE_FAILED_INIT; + curlx_str_passblanks(&p); + if(curlx_str_number(&p, &CSeq, LONG_MAX)) { failf(data, "Unable to read the CSeq header: [%s]", header); return CURLE_RTSP_CSEQ_ERROR; } + rtsp->CSeq_recv = (long)CSeq; /* mark the request */ + data->state.rtsp_CSeq_recv = (long)CSeq; /* update the handle */ } else if(checkprefix("Session:", header)) { const char *start, *end; @@ -947,8 +1001,7 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header) /* Find the first non-space letter */ start = header + 8; - while(*start && ISBLANK(*start)) - start++; + curlx_str_passblanks(&start); if(!*start) { failf(data, "Got a blank Session ID"); @@ -962,7 +1015,7 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header) * gstreamer does url-encoded session ID's not covered by the standard. */ end = start; - while(*end && *end != ';' && !ISSPACE(*end)) + while((*end > ' ') && (*end != ';')) end++; idlen = end - start; @@ -1006,29 +1059,24 @@ CURLcode rtsp_parse_transport(struct Curl_easy *data, const char *transport) const char *start, *end; start = transport; while(start && *start) { - while(*start && ISBLANK(*start) ) - start++; + curlx_str_passblanks(&start); end = strchr(start, ';'); if(checkprefix("interleaved=", start)) { - long chan1, chan2, chan; - char *endp; + curl_off_t chan1, chan2, chan; const char *p = start + 12; - chan1 = strtol(p, &endp, 10); - if(p != endp && chan1 >= 0 && chan1 <= 255) { + if(!curlx_str_number(&p, &chan1, 255)) { unsigned char *rtp_channel_mask = data->state.rtp_channel_mask; chan2 = chan1; - if(*endp == '-') { - p = endp + 1; - chan2 = strtol(p, &endp, 10); - if(p == endp || chan2 < 0 || chan2 > 255) { + if(!curlx_str_single(&p, '-')) { + if(curlx_str_number(&p, &chan2, 255)) { infof(data, "Unable to read the interleaved parameter from " "Transport header: [%s]", transport); chan2 = chan1; } } for(chan = chan1; chan <= chan2; chan++) { - long idx = chan / 8; - long off = chan % 8; + int idx = (int)chan / 8; + int off = (int)chan % 8; rtp_channel_mask[idx] |= (unsigned char)(1 << off); } } diff --git a/Utilities/cmcurl/lib/rtsp.h b/Utilities/cmcurl/lib/rtsp.h index 68f6f4fe01..59f20a9f16 100644 --- a/Utilities/cmcurl/lib/rtsp.h +++ b/Utilities/cmcurl/lib/rtsp.h @@ -36,32 +36,4 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const 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 - * - * Currently, only used for tracking incomplete RTP data reads - */ -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 { - long CSeq_sent; /* CSeq of this request */ - long CSeq_recv; /* CSeq received */ -}; - - #endif /* HEADER_CURL_RTSP_H */ diff --git a/Utilities/cmcurl/lib/select.c b/Utilities/cmcurl/lib/select.c index ef8d554f1c..ee239b7321 100644 --- a/Utilities/cmcurl/lib/select.c +++ b/Utilities/cmcurl/lib/select.c @@ -45,8 +45,8 @@ #include "urldata.h" #include "connect.h" #include "select.h" -#include "timediff.h" -#include "warnless.h" +#include "curlx/timediff.h" +#include "curlx/warnless.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -74,7 +74,7 @@ int Curl_wait_ms(timediff_t timeout_ms) if(!timeout_ms) return 0; if(timeout_ms < 0) { - SET_SOCKERRNO(EINVAL); + SET_SOCKERRNO(SOCKEINVAL); return -1; } #if defined(MSDOS) @@ -86,7 +86,7 @@ int Curl_wait_ms(timediff_t timeout_ms) timeout_ms = ULONG_MAX-1; /* do not use ULONG_MAX, because that is equal to INFINITE */ #endif - Sleep((ULONG)timeout_ms); + Sleep((DWORD)timeout_ms); #else /* avoid using poll() for this since it behaves incorrectly with no sockets on Apple operating systems */ @@ -96,7 +96,7 @@ int Curl_wait_ms(timediff_t timeout_ms) } #endif /* _WIN32 */ if(r) { - if((r == -1) && (SOCKERRNO == EINTR)) + if((r == -1) && (SOCKERRNO == SOCKEINTR)) /* make EINTR from select or poll not a "lethal" error */ r = 0; else @@ -197,7 +197,7 @@ int Curl_socket_check(curl_socket_t readfd0, /* two sockets to read from */ return Curl_wait_ms(timeout_ms); } - /* Avoid initial timestamp, avoid Curl_now() call, when elapsed + /* Avoid initial timestamp, avoid curlx_now() call, when elapsed time in this function does not need to be measured. This happens when function is called with a zero timeout or a negative timeout value indicating a blocking call should be performed. */ @@ -292,7 +292,7 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) return Curl_wait_ms(timeout_ms); } - /* Avoid initial timestamp, avoid Curl_now() call, when elapsed + /* Avoid initial timestamp, avoid curlx_now() call, when elapsed time in this function does not need to be measured. This happens when function is called with a zero timeout or a negative timeout value indicating a blocking call should be performed. */ @@ -312,7 +312,7 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) pending_ms = 0; r = poll(ufds, nfds, pending_ms); if(r <= 0) { - if((r == -1) && (SOCKERRNO == EINTR)) + if((r == -1) && (SOCKERRNO == SOCKEINTR)) /* make EINTR from select or poll not a "lethal" error */ r = 0; return r; @@ -360,7 +360,7 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) */ r = our_select(maxfd, &fds_read, &fds_write, &fds_err, timeout_ms); if(r <= 0) { - if((r == -1) && (SOCKERRNO == EINTR)) + if((r == -1) && (SOCKERRNO == SOCKEINTR)) /* make EINTR from select or poll not a "lethal" error */ r = 0; return r; @@ -410,6 +410,11 @@ void Curl_pollfds_init(struct curl_pollfds *cpfds, } } +void Curl_pollfds_reset(struct curl_pollfds *cpfds) +{ + cpfds->n = 0; +} + void Curl_pollfds_cleanup(struct curl_pollfds *cpfds) { DEBUGASSERT(cpfds); diff --git a/Utilities/cmcurl/lib/select.h b/Utilities/cmcurl/lib/select.h index 608395ff34..10968fab7f 100644 --- a/Utilities/cmcurl/lib/select.h +++ b/Utilities/cmcurl/lib/select.h @@ -93,7 +93,7 @@ int Curl_wait_ms(timediff_t timeout_ms); #define FDSET_SOCK(x) 1 #define VERIFY_SOCK(x) do { \ if(!VALID_SOCK(x)) { \ - SET_SOCKERRNO(WSAEINVAL); \ + SET_SOCKERRNO(SOCKEINVAL); \ return -1; \ } \ } while(0) @@ -105,7 +105,7 @@ int Curl_wait_ms(timediff_t timeout_ms); #define VERIFY_SOCK(x) do { \ if(!VALID_SOCK(x) || !FDSET_SOCK(x)) { \ - SET_SOCKERRNO(EINVAL); \ + SET_SOCKERRNO(SOCKEINVAL); \ return -1; \ } \ } while(0) @@ -122,6 +122,8 @@ void Curl_pollfds_init(struct curl_pollfds *cpfds, struct pollfd *static_pfds, unsigned int static_count); +void Curl_pollfds_reset(struct curl_pollfds *cpfds); + void Curl_pollfds_cleanup(struct curl_pollfds *cpfds); CURLcode Curl_pollfds_add_ps(struct curl_pollfds *cpfds, diff --git a/Utilities/cmcurl/lib/sendf.c b/Utilities/cmcurl/lib/sendf.c index bffbd6401e..feb4598b06 100644 --- a/Utilities/cmcurl/lib/sendf.c +++ b/Utilities/cmcurl/lib/sendf.c @@ -42,6 +42,7 @@ #include "connect.h" #include "content_encoding.h" #include "cw-out.h" +#include "cw-pause.h" #include "vtls/vtls.h" #include "vssh/ssh.h" #include "easyif.h" @@ -51,7 +52,7 @@ #include "strdup.h" #include "http2.h" #include "progress.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "ws.h" /* The last 3 #include files should be in this order */ @@ -359,7 +360,7 @@ static CURLcode cw_raw_write(struct Curl_easy *data, 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); + Curl_debug(data, CURLINFO_DATA_IN, buf, nbytes); } return Curl_cwriter_write(data, writer->next, type, buf, nbytes); } @@ -433,21 +434,37 @@ static CURLcode do_init_writer_stack(struct Curl_easy *data) if(result) return result; - result = Curl_cwriter_create(&writer, data, &cw_download, CURL_CW_PROTOCOL); + /* This places the "pause" writer behind the "download" writer that + * is added below. Meaning the "download" can do checks on content length + * and other things *before* write outs are buffered for paused transfers. */ + result = Curl_cwriter_create(&writer, data, &Curl_cwt_pause, + CURL_CW_PROTOCOL); + if(!result) { + result = Curl_cwriter_add(data, writer); + if(result) + Curl_cwriter_free(data, writer); + } if(result) return result; - result = Curl_cwriter_add(data, writer); - if(result) { - Curl_cwriter_free(data, writer); + + result = Curl_cwriter_create(&writer, data, &cw_download, CURL_CW_PROTOCOL); + if(!result) { + result = Curl_cwriter_add(data, writer); + if(result) + Curl_cwriter_free(data, writer); } + if(result) + return result; result = Curl_cwriter_create(&writer, data, &cw_raw, CURL_CW_RAW); + if(!result) { + result = Curl_cwriter_add(data, writer); + if(result) + Curl_cwriter_free(data, writer); + } if(result) return result; - result = Curl_cwriter_add(data, writer); - if(result) { - Curl_cwriter_free(data, writer); - } + return result; } @@ -494,6 +511,16 @@ struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data, return NULL; } +bool Curl_cwriter_is_content_decoding(struct Curl_easy *data) +{ + struct Curl_cwriter *writer; + for(writer = data->req.writer_stack; writer; writer = writer->next) { + if(writer->phase == CURL_CW_CONTENT_DECODE) + return TRUE; + } + return FALSE; +} + bool Curl_cwriter_is_paused(struct Curl_easy *data) { return Curl_cw_out_is_paused(data); @@ -1003,13 +1030,6 @@ static CURLcode cr_lc_read(struct Curl_easy *data, 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... */ - } } if(start < i) { /* leftover */ @@ -1286,6 +1306,15 @@ static bool cr_buf_needs_rewind(struct Curl_easy *data, return ctx->index > 0; } +static CURLcode cr_buf_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_buf_ctx *ctx = reader->ctx; + (void)data; + ctx->index = 0; + return CURLE_OK; +} + static curl_off_t cr_buf_total_length(struct Curl_easy *data, struct Curl_creader *reader) { @@ -1325,7 +1354,7 @@ static const struct Curl_crtype cr_buf = { cr_buf_needs_rewind, cr_buf_total_length, cr_buf_resume_from, - Curl_creader_def_rewind, + cr_buf_rewind, Curl_creader_def_unpause, Curl_creader_def_is_paused, Curl_creader_def_done, diff --git a/Utilities/cmcurl/lib/sendf.h b/Utilities/cmcurl/lib/sendf.h index 41ca8659c3..e5cc600bfe 100644 --- a/Utilities/cmcurl/lib/sendf.h +++ b/Utilities/cmcurl/lib/sendf.h @@ -182,6 +182,8 @@ CURLcode Curl_cwriter_write(struct Curl_easy *data, */ bool Curl_cwriter_is_paused(struct Curl_easy *data); +bool Curl_cwriter_is_content_decoding(struct Curl_easy *data); + /** * Unpause client writer and flush any buffered date to the client. */ diff --git a/Utilities/cmcurl/lib/setopt.c b/Utilities/cmcurl/lib/setopt.c index 8a87d24446..d5ddf6d0c2 100644 --- a/Utilities/cmcurl/lib/setopt.c +++ b/Utilities/cmcurl/lib/setopt.c @@ -43,8 +43,9 @@ #include "strcase.h" #include "share.h" #include "vtls/vtls.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "sendf.h" +#include "hostip.h" #include "http2.h" #include "setopt.h" #include "multiif.h" @@ -255,6 +256,157 @@ static CURLcode httpauth(struct Curl_easy *data, bool proxy, return CURLE_OK; } +#ifndef CURL_DISABLE_HTTP +static CURLcode setopt_HTTP_VERSION(struct Curl_easy *data, long arg) +{ + /* + * This sets a requested HTTP version to be used. The value is one of + * the listed enums in curl/curl.h. + */ + switch(arg) { + case CURL_HTTP_VERSION_NONE: + /* accepted */ + break; + case CURL_HTTP_VERSION_1_0: + case CURL_HTTP_VERSION_1_1: + /* accepted */ + break; +#ifdef USE_HTTP2 + case CURL_HTTP_VERSION_2_0: + case CURL_HTTP_VERSION_2TLS: + case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE: + /* accepted */ + break; +#endif +#ifdef USE_HTTP3 + case CURL_HTTP_VERSION_3: + case CURL_HTTP_VERSION_3ONLY: + /* accepted */ + break; +#endif + default: + /* not accepted */ + if(arg < CURL_HTTP_VERSION_NONE) + return CURLE_BAD_FUNCTION_ARGUMENT; + return CURLE_UNSUPPORTED_PROTOCOL; + } + data->set.httpwant = (unsigned char)arg; + return CURLE_OK; +} +#endif /* ! CURL_DISABLE_HTTP */ + +#ifdef USE_SSL +static CURLcode setopt_SSLVERSION(struct Curl_easy *data, CURLoption option, + long arg) +{ + /* + * Set explicit SSL version to try to connect with, as some SSL + * implementations are lame. + */ + { + long version, version_max; + struct ssl_primary_config *primary = &data->set.ssl.primary; +#ifndef CURL_DISABLE_PROXY + if(option != CURLOPT_SSLVERSION) + primary = &data->set.proxy_ssl.primary; +#else + if(option) {} +#endif + version = C_SSLVERSION_VALUE(arg); + version_max = (long)C_SSLVERSION_MAX_VALUE(arg); + + if(version < CURL_SSLVERSION_DEFAULT || + version == CURL_SSLVERSION_SSLv2 || + version == CURL_SSLVERSION_SSLv3 || + version >= CURL_SSLVERSION_LAST || + version_max < CURL_SSLVERSION_MAX_NONE || + version_max >= CURL_SSLVERSION_MAX_LAST) + return CURLE_BAD_FUNCTION_ARGUMENT; + + primary->version = (unsigned char)version; + primary->version_max = (unsigned int)version_max; + } + return CURLE_OK; +} +#endif /* ! USE_SSL */ + +#ifndef CURL_DISABLE_RTSP +static CURLcode setopt_RTSP_REQUEST(struct Curl_easy *data, long arg) +{ + /* + * Set the RTSP request method (OPTIONS, SETUP, PLAY, etc...) + * Would this be better if the RTSPREQ_* were just moved into here? + */ + 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; + return CURLE_OK; +} +#endif /* ! CURL_DISABLE_RTSP */ + +#ifdef USE_SSL +static void set_ssl_options(struct ssl_config_data *ssl, + struct ssl_primary_config *config, + long arg) +{ + config->ssl_options = (unsigned char)(arg & 0xff); + ssl->enable_beast = !!(arg & CURLSSLOPT_ALLOW_BEAST); + ssl->no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE); + ssl->no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN); + ssl->revoke_best_effort = !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT); + ssl->native_ca_store = !!(arg & CURLSSLOPT_NATIVE_CA); + ssl->auto_client_cert = !!(arg & CURLSSLOPT_AUTO_CLIENT_CERT); + ssl->earlydata = !!(arg & CURLSSLOPT_EARLYDATA); +} +#endif + static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, long arg) { @@ -290,7 +442,7 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, return CURLE_BAD_FUNCTION_ARGUMENT; 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. @@ -321,11 +473,7 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, /* * Shut off the internal supported progress meter */ - data->set.hide_progress = enabled; - if(data->set.hide_progress) - data->progress.flags |= PGRS_HIDE; - else - data->progress.flags &= ~PGRS_HIDE; + data->progress.hide = enabled; break; case CURLOPT_NOBODY: /* @@ -438,7 +586,7 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, */ if((arg < CURL_TIMECOND_NONE) || (arg >= CURL_TIMECOND_LAST)) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.timecondition = (unsigned char)(curl_TimeCond)arg; + data->set.timecondition = (unsigned char)arg; break; case CURLOPT_TIMEVALUE: /* @@ -451,36 +599,12 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_SSLVERSION: #endif - /* - * Set explicit SSL version to try to connect with, as some SSL - * implementations are lame. - */ #ifdef USE_SSL - { - long version, version_max; - struct ssl_primary_config *primary = &data->set.ssl.primary; -#ifndef CURL_DISABLE_PROXY - if(option != CURLOPT_SSLVERSION) - primary = &data->set.proxy_ssl.primary; -#endif - version = C_SSLVERSION_VALUE(arg); - version_max = (long)C_SSLVERSION_MAX_VALUE(arg); - - if(version < CURL_SSLVERSION_DEFAULT || - version == CURL_SSLVERSION_SSLv2 || - version == CURL_SSLVERSION_SSLv3 || - version >= CURL_SSLVERSION_LAST || - version_max < CURL_SSLVERSION_MAX_NONE || - version_max >= CURL_SSLVERSION_MAX_LAST) - return CURLE_BAD_FUNCTION_ARGUMENT; - - primary->version = (unsigned char)version; - primary->version_max = (unsigned int)version_max; - } + return setopt_SSLVERSION(data, option, arg); #else return CURLE_NOT_BUILT_IN; #endif - break; + case CURLOPT_POSTFIELDSIZE: /* * The size of the POSTFIELD data to prevent libcurl to do strlen() to @@ -525,7 +649,9 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, /* * Follow Location: header hints on an HTTP-server. */ - data->set.http_follow_location = enabled; + if(uarg > 3) + return CURLE_BAD_FUNCTION_ARGUMENT; + data->set.http_follow_mode = (unsigned char)uarg; break; case CURLOPT_UNRESTRICTED_AUTH: @@ -594,44 +720,7 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, 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. - */ - switch(arg) { - case CURL_HTTP_VERSION_NONE: -#ifdef USE_HTTP2 - /* This seems an undesirable quirk to force a behaviour on lower - * implementations that they should recognize independently? */ - arg = CURL_HTTP_VERSION_2TLS; -#endif - /* accepted */ - break; - case CURL_HTTP_VERSION_1_0: - case CURL_HTTP_VERSION_1_1: - /* accepted */ - break; -#ifdef USE_HTTP2 - case CURL_HTTP_VERSION_2_0: - case CURL_HTTP_VERSION_2TLS: - case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE: - /* accepted */ - break; -#endif -#ifdef USE_HTTP3 - case CURL_HTTP_VERSION_3: - case CURL_HTTP_VERSION_3ONLY: - /* accepted */ - break; -#endif - default: - /* not accepted */ - if(arg < CURL_HTTP_VERSION_NONE) - return CURLE_BAD_FUNCTION_ARGUMENT; - return CURLE_UNSUPPORTED_PROTOCOL; - } - data->set.httpwant = (unsigned char)arg; - break; + return setopt_HTTP_VERSION(data, arg); case CURLOPT_EXPECT_100_TIMEOUT_MS: /* @@ -692,7 +781,7 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, data->set.proxy_transfer_mode = (bool)uarg; break; case CURLOPT_SOCKS5_AUTH: - if(data->set.socks5auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) + if(uarg & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) return CURLE_NOT_BUILT_IN; data->set.socks5auth = (unsigned char)uarg; break; @@ -1054,29 +1143,12 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, data->set.use_ssl = (unsigned char)arg; break; 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. */ + set_ssl_options(&data->set.ssl, &data->set.ssl.primary, arg); break; #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); + set_ssl_options(&data->set.proxy_ssl, &data->set.proxy_ssl.primary, arg); break; #endif @@ -1132,12 +1204,8 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, /* * disable libcurl transfer encoding is used */ -#ifndef USE_HYPER data->set.http_te_skip = !enabled; /* reversed */ break; -#else - return CURLE_NOT_BUILT_IN; /* hyper does not support */ -#endif case CURLOPT_HTTP_CONTENT_DECODING: /* @@ -1207,63 +1275,7 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, break; #ifndef CURL_DISABLE_RTSP case CURLOPT_RTSP_REQUEST: - { - /* - * Set the RTSP request method (OPTIONS, SETUP, PLAY, etc...) - * Would this be better if the RTSPREQ_* were just moved into here? - */ - 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; - } + return setopt_RTSP_REQUEST(data, arg); case CURLOPT_RTSP_CLIENT_CSEQ: /* * Set the CSEQ number to issue for the next RTSP request. Useful if the @@ -1391,7 +1403,8 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, #endif /* ! CURL_DISABLE_ALTSVC */ #ifndef CURL_DISABLE_WEBSOCKETS case CURLOPT_WS_OPTIONS: - data->set.ws_raw_mode = (bool)(arg & CURLWS_RAW_MODE); + data->set.ws_raw_mode = (bool)(arg & CURLWS_RAW_MODE); + data->set.ws_no_auto_pong = (bool)(arg & CURLWS_NOAUTOPONG); break; #endif case CURLOPT_QUICK_EXIT: @@ -1406,7 +1419,9 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option, */ Curl_safefree(data->set.str[STRING_SSL_ENGINE]); return Curl_ssl_set_engine_default(data); - + case CURLOPT_UPLOAD_FLAGS: + data->set.upload_flags = (unsigned char)arg; + break; default: /* unknown option */ return CURLE_UNKNOWN_OPTION; @@ -1534,7 +1549,7 @@ static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option, #if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) || \ !defined(CURL_DISABLE_IMAP) # ifndef CURL_DISABLE_MIME - case CURLOPT_MIMEPOST: + case CURLOPT_MIMEPOST: /* * Set to make us do MIME POST */ @@ -1570,11 +1585,6 @@ static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option, 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; @@ -1588,6 +1598,10 @@ static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option, if(data->psl == &data->share->psl) data->psl = data->multi ? &data->multi->psl : NULL; #endif + if(data->share->specifier & (1 << CURL_LOCK_DATA_DNS)) { + Curl_resolv_unlink(data, &data->state.dns[0]); + Curl_resolv_unlink(data, &data->state.dns[1]); + } data->share->dirty--; @@ -1604,11 +1618,6 @@ static CURLcode setopt_pointers(struct Curl_easy *data, CURLoption option, 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 */ @@ -1662,8 +1671,8 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, 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; + else + return CURLE_NOT_BUILT_IN; #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_SSL_CIPHER_LIST: if(Curl_ssl_supports(data, SSLSUPP_CIPHER_LIST)) { @@ -1673,7 +1682,6 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, } else return CURLE_NOT_BUILT_IN; - break; #endif case CURLOPT_TLS13_CIPHERS: if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) { @@ -1682,7 +1690,6 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, } else return CURLE_NOT_BUILT_IN; - break; #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_TLS13_CIPHERS: if(Curl_ssl_supports(data, SSLSUPP_TLS13_CIPHERSUITES)) @@ -1691,7 +1698,6 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, ptr); else return CURLE_NOT_BUILT_IN; - break; #endif case CURLOPT_RANDOM_FILE: break; @@ -1944,7 +1950,6 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * to decide for us (if CURLOPT_SOCKS_PROXY setting it to NULL). */ return Curl_setstropt(&data->set.str[STRING_PROXY], ptr); - break; case CURLOPT_PRE_PROXY: /* @@ -1969,95 +1974,95 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * Set authentication service name for DIGEST-MD5, Kerberos 5 and SPNEGO */ return Curl_setstropt(&data->set.str[STRING_SERVICE_NAME], ptr); - break; case CURLOPT_HEADERDATA: /* * Custom pointer to pass the header write callback function */ - data->set.writeheader = (void *)ptr; + data->set.writeheader = ptr; break; 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 = (void *)ptr; + data->set.in_set = ptr; break; case CURLOPT_WRITEDATA: /* * FILE pointer to write to. Or possibly used as argument to the write * callback. */ - data->set.out = (void *)ptr; + data->set.out = ptr; 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 = (void *)ptr; + data->set.debugdata = ptr; break; case CURLOPT_PROGRESSDATA: /* * Custom client data to pass to the progress callback */ - data->set.progress_client = (void *)ptr; + data->set.progress_client = ptr; break; case CURLOPT_SEEKDATA: /* * Seek control callback. Might be NULL. */ - data->set.seek_client = (void *)ptr; + data->set.seek_client = ptr; break; case CURLOPT_IOCTLDATA: /* * I/O control data pointer. Might be NULL. */ - data->set.ioctl_client = (void *)ptr; + data->set.ioctl_client = ptr; break; case CURLOPT_SSL_CTX_DATA: /* - * Set a SSL_CTX callback parameter pointer + * Set an SSL_CTX callback parameter pointer */ #ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) - data->set.ssl.fsslctxp = (void *)ptr; + if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) { + data->set.ssl.fsslctxp = ptr; + break; + } else #endif return CURLE_NOT_BUILT_IN; - break; case CURLOPT_SOCKOPTDATA: /* * socket callback data pointer. Might be NULL. */ - data->set.sockopt_client = (void *)ptr; + data->set.sockopt_client = ptr; break; case CURLOPT_OPENSOCKETDATA: /* * socket callback data pointer. Might be NULL. */ - data->set.opensocket_client = (void *)ptr; + data->set.opensocket_client = ptr; break; case CURLOPT_RESOLVER_START_DATA: /* * resolver start callback data pointer. Might be NULL. */ - data->set.resolver_start_client = (void *)ptr; + data->set.resolver_start_client = ptr; break; case CURLOPT_CLOSESOCKETDATA: /* * socket callback data pointer. Might be NULL. */ - data->set.closesocket_client = (void *)ptr; + data->set.closesocket_client = ptr; break; case CURLOPT_TRAILERDATA: #ifndef CURL_DISABLE_HTTP - data->set.trailer_data = (void *)ptr; + data->set.trailer_data = ptr; #endif break; case CURLOPT_PREREQDATA: - data->set.prereq_userp = (void *)ptr; + data->set.prereq_userp = ptr; break; case CURLOPT_ERRORBUFFER: @@ -2146,12 +2151,16 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, result = setstropt_userpwd(ptr, &u, &p); /* URL decode the components */ - if(!result && u) + if(!result && u) { + Curl_safefree(data->set.str[STRING_PROXYUSERNAME]); result = Curl_urldecode(u, 0, &data->set.str[STRING_PROXYUSERNAME], NULL, REJECT_ZERO); - if(!result && p) + } + if(!result && p) { + Curl_safefree(data->set.str[STRING_PROXYPASSWORD]); result = Curl_urldecode(p, 0, &data->set.str[STRING_PROXYPASSWORD], NULL, REJECT_ZERO); + } free(u); free(p); } @@ -2220,6 +2229,7 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * String that holds file type of the SSL certificate to use for proxy */ return Curl_setstropt(&data->set.str[STRING_CERT_TYPE_PROXY], ptr); + #endif case CURLOPT_SSLKEY: /* @@ -2240,7 +2250,7 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * String that holds file type of the SSL key to use */ return Curl_setstropt(&data->set.str[STRING_KEY_TYPE], ptr); - break; + #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_SSLKEYTYPE: /* @@ -2261,6 +2271,7 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * 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: /* @@ -2283,6 +2294,7 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, /* enable the HAProxy protocol */ data->set.haproxyprotocol = TRUE; break; + #endif case CURLOPT_INTERFACE: /* @@ -2331,8 +2343,8 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * CA certificate */ return Curl_setstropt(&data->set.str[STRING_SSL_CAFILE_PROXY], ptr); -#endif +#endif case CURLOPT_CAPATH: /* * Set CA path info for SSL connection. Specify directory name of the CA @@ -2371,6 +2383,7 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * CRL to check certificates revocation */ return Curl_setstropt(&data->set.str[STRING_SSL_CRLFILE_PROXY], ptr); + #endif case CURLOPT_ISSUERCERT: /* @@ -2388,12 +2401,11 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, return Curl_setstropt(&data->set.str[STRING_SSL_ISSUERCERT_PROXY], ptr); #endif - case CURLOPT_PRIVATE: /* * Set private data pointer. */ - data->set.private_data = (void *)ptr; + data->set.private_data = ptr; break; #ifdef USE_SSL @@ -2403,6 +2415,16 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * Specify colon-delimited list of curve algorithm names. */ return Curl_setstropt(&data->set.str[STRING_SSL_EC_CURVES], ptr); + + case CURLOPT_SSL_SIGNATURE_ALGORITHMS: + /* + * Set accepted signature algorithms. + * Specify colon-delimited list of signature scheme names. + */ + if(Curl_ssl_supports(data, SSLSUPP_SIGNATURE_ALGORITHMS)) + return Curl_setstropt(&data->set.str[STRING_SSL_SIGNATURE_ALGORITHMS], + ptr); + return CURLE_NOT_BUILT_IN; #endif #ifdef USE_SSH case CURLOPT_SSH_PUBLIC_KEYFILE: @@ -2435,7 +2457,7 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, /* * Custom client data to pass to the SSH keyfunc callback */ - data->set.ssh_keyfunc_userp = (void *)ptr; + data->set.ssh_keyfunc_userp = ptr; break; #ifdef USE_LIBSSH2 case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256: @@ -2450,7 +2472,7 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, /* * Custom client data to pass to the SSH keyfunc callback */ - data->set.ssh_hostkeyfunc_userp = (void *)ptr; + data->set.ssh_hostkeyfunc_userp = ptr; break; #endif /* USE_LIBSSH2 */ #endif /* USE_SSH */ @@ -2481,7 +2503,6 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, /* 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); @@ -2500,7 +2521,6 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, * for generic server options, the application will need to set this. */ return Curl_setstropt(&data->set.str[STRING_RTSP_STREAM_URI], ptr); - break; case CURLOPT_RTSP_TRANSPORT: /* @@ -2509,15 +2529,15 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, return Curl_setstropt(&data->set.str[STRING_RTSP_TRANSPORT], ptr); case CURLOPT_INTERLEAVEDATA: - data->set.rtp_out = (void *)ptr; + data->set.rtp_out = ptr; break; #endif /* ! CURL_DISABLE_RTSP */ #ifndef CURL_DISABLE_FTP case CURLOPT_CHUNK_DATA: - data->set.wildcardptr = (void *)ptr; + data->set.wildcardptr = ptr; break; case CURLOPT_FNMATCH_DATA: - data->set.fnmatch_data = (void *)ptr; + data->set.fnmatch_data = ptr; break; #endif #ifdef USE_TLS_SRP @@ -2547,30 +2567,30 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, break; #endif #endif -#ifdef USE_ARES +#ifdef CURLRES_ARES case CURLOPT_DNS_SERVERS: result = Curl_setstropt(&data->set.str[STRING_DNS_SERVERS], ptr); if(result) return result; - return Curl_set_dns_servers(data, data->set.str[STRING_DNS_SERVERS]); + return Curl_async_ares_set_dns_servers(data); case CURLOPT_DNS_INTERFACE: result = Curl_setstropt(&data->set.str[STRING_DNS_INTERFACE], ptr); if(result) return result; - return Curl_set_dns_interface(data, data->set.str[STRING_DNS_INTERFACE]); + return Curl_async_ares_set_dns_interface(data); case CURLOPT_DNS_LOCAL_IP4: result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP4], ptr); if(result) return result; - return Curl_set_dns_local_ip4(data, data->set.str[STRING_DNS_LOCAL_IP4]); + return Curl_async_ares_set_dns_local_ip4(data); case CURLOPT_DNS_LOCAL_IP6: result = Curl_setstropt(&data->set.str[STRING_DNS_LOCAL_IP6], ptr); if(result) return result; - return Curl_set_dns_local_ip6(data, data->set.str[STRING_DNS_LOCAL_IP6]); + return Curl_async_ares_set_dns_local_ip6(data); #endif #ifdef USE_UNIX_SOCKETS @@ -2592,10 +2612,10 @@ static CURLcode setopt_cptr(struct Curl_easy *data, CURLoption option, #endif #ifndef CURL_DISABLE_HSTS case CURLOPT_HSTSREADDATA: - data->set.hsts_read_userp = (void *)ptr; + data->set.hsts_read_userp = ptr; break; case CURLOPT_HSTSWRITEDATA: - data->set.hsts_write_userp = (void *)ptr; + data->set.hsts_write_userp = ptr; break; case CURLOPT_HSTS: { struct curl_slist *h; @@ -2767,15 +2787,16 @@ static CURLcode setopt_func(struct Curl_easy *data, CURLoption option, break; case CURLOPT_SSL_CTX_FUNCTION: /* - * Set a SSL_CTX callback + * Set an SSL_CTX callback */ #ifdef USE_SSL - if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) + if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) { data->set.ssl.fsslctx = va_arg(param, curl_ssl_ctx_callback); + break; + } else #endif return CURLE_NOT_BUILT_IN; - break; case CURLOPT_SOCKOPTFUNCTION: /* @@ -2808,7 +2829,6 @@ static CURLcode setopt_func(struct Curl_easy *data, CURLoption option, data->set.resolver_start = va_arg(param, curl_resolver_start_callback); break; - #ifdef USE_SSH #ifdef USE_LIBSSH2 case CURLOPT_SSH_HOSTKEYFUNCTION: @@ -3073,9 +3093,7 @@ CURLcode curl_easy_setopt(CURL *d, 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 + failf(data, "setopt 0x%x got bad argument", tag); return result; } diff --git a/Utilities/cmcurl/lib/setup-vms.h b/Utilities/cmcurl/lib/setup-vms.h index 59b69b6806..d74f4ad630 100644 --- a/Utilities/cmcurl/lib/setup-vms.h +++ b/Utilities/cmcurl/lib/setup-vms.h @@ -394,51 +394,11 @@ static struct passwd *vms_getpwuid(uid_t uid) /* that way a newer port will also work if some one has one */ #ifdef __VAX -# if (OPENSSL_VERSION_NUMBER < 0x00907001L) -# define des_set_odd_parity DES_SET_ODD_PARITY -# define des_set_key DES_SET_KEY -# define des_ecb_encrypt DES_ECB_ENCRYPT - -# endif # include # ifndef OpenSSL_add_all_algorithms # define OpenSSL_add_all_algorithms OPENSSL_ADD_ALL_ALGORITHMS void OPENSSL_ADD_ALL_ALGORITHMS(void); # endif - - /* Curl defines these to lower case and VAX needs them in upper case */ - /* So we need static routines */ -# if (OPENSSL_VERSION_NUMBER < 0x00907001L) - -# undef des_set_odd_parity -# undef DES_set_odd_parity -# undef des_set_key -# undef DES_set_key -# undef des_ecb_encrypt -# undef DES_ecb_encrypt - - static void des_set_odd_parity(des_cblock *key) { - DES_SET_ODD_PARITY(key); - } - - static int des_set_key(const_des_cblock *key, - des_key_schedule schedule) { - return DES_SET_KEY(key, schedule); - } - - static void des_ecb_encrypt(const_des_cblock *input, - des_cblock *output, - des_key_schedule ks, int enc) { - DES_ECB_ENCRYPT(input, output, ks, enc); - } -#endif -/* Need this to stop a macro redefinition error */ -#if OPENSSL_VERSION_NUMBER < 0x00907000L -# ifdef X509_STORE_set_flags -# undef X509_STORE_set_flags -# define X509_STORE_set_flags(x,y) Curl_nop_stmt -# endif -#endif #endif #endif /* HEADER_CURL_SETUP_VMS_H */ diff --git a/Utilities/cmcurl/lib/setup-win32.h b/Utilities/cmcurl/lib/setup-win32.h index 70d83ad8e9..35fe513a84 100644 --- a/Utilities/cmcurl/lib/setup-win32.h +++ b/Utilities/cmcurl/lib/setup-win32.h @@ -93,12 +93,6 @@ * newer symbols. */ -#ifndef _WIN32_WINNT_NT4 -#define _WIN32_WINNT_NT4 0x0400 /* Windows NT 4.0 */ -#endif -#ifndef _WIN32_WINNT_WIN2K -#define _WIN32_WINNT_WIN2K 0x0500 /* Windows 2000 */ -#endif #ifndef _WIN32_WINNT_WINXP #define _WIN32_WINNT_WINXP 0x0501 /* Windows XP */ #endif diff --git a/Utilities/cmcurl/lib/sha256.c b/Utilities/cmcurl/lib/sha256.c index 4af858eb70..d73e0ef093 100644 --- a/Utilities/cmcurl/lib/sha256.c +++ b/Utilities/cmcurl/lib/sha256.c @@ -28,45 +28,20 @@ #if !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) \ || defined(USE_LIBSSH2) || defined(USE_SSL) -#include "warnless.h" +#include "curlx/warnless.h" #include "curl_sha256.h" #include "curl_hmac.h" -#ifdef USE_WOLFSSL -#include -#endif - -#if defined(USE_OPENSSL) - -#include - -#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) -#define USE_OPENSSL_SHA256 -#endif - -#endif /* USE_OPENSSL */ - -#ifdef USE_MBEDTLS +#ifdef USE_OPENSSL +#include +#elif defined(USE_GNUTLS) +#include +#elif defined(USE_MBEDTLS) #include - #if(MBEDTLS_VERSION_NUMBER >= 0x02070000) && \ (MBEDTLS_VERSION_NUMBER < 0x03000000) #define HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS #endif -#endif /* USE_MBEDTLS */ - -#if defined(USE_OPENSSL_SHA256) - -/* When OpenSSL or wolfSSL is available we use their SHA256-functions. */ -#if defined(USE_OPENSSL) -#include -#elif defined(USE_WOLFSSL) -#include -#endif - -#elif defined(USE_GNUTLS) -#include -#elif defined(USE_MBEDTLS) #include #elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1040)) || \ @@ -95,7 +70,7 @@ * file even if multiple backends are enabled at the same time. */ -#if defined(USE_OPENSSL_SHA256) +#ifdef USE_OPENSSL struct ossl_sha256_ctx { EVP_MD_CTX *openssl_ctx; @@ -241,7 +216,11 @@ static void my_sha256_update(void *in, unsigned int length) { my_sha256_ctx *ctx = (my_sha256_ctx *)in; - CryptHashData(ctx->hHash, (unsigned char *) data, length, 0); +#ifdef __MINGW32CE__ + CryptHashData(ctx->hHash, (BYTE *)CURL_UNCONST(data), length, 0); +#else + CryptHashData(ctx->hHash, (const BYTE *)data, length, 0); +#endif } static void my_sha256_final(unsigned char *digest, void *in) @@ -348,7 +327,7 @@ static const unsigned long K[64] = { /* Compress 512-bits */ static int sha256_compress(struct sha256_state *md, - unsigned char *buf) + const unsigned char *buf) { unsigned long S[8], W[64]; int i; @@ -426,7 +405,7 @@ static void my_sha256_update(void *ctx, return; while(inlen > 0) { if(md->curlen == 0 && inlen >= CURL_SHA256_BLOCK_SIZE) { - if(sha256_compress(md, (unsigned char *)in) < 0) + if(sha256_compress(md, in) < 0) return; md->length += CURL_SHA256_BLOCK_SIZE * 8; in += CURL_SHA256_BLOCK_SIZE; @@ -533,5 +512,4 @@ const struct HMAC_params Curl_HMAC_SHA256 = { 32 /* Result size. */ }; - #endif /* AWS, DIGEST, or libssh2 */ diff --git a/Utilities/cmcurl/lib/share.c b/Utilities/cmcurl/lib/share.c index 4145e0c653..d1ab55eb27 100644 --- a/Utilities/cmcurl/lib/share.c +++ b/Utilities/cmcurl/lib/share.c @@ -46,7 +46,19 @@ curl_share_init(void) if(share) { share->magic = CURL_GOOD_SHARE; share->specifier |= (1 << CURL_LOCK_DATA_SHARE); - Curl_init_dnscache(&share->hostcache, 23); + Curl_dnscache_init(&share->dnscache, 23); + share->admin = curl_easy_init(); + if(!share->admin) { + free(share); + return NULL; + } + /* admin handles have mid 0 */ + share->admin->mid = 0; + share->admin->state.internal = TRUE; +#ifdef DEBUGBUILD + if(getenv("CURL_DEBUG")) + share->admin->set.verbose = TRUE; +#endif } return share; @@ -125,10 +137,8 @@ curl_share_setopt(CURLSH *sh, CURLSHoption option, ...) case CURL_LOCK_DATA_CONNECT: /* 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; + if(!share->cpool.initialised) { + Curl_cpool_init(&share->cpool, share->admin, share, 103); } break; @@ -239,7 +249,8 @@ curl_share_cleanup(CURLSH *sh) if(share->specifier & (1 << CURL_LOCK_DATA_CONNECT)) { Curl_cpool_destroy(&share->cpool); } - Curl_hash_destroy(&share->hostcache); + + Curl_dnscache_destroy(&share->dnscache); #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) Curl_cookie_cleanup(share->cookies); @@ -257,6 +268,7 @@ curl_share_cleanup(CURLSH *sh) #endif Curl_psl_destroy(&share->psl); + Curl_close(&share->admin); if(share->unlockfunc) share->unlockfunc(NULL, CURL_LOCK_DATA_SHARE, share->clientdata); diff --git a/Utilities/cmcurl/lib/share.h b/Utilities/cmcurl/lib/share.h index d0cdb1b268..974c99dc20 100644 --- a/Utilities/cmcurl/lib/share.h +++ b/Utilities/cmcurl/lib/share.h @@ -31,6 +31,7 @@ #include "urldata.h" #include "conncache.h" +struct Curl_easy; struct Curl_ssl_scache; #define CURL_GOOD_SHARE 0x7e117a1e @@ -48,8 +49,9 @@ struct Curl_share { curl_lock_function lockfunc; curl_unlock_function unlockfunc; void *clientdata; + struct Curl_easy *admin; struct cpool cpool; - struct Curl_hash hostcache; + struct Curl_dnscache dnscache; /* DNS cache */ #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) struct CookieInfo *cookies; #endif diff --git a/Utilities/cmcurl/lib/sigpipe.h b/Utilities/cmcurl/lib/sigpipe.h index c57580f434..1be3a111e9 100644 --- a/Utilities/cmcurl/lib/sigpipe.h +++ b/Utilities/cmcurl/lib/sigpipe.h @@ -31,7 +31,7 @@ struct sigpipe_ignore { struct sigaction old_pipe_act; - bool no_signal; + BIT(no_signal); }; #define SIGPIPE_VARIABLE(x) struct sigpipe_ignore x diff --git a/Utilities/cmcurl/lib/smb.c b/Utilities/cmcurl/lib/smb.c index d22030ccc7..ceca9564bf 100644 --- a/Utilities/cmcurl/lib/smb.c +++ b/Utilities/cmcurl/lib/smb.c @@ -29,6 +29,7 @@ #include "smb.h" #include "urldata.h" +#include "url.h" #include "sendf.h" #include "multiif.h" #include "cfilters.h" @@ -45,6 +46,58 @@ #include "curl_memory.h" #include "memdebug.h" + +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_SMB_EASY "meta:proto:smb:easy" +/* meta key for storing protocol meta at connection */ +#define CURL_META_SMB_CONN "meta:proto:smb:conn" + +enum smb_conn_state { + SMB_NOT_CONNECTED = 0, + SMB_CONNECTING, + SMB_NEGOTIATE, + SMB_SETUP, + SMB_CONNECTED +}; + +/* SMB connection data, kept at connection */ +struct smb_conn { + enum smb_conn_state state; + char *user; + char *domain; + char *share; + unsigned char challenge[8]; + unsigned int session_key; + unsigned short uid; + char *recv_buf; + char *send_buf; + size_t upload_size; + size_t send_size; + size_t sent; + size_t got; +}; + +/* SMB request state */ +enum smb_req_state { + SMB_REQUESTING, + SMB_TREE_CONNECT, + SMB_OPEN, + SMB_DOWNLOAD, + SMB_UPLOAD, + SMB_CLOSE, + SMB_TREE_DISCONNECT, + SMB_DONE +}; + +/* SMB request data, kept at easy handle */ +struct smb_request { + enum smb_req_state state; + char *path; + unsigned short tid; /* Even if we connect to the same tree as another */ + unsigned short fid; /* request, the tid will be different */ + CURLcode result; +}; + /* * Definitions for SMB protocol data structures */ @@ -77,7 +130,7 @@ #define SMB_FLAGS_CANONICAL_PATHNAMES 0x10 #define SMB_FLAGS_CASELESS_PATHNAMES 0x08 -#define SMB_FLAGS2_UNICODE_STRINGS 0x8000 +/* #define SMB_FLAGS2_UNICODE_STRINGS 0x8000 */ #define SMB_FLAGS2_IS_LONG_NAME 0x0040 #define SMB_FLAGS2_KNOWS_LONG_NAME 0x0001 @@ -245,12 +298,11 @@ static CURLcode smb_connect(struct Curl_easy *data, bool *done); static CURLcode smb_connection_state(struct Curl_easy *data, bool *done); static CURLcode smb_do(struct Curl_easy *data, bool *done); static CURLcode smb_request_state(struct Curl_easy *data, bool *done); -static CURLcode smb_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead); static int smb_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks); static CURLcode smb_parse_url_path(struct Curl_easy *data, - struct connectdata *conn); + struct smb_conn *smbc, + struct smb_request *req); /* * SMB handler interface @@ -268,7 +320,7 @@ const struct Curl_handler Curl_handler_smb = { smb_getsock, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ - smb_disconnect, /* disconnect */ + ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ @@ -297,7 +349,7 @@ const struct Curl_handler Curl_handler_smbs = { smb_getsock, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ - smb_disconnect, /* disconnect */ + ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ @@ -341,30 +393,9 @@ static curl_off_t smb_swap64(curl_off_t x) # define smb_swap64(x) (x) #endif -/* SMB request state */ -enum smb_req_state { - SMB_REQUESTING, - SMB_TREE_CONNECT, - SMB_OPEN, - SMB_DOWNLOAD, - SMB_UPLOAD, - SMB_CLOSE, - SMB_TREE_DISCONNECT, - SMB_DONE -}; - -/* SMB request data */ -struct smb_request { - enum smb_req_state state; - char *path; - unsigned short tid; /* Even if we connect to the same tree as another */ - unsigned short fid; /* request, the tid will be different */ - CURLcode result; -}; - -static void conn_state(struct Curl_easy *data, enum smb_conn_state newstate) +static void conn_state(struct Curl_easy *data, struct smb_conn *smbc, + enum smb_conn_state newstate) { - struct smb_conn *smbc = &data->conn->proto.smbc; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* For debug purposes */ static const char * const names[] = { @@ -380,34 +411,59 @@ static void conn_state(struct Curl_easy *data, enum smb_conn_state newstate) infof(data, "SMB conn %p state change from %s to %s", (void *)smbc, names[smbc->state], names[newstate]); #endif - + (void)data; smbc->state = newstate; } static void request_state(struct Curl_easy *data, enum smb_req_state newstate) { - struct smb_request *req = data->req.p.smb; + struct smb_request *req = Curl_meta_get(data, CURL_META_SMB_EASY); + if(req) { #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - /* For debug purposes */ - static const char * const names[] = { - "SMB_REQUESTING", - "SMB_TREE_CONNECT", - "SMB_OPEN", - "SMB_DOWNLOAD", - "SMB_UPLOAD", - "SMB_CLOSE", - "SMB_TREE_DISCONNECT", - "SMB_DONE", - /* LAST */ - }; + /* For debug purposes */ + static const char * const names[] = { + "SMB_REQUESTING", + "SMB_TREE_CONNECT", + "SMB_OPEN", + "SMB_DOWNLOAD", + "SMB_UPLOAD", + "SMB_CLOSE", + "SMB_TREE_DISCONNECT", + "SMB_DONE", + /* LAST */ + }; - if(req->state != newstate) - infof(data, "SMB request %p state change from %s to %s", - (void *)req, names[req->state], names[newstate]); + if(req->state != newstate) + infof(data, "SMB request %p state change from %s to %s", + (void *)req, names[req->state], names[newstate]); #endif - req->state = newstate; + req->state = newstate; + } +} + +static void smb_easy_dtor(void *key, size_t klen, void *entry) +{ + struct smb_request *req = entry; + (void)key; + (void)klen; + /* `req->path` points to somewhere in `struct smb_conn` which is + * kept at the connection meta. If the connection is destroyed first, + * req->path points to free'd memory. */ + free(req); +} + +static void smb_conn_dtor(void *key, size_t klen, void *entry) +{ + struct smb_conn *smbc = entry; + (void)key; + (void)klen; + Curl_safefree(smbc->share); + Curl_safefree(smbc->domain); + Curl_safefree(smbc->recv_buf); + Curl_safefree(smbc->send_buf); + free(smbc); } /* this should setup things in the connection, not in the easy @@ -415,24 +471,34 @@ static void request_state(struct Curl_easy *data, static CURLcode smb_setup_connection(struct Curl_easy *data, struct connectdata *conn) { + struct smb_conn *smbc; struct smb_request *req; + /* Initialize the connection state */ + smbc = calloc(1, sizeof(*smbc)); + if(!smbc || + Curl_conn_meta_set(conn, CURL_META_SMB_CONN, smbc, smb_conn_dtor)) + return CURLE_OUT_OF_MEMORY; + /* Initialize the request state */ - data->req.p.smb = req = calloc(1, sizeof(struct smb_request)); - if(!req) + req = calloc(1, sizeof(*req)); + if(!req || + Curl_meta_set(data, CURL_META_SMB_EASY, req, smb_easy_dtor)) return CURLE_OUT_OF_MEMORY; /* Parse the URL path */ - return smb_parse_url_path(data, conn); + return smb_parse_url_path(data, smbc, req); } static CURLcode smb_connect(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; + struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); char *slash; (void) done; + if(!smbc) + return CURLE_FAILED_INIT; /* Check we have a username and password to authenticate with */ if(!data->state.aptr.user) @@ -472,10 +538,10 @@ static CURLcode smb_connect(struct Curl_easy *data, bool *done) return CURLE_OK; } -static CURLcode smb_recv_message(struct Curl_easy *data, void **msg) +static CURLcode smb_recv_message(struct Curl_easy *data, + struct smb_conn *smbc, + void **msg) { - struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; char *buf = smbc->recv_buf; ssize_t bytes_read; size_t nbt_size; @@ -520,20 +586,17 @@ static CURLcode smb_recv_message(struct Curl_easy *data, void **msg) return CURLE_OK; } -static void smb_pop_message(struct connectdata *conn) +static void smb_pop_message(struct smb_conn *smbc) { - struct smb_conn *smbc = &conn->proto.smbc; - smbc->got = 0; } -static void smb_format_message(struct Curl_easy *data, struct smb_header *h, +static void smb_format_message(struct smb_conn *smbc, + struct smb_request *req, + struct smb_header *h, unsigned char cmd, size_t len) { - struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; - struct smb_request *req = data->req.p.smb; - unsigned int pid; + const unsigned int pid = 0xbad71d; /* made up */ memset(h, 0, sizeof(*h)); h->nbt_length = htons((unsigned short) (sizeof(*h) - sizeof(unsigned int) + @@ -544,16 +607,13 @@ 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 = (unsigned int)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, size_t len, - size_t upload_size) +static CURLcode smb_send(struct Curl_easy *data, struct smb_conn *smbc, + size_t len, size_t upload_size) { - struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; size_t bytes_written; CURLcode result; @@ -571,10 +631,8 @@ static CURLcode smb_send(struct Curl_easy *data, size_t len, return CURLE_OK; } -static CURLcode smb_flush(struct Curl_easy *data) +static CURLcode smb_flush(struct Curl_easy *data, struct smb_conn *smbc) { - struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; size_t bytes_written; size_t len = smbc->send_size - smbc->sent; CURLcode result; @@ -595,39 +653,46 @@ static CURLcode smb_flush(struct Curl_easy *data) return CURLE_OK; } -static CURLcode smb_send_message(struct Curl_easy *data, unsigned char cmd, +static CURLcode smb_send_message(struct Curl_easy *data, + struct smb_conn *smbc, + struct smb_request *req, + unsigned char cmd, const void *msg, size_t msg_len) { - struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; - - smb_format_message(data, (struct smb_header *)smbc->send_buf, + smb_format_message(smbc, req, (struct smb_header *)smbc->send_buf, cmd, 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); + return smb_send(data, smbc, sizeof(struct smb_header) + msg_len, 0); } -static CURLcode smb_send_negotiate(struct Curl_easy *data) +static CURLcode smb_send_negotiate(struct Curl_easy *data, + struct smb_conn *smbc, + struct smb_request *req) { const char *msg = "\x00\x0c\x00\x02NT LM 0.12"; - return smb_send_message(data, SMB_COM_NEGOTIATE, msg, 15); + return smb_send_message(data, smbc, req, SMB_COM_NEGOTIATE, msg, 15); } static CURLcode smb_send_setup(struct Curl_easy *data) { struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; + struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); + struct smb_request *req = Curl_meta_get(data, CURL_META_SMB_EASY); struct smb_setup msg; char *p = msg.bytes; unsigned char lm_hash[21]; unsigned char lm[24]; unsigned char nt_hash[21]; unsigned char nt[24]; + size_t byte_count; - const size_t byte_count = sizeof(lm) + sizeof(nt) + + if(!smbc || !req) + return CURLE_FAILED_INIT; + + 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)) @@ -658,23 +723,24 @@ static CURLcode smb_send_setup(struct Curl_easy *data) "%s%c" /* OS */ "%s", /* client name */ smbc->user, 0, smbc->domain, 0, CURL_OS, 0, CLIENTNAME); - p++; /* count the final null termination */ + 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, + return smb_send_message(data, smbc, req, SMB_COM_SETUP_ANDX, &msg, sizeof(msg) - sizeof(msg.bytes) + byte_count); } -static CURLcode smb_send_tree_connect(struct Curl_easy *data) +static CURLcode smb_send_tree_connect(struct Curl_easy *data, + struct smb_conn *smbc, + struct smb_request *req) { struct smb_tree_connect msg; struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; char *p = msg.bytes; - 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; @@ -688,17 +754,18 @@ static CURLcode smb_send_tree_connect(struct Curl_easy *data) "%s%c" /* share */ "%s", /* service */ conn->host.name, smbc->share, 0, SERVICENAME); - p++; /* count the final null termination */ + 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, + return smb_send_message(data, smbc, req, SMB_COM_TREE_CONNECT_ANDX, &msg, sizeof(msg) - sizeof(msg.bytes) + byte_count); } -static CURLcode smb_send_open(struct Curl_easy *data) +static CURLcode smb_send_open(struct Curl_easy *data, + struct smb_conn *smbc, + struct smb_request *req) { - struct smb_request *req = data->req.p.smb; struct smb_nt_create msg; const size_t byte_count = strlen(req->path) + 1; @@ -721,34 +788,37 @@ static CURLcode smb_send_open(struct Curl_easy *data) 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, + return smb_send_message(data, smbc, req, SMB_COM_NT_CREATE_ANDX, &msg, sizeof(msg) - sizeof(msg.bytes) + byte_count); } -static CURLcode smb_send_close(struct Curl_easy *data) +static CURLcode smb_send_close(struct Curl_easy *data, + struct smb_conn *smbc, + struct smb_request *req) { - struct smb_request *req = data->req.p.smb; struct smb_close msg; memset(&msg, 0, sizeof(msg)); msg.word_count = SMB_WC_CLOSE; msg.fid = smb_swap16(req->fid); - return smb_send_message(data, SMB_COM_CLOSE, &msg, sizeof(msg)); + return smb_send_message(data, smbc, req, SMB_COM_CLOSE, &msg, sizeof(msg)); } -static CURLcode smb_send_tree_disconnect(struct Curl_easy *data) +static CURLcode smb_send_tree_disconnect(struct Curl_easy *data, + struct smb_conn *smbc, + struct smb_request *req) { struct smb_tree_disconnect msg; - memset(&msg, 0, sizeof(msg)); - - return smb_send_message(data, SMB_COM_TREE_DISCONNECT, &msg, sizeof(msg)); + return smb_send_message(data, smbc, req, SMB_COM_TREE_DISCONNECT, + &msg, sizeof(msg)); } -static CURLcode smb_send_read(struct Curl_easy *data) +static CURLcode smb_send_read(struct Curl_easy *data, + struct smb_conn *smbc, + struct smb_request *req) { - struct smb_request *req = data->req.p.smb; curl_off_t offset = data->req.offset; struct smb_read msg; @@ -761,15 +831,15 @@ static CURLcode smb_send_read(struct Curl_easy *data) msg.min_bytes = smb_swap16(MAX_PAYLOAD_SIZE); msg.max_bytes = smb_swap16(MAX_PAYLOAD_SIZE); - return smb_send_message(data, SMB_COM_READ_ANDX, &msg, sizeof(msg)); + return smb_send_message(data, smbc, req, SMB_COM_READ_ANDX, + &msg, sizeof(msg)); } -static CURLcode smb_send_write(struct Curl_easy *data) +static CURLcode smb_send_write(struct Curl_easy *data, + struct smb_conn *smbc, + struct smb_request *req) { - 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; @@ -787,16 +857,15 @@ static CURLcode smb_send_write(struct Curl_easy *data) msg->data_offset = smb_swap16(sizeof(*msg) - sizeof(unsigned int)); msg->byte_count = smb_swap16((unsigned short) (upload_size + 1)); - smb_format_message(data, &msg->h, SMB_COM_WRITE_ANDX, + smb_format_message(smbc, req, &msg->h, SMB_COM_WRITE_ANDX, sizeof(*msg) - sizeof(msg->h) + (size_t) upload_size); - return smb_send(data, sizeof(*msg), (size_t) upload_size); + return smb_send(data, smbc, sizeof(*msg), (size_t) upload_size); } -static CURLcode smb_send_and_recv(struct Curl_easy *data, void **msg) +static CURLcode smb_send_and_recv(struct Curl_easy *data, + struct smb_conn *smbc, void **msg) { - struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; CURLcode result; *msg = NULL; /* if it returns early */ @@ -819,7 +888,7 @@ static CURLcode smb_send_and_recv(struct Curl_easy *data, void **msg) /* Check if there is data to send */ if(smbc->send_size) { - result = smb_flush(data); + result = smb_flush(data, smbc); if(result) return result; } @@ -828,18 +897,22 @@ static CURLcode smb_send_and_recv(struct Curl_easy *data, void **msg) if(smbc->send_size || smbc->upload_size) return CURLE_AGAIN; - return smb_recv_message(data, msg); + return smb_recv_message(data, smbc, msg); } static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; + struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); + struct smb_request *req = Curl_meta_get(data, CURL_META_SMB_EASY); struct smb_negotiate_response *nrsp; struct smb_header *h; CURLcode result; void *msg = NULL; + if(!smbc || !req) + return CURLE_FAILED_INIT; + if(smbc->state == SMB_CONNECTING) { #ifdef USE_SSL if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) { @@ -852,17 +925,17 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) } #endif - result = smb_send_negotiate(data); + result = smb_send_negotiate(data, smbc, req); if(result) { connclose(conn, "SMB: failed to send negotiate message"); return result; } - conn_state(data, SMB_NEGOTIATE); + conn_state(data, smbc, SMB_NEGOTIATE); } /* Send the previous message and check for a response */ - result = smb_send_and_recv(data, &msg); + result = smb_send_and_recv(data, smbc, &msg); if(result && result != CURLE_AGAIN) { connclose(conn, "SMB: failed to communicate"); return result; @@ -897,7 +970,7 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) connclose(conn, "SMB: failed to send setup message"); return result; } - conn_state(data, SMB_SETUP); + conn_state(data, smbc, SMB_SETUP); break; case SMB_SETUP: @@ -906,16 +979,16 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) return CURLE_LOGIN_DENIED; } smbc->uid = smb_swap16(h->uid); - conn_state(data, SMB_CONNECTED); + conn_state(data, smbc, SMB_CONNECTED); *done = TRUE; break; default: - smb_pop_message(conn); + smb_pop_message(smbc); return CURLE_OK; /* ignore */ } - smb_pop_message(conn); + smb_pop_message(smbc); return CURLE_OK; } @@ -926,24 +999,28 @@ static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) */ static void get_posix_time(time_t *out, curl_off_t timestamp) { - timestamp -= CURL_OFF_T_C(116444736000000000); - timestamp /= 10000000; + if(timestamp >= CURL_OFF_T_C(116444736000000000)) { + timestamp -= CURL_OFF_T_C(116444736000000000); + timestamp /= 10000000; #if SIZEOF_TIME_T < SIZEOF_CURL_OFF_T - if(timestamp > TIME_T_MAX) - *out = TIME_T_MAX; - else if(timestamp < TIME_T_MIN) - *out = TIME_T_MIN; - else + if(timestamp > TIME_T_MAX) + *out = TIME_T_MAX; + else if(timestamp < TIME_T_MIN) + *out = TIME_T_MIN; + else #endif - *out = (time_t) timestamp; + *out = (time_t) timestamp; + } + else + *out = 0; } static CURLcode smb_request_state(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct smb_request *req = data->req.p.smb; + struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); + struct smb_request *req = Curl_meta_get(data, CURL_META_SMB_EASY); struct smb_header *h; - struct smb_conn *smbc = &conn->proto.smbc; enum smb_req_state next_state = SMB_DONE; unsigned short len; unsigned short off; @@ -951,6 +1028,9 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) void *msg = NULL; const struct smb_nt_create_response *smb_m; + if(!smbc || !req) + return CURLE_FAILED_INIT; + if(data->state.upload && (data->state.infilesize < 0)) { failf(data, "SMB upload needs to know the size up front"); return CURLE_SEND_ERROR; @@ -958,7 +1038,7 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) /* Start the request */ if(req->state == SMB_REQUESTING) { - result = smb_send_tree_connect(data); + result = smb_send_tree_connect(data, smbc, req); if(result) { connclose(conn, "SMB: failed to send tree connect message"); return result; @@ -968,7 +1048,7 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) } /* Send the previous message and check for a response */ - result = smb_send_and_recv(data, &msg); + result = smb_send_and_recv(data, smbc, &msg); if(result && result != CURLE_AGAIN) { connclose(conn, "SMB: failed to communicate"); return result; @@ -1078,31 +1158,31 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) break; default: - smb_pop_message(conn); + smb_pop_message(smbc); return CURLE_OK; /* ignore */ } - smb_pop_message(conn); + smb_pop_message(smbc); switch(next_state) { case SMB_OPEN: - result = smb_send_open(data); + result = smb_send_open(data, smbc, req); break; case SMB_DOWNLOAD: - result = smb_send_read(data); + result = smb_send_read(data, smbc, req); break; case SMB_UPLOAD: - result = smb_send_write(data); + result = smb_send_write(data, smbc, req); break; case SMB_CLOSE: - result = smb_send_close(data); + result = smb_send_close(data, smbc, req); break; case SMB_TREE_DISCONNECT: - result = smb_send_tree_disconnect(data); + result = smb_send_tree_disconnect(data, smbc, req); break; case SMB_DONE: @@ -1124,19 +1204,6 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) return CURLE_OK; } -static CURLcode smb_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead) -{ - struct smb_conn *smbc = &conn->proto.smbc; - (void) dead; - (void) data; - Curl_safefree(smbc->share); - Curl_safefree(smbc->domain); - Curl_safefree(smbc->recv_buf); - Curl_safefree(smbc->send_buf); - return CURLE_OK; -} - static int smb_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks) { @@ -1148,26 +1215,26 @@ static int smb_getsock(struct Curl_easy *data, static CURLcode smb_do(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct smb_conn *smbc = &conn->proto.smbc; + struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); *done = FALSE; - if(smbc->share) { + if(!smbc) + return CURLE_FAILED_INIT; + if(smbc->share) return CURLE_OK; - } return CURLE_URL_MALFORMAT; } static CURLcode smb_parse_url_path(struct Curl_easy *data, - struct connectdata *conn) + struct smb_conn *smbc, + struct smb_request *req) { - struct smb_request *req = data->req.p.smb; - struct smb_conn *smbc = &conn->proto.smbc; char *path; char *slash; + CURLcode result; /* URL decode the path */ - CURLcode result = Curl_urldecode(data->state.up.path, 0, &path, NULL, - REJECT_CTRL); + result = Curl_urldecode(data->state.up.path, 0, &path, NULL, REJECT_CTRL); if(result) return result; diff --git a/Utilities/cmcurl/lib/smb.h b/Utilities/cmcurl/lib/smb.h index 9ea2a8cc31..eb4df6550a 100644 --- a/Utilities/cmcurl/lib/smb.h +++ b/Utilities/cmcurl/lib/smb.h @@ -25,30 +25,6 @@ * ***************************************************************************/ -enum smb_conn_state { - SMB_NOT_CONNECTED = 0, - SMB_CONNECTING, - SMB_NEGOTIATE, - SMB_SETUP, - SMB_CONNECTED -}; - -struct smb_conn { - enum smb_conn_state state; - char *user; - char *domain; - char *share; - unsigned char challenge[8]; - unsigned int session_key; - unsigned short uid; - char *recv_buf; - char *send_buf; - size_t upload_size; - size_t send_size; - size_t sent; - size_t got; -}; - #if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) && \ (SIZEOF_CURL_OFF_T > 4) diff --git a/Utilities/cmcurl/lib/smtp.c b/Utilities/cmcurl/lib/smtp.c index b763557a07..d39bb58d5d 100644 --- a/Utilities/cmcurl/lib/smtp.c +++ b/Utilities/cmcurl/lib/smtp.c @@ -68,7 +68,6 @@ #include "mime.h" #include "socks.h" #include "smtp.h" -#include "strtoofft.h" #include "strcase.h" #include "vtls/vtls.h" #include "cfilters.h" @@ -79,15 +78,79 @@ #include "curl_gethostname.h" #include "bufref.h" #include "curl_sasl.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "idn.h" +#include "curlx/strparse.h" + /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_SMTP_EASY "meta:proto:smtp:easy" +/* meta key for storing protocol meta at connection */ +#define CURL_META_SMTP_CONN "meta:proto:smtp:conn" + +/**************************************************************************** + * SMTP unique setup + ***************************************************************************/ +typedef enum { + SMTP_STOP, /* do nothing state, stops the state machine */ + SMTP_SERVERGREET, /* waiting for the initial greeting immediately after + a connect */ + SMTP_EHLO, + SMTP_HELO, + SMTP_STARTTLS, + SMTP_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS + (multi mode only) */ + SMTP_AUTH, + SMTP_COMMAND, /* VRFY, EXPN, NOOP, RSET and HELP */ + SMTP_MAIL, /* MAIL FROM */ + SMTP_RCPT, /* RCPT TO */ + SMTP_DATA, + SMTP_POSTDATA, + SMTP_QUIT, + SMTP_LAST /* never used */ +} smtpstate; + +/* smtp_conn is used for struct connection-oriented data in the connectdata + struct */ +struct smtp_conn { + struct pingpong pp; + struct SASL sasl; /* SASL-related storage */ + smtpstate state; /* Always use smtp.c:state() to change state! */ + char *domain; /* Client address/name to send in the EHLO */ + BIT(ssldone); /* Is connect() over SSL done? */ + BIT(tls_supported); /* StartTLS capability supported by server */ + BIT(size_supported); /* If server supports SIZE extension according to + RFC 1870 */ + BIT(utf8_supported); /* If server supports SMTPUTF8 extension according + to RFC 6531 */ + BIT(auth_supported); /* AUTH capability supported by server */ +}; + +/* This SMTP struct is used in the Curl_easy. All SMTP data that is + connection-oriented must be in smtp_conn to properly deal with the fact that + perhaps the Curl_easy is changed between the times the connection is + used. */ +struct SMTP { + curl_pp_transfer transfer; + char *custom; /* Custom Request */ + struct curl_slist *rcpt; /* Recipient list */ + int rcpt_last_error; /* The last error received for RCPT TO command */ + size_t eob; /* Number of bytes of the EOB (End Of Body) that + have been received so far */ + BIT(rcpt_had_ok); /* Whether any of RCPT TO commands (depends on + total number of recipients) succeeded so far */ + BIT(trailing_crlf); /* Specifies if the trailing CRLF is present */ +}; + /* Local API functions */ -static CURLcode smtp_regular_transfer(struct Curl_easy *data, bool *done); +static CURLcode smtp_regular_transfer(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, + bool *done); static CURLcode smtp_do(struct Curl_easy *data, bool *done); static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, bool premature); @@ -100,9 +163,12 @@ static int smtp_getsock(struct Curl_easy *data, static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done); static CURLcode smtp_setup_connection(struct Curl_easy *data, struct connectdata *conn); -static CURLcode smtp_parse_url_options(struct connectdata *conn); -static CURLcode smtp_parse_url_path(struct Curl_easy *data); -static CURLcode smtp_parse_custom_request(struct Curl_easy *data); +static CURLcode smtp_parse_url_options(struct connectdata *conn, + struct smtp_conn *smtpc); +static CURLcode smtp_parse_url_path(struct Curl_easy *data, + struct smtp_conn *smtpc); +static CURLcode smtp_parse_custom_request(struct Curl_easy *data, + struct SMTP *smtp); static CURLcode smtp_parse_address(const char *fqma, char **address, struct hostname *host); static CURLcode smtp_perform_auth(struct Curl_easy *data, const char *mech, @@ -198,12 +264,16 @@ static const struct SASLproto saslsmtp = { * supported authentication mechanisms. */ static bool smtp_endofresp(struct Curl_easy *data, struct connectdata *conn, - char *line, size_t len, int *resp) + const char *line, size_t len, int *resp) { - struct smtp_conn *smtpc = &conn->proto.smtpc; + struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN); bool result = FALSE; (void)data; + DEBUGASSERT(smtpc); + if(!smtpc) + return FALSE; + /* Nothing for us */ if(len < 4 || !ISDIGIT(line[0]) || !ISDIGIT(line[1]) || !ISDIGIT(line[2])) return FALSE; @@ -214,11 +284,14 @@ static bool smtp_endofresp(struct Curl_easy *data, struct connectdata *conn, only send the response code instead as per Section 4.2. */ if(line[3] == ' ' || len == 5) { char tmpline[6]; - + curl_off_t code; + const char *p = tmpline; result = TRUE; - memset(tmpline, '\0', sizeof(tmpline)); memcpy(tmpline, line, (len == 5 ? 5 : 3)); - *resp = curlx_sltosi(strtol(tmpline, NULL, 10)); + tmpline[len == 5 ? 5 : 3 ] = 0; + if(curlx_str_number(&p, &code, len == 5 ? 99999 : 999)) + return FALSE; + *resp = (int) code; /* Make sure real server never sends internal value */ if(*resp == 1) @@ -242,9 +315,16 @@ 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 = Curl_dyn_ptr(&data->conn->proto.smtpc.pp.recvbuf); - size_t len = data->conn->proto.smtpc.pp.nfinal; + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); + char *message; + size_t len; + if(!smtpc) + return CURLE_FAILED_INIT; + + message = curlx_dyn_ptr(&smtpc->pp.recvbuf); + len = smtpc->pp.nfinal; if(len > 4) { /* Find the start of the message */ len -= 4; @@ -274,9 +354,10 @@ static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out) * * This is the ONLY way to change SMTP state! */ -static void smtp_state(struct Curl_easy *data, smtpstate newstate) +static void smtp_state(struct Curl_easy *data, + struct smtp_conn *smtpc, + smtpstate newstate) { - struct smtp_conn *smtpc = &data->conn->proto.smtpc; #if !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char * const names[] = { @@ -311,11 +392,10 @@ static void smtp_state(struct Curl_easy *data, smtpstate newstate) * Sends the EHLO command to not only initialise communication with the ESMTP * server but to also obtain a list of server side supported capabilities. */ -static CURLcode smtp_perform_ehlo(struct Curl_easy *data) +static CURLcode smtp_perform_ehlo(struct Curl_easy *data, + struct smtp_conn *smtpc) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; smtpc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanism yet */ smtpc->sasl.authused = SASL_AUTH_NONE; /* Clear the authentication mechanism @@ -327,7 +407,7 @@ static CURLcode smtp_perform_ehlo(struct Curl_easy *data) result = Curl_pp_sendf(data, &smtpc->pp, "EHLO %s", smtpc->domain); if(!result) - smtp_state(data, SMTP_EHLO); + smtp_state(data, smtpc, SMTP_EHLO); return result; } @@ -339,10 +419,9 @@ static CURLcode smtp_perform_ehlo(struct Curl_easy *data) * Sends the HELO command to initialise communication with the SMTP server. */ static CURLcode smtp_perform_helo(struct Curl_easy *data, - struct connectdata *conn) + struct smtp_conn *smtpc) { CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = &conn->proto.smtpc; smtpc->sasl.authused = SASL_AUTH_NONE; /* No authentication mechanism used in smtp connections */ @@ -351,7 +430,7 @@ static CURLcode smtp_perform_helo(struct Curl_easy *data, result = Curl_pp_sendf(data, &smtpc->pp, "HELO %s", smtpc->domain); if(!result) - smtp_state(data, SMTP_HELO); + smtp_state(data, smtpc, SMTP_HELO); return result; } @@ -363,14 +442,13 @@ static CURLcode smtp_perform_helo(struct Curl_easy *data, * Sends the STLS command to start the upgrade to TLS. */ static CURLcode smtp_perform_starttls(struct Curl_easy *data, - struct connectdata *conn) + struct smtp_conn *smtpc) { /* Send the STARTTLS command */ - CURLcode result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, - "%s", "STARTTLS"); + CURLcode result = Curl_pp_sendf(data, &smtpc->pp, "%s", "STARTTLS"); if(!result) - smtp_state(data, SMTP_STARTTLS); + smtp_state(data, smtpc, SMTP_STARTTLS); return result; } @@ -381,12 +459,12 @@ static CURLcode smtp_perform_starttls(struct Curl_easy *data, * * Performs the upgrade to TLS. */ -static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data) +static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data, + struct smtp_conn *smtpc) { #ifdef USE_SSL /* Start the SSL connection */ struct connectdata *conn = data->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; CURLcode result; bool ssldone = FALSE; @@ -397,7 +475,6 @@ static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data) goto out; /* Change the connection handler and SMTP state */ conn->handler = &Curl_handler_smtps; - conn->bits.tls_upgraded = TRUE; } DEBUGASSERT(!smtpc->ssldone); @@ -406,13 +483,14 @@ static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data) result, ssldone)); if(!result && ssldone) { smtpc->ssldone = ssldone; - /* perform EHLO now, changes smpt->state out of SMTP_UPGRADETLS */ - result = smtp_perform_ehlo(data); + /* perform EHLO now, changes smtp->state out of SMTP_UPGRADETLS */ + result = smtp_perform_ehlo(data, smtpc); } out: return result; #else (void)data; + (void)smtpc; return CURLE_NOT_BUILT_IN; #endif } @@ -429,9 +507,13 @@ static CURLcode smtp_perform_auth(struct Curl_easy *data, const struct bufref *initresp) { CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = &data->conn->proto.smtpc; + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); const char *ir = (const char *) Curl_bufref_ptr(initresp); + if(!smtpc) + return CURLE_FAILED_INIT; + if(ir) { /* AUTH ... */ /* Send the AUTH command with the initial response */ result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s %s", mech, ir); @@ -454,10 +536,12 @@ static CURLcode smtp_continue_auth(struct Curl_easy *data, const char *mech, const struct bufref *resp) { - struct smtp_conn *smtpc = &data->conn->proto.smtpc; + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); (void)mech; - + if(!smtpc) + return CURLE_FAILED_INIT; return Curl_pp_sendf(data, &smtpc->pp, "%s", (const char *) Curl_bufref_ptr(resp)); } @@ -470,10 +554,12 @@ static CURLcode smtp_continue_auth(struct Curl_easy *data, */ static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech) { - struct smtp_conn *smtpc = &data->conn->proto.smtpc; + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); (void)mech; - + if(!smtpc) + return CURLE_FAILED_INIT; return Curl_pp_sendf(data, &smtpc->pp, "*"); } @@ -484,18 +570,17 @@ static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech) * Initiates the authentication sequence, with the appropriate SASL * authentication mechanism. */ -static CURLcode smtp_perform_authentication(struct Curl_easy *data) +static CURLcode smtp_perform_authentication(struct Curl_easy *data, + struct smtp_conn *smtpc) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; saslprogress progress; /* Check we have enough data to authenticate with, and the server supports authentication, and end the connect phase if not */ if(!smtpc->auth_supported || !Curl_sasl_can_authenticate(&smtpc->sasl, data)) { - smtp_state(data, SMTP_STOP); + smtp_state(data, smtpc, SMTP_STOP); return result; } @@ -504,12 +589,9 @@ static CURLcode smtp_perform_authentication(struct Curl_easy *data) if(!result) { if(progress == SASL_INPROGRESS) - smtp_state(data, SMTP_AUTH); - else { - /* Other mechanisms not supported */ - infof(data, "No known authentication mechanisms supported"); - result = CURLE_LOGIN_DENIED; - } + smtp_state(data, smtpc, SMTP_AUTH); + else + result = Curl_sasl_is_blocked(&smtpc->sasl, data); } return result; @@ -519,13 +601,13 @@ static CURLcode smtp_perform_authentication(struct Curl_easy *data) * * smtp_perform_command() * - * Sends a SMTP based command. + * Sends an SMTP based command. */ -static CURLcode smtp_perform_command(struct Curl_easy *data) +static CURLcode smtp_perform_command(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct SMTP *smtp = data->req.p.smtp; if(smtp->rcpt) { /* We notify the server we are sending UTF-8 data if a) it supports the @@ -547,13 +629,13 @@ static CURLcode smtp_perform_command(struct Curl_easy *data) /* Establish whether we should report SMTPUTF8 to the server for this mailbox as per RFC-6531 sect. 3.1 point 6 */ - utf8 = (conn->proto.smtpc.utf8_supported) && + utf8 = (smtpc->utf8_supported) && ((host.encalloc) || (!Curl_is_ASCII_name(address)) || (!Curl_is_ASCII_name(host.name))); /* 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", + result = Curl_pp_sendf(data, &smtpc->pp, "VRFY %s%s%s%s", address, host.name ? "@" : "", host.name ? host.name : "", @@ -565,11 +647,10 @@ static CURLcode smtp_perform_command(struct Curl_easy *data) else { /* Establish whether we should report that we support SMTPUTF8 for EXPN commands to the server as per RFC-6531 sect. 3.1 point 6 */ - utf8 = (conn->proto.smtpc.utf8_supported) && - (!strcmp(smtp->custom, "EXPN")); + utf8 = (smtpc->utf8_supported) && (!strcmp(smtp->custom, "EXPN")); /* Send the custom recipient based command such as the EXPN command */ - result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, + result = Curl_pp_sendf(data, &smtpc->pp, "%s %s%s", smtp->custom, smtp->rcpt->data, utf8 ? " SMTPUTF8" : ""); @@ -577,12 +658,12 @@ static CURLcode smtp_perform_command(struct Curl_easy *data) } else /* Send the non-recipient based command such as HELP */ - result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "%s", + result = Curl_pp_sendf(data, &smtpc->pp, "%s", smtp->custom && smtp->custom[0] != '\0' ? smtp->custom : "HELP"); if(!result) - smtp_state(data, SMTP_COMMAND); + smtp_state(data, smtpc, SMTP_COMMAND); return result; } @@ -593,13 +674,14 @@ static CURLcode smtp_perform_command(struct Curl_easy *data) * * Sends an MAIL command to initiate the upload of a message. */ -static CURLcode smtp_perform_mail(struct Curl_easy *data) +static CURLcode smtp_perform_mail(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp) { char *from = NULL; char *auth = NULL; char *size = NULL; CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; /* 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 @@ -621,7 +703,7 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) /* 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 */ - utf8 = (conn->proto.smtpc.utf8_supported) && + utf8 = (smtpc->utf8_supported) && ((host.encalloc) || (!Curl_is_ASCII_name(address)) || (!Curl_is_ASCII_name(host.name))); @@ -647,7 +729,7 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) } /* Calculate the optional AUTH parameter */ - if(data->set.str[STRING_MAIL_AUTH] && conn->proto.smtpc.sasl.authused) { + if(data->set.str[STRING_MAIL_AUTH] && smtpc->sasl.authused) { if(data->set.str[STRING_MAIL_AUTH][0] != '\0') { char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; @@ -661,7 +743,7 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) /* 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 */ - if((!utf8) && (conn->proto.smtpc.utf8_supported) && + if((!utf8) && (smtpc->utf8_supported) && ((host.encalloc) || (!Curl_is_ASCII_name(address)) || (!Curl_is_ASCII_name(host.name)))) utf8 = TRUE; @@ -718,7 +800,7 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) } /* Calculate the optional SIZE parameter */ - if(conn->proto.smtpc.size_supported && data->state.infilesize > 0) { + if(smtpc->size_supported && data->state.infilesize > 0) { size = aprintf("%" FMT_OFF_T, data->state.infilesize); if(!size) { @@ -731,8 +813,7 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) 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 */ - if(conn->proto.smtpc.utf8_supported && !utf8) { - struct SMTP *smtp = data->req.p.smtp; + if(smtpc->utf8_supported && !utf8) { struct curl_slist *rcpt = smtp->rcpt; while(rcpt && !utf8) { @@ -750,7 +831,7 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data) goto out; /* Send the MAIL command */ - result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, + result = Curl_pp_sendf(data, &smtpc->pp, "MAIL FROM:%s%s%s%s%s%s", from, /* Mandatory */ auth ? " AUTH=" : "", /* Optional on AUTH support */ @@ -766,7 +847,7 @@ out: free(size); if(!result) - smtp_state(data, SMTP_MAIL); + smtp_state(data, smtpc, SMTP_MAIL); return result; } @@ -778,11 +859,11 @@ out: * Sends a RCPT TO command for a given recipient as part of the message upload * process. */ -static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data) +static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct SMTP *smtp = data->req.p.smtp; char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; @@ -795,19 +876,18 @@ static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data) /* Send the RCPT TO command */ if(host.name) - result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "RCPT TO:<%s@%s>", + result = Curl_pp_sendf(data, &smtpc->pp, "RCPT TO:<%s@%s>", address, host.name); else /* 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); + result = Curl_pp_sendf(data, &smtpc->pp, "RCPT TO:<%s>", address); Curl_free_idnconverted_hostname(&host); free(address); if(!result) - smtp_state(data, SMTP_RCPT); + smtp_state(data, smtpc, SMTP_RCPT); return result; } @@ -819,19 +899,20 @@ static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data) * Performs the quit action prior to sclose() being called. */ static CURLcode smtp_perform_quit(struct Curl_easy *data, - struct connectdata *conn) + struct smtp_conn *smtpc) { /* Send the QUIT command */ - CURLcode result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "%s", "QUIT"); + CURLcode result = Curl_pp_sendf(data, &smtpc->pp, "%s", "QUIT"); if(!result) - smtp_state(data, SMTP_QUIT); + smtp_state(data, smtpc, SMTP_QUIT); return result; } /* For the initial server greeting */ static CURLcode smtp_state_servergreet_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, int smtpcode, smtpstate instate) { @@ -843,13 +924,14 @@ static CURLcode smtp_state_servergreet_resp(struct Curl_easy *data, result = CURLE_WEIRD_SERVER_REPLY; } else - result = smtp_perform_ehlo(data); + result = smtp_perform_ehlo(data, smtpc); return result; } /* For STARTTLS responses */ static CURLcode smtp_state_starttls_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, int smtpcode, smtpstate instate) { @@ -857,7 +939,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.overflow) + if(smtpc->pp.overflow) return CURLE_WEIRD_SERVER_REPLY; if(smtpcode != 220) { @@ -866,30 +948,30 @@ static CURLcode smtp_state_starttls_resp(struct Curl_easy *data, result = CURLE_USE_SSL_FAILED; } else - result = smtp_perform_authentication(data); + result = smtp_perform_authentication(data, smtpc); } else - smtp_state(data, SMTP_UPGRADETLS); + smtp_state(data, smtpc, SMTP_UPGRADETLS); return result; } /* For EHLO responses */ static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data, - struct connectdata *conn, int smtpcode, + struct smtp_conn *smtpc, + int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = &conn->proto.smtpc; - const char *line = Curl_dyn_ptr(&smtpc->pp.recvbuf); + const char *line = curlx_dyn_ptr(&smtpc->pp.recvbuf); size_t len = smtpc->pp.nfinal; (void)instate; /* no use for this yet */ if(smtpcode/100 != 2 && smtpcode != 1) { if(data->set.use_ssl <= CURLUSESSL_TRY - || Curl_conn_is_ssl(conn, FIRSTSOCKET)) - result = smtp_perform_helo(data, conn); + || Curl_conn_is_ssl(data->conn, FIRSTSOCKET)) + result = smtp_perform_helo(data, smtpc); else { failf(data, "Remote access denied: %d", smtpcode); result = CURLE_REMOTE_ACCESS_DENIED; @@ -953,21 +1035,21 @@ 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 do not have a SSL/TLS connection yet, but SSL is requested */ + if(data->set.use_ssl && !Curl_conn_is_ssl(data->conn, FIRSTSOCKET)) { + /* We do not have an SSL/TLS connection yet, but SSL is requested */ if(smtpc->tls_supported) /* Switch to TLS connection now */ - result = smtp_perform_starttls(data, conn); + result = smtp_perform_starttls(data, smtpc); else if(data->set.use_ssl == CURLUSESSL_TRY) /* Fallback and carry on with authentication */ - result = smtp_perform_authentication(data); + result = smtp_perform_authentication(data, smtpc); else { failf(data, "STARTTLS not supported."); result = CURLE_USE_SSL_FAILED; } } else - result = smtp_perform_authentication(data); + result = smtp_perform_authentication(data, smtpc); } } else { @@ -979,7 +1061,9 @@ static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data, } /* For HELO responses */ -static CURLcode smtp_state_helo_resp(struct Curl_easy *data, int smtpcode, +static CURLcode smtp_state_helo_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; @@ -991,19 +1075,18 @@ static CURLcode smtp_state_helo_resp(struct Curl_easy *data, int smtpcode, } else /* End of connect phase */ - smtp_state(data, SMTP_STOP); + smtp_state(data, smtpc, SMTP_STOP); return result; } /* For SASL authentication responses */ static CURLcode smtp_state_auth_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; saslprogress progress; (void)instate; /* no use for this yet */ @@ -1012,7 +1095,7 @@ static CURLcode smtp_state_auth_resp(struct Curl_easy *data, if(!result) switch(progress) { case SASL_DONE: - smtp_state(data, SMTP_STOP); /* Authenticated */ + smtp_state(data, smtpc, SMTP_STOP); /* Authenticated */ break; case SASL_IDLE: /* No mechanism left after cancellation */ failf(data, "Authentication cancelled"); @@ -1026,13 +1109,15 @@ static CURLcode smtp_state_auth_resp(struct Curl_easy *data, } /* For command responses */ -static CURLcode smtp_state_command_resp(struct Curl_easy *data, int smtpcode, +static CURLcode smtp_state_command_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, + int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct SMTP *smtp = data->req.p.smtp; - char *line = Curl_dyn_ptr(&data->conn->proto.smtpc.pp.recvbuf); - size_t len = data->conn->proto.smtpc.pp.nfinal; + char *line = curlx_dyn_ptr(&smtpc->pp.recvbuf); + size_t len = smtpc->pp.nfinal; (void)instate; /* no use for this yet */ @@ -1051,15 +1136,15 @@ static CURLcode smtp_state_command_resp(struct Curl_easy *data, int smtpcode, if(smtp->rcpt) { /* Send the next command */ - result = smtp_perform_command(data); + result = smtp_perform_command(data, smtpc, smtp); } else /* End of DO phase */ - smtp_state(data, SMTP_STOP); + smtp_state(data, smtpc, SMTP_STOP); } else /* End of DO phase */ - smtp_state(data, SMTP_STOP); + smtp_state(data, smtpc, SMTP_STOP); } } @@ -1067,7 +1152,10 @@ static CURLcode smtp_state_command_resp(struct Curl_easy *data, int smtpcode, } /* For MAIL responses */ -static CURLcode smtp_state_mail_resp(struct Curl_easy *data, int smtpcode, +static CURLcode smtp_state_mail_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, + int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; @@ -1079,18 +1167,19 @@ static CURLcode smtp_state_mail_resp(struct Curl_easy *data, int smtpcode, } else /* Start the RCPT TO command */ - result = smtp_perform_rcpt_to(data); + result = smtp_perform_rcpt_to(data, smtpc, smtp); return result; } /* For RCPT responses */ static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, - struct connectdata *conn, int smtpcode, + struct smtp_conn *smtpc, + struct SMTP *smtp, + int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct SMTP *smtp = data->req.p.smtp; bool is_smtp_err = FALSE; bool is_smtp_blocking_err = FALSE; @@ -1122,7 +1211,7 @@ static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, if(smtp->rcpt) /* Send the next RCPT TO command */ - result = smtp_perform_rcpt_to(data); + result = smtp_perform_rcpt_to(data, smtpc, smtp); else { /* We were not able to issue a successful RCPT TO command while going over recipients (potentially multiple). Sending back last error. */ @@ -1132,10 +1221,10 @@ static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, } else { /* Send the DATA command */ - result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "%s", "DATA"); + result = Curl_pp_sendf(data, &smtpc->pp, "%s", "DATA"); if(!result) - smtp_state(data, SMTP_DATA); + smtp_state(data, smtpc, SMTP_DATA); } } } @@ -1144,7 +1233,9 @@ static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, } /* For DATA response */ -static CURLcode smtp_state_data_resp(struct Curl_easy *data, int smtpcode, +static CURLcode smtp_state_data_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; @@ -1162,7 +1253,7 @@ static CURLcode smtp_state_data_resp(struct Curl_easy *data, int smtpcode, Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); /* End of DO phase */ - smtp_state(data, SMTP_STOP); + smtp_state(data, smtpc, SMTP_STOP); } return result; @@ -1171,6 +1262,7 @@ static CURLcode smtp_state_data_resp(struct Curl_easy *data, int smtpcode, /* For POSTDATA responses, which are received after the entire DATA part has been sent to the server */ static CURLcode smtp_state_postdata_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, int smtpcode, smtpstate instate) { @@ -1182,35 +1274,39 @@ static CURLcode smtp_state_postdata_resp(struct Curl_easy *data, result = CURLE_WEIRD_SERVER_REPLY; /* End of DONE phase */ - smtp_state(data, SMTP_STOP); + smtp_state(data, smtpc, SMTP_STOP); return result; } -static CURLcode smtp_statemachine(struct Curl_easy *data, - struct connectdata *conn) +static CURLcode smtp_pp_statemachine(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result = CURLE_OK; int smtpcode; - struct smtp_conn *smtpc = &conn->proto.smtpc; - struct pingpong *pp = &smtpc->pp; + struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN); + struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); size_t nread = 0; + if(!smtpc || !smtp) + return CURLE_FAILED_INIT; + /* Busy upgrading the connection; right now all I/O is SSL/TLS, not SMTP */ upgrade_tls: if(smtpc->state == SMTP_UPGRADETLS) { - result = smtp_perform_upgrade_tls(data); + result = smtp_perform_upgrade_tls(data, smtpc); if(result || (smtpc->state == SMTP_UPGRADETLS)) return result; } /* Flush any data that needs to be sent */ - if(pp->sendleft) - return Curl_pp_flushsend(data, pp); + if(smtpc->pp.sendleft) + return Curl_pp_flushsend(data, &smtpc->pp); do { /* Read the response from the server */ - result = Curl_pp_readresp(data, FIRSTSOCKET, pp, &smtpcode, &nread); + result = Curl_pp_readresp(data, FIRSTSOCKET, &smtpc->pp, + &smtpcode, &nread); if(result) return result; @@ -1224,19 +1320,20 @@ upgrade_tls: /* We have now received a full SMTP server response */ switch(smtpc->state) { case SMTP_SERVERGREET: - result = smtp_state_servergreet_resp(data, smtpcode, smtpc->state); + result = smtp_state_servergreet_resp(data, smtpc, + smtpcode, smtpc->state); break; case SMTP_EHLO: - result = smtp_state_ehlo_resp(data, conn, smtpcode, smtpc->state); + result = smtp_state_ehlo_resp(data, smtpc, smtpcode, smtpc->state); break; case SMTP_HELO: - result = smtp_state_helo_resp(data, smtpcode, smtpc->state); + result = smtp_state_helo_resp(data, smtpc, smtpcode, smtpc->state); break; case SMTP_STARTTLS: - result = smtp_state_starttls_resp(data, smtpcode, smtpc->state); + result = smtp_state_starttls_resp(data, smtpc, 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)) @@ -1244,36 +1341,38 @@ upgrade_tls: break; case SMTP_AUTH: - result = smtp_state_auth_resp(data, smtpcode, smtpc->state); + result = smtp_state_auth_resp(data, smtpc, smtpcode, smtpc->state); break; case SMTP_COMMAND: - result = smtp_state_command_resp(data, smtpcode, smtpc->state); + result = smtp_state_command_resp(data, smtpc, smtp, + smtpcode, smtpc->state); break; case SMTP_MAIL: - result = smtp_state_mail_resp(data, smtpcode, smtpc->state); + result = smtp_state_mail_resp(data, smtpc, smtp, smtpcode, smtpc->state); break; case SMTP_RCPT: - result = smtp_state_rcpt_resp(data, conn, smtpcode, smtpc->state); + result = smtp_state_rcpt_resp(data, smtpc, smtp, smtpcode, smtpc->state); break; case SMTP_DATA: - result = smtp_state_data_resp(data, smtpcode, smtpc->state); + result = smtp_state_data_resp(data, smtpc, smtpcode, smtpc->state); break; case SMTP_POSTDATA: - result = smtp_state_postdata_resp(data, smtpcode, smtpc->state); + result = smtp_state_postdata_resp(data, smtpc, smtpcode, smtpc->state); break; case SMTP_QUIT: default: /* internal error */ - smtp_state(data, SMTP_STOP); + smtp_state(data, smtpc, SMTP_STOP); break; } - } while(!result && smtpc->state != SMTP_STOP && Curl_pp_moredata(pp)); + } while(!result && smtpc->state != SMTP_STOP && + Curl_pp_moredata(&smtpc->pp)); return result; } @@ -1282,21 +1381,23 @@ upgrade_tls: static CURLcode smtp_multi_statemach(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); + + *done = FALSE; + if(!smtpc) + return CURLE_FAILED_INIT; result = Curl_pp_statemach(data, &smtpc->pp, FALSE, FALSE); *done = (smtpc->state == SMTP_STOP); - return result; } static CURLcode smtp_block_statemach(struct Curl_easy *data, - struct connectdata *conn, + struct smtp_conn *smtpc, bool disconnecting) { CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = &conn->proto.smtpc; while(smtpc->state != SMTP_STOP && !result) result = Curl_pp_statemach(data, &smtpc->pp, TRUE, disconnecting); @@ -1304,25 +1405,13 @@ static CURLcode smtp_block_statemach(struct Curl_easy *data, return result; } -/* Allocate and initialize the SMTP struct for the current Curl_easy if - required */ -static CURLcode smtp_init(struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct SMTP *smtp; - - smtp = data->req.p.smtp = calloc(1, sizeof(struct SMTP)); - if(!smtp) - result = CURLE_OUT_OF_MEMORY; - - return result; -} - /* For the SMTP "protocol connect" and "doing" phases only */ static int smtp_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks) { - return Curl_pp_getsock(data, &conn->proto.smtpc.pp, socks); + struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN); + return smtpc ? + Curl_pp_getsock(data, &smtpc->pp, socks) : GETSOCK_BLANK; } /*********************************************************************** @@ -1337,36 +1426,37 @@ static int smtp_getsock(struct Curl_easy *data, */ static CURLcode smtp_connect(struct Curl_easy *data, bool *done) { + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; - struct pingpong *pp = &smtpc->pp; *done = FALSE; /* default to not done yet */ + if(!smtpc) + return CURLE_FAILED_INIT; /* We always support persistent connections in SMTP */ - connkeep(conn, "SMTP default"); + connkeep(data->conn, "SMTP default"); - PINGPONG_SETUP(pp, smtp_statemachine, smtp_endofresp); + PINGPONG_SETUP(&smtpc->pp, smtp_pp_statemachine, smtp_endofresp); /* Initialize the SASL storage */ Curl_sasl_init(&smtpc->sasl, data, &saslsmtp); /* Initialise the pingpong layer */ - Curl_pp_init(pp); + Curl_pp_init(&smtpc->pp); /* Parse the URL options */ - result = smtp_parse_url_options(conn); + result = smtp_parse_url_options(data->conn, smtpc); if(result) return result; /* Parse the URL path */ - result = smtp_parse_url_path(data); + result = smtp_parse_url_path(data, smtpc); if(result) return result; /* Start off waiting for the server greeting response */ - smtp_state(data, SMTP_SERVERGREET); + smtp_state(data, smtpc, SMTP_SERVERGREET); result = smtp_multi_statemach(data, done); @@ -1385,12 +1475,16 @@ static CURLcode smtp_connect(struct Curl_easy *data, bool *done) static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, bool premature) { + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct SMTP *smtp = data->req.p.smtp; + struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); (void)premature; + if(!smtpc) + return CURLE_FAILED_INIT; if(!smtp) return CURLE_OK; @@ -1404,10 +1498,10 @@ static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, else if(!data->set.connect_only && data->set.mail_rcpt && (data->state.upload || IS_MIME_POST(data))) { - smtp_state(data, SMTP_POSTDATA); + smtp_state(data, smtpc, SMTP_POSTDATA); /* Run the state-machine */ - result = smtp_block_statemach(data, conn, FALSE); + result = smtp_block_statemach(data, smtpc, FALSE); } /* Clear the transfer mode for the next request */ @@ -1424,12 +1518,14 @@ static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, * This is the actual DO function for SMTP. Transfer a mail, send a command * or get some data according to the options previously setup. */ -static CURLcode smtp_perform(struct Curl_easy *data, bool *connected, +static CURLcode smtp_perform(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, + bool *connected, bool *dophase_done) { /* This is SMTP and no proxy */ CURLcode result = CURLE_OK; - struct SMTP *smtp = data->req.p.smtp; CURL_TRC_SMTP(data, "smtp_perform(), start"); @@ -1457,10 +1553,10 @@ static CURLcode smtp_perform(struct Curl_easy *data, bool *connected, /* Start the first command in the DO phase */ if((data->state.upload || IS_MIME_POST(data)) && data->set.mail_rcpt) /* MAIL transfer */ - result = smtp_perform_mail(data); + result = smtp_perform_mail(data, smtpc, smtp); else /* SMTP based command (VRFY, EXPN, NOOP, RSET or HELP) */ - result = smtp_perform_command(data); + result = smtp_perform_command(data, smtpc, smtp); if(result) goto out; @@ -1487,17 +1583,23 @@ out: */ static CURLcode smtp_do(struct Curl_easy *data, bool *done) { + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); + struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); CURLcode result = CURLE_OK; + DEBUGASSERT(data); DEBUGASSERT(data->conn); *done = FALSE; /* default to false */ + if(!smtpc || !smtp) + return CURLE_FAILED_INIT; /* Parse the custom request */ - result = smtp_parse_custom_request(data); + result = smtp_parse_custom_request(data, smtp); if(result) return result; - result = smtp_regular_transfer(data, done); + result = smtp_regular_transfer(data, smtpc, smtp, done); CURL_TRC_SMTP(data, "smtp_do() -> %d, done=%d", result, *done); return result; } @@ -1513,36 +1615,32 @@ static CURLcode smtp_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { - struct smtp_conn *smtpc = &conn->proto.smtpc; + struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN); + (void)data; + if(!smtpc) + return CURLE_FAILED_INIT; /* We cannot send quit unconditionally. If this connection is stale or 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. */ if(!dead_connection && conn->bits.protoconnstart) { - if(!smtp_perform_quit(data, conn)) - (void)smtp_block_statemach(data, conn, TRUE); /* ignore errors on QUIT */ + if(!smtp_perform_quit(data, smtpc)) + (void)smtp_block_statemach(data, smtpc, TRUE); /* ignore on QUIT */ } - /* Disconnect from the server */ - Curl_pp_disconnect(&smtpc->pp); - /* Cleanup the SASL module */ Curl_sasl_cleanup(conn, smtpc->sasl.authused); - - /* Cleanup our connection based variables */ - Curl_safefree(smtpc->domain); CURL_TRC_SMTP(data, "smtp_disconnect(), finished"); - return CURLE_OK; } /* Call this when the DO phase has completed */ -static CURLcode smtp_dophase_done(struct Curl_easy *data, bool connected) +static CURLcode smtp_dophase_done(struct Curl_easy *data, + struct SMTP *smtp, + bool connected) { - struct SMTP *smtp = data->req.p.smtp; - (void)connected; if(smtp->transfer != PPTRANSFER_BODY) @@ -1555,12 +1653,16 @@ static CURLcode smtp_dophase_done(struct Curl_easy *data, bool connected) /* Called from multi.c while DOing */ static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done) { - CURLcode result = smtp_multi_statemach(data, dophase_done); + struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); + CURLcode result; + if(!smtp) + return CURLE_FAILED_INIT; + result = smtp_multi_statemach(data, dophase_done); if(result) DEBUGF(infof(data, "DO phase failed")); else if(*dophase_done) { - result = smtp_dophase_done(data, FALSE /* not connected */); + result = smtp_dophase_done(data, smtp, FALSE /* not connected */); DEBUGF(infof(data, "DO phase is complete")); } @@ -1579,6 +1681,8 @@ static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done) * remote host. */ static CURLcode smtp_regular_transfer(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, bool *dophase_done) { CURLcode result = CURLE_OK; @@ -1594,27 +1698,56 @@ static CURLcode smtp_regular_transfer(struct Curl_easy *data, Curl_pgrsSetDownloadSize(data, -1); /* Carry out the perform */ - result = smtp_perform(data, &connected, dophase_done); + result = smtp_perform(data, smtpc, smtp, &connected, dophase_done); /* Perform post DO phase operations if necessary */ if(!result && *dophase_done) - result = smtp_dophase_done(data, connected); + result = smtp_dophase_done(data, smtp, connected); CURL_TRC_SMTP(data, "smtp_regular_transfer() -> %d, done=%d", result, *dophase_done); return result; } + +static void smtp_easy_dtor(void *key, size_t klen, void *entry) +{ + struct SMTP *smtp = entry; + (void)key; + (void)klen; + free(smtp); +} + +static void smtp_conn_dtor(void *key, size_t klen, void *entry) +{ + struct smtp_conn *smtpc = entry; + (void)key; + (void)klen; + Curl_pp_disconnect(&smtpc->pp); + Curl_safefree(smtpc->domain); + free(smtpc); +} + static CURLcode smtp_setup_connection(struct Curl_easy *data, struct connectdata *conn) { - CURLcode result; + struct smtp_conn *smtpc; + struct SMTP *smtp; + CURLcode result = CURLE_OK; - /* Clear the TLS upgraded flag */ - conn->bits.tls_upgraded = FALSE; + smtpc = calloc(1, sizeof(*smtpc)); + if(!smtpc || + Curl_conn_meta_set(conn, CURL_META_SMTP_CONN, smtpc, smtp_conn_dtor)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } - /* Initialise the SMTP layer */ - result = smtp_init(data); + smtp = calloc(1, sizeof(*smtp)); + if(!smtp || + Curl_meta_set(data, CURL_META_SMTP_EASY, smtp, smtp_easy_dtor)) + result = CURLE_OUT_OF_MEMORY; + +out: CURL_TRC_SMTP(data, "smtp_setup_connection() -> %d", result); return result; } @@ -1625,10 +1758,10 @@ static CURLcode smtp_setup_connection(struct Curl_easy *data, * * Parse the URL login options. */ -static CURLcode smtp_parse_url_options(struct connectdata *conn) +static CURLcode smtp_parse_url_options(struct connectdata *conn, + struct smtp_conn *smtpc) { CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = &conn->proto.smtpc; const char *ptr = conn->options; while(!result && ptr && *ptr) { @@ -1662,11 +1795,10 @@ static CURLcode smtp_parse_url_options(struct connectdata *conn) * * Parse the URL path into separate path components. */ -static CURLcode smtp_parse_url_path(struct Curl_easy *data) +static CURLcode smtp_parse_url_path(struct Curl_easy *data, + struct smtp_conn *smtpc) { /* The SMTP struct is already initialised in smtp_connect() */ - struct connectdata *conn = data->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; const char *path = &data->state.up.path[1]; /* skip leading path */ char localhost[HOSTNAME_MAX + 1]; @@ -1688,10 +1820,10 @@ static CURLcode smtp_parse_url_path(struct Curl_easy *data) * * Parse the custom request. */ -static CURLcode smtp_parse_custom_request(struct Curl_easy *data) +static CURLcode smtp_parse_custom_request(struct Curl_easy *data, + struct SMTP *smtp) { CURLcode result = CURLE_OK; - struct SMTP *smtp = data->req.p.smtp; const char *custom = data->set.str[STRING_CUSTOMREQUEST]; /* URL decode the custom request */ diff --git a/Utilities/cmcurl/lib/smtp.h b/Utilities/cmcurl/lib/smtp.h index 7c2af68073..ed9824b14c 100644 --- a/Utilities/cmcurl/lib/smtp.h +++ b/Utilities/cmcurl/lib/smtp.h @@ -27,60 +27,6 @@ #include "pingpong.h" #include "curl_sasl.h" -/**************************************************************************** - * SMTP unique setup - ***************************************************************************/ -typedef enum { - SMTP_STOP, /* do nothing state, stops the state machine */ - SMTP_SERVERGREET, /* waiting for the initial greeting immediately after - a connect */ - SMTP_EHLO, - SMTP_HELO, - SMTP_STARTTLS, - SMTP_UPGRADETLS, /* asynchronously upgrade the connection to SSL/TLS - (multi mode only) */ - SMTP_AUTH, - SMTP_COMMAND, /* VRFY, EXPN, NOOP, RSET and HELP */ - SMTP_MAIL, /* MAIL FROM */ - SMTP_RCPT, /* RCPT TO */ - SMTP_DATA, - SMTP_POSTDATA, - SMTP_QUIT, - SMTP_LAST /* never used */ -} smtpstate; - -/* This SMTP struct is used in the Curl_easy. All SMTP data that is - connection-oriented must be in smtp_conn to properly deal with the fact that - perhaps the Curl_easy is changed between the times the connection is - used. */ -struct SMTP { - curl_pp_transfer transfer; - char *custom; /* Custom Request */ - struct curl_slist *rcpt; /* Recipient list */ - int rcpt_last_error; /* The last error received for RCPT TO command */ - size_t eob; /* Number of bytes of the EOB (End Of Body) that - have been received so far */ - BIT(rcpt_had_ok); /* Whether any of RCPT TO commands (depends on - total number of recipients) succeeded so far */ - BIT(trailing_crlf); /* Specifies if the trailing CRLF is present */ -}; - -/* smtp_conn is used for struct connection-oriented data in the connectdata - struct */ -struct smtp_conn { - struct pingpong pp; - struct SASL sasl; /* SASL-related storage */ - smtpstate state; /* Always use smtp.c:state() to change state! */ - char *domain; /* Client address/name to send in the EHLO */ - BIT(ssldone); /* Is connect() over SSL done? */ - BIT(tls_supported); /* StartTLS capability supported by server */ - BIT(size_supported); /* If server supports SIZE extension according to - RFC 1870 */ - BIT(utf8_supported); /* If server supports SMTPUTF8 extension according - to RFC 6531 */ - BIT(auth_supported); /* AUTH capability supported by server */ -}; - extern const struct Curl_handler Curl_handler_smtp; extern const struct Curl_handler Curl_handler_smtps; diff --git a/Utilities/cmcurl/lib/socketpair.c b/Utilities/cmcurl/lib/socketpair.c index c4f558ea6b..1d1d5a66f3 100644 --- a/Utilities/cmcurl/lib/socketpair.c +++ b/Utilities/cmcurl/lib/socketpair.c @@ -27,10 +27,9 @@ #include "urldata.h" #include "rand.h" -#if defined(USE_EVENTFD) -#ifdef HAVE_SYS_EVENTFD_H +#ifdef USE_EVENTFD + #include -#endif int Curl_eventfd(curl_socket_t socks[2], bool nonblocking) { @@ -42,13 +41,20 @@ int Curl_eventfd(curl_socket_t socks[2], bool nonblocking) 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) { +#ifdef HAVE_PIPE2 + int flags = nonblocking ? O_NONBLOCK | O_CLOEXEC : O_CLOEXEC; + if(pipe2(socks, flags)) + return -1; +#else if(pipe(socks)) return -1; #ifdef HAVE_FCNTL @@ -69,11 +75,12 @@ int Curl_pipe(curl_socket_t socks[2], bool nonblocking) return -1; } } +#endif return 0; } -#endif +#endif /* USE_EVENTFD */ #ifndef CURL_DISABLE_SOCKETPAIR #ifdef HAVE_SOCKETPAIR @@ -103,7 +110,9 @@ int Curl_socketpair(int domain, int type, int protocol, * This is a socketpair() implementation for Windows. */ #include +#ifdef HAVE_IO_H #include +#endif #else #ifdef HAVE_NETDB_H #include @@ -119,8 +128,8 @@ int Curl_socketpair(int domain, int type, int protocol, #endif /* !INADDR_LOOPBACK */ #endif /* !_WIN32 */ -#include "nonblock.h" /* for curlx_nonblock */ -#include "timeval.h" /* needed before select.h */ +#include "curlx/nonblock.h" /* for curlx_nonblock */ +#include "curlx/timeval.h" /* needed before select.h */ #include "select.h" /* for Curl_poll */ /* The last 3 #include files should be in this order */ @@ -194,7 +203,7 @@ int Curl_socketpair(int domain, int type, int protocol, if(socks[1] == CURL_SOCKET_BAD) goto error; else { - struct curltime start = Curl_now(); + struct curltime start = curlx_now(); char rnd[9]; char check[sizeof(rnd)]; char *p = &check[0]; @@ -218,18 +227,18 @@ int Curl_socketpair(int domain, int type, int protocol, if(nread == -1) { int sockerr = SOCKERRNO; /* Do not block forever */ - if(Curl_timediff(Curl_now(), start) > (60 * 1000)) + if(curlx_timediff(curlx_now(), start) > (60 * 1000)) goto error; if( -#ifdef WSAEWOULDBLOCK +#ifdef USE_WINSOCK /* This is how Windows does it */ - (WSAEWOULDBLOCK == sockerr) + (SOCKEWOULDBLOCK == 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) || (EINPROGRESS == sockerr) + (SOCKEWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || + (SOCKEINTR == sockerr) || (SOCKEINPROGRESS == sockerr) #endif ) { continue; diff --git a/Utilities/cmcurl/lib/socketpair.h b/Utilities/cmcurl/lib/socketpair.h index ed69c5af82..5541899445 100644 --- a/Utilities/cmcurl/lib/socketpair.h +++ b/Utilities/cmcurl/lib/socketpair.h @@ -26,24 +26,7 @@ #include "curl_setup.h" -#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 +#ifdef USE_EVENTFD #define wakeup_write write #define wakeup_read read diff --git a/Utilities/cmcurl/lib/socks.c b/Utilities/cmcurl/lib/socks.c index d16a30b90a..08bc874f11 100644 --- a/Utilities/cmcurl/lib/socks.c +++ b/Utilities/cmcurl/lib/socks.c @@ -38,10 +38,10 @@ #include "select.h" #include "cfilters.h" #include "connect.h" -#include "timeval.h" +#include "curlx/timeval.h" #include "socks.h" #include "multiif.h" /* for getsock macros */ -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "url.h" /* The last 3 #include files should be in this order */ @@ -321,16 +321,16 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, /* DNS resolve only for SOCKS4, not SOCKS4a */ if(!protocol4a) { - enum resolve_t rc = - Curl_resolv(data, sx->hostname, sx->remote_port, TRUE, &dns); + result = Curl_resolv(data, sx->hostname, sx->remote_port, + cf->conn->ip_version, TRUE, &dns); - if(rc == CURLRESOLV_ERROR) - return CURLPX_RESOLVE_HOST; - else if(rc == CURLRESOLV_PENDING) { + if(result == CURLE_AGAIN) { sxstate(sx, data, CONNECT_RESOLVING); infof(data, "SOCKS4 non-blocking resolve of %s", sx->hostname); return CURLPX_OK; } + else if(result) + return CURLPX_RESOLVE_HOST; sxstate(sx, data, CONNECT_RESOLVED); goto CONNECT_RESOLVED; } @@ -341,23 +341,11 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, case CONNECT_RESOLVING: /* check if we have the name resolved by now */ - dns = Curl_fetch_addr(data, sx->hostname, conn->primary.remote_port); - - if(dns) { -#ifdef CURLRES_ASYNCH - data->state.async.dns = dns; - data->state.async.done = TRUE; -#endif - infof(data, "Hostname '%s' was found", sx->hostname); - sxstate(sx, data, CONNECT_RESOLVED); - } - else { - result = Curl_resolv_check(data, &dns); - if(!dns) { - if(result) - return CURLPX_RESOLVE_HOST; - return CURLPX_OK; - } + result = Curl_resolv_check(data, &dns); + if(!dns) { + if(result) + return CURLPX_RESOLVE_HOST; + return CURLPX_OK; } FALLTHROUGH(); case CONNECT_RESOLVED: @@ -406,7 +394,7 @@ CONNECT_REQ_INIT: /* * This is currently not supporting "Identification Protocol (RFC1413)". */ - socksreq[8] = 0; /* ensure empty userid is NUL-terminated */ + socksreq[8] = 0; /* ensure empty userid is null-terminated */ if(sx->proxy_user) { size_t plen = strlen(sx->proxy_user); if(plen > 255) { @@ -590,7 +578,6 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, bool allow_gssapi = FALSE; struct Curl_dns_entry *dns = NULL; - DEBUGASSERT(auth & (CURLAUTH_BASIC | CURLAUTH_GSSAPI)); switch(sx->state) { case CONNECT_SOCKS_INIT: if(conn->bits.httpproxy) @@ -795,16 +782,15 @@ CONNECT_AUTH_INIT: 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); + result = Curl_resolv(data, sx->hostname, sx->remote_port, + cf->conn->ip_version, TRUE, &dns); - if(rc == CURLRESOLV_ERROR) - return CURLPX_RESOLVE_HOST; - - if(rc == CURLRESOLV_PENDING) { + if(result == CURLE_AGAIN) { sxstate(sx, data, CONNECT_RESOLVING); return CURLPX_OK; } + else if(result) + return CURLPX_RESOLVE_HOST; sxstate(sx, data, CONNECT_RESOLVED); goto CONNECT_RESOLVED; } @@ -812,23 +798,11 @@ CONNECT_REQ_INIT: case CONNECT_RESOLVING: /* check if we have the name resolved by now */ - dns = Curl_fetch_addr(data, sx->hostname, sx->remote_port); - - if(dns) { -#ifdef CURLRES_ASYNCH - data->state.async.dns = dns; - data->state.async.done = TRUE; -#endif - infof(data, "SOCKS5: hostname '%s' found", sx->hostname); - } - + result = Curl_resolv_check(data, &dns); if(!dns) { - result = Curl_resolv_check(data, &dns); - if(!dns) { - if(result) - return CURLPX_RESOLVE_HOST; - return CURLPX_OK; - } + if(result) + return CURLPX_RESOLVE_HOST; + return CURLPX_OK; } FALLTHROUGH(); case CONNECT_RESOLVED: @@ -912,7 +886,7 @@ CONNECT_RESOLVE_REMOTE: #ifdef USE_IPV6 if(conn->bits.ipv6_ip) { char ip6[16]; - if(1 != Curl_inet_pton(AF_INET6, sx->hostname, ip6)) + if(1 != curlx_inet_pton(AF_INET6, sx->hostname, ip6)) return CURLPX_BAD_ADDRESS_TYPE; socksreq[len++] = 4; memcpy(&socksreq[len], ip6, sizeof(ip6)); @@ -920,7 +894,7 @@ CONNECT_RESOLVE_REMOTE: } else #endif - if(1 == Curl_inet_pton(AF_INET, sx->hostname, ip4)) { + if(1 == curlx_inet_pton(AF_INET, sx->hostname, ip4)) { socksreq[len++] = 1; memcpy(&socksreq[len], ip4, sizeof(ip4)); len += sizeof(ip4); @@ -1128,7 +1102,7 @@ static void socks_proxy_cf_free(struct Curl_cfilter *cf) */ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { CURLcode result; struct connectdata *conn = cf->conn; @@ -1140,7 +1114,7 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, return CURLE_OK; } - result = cf->next->cft->do_connect(cf->next, data, blocking, done); + result = cf->next->cft->do_connect(cf->next, data, done); if(result || !*done) return result; diff --git a/Utilities/cmcurl/lib/socks_gssapi.c b/Utilities/cmcurl/lib/socks_gssapi.c index 776dbdae80..697e301b77 100644 --- a/Utilities/cmcurl/lib/socks_gssapi.c +++ b/Utilities/cmcurl/lib/socks_gssapi.c @@ -32,9 +32,9 @@ #include "sendf.h" #include "cfilters.h" #include "connect.h" -#include "timeval.h" +#include "curlx/timeval.h" #include "socks.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "strdup.h" /* The last 3 #include files should be in this order */ @@ -65,7 +65,7 @@ static int check_gss_err(struct Curl_easy *data, gss_buffer_desc status_string = GSS_C_EMPTY_BUFFER; struct dynbuf dbuf; - Curl_dyn_init(&dbuf, MAX_GSS_LEN); + curlx_dyn_init(&dbuf, MAX_GSS_LEN); msg_ctx = 0; while(!msg_ctx) { /* convert major status code (GSS-API error) to text */ @@ -74,7 +74,7 @@ static int check_gss_err(struct Curl_easy *data, GSS_C_NULL_OID, &msg_ctx, &status_string); if(maj_stat == GSS_S_COMPLETE) { - if(Curl_dyn_addn(&dbuf, status_string.value, + if(curlx_dyn_addn(&dbuf, status_string.value, status_string.length)) return 1; /* error */ gss_release_buffer(&min_stat, &status_string); @@ -82,7 +82,7 @@ static int check_gss_err(struct Curl_easy *data, } gss_release_buffer(&min_stat, &status_string); } - if(Curl_dyn_addn(&dbuf, ".\n", 2)) + if(curlx_dyn_addn(&dbuf, ".\n", 2)) return 1; /* error */ msg_ctx = 0; while(!msg_ctx) { @@ -92,7 +92,7 @@ static int check_gss_err(struct Curl_easy *data, GSS_C_NULL_OID, &msg_ctx, &status_string); if(maj_stat == GSS_S_COMPLETE) { - if(Curl_dyn_addn(&dbuf, status_string.value, + if(curlx_dyn_addn(&dbuf, status_string.value, status_string.length)) return 1; /* error */ gss_release_buffer(&min_stat, &status_string); @@ -100,8 +100,9 @@ static int check_gss_err(struct Curl_easy *data, } gss_release_buffer(&min_stat, &status_string); } - failf(data, "GSS-API error: %s failed: %s", function, Curl_dyn_ptr(&dbuf)); - Curl_dyn_free(&dbuf); + failf(data, "GSS-API error: %s failed: %s", function, + curlx_dyn_ptr(&dbuf)); + curlx_dyn_free(&dbuf); return 1; } diff --git a/Utilities/cmcurl/lib/socks_sspi.c b/Utilities/cmcurl/lib/socks_sspi.c index 6d8e6ef730..1f0846dda4 100644 --- a/Utilities/cmcurl/lib/socks_sspi.c +++ b/Utilities/cmcurl/lib/socks_sspi.c @@ -32,11 +32,11 @@ #include "cfilters.h" #include "connect.h" #include "strerror.h" -#include "timeval.h" +#include "curlx/timeval.h" #include "socks.h" #include "curl_sspi.h" -#include "curl_multibyte.h" -#include "warnless.h" +#include "curlx/multibyte.h" +#include "curlx/warnless.h" #include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -140,14 +140,14 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, cred_handle.dwUpper = 0; status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *) TEXT("Kerberos"), - SECPKG_CRED_OUTBOUND, - NULL, - NULL, - NULL, - NULL, - &cred_handle, - &expiry); + (TCHAR *)CURL_UNCONST(TEXT("Kerberos")), + SECPKG_CRED_OUTBOUND, + NULL, + NULL, + NULL, + NULL, + &cred_handle, + &expiry); if(check_sspi_err(data, status, "AcquireCredentialsHandle")) { failf(data, "Failed to acquire credentials."); diff --git a/Utilities/cmcurl/lib/speedcheck.c b/Utilities/cmcurl/lib/speedcheck.c index 580efbde75..16d7d72562 100644 --- a/Utilities/cmcurl/lib/speedcheck.c +++ b/Utilities/cmcurl/lib/speedcheck.c @@ -52,7 +52,7 @@ CURLcode Curl_speedcheck(struct Curl_easy *data, data->state.keeps_speed = now; else { /* how long has it been under the limit */ - timediff_t howlong = Curl_timediff(now, data->state.keeps_speed); + timediff_t howlong = curlx_timediff(now, data->state.keeps_speed); if(howlong >= data->set.low_speed_time * 1000) { /* too long */ diff --git a/Utilities/cmcurl/lib/speedcheck.h b/Utilities/cmcurl/lib/speedcheck.h index 8b116f1528..f54365cadf 100644 --- a/Utilities/cmcurl/lib/speedcheck.h +++ b/Utilities/cmcurl/lib/speedcheck.h @@ -26,7 +26,7 @@ #include "curl_setup.h" -#include "timeval.h" +#include "curlx/timeval.h" struct Curl_easy; void Curl_speedinit(struct Curl_easy *data); CURLcode Curl_speedcheck(struct Curl_easy *data, diff --git a/Utilities/cmcurl/lib/splay.c b/Utilities/cmcurl/lib/splay.c index 3f2bae0238..7a0e419ce6 100644 --- a/Utilities/cmcurl/lib/splay.c +++ b/Utilities/cmcurl/lib/splay.c @@ -24,7 +24,7 @@ #include "curl_setup.h" -#include "timeval.h" +#include "curlx/timeval.h" #include "splay.h" /* @@ -34,7 +34,7 @@ * zero : when i is equal to j * positive when : when i is larger than j */ -#define compare(i,j) Curl_timediff_us(i,j) +#define compare(i,j) curlx_timediff_us(i,j) /* * Splay using the key i (which may or may not be in the tree.) The starting diff --git a/Utilities/cmcurl/lib/splay.h b/Utilities/cmcurl/lib/splay.h index b8c9360e57..ccc3781ec1 100644 --- a/Utilities/cmcurl/lib/splay.h +++ b/Utilities/cmcurl/lib/splay.h @@ -24,7 +24,7 @@ * ***************************************************************************/ #include "curl_setup.h" -#include "timeval.h" +#include "curlx/timeval.h" /* only use function calls to access this struct */ struct Curl_tree { diff --git a/Utilities/cmcurl/lib/strcase.c b/Utilities/cmcurl/lib/strcase.c index 112aedb193..ee5b9072c4 100644 --- a/Utilities/cmcurl/lib/strcase.c +++ b/Utilities/cmcurl/lib/strcase.c @@ -82,69 +82,10 @@ char Curl_raw_tolower(char in) return (char)tolowermap[(unsigned char) in]; } -/* - * curl_strequal() is for doing "raw" case insensitive strings. This is meant - * to be locale independent and only compare strings we know are safe for - * this. See https://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for - * further explanations as to why this function is necessary. - */ - -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 do not match */ - return 0; - first++; - second++; - } - /* 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. */ - return !*first == !*second; -} - -/* --- public function --- */ -int curl_strequal(const char *first, const char *second) -{ - if(first && second) - /* both pointers point to something then compare them */ - return casecompare(first, second); - - /* if both pointers are NULL then treat them as equal */ - return NULL == first && NULL == second; -} - -static int ncasecompare(const char *first, const char *second, size_t max) -{ - while(*first && *second && max) { - if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) - return 0; - max--; - first++; - second++; - } - if(0 == max) - return 1; /* they are equal this far */ - - return Curl_raw_toupper(*first) == Curl_raw_toupper(*second); -} - -/* --- public function --- */ -int curl_strnequal(const char *first, const char *second, size_t max) -{ - if(first && second) - /* both pointers point to something then compare them */ - return ncasecompare(first, second, 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 * (including any NUL) and the destination string will NOT be - * NUL-terminated if that limit is reached. + * null-terminated if that limit is reached. */ void Curl_strntoupper(char *dest, const char *src, size_t n) { @@ -159,7 +100,7 @@ void Curl_strntoupper(char *dest, const char *src, size_t 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 * (including any NUL) and the destination string will NOT be - * NUL-terminated if that limit is reached. + * null-terminated if that limit is reached. */ void Curl_strntolower(char *dest, const char *src, size_t n) { @@ -171,7 +112,7 @@ void Curl_strntolower(char *dest, const char *src, size_t n) } while(*src++ && --n); } -/* Compare case-sensitive NUL-terminated strings, taking care of possible +/* Compare case-sensitive null-terminated strings, taking care of possible * null pointers. Return true if arguments match. */ bool Curl_safecmp(char *a, char *b) diff --git a/Utilities/cmcurl/lib/strdup.c b/Utilities/cmcurl/lib/strdup.c index 299c9cc36b..69c41a5e25 100644 --- a/Utilities/cmcurl/lib/strdup.c +++ b/Utilities/cmcurl/lib/strdup.c @@ -104,7 +104,7 @@ void *Curl_memdup(const void *src, size_t length) * Curl_memdup0(source, length) * * Copies the 'source' string to a newly allocated buffer (that is returned). - * Copies 'length' bytes then adds a null terminator. + * Copies 'length' bytes then adds a null-terminator. * * Returns the new pointer or NULL on failure. * @@ -114,7 +114,10 @@ void *Curl_memdup0(const char *src, size_t length) char *buf = malloc(length + 1); if(!buf) return NULL; - memcpy(buf, src, length); + if(length) { + DEBUGASSERT(src); /* must never be NULL */ + memcpy(buf, src, length); + } buf[length] = 0; return buf; } diff --git a/Utilities/cmcurl/lib/strequal.c b/Utilities/cmcurl/lib/strequal.c new file mode 100644 index 0000000000..d1ba91501b --- /dev/null +++ b/Utilities/cmcurl/lib/strequal.c @@ -0,0 +1,88 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "strcase.h" + +/* + * curl_strequal() is for doing "raw" case insensitive strings. This is meant + * to be locale independent and only compare strings we know are safe for + * this. See https://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for + * further explanations as to why this function is necessary. + */ + +static int casecompare(const char *first, const char *second) +{ + while(*first) { + if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) + /* get out of the loop as soon as they do not match */ + return 0; + first++; + second++; + } + /* 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. */ + return !*first == !*second; +} + +static int ncasecompare(const char *first, const char *second, size_t max) +{ + while(*first && max) { + if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) + return 0; + max--; + first++; + second++; + } + if(0 == max) + return 1; /* they are equal this far */ + + return Curl_raw_toupper(*first) == Curl_raw_toupper(*second); +} + +/* --- public function --- */ +int curl_strequal(const char *first, const char *second) +{ + if(first && second) + /* both pointers point to something then compare them */ + return casecompare(first, second); + + /* if both pointers are NULL then treat them as equal */ + return NULL == first && NULL == second; +} + +/* --- public function --- */ +int curl_strnequal(const char *first, const char *second, size_t max) +{ + if(first && second) + /* both pointers point to something then compare them */ + return ncasecompare(first, second, max); + + /* if both pointers are NULL then treat them as equal if max is non-zero */ + return NULL == first && NULL == second && max; +} diff --git a/Utilities/cmcurl/lib/strerror.c b/Utilities/cmcurl/lib/strerror.c index 20495c9791..9cf93dd66e 100644 --- a/Utilities/cmcurl/lib/strerror.c +++ b/Utilities/cmcurl/lib/strerror.c @@ -42,16 +42,13 @@ #include "curl_sspi.h" #endif +#include "curlx/winapi.h" #include "strerror.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -#if defined(_WIN32) || defined(_WIN32_WCE) -#define PRESERVE_WINDOWS_ERROR_CODE -#endif - const char * curl_easy_strerror(CURLcode error) { @@ -335,6 +332,20 @@ curl_easy_strerror(CURLcode error) case CURLE_OBSOLETE62: case CURLE_OBSOLETE75: case CURLE_OBSOLETE76: + + /* error codes used by curl tests */ + case CURLE_RESERVED115: + case CURLE_RESERVED116: + case CURLE_RESERVED117: + case CURLE_RESERVED118: + case CURLE_RESERVED119: + case CURLE_RESERVED120: + case CURLE_RESERVED121: + case CURLE_RESERVED122: + case CURLE_RESERVED123: + case CURLE_RESERVED124: + case CURLE_RESERVED125: + case CURLE_RESERVED126: case CURL_LAST: break; } @@ -768,50 +779,6 @@ get_winsock_error(int err, char *buf, size_t len) } #endif /* USE_WINSOCK */ -#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. - */ -static const char * -get_winapi_error(int err, char *buf, size_t buflen) -{ - char *p; - wchar_t wbuf[256]; - - if(!buflen) - return NULL; - - *buf = '\0'; - *wbuf = L'\0'; - - /* We return the local codepage version of the error string because if it is - output to the user's terminal it will likely be with functions which - 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, (DWORD)err, - LANG_NEUTRAL, wbuf, sizeof(wbuf)/sizeof(wchar_t), NULL)) { - size_t written = wcstombs(buf, wbuf, buflen - 1); - if(written != (size_t)-1) - buf[written] = '\0'; - else - *buf = '\0'; - } - - /* Truncate multiple lines */ - p = strchr(buf, '\n'); - if(p) { - if(p > buf && *(p-1) == '\r') - *(p-1) = '\0'; - else - *p = '\0'; - } - - return *buf ? buf : NULL; -} -#endif /* _WIN32 || _WIN32_WCE */ - /* * Our thread-safe and smart strerror() replacement. * @@ -829,11 +796,11 @@ get_winapi_error(int err, char *buf, size_t buflen) * * It may be more correct to call one of the variant functions instead: * Call Curl_sspi_strerror if the error code is definitely Windows SSPI. - * Call Curl_winapi_strerror if the error code is definitely Windows API. + * Call curlx_winapi_strerror if the error code is definitely Windows API. */ const char *Curl_strerror(int err, char *buf, size_t buflen) { -#ifdef PRESERVE_WINDOWS_ERROR_CODE +#ifdef _WIN32 DWORD old_win_err = GetLastError(); #endif int old_errno = errno; @@ -848,20 +815,20 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) *buf = '\0'; -#if defined(_WIN32) || defined(_WIN32_WCE) -#if defined(_WIN32) +#ifdef _WIN32 +#ifndef UNDER_CE /* 'sys_nerr' is the maximum errno number, it is not widely portable */ if(err >= 0 && err < sys_nerr) - msnprintf(buf, buflen, "%s", sys_errlist[err]); + curl_msnprintf(buf, buflen, "%s", sys_errlist[err]); else #endif { if( #ifdef USE_WINSOCK - !get_winsock_error(err, buf, buflen) && + !get_winsock_error(err, buf, buflen) && #endif - !get_winapi_error(err, buf, buflen)) - msnprintf(buf, buflen, "Unknown error %d (%#x)", err, err); + !curlx_get_winapi_error(err, buf, buflen)) + curl_msnprintf(buf, buflen, "Unknown error %d (%#x)", err, err); } #else /* not Windows coming up */ @@ -873,7 +840,7 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) */ if(0 != strerror_r(err, buf, buflen)) { if('\0' == buf[0]) - msnprintf(buf, buflen, "Unknown error %d", err); + curl_msnprintf(buf, buflen, "Unknown error %d", err); } #elif defined(HAVE_STRERROR_R) && defined(HAVE_GLIBC_STRERROR_R) /* @@ -885,18 +852,18 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) char buffer[256]; char *msg = strerror_r(err, buffer, sizeof(buffer)); if(msg) - msnprintf(buf, buflen, "%s", msg); + curl_msnprintf(buf, buflen, "%s", msg); else - msnprintf(buf, buflen, "Unknown error %d", err); + curl_msnprintf(buf, buflen, "Unknown error %d", err); } #else { /* !checksrc! disable BANNEDFUNC 1 */ const char *msg = strerror(err); if(msg) - msnprintf(buf, buflen, "%s", msg); + curl_msnprintf(buf, buflen, "%s", msg); else - msnprintf(buf, buflen, "Unknown error %d", err); + curl_msnprintf(buf, buflen, "Unknown error %d", err); } #endif @@ -911,9 +878,9 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) *p = '\0'; if(errno != old_errno) - errno = old_errno; + CURL_SETERRNO(old_errno); -#ifdef PRESERVE_WINDOWS_ERROR_CODE +#ifdef _WIN32 if(old_win_err != GetLastError()) SetLastError(old_win_err); #endif @@ -921,47 +888,6 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) return buf; } -/* - * Curl_winapi_strerror: - * Variant of Curl_strerror if the error code is definitely Windows API. - */ -#if defined(_WIN32) || defined(_WIN32_WCE) -const char *Curl_winapi_strerror(DWORD err, char *buf, size_t buflen) -{ -#ifdef PRESERVE_WINDOWS_ERROR_CODE - DWORD old_win_err = GetLastError(); -#endif - int old_errno = errno; - - if(!buflen) - return NULL; - - *buf = '\0'; - -#ifndef CURL_DISABLE_VERBOSE_STRINGS - 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"; - if(strlen(txt) < buflen) - strcpy(buf, txt); - } -#endif - - if(errno != old_errno) - errno = old_errno; - -#ifdef PRESERVE_WINDOWS_ERROR_CODE - if(old_win_err != GetLastError()) - SetLastError(old_win_err); -#endif - - return buf; -} -#endif /* _WIN32 || _WIN32_WCE */ - #ifdef USE_WINDOWS_SSPI /* * Curl_sspi_strerror: @@ -969,7 +895,7 @@ const char *Curl_winapi_strerror(DWORD err, char *buf, size_t buflen) */ const char *Curl_sspi_strerror(int err, char *buf, size_t buflen) { -#ifdef PRESERVE_WINDOWS_ERROR_CODE +#ifdef _WIN32 DWORD old_win_err = GetLastError(); #endif int old_errno = errno; @@ -1076,18 +1002,18 @@ const char *Curl_sspi_strerror(int err, char *buf, size_t buflen) } if(err == SEC_E_ILLEGAL_MESSAGE) { - msnprintf(buf, buflen, - "SEC_E_ILLEGAL_MESSAGE (0x%08X) - This error usually occurs " - "when a fatal SSL/TLS alert is received (e.g. handshake failed)." - " More detail may be available in the Windows System event log.", - err); + curl_msnprintf(buf, buflen, + "SEC_E_ILLEGAL_MESSAGE (0x%08X) - This error usually " + "occurs when a fatal SSL/TLS alert is received (e.g. " + "handshake failed). More detail may be available in " + "the Windows System event log.", err); } else { char msgbuf[256]; - if(get_winapi_error(err, msgbuf, sizeof(msgbuf))) - msnprintf(buf, buflen, "%s (0x%08X) - %s", txt, err, msgbuf); + if(curlx_get_winapi_error(err, msgbuf, sizeof(msgbuf))) + curl_msnprintf(buf, buflen, "%s (0x%08X) - %s", txt, err, msgbuf); else - msnprintf(buf, buflen, "%s (0x%08X)", txt, err); + curl_msnprintf(buf, buflen, "%s (0x%08X)", txt, err); } #else @@ -1100,9 +1026,9 @@ const char *Curl_sspi_strerror(int err, char *buf, size_t buflen) #endif if(errno != old_errno) - errno = old_errno; + CURL_SETERRNO(old_errno); -#ifdef PRESERVE_WINDOWS_ERROR_CODE +#ifdef _WIN32 if(old_win_err != GetLastError()) SetLastError(old_win_err); #endif diff --git a/Utilities/cmcurl/lib/strerror.h b/Utilities/cmcurl/lib/strerror.h index 6806867345..424fb5b7b5 100644 --- a/Utilities/cmcurl/lib/strerror.h +++ b/Utilities/cmcurl/lib/strerror.h @@ -29,9 +29,6 @@ #define STRERROR_LEN 256 /* a suitable length */ const char *Curl_strerror(int err, char *buf, size_t buflen); -#if defined(_WIN32) || defined(_WIN32_WCE) -const char *Curl_winapi_strerror(DWORD err, char *buf, size_t buflen); -#endif #ifdef USE_WINDOWS_SSPI const char *Curl_sspi_strerror(int err, char *buf, size_t buflen); #endif diff --git a/Utilities/cmcurl/lib/strparse.c b/Utilities/cmcurl/lib/strparse.c deleted file mode 100644 index dce08251f8..0000000000 --- a/Utilities/cmcurl/lib/strparse.c +++ /dev/null @@ -1,136 +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 "strparse.h" - -/* Get a word until the first DELIM or end of string. At least one byte long. - return non-zero on error */ -int Curl_str_until(char **linep, struct Curl_str *out, - const size_t max, char delim) -{ - char *s = *linep; - size_t len = 0; - DEBUGASSERT(linep && *linep && out && max && delim); - - out->str = NULL; - out->len = 0; - while(*s && (*s != delim)) { - s++; - if(++len > max) { - return STRE_BIG; - } - } - if(!len) - return STRE_SHORT; - out->str = *linep; - out->len = len; - *linep = s; /* point to the first byte after the word */ - return STRE_OK; -} - -/* Get a word until the first space or end of string. At least one byte long. - return non-zero on error */ -int Curl_str_word(char **linep, struct Curl_str *out, - const size_t max) -{ - return Curl_str_until(linep, out, max, ' '); -} - - -/* Get a "quoted" word. No escaping possible. - return non-zero on error */ -int Curl_str_quotedword(char **linep, struct Curl_str *out, - const size_t max) -{ - char *s = *linep; - size_t len = 0; - DEBUGASSERT(linep && *linep && out && max); - - out->str = NULL; - out->len = 0; - if(*s != '\"') - return STRE_BEGQUOTE; - s++; - while(*s && (*s != '\"')) { - s++; - if(++len > max) - return STRE_BIG; - } - if(*s != '\"') - return STRE_ENDQUOTE; - out->str = (*linep) + 1; - out->len = len; - *linep = s + 1; - return STRE_OK; -} - -/* Advance over a single character. - return non-zero on error */ -int Curl_str_single(char **linep, char byte) -{ - DEBUGASSERT(linep && *linep); - if(**linep != byte) - return STRE_BYTE; - (*linep)++; /* move over it */ - return STRE_OK; -} - -/* Advance over a single space. - return non-zero on error */ -int Curl_str_singlespace(char **linep) -{ - return Curl_str_single(linep, ' '); -} - -/* Get an unsigned number. Leading zeroes are accepted. - return non-zero on error */ -int Curl_str_number(char **linep, size_t *nump, size_t max) -{ - size_t num = 0; - DEBUGASSERT(linep && *linep && nump); - *nump = 0; - while(ISDIGIT(**linep)) { - int n = **linep - '0'; - if(num > ((SIZE_T_MAX - n) / 10)) - return STRE_OVERFLOW; - num = num * 10 + n; - if(num > max) - return STRE_BIG; /** too big */ - (*linep)++; - } - *nump = num; - return STRE_OK; -} - -/* CR or LF - return non-zero on error */ -int Curl_str_newline(char **linep) -{ - DEBUGASSERT(linep && *linep); - if(ISNEWLINE(**linep)) { - (*linep)++; - return STRE_OK; /* yessir */ - } - return STRE_NEWLINE; -} diff --git a/Utilities/cmcurl/lib/strparse.h b/Utilities/cmcurl/lib/strparse.h deleted file mode 100644 index 189927b059..0000000000 --- a/Utilities/cmcurl/lib/strparse.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef HEADER_CURL_STRPARSE_H -#define HEADER_CURL_STRPARSE_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" - -#define STRE_OK 0 -#define STRE_BIG 1 -#define STRE_SHORT 2 -#define STRE_BEGQUOTE 3 -#define STRE_ENDQUOTE 4 -#define STRE_BYTE 5 -#define STRE_NEWLINE 6 -#define STRE_OVERFLOW 7 - -struct Curl_str { - char *str; - size_t len; -}; - -/* Get a word until the first space - return non-zero on error */ -int Curl_str_word(char **linep, struct Curl_str *out, const size_t max); - -/* Get a word until the first DELIM or end of string - return non-zero on error */ -int Curl_str_until(char **linep, struct Curl_str *out, const size_t max, - char delim); - -/* Get a "quoted" word. No escaping possible. - return non-zero on error */ -int Curl_str_quotedword(char **linep, struct Curl_str *out, const size_t max); - -/* Advance over a single character. - return non-zero on error */ -int Curl_str_single(char **linep, char byte); - -/* Advance over a single space. - return non-zero on error */ -int Curl_str_singlespace(char **linep); - -/* Get an unsigned number - return non-zero on error */ -int Curl_str_number(char **linep, size_t *nump, size_t max); - -/* Check for CR or LF - return non-zero on error */ -int Curl_str_newline(char **linep); - -#endif /* HEADER_CURL_STRPARSE_H */ diff --git a/Utilities/cmcurl/lib/strtok.c b/Utilities/cmcurl/lib/strtok.c deleted file mode 100644 index d2cc71c47d..0000000000 --- a/Utilities/cmcurl/lib/strtok.c +++ /dev/null @@ -1,68 +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" - -#ifndef HAVE_STRTOK_R -#include - -#include "strtok.h" - -char * -Curl_strtok_r(char *ptr, const char *sep, char **end) -{ - if(!ptr) - /* we got NULL input so then we get our last position instead */ - ptr = *end; - - /* pass all letters that are including in the separator string */ - while(*ptr && strchr(sep, *ptr)) - ++ptr; - - if(*ptr) { - /* so this is where the next piece of string starts */ - char *start = ptr; - - /* set the end pointer to the first byte after the start */ - *end = start + 1; - - /* scan through the string to find where it ends, it ends on a - null byte or a character that exists in the separator string */ - while(**end && !strchr(sep, **end)) - ++*end; - - if(**end) { - /* the end is not a null byte */ - **end = '\0'; /* null-terminate it! */ - ++*end; /* advance the last pointer to beyond the null byte */ - } - - return start; /* return the position where the string starts */ - } - - /* we ended up on a null byte, there are no more strings to find! */ - return NULL; -} - -#endif /* this was only compiled if strtok_r was not present */ diff --git a/Utilities/cmcurl/lib/strtoofft.c b/Utilities/cmcurl/lib/strtoofft.c deleted file mode 100644 index 05536c1826..0000000000 --- a/Utilities/cmcurl/lib/strtoofft.c +++ /dev/null @@ -1,240 +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 -#include "curl_setup.h" - -#include "strtoofft.h" - -/* - * NOTE: - * - * In the ISO C standard (IEEE Std 1003.1), there is a strtoimax() function we - * could use in case strtoll() does not exist... See - * https://www.opengroup.org/onlinepubs/009695399/functions/strtoimax.html - */ - -#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG) -# ifdef HAVE_STRTOLL -# define strtooff strtoll -# else -# if defined(_MSC_VER) && (_MSC_VER >= 1300) -# if defined(_SAL_VERSION) - _Check_return_ _CRTIMP __int64 __cdecl _strtoi64( - _In_z_ const char *_String, - _Out_opt_ _Deref_post_z_ char **_EndPtr, _In_ int _Radix); -# else - _CRTIMP __int64 __cdecl _strtoi64(const char *_String, - char **_EndPtr, int _Radix); -# endif -# define strtooff _strtoi64 -# else -# define PRIVATE_STRTOOFF 1 -# endif -# endif -#else -# define strtooff strtol -#endif - -#ifdef PRIVATE_STRTOOFF - -/* Range tests can be used for alphanum decoding if characters are consecutive, - like in ASCII. Else an array is scanned. Determine this condition now. */ - -#if('9' - '0') != 9 || ('Z' - 'A') != 25 || ('z' - 'a') != 25 - -#define NO_RANGE_TEST - -static const char valchars[] = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; -#endif - -static int get_char(char c, int base); - -/** - * 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; - bool is_negative = FALSE; - bool overflow = FALSE; - int i; - curl_off_t value = 0; - - /* Skip leading whitespace. */ - end = (char *)nptr; - while(ISBLANK(end[0])) { - end++; - } - - /* Handle the sign, if any. */ - if(end[0] == '-') { - is_negative = TRUE; - end++; - } - else if(end[0] == '+') { - end++; - } - else if(end[0] == '\0') { - /* We had nothing but perhaps some whitespace -- there was no number. */ - if(endptr) { - *endptr = end; - } - return 0; - } - - /* Handle special beginnings, if present and allowed. */ - if(end[0] == '0' && end[1] == 'x') { - if(base == 16 || base == 0) { - end += 2; - base = 16; - } - } - else if(end[0] == '0') { - if(base == 8 || base == 0) { - end++; - base = 8; - } - } - - /* 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. */ - for(i = get_char(end[0], base); - i != -1; - end++, i = get_char(end[0], base)) { - - if(value > (CURL_OFF_T_MAX - i) / base) { - overflow = TRUE; - break; - } - value = base * value + i; - } - - if(!overflow) { - if(is_negative) { - /* Fix the sign. */ - value *= -1; - } - } - else { - if(is_negative) - value = CURL_OFF_T_MIN; - else - value = CURL_OFF_T_MAX; - - errno = ERANGE; - } - - if(endptr) - *endptr = end; - - return value; -} - -/** - * Returns the value of c in the given base, or -1 if c cannot - * be interpreted properly in that base (i.e., is out of range, - * is a null, etc.). - * - * @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 is not in range - */ -static int get_char(char c, int base) -{ -#ifndef NO_RANGE_TEST - int value = -1; - if(c <= '9' && c >= '0') { - value = c - '0'; - } - else if(c <= 'Z' && c >= 'A') { - value = c - 'A' + 10; - } - else if(c <= 'z' && c >= 'a') { - value = c - 'a' + 10; - } -#else - const char *cp; - int value; - - cp = memchr(valchars, c, 10 + 26 + 26); - - if(!cp) - return -1; - - value = cp - valchars; - - if(value >= 10 + 26) - value -= 26; /* Lowercase. */ -#endif - - if(value >= base) { - value = -1; - } - - return value; -} -#endif /* Only present if we need strtoll, but do not have it. */ - -/* - * 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 = NULL; - curl_off_t number; - errno = 0; - *num = 0; /* clear by default */ - DEBUGASSERT(base); /* starting now, avoid base zero */ - - while(*str && ISBLANK(*str)) - str++; - if(('-' == *str) || (ISSPACE(*str))) { - if(endp) - *endp = (char *)str; /* did not actually move */ - return CURL_OFFT_INVAL; /* nothing parsed */ - } - number = strtooff(str, &end, base); - if(endp) - *endp = end; - if(errno == ERANGE) - /* overflow/underflow */ - return CURL_OFFT_FLOW; - else if(str == end) - /* nothing parsed */ - return CURL_OFFT_INVAL; - - *num = number; - return CURL_OFFT_OK; -} diff --git a/Utilities/cmcurl/lib/system_win32.c b/Utilities/cmcurl/lib/system_win32.c index 5ab711871e..5212887580 100644 --- a/Utilities/cmcurl/lib/system_win32.c +++ b/Utilities/cmcurl/lib/system_win32.c @@ -24,21 +24,18 @@ #include "curl_setup.h" -#if defined(_WIN32) +#ifdef _WIN32 #include #include "system_win32.h" -#include "version_win32.h" +#include "curlx/version_win32.h" #include "curl_sspi.h" -#include "warnless.h" +#include "curlx/warnless.h" /* The last #include files should be: */ #include "curl_memory.h" #include "memdebug.h" -LARGE_INTEGER Curl_freq; -bool Curl_isVistaOrGreater; - /* Handle of iphlpapp.dll */ static HMODULE s_hIpHlpApiDll = NULL; @@ -96,9 +93,15 @@ CURLcode Curl_win32_init(long flags) s_hIpHlpApiDll = Curl_load_library(TEXT("iphlpapi.dll")); if(s_hIpHlpApiDll) { /* Get the address of the if_nametoindex function */ +#ifdef UNDER_CE + #define CURL_TEXT(n) TEXT(n) +#else + #define CURL_TEXT(n) (n) +#endif IF_NAMETOINDEX_FN pIfNameToIndex = CURLX_FUNCTION_CAST(IF_NAMETOINDEX_FN, - (GetProcAddress(s_hIpHlpApiDll, "if_nametoindex"))); + (GetProcAddress(s_hIpHlpApiDll, + CURL_TEXT("if_nametoindex")))); if(pIfNameToIndex) Curl_if_nametoindex = pIfNameToIndex; @@ -150,7 +153,7 @@ typedef HMODULE (APIENTRY *LOADLIBRARYEX_FN)(LPCTSTR, HANDLE, DWORD); /* See function definitions in winbase.h */ #ifdef UNICODE -# ifdef _WIN32_WCE +# ifdef UNDER_CE # define LOADLIBARYEX L"LoadLibraryExW" # else # define LOADLIBARYEX "LoadLibraryExW" @@ -175,7 +178,7 @@ typedef HMODULE (APIENTRY *LOADLIBRARYEX_FN)(LPCTSTR, HANDLE, DWORD); */ HMODULE Curl_load_library(LPCTSTR filename) { -#ifndef CURL_WINDOWS_UWP +#if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE) HMODULE hModule = NULL; LOADLIBRARYEX_FN pLoadLibraryEx = NULL; @@ -211,7 +214,7 @@ HMODULE Curl_load_library(LPCTSTR filename) /* Attempt to get the Windows system path */ UINT systemdirlen = GetSystemDirectory(NULL, 0); if(systemdirlen) { - /* Allocate space for the full DLL path (Room for the null terminator + /* Allocate space for the full DLL path (Room for the null-terminator is included in systemdirlen) */ size_t filenamelen = _tcslen(filename); TCHAR *path = malloc(sizeof(TCHAR) * (systemdirlen + 1 + filenamelen)); diff --git a/Utilities/cmcurl/lib/system_win32.h b/Utilities/cmcurl/lib/system_win32.h index 024d959f32..b8333c647e 100644 --- a/Utilities/cmcurl/lib/system_win32.h +++ b/Utilities/cmcurl/lib/system_win32.h @@ -26,7 +26,7 @@ #include "curl_setup.h" -#if defined(_WIN32) +#ifdef _WIN32 #include diff --git a/Utilities/cmcurl/lib/telnet.c b/Utilities/cmcurl/lib/telnet.c index e383917eed..737db36d72 100644 --- a/Utilities/cmcurl/lib/telnet.c +++ b/Utilities/cmcurl/lib/telnet.c @@ -47,6 +47,7 @@ #endif #include "urldata.h" +#include "url.h" #include #include "transfer.h" #include "sendf.h" @@ -57,7 +58,8 @@ #include "arpa_telnet.h" #include "select.h" #include "strcase.h" -#include "warnless.h" +#include "curlx/warnless.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -89,36 +91,6 @@ #define printoption(a,b,c,d) Curl_nop_stmt #endif -static -CURLcode telrcv(struct Curl_easy *data, - const unsigned char *inbuf, /* Data received from socket */ - ssize_t count); /* Number of bytes received */ - -#ifndef CURL_DISABLE_VERBOSE_STRINGS -static void printoption(struct Curl_easy *data, - const char *direction, - int cmd, int option); -#endif - -static void negotiate(struct Curl_easy *data); -static void send_negotiation(struct Curl_easy *data, int cmd, int option); -static void set_local_option(struct Curl_easy *data, - int option, int newstate); -static void set_remote_option(struct Curl_easy *data, - int option, int newstate); - -static void printsub(struct Curl_easy *data, - int direction, unsigned char *pointer, - size_t length); -static void suboption(struct Curl_easy *data); -static void sendsuboption(struct Curl_easy *data, int option); - -static CURLcode telnet_do(struct Curl_easy *data, bool *done); -static CURLcode telnet_done(struct Curl_easy *data, - CURLcode, bool premature); -static CURLcode send_telnet_data(struct Curl_easy *data, - char *buffer, ssize_t nread); - /* For negotiation compliant to RFC 1143 */ #define CURL_NO 0 #define CURL_YES 1 @@ -128,6 +100,10 @@ static CURLcode send_telnet_data(struct Curl_easy *data, #define CURL_EMPTY 0 #define CURL_OPPOSITE 1 + +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_TELNET_EASY "meta:proto:telnet:easy" + /* * Telnet receiver states for fsm */ @@ -168,6 +144,38 @@ struct TELNET { }; +static +CURLcode telrcv(struct Curl_easy *data, + struct TELNET *tn, + const unsigned char *inbuf, /* Data received from socket */ + ssize_t count); /* Number of bytes received */ + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void printoption(struct Curl_easy *data, + const char *direction, + int cmd, int option); +#endif + +static void send_negotiation(struct Curl_easy *data, int cmd, int option); +static void set_local_option(struct Curl_easy *data, struct TELNET *tn, + int option, int newstate); +static void set_remote_option(struct Curl_easy *data, struct TELNET *tn, + int option, int newstate); + +static void printsub(struct Curl_easy *data, + int direction, unsigned char *pointer, + size_t length); +static void suboption(struct Curl_easy *data, struct TELNET *tn); +static void sendsuboption(struct Curl_easy *data, + struct TELNET *tn, int option); + +static CURLcode telnet_do(struct Curl_easy *data, bool *done); +static CURLcode telnet_done(struct Curl_easy *data, + CURLcode, bool premature); +static CURLcode send_telnet_data(struct Curl_easy *data, + struct TELNET *tn, + char *buffer, ssize_t nread); + /* * TELNET protocol handler. */ @@ -198,6 +206,16 @@ const struct Curl_handler Curl_handler_telnet = { }; +static void telnet_easy_dtor(void *key, size_t klen, void *entry) +{ + struct TELNET *tn = entry; + (void)key; + (void)klen; + curl_slist_free_all(tn->telnet_vars); + curlx_dyn_free(&tn->out); + free(tn); +} + static CURLcode init_telnet(struct Curl_easy *data) { @@ -207,8 +225,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 */ + curlx_dyn_init(&tn->out, 0xffff); tn->telrcv_state = CURL_TS_DATA; @@ -247,23 +264,23 @@ CURLcode init_telnet(struct Curl_easy *data) based upon the terminal type information that may have been sent using the TERMINAL TYPE Telnet option). */ tn->subnegotiation[CURL_TELOPT_NAWS] = CURL_YES; - return CURLE_OK; + + return Curl_meta_set(data, CURL_META_TELNET_EASY, tn, telnet_easy_dtor); } -static void negotiate(struct Curl_easy *data) +static void telnet_negotiate(struct Curl_easy *data, struct TELNET *tn) { int i; - struct TELNET *tn = data->req.p.telnet; for(i = 0; i < CURL_NTELOPTS; i++) { if(i == CURL_TELOPT_ECHO) continue; if(tn->us_preferred[i] == CURL_YES) - set_local_option(data, i, CURL_YES); + set_local_option(data, tn, i, CURL_YES); if(tn->him_preferred[i] == CURL_YES) - set_remote_option(data, i, CURL_YES); + set_remote_option(data, tn, i, CURL_YES); } } @@ -324,9 +341,9 @@ static void send_negotiation(struct Curl_easy *data, int cmd, int option) } static -void set_remote_option(struct Curl_easy *data, int option, int newstate) +void set_remote_option(struct Curl_easy *data, struct TELNET *tn, + int option, int newstate) { - struct TELNET *tn = data->req.p.telnet; if(newstate == CURL_YES) { switch(tn->him[option]) { case CURL_NO: @@ -398,9 +415,8 @@ void set_remote_option(struct Curl_easy *data, int option, int newstate) } static -void rec_will(struct Curl_easy *data, int option) +void rec_will(struct Curl_easy *data, struct TELNET *tn, int option) { - struct TELNET *tn = data->req.p.telnet; switch(tn->him[option]) { case CURL_NO: if(tn->him_preferred[option] == CURL_YES) { @@ -446,9 +462,8 @@ void rec_will(struct Curl_easy *data, int option) } static -void rec_wont(struct Curl_easy *data, int option) +void rec_wont(struct Curl_easy *data, struct TELNET *tn, int option) { - struct TELNET *tn = data->req.p.telnet; switch(tn->him[option]) { case CURL_NO: /* Already disabled */ @@ -488,9 +503,9 @@ void rec_wont(struct Curl_easy *data, int option) } static void -set_local_option(struct Curl_easy *data, int option, int newstate) +set_local_option(struct Curl_easy *data, struct TELNET *tn, + int option, int newstate) { - struct TELNET *tn = data->req.p.telnet; if(newstate == CURL_YES) { switch(tn->us[option]) { case CURL_NO: @@ -562,9 +577,8 @@ set_local_option(struct Curl_easy *data, int option, int newstate) } static -void rec_do(struct Curl_easy *data, int option) +void rec_do(struct Curl_easy *data, struct TELNET *tn, int option) { - struct TELNET *tn = data->req.p.telnet; switch(tn->us[option]) { case CURL_NO: if(tn->us_preferred[option] == CURL_YES) { @@ -572,13 +586,13 @@ void rec_do(struct Curl_easy *data, int option) send_negotiation(data, CURL_WILL, option); if(tn->subnegotiation[option] == CURL_YES) /* transmission of data option */ - sendsuboption(data, option); + sendsuboption(data, tn, option); } else if(tn->subnegotiation[option] == CURL_YES) { /* send information to achieve this option */ tn->us[option] = CURL_YES; send_negotiation(data, CURL_WILL, option); - sendsuboption(data, option); + sendsuboption(data, tn, option); } else send_negotiation(data, CURL_WONT, option); @@ -608,7 +622,7 @@ void rec_do(struct Curl_easy *data, int option) tn->us[option] = CURL_YES; if(tn->subnegotiation[option] == CURL_YES) { /* transmission of data option */ - sendsuboption(data, option); + sendsuboption(data, tn, option); } break; case CURL_OPPOSITE: @@ -622,9 +636,8 @@ void rec_do(struct Curl_easy *data, int option) } static -void rec_dont(struct Curl_easy *data, int option) +void rec_dont(struct Curl_easy *data, struct TELNET *tn, int option) { - struct TELNET *tn = data->req.p.telnet; switch(tn->us[option]) { case CURL_NO: /* Already disabled */ @@ -787,11 +800,11 @@ static bool str_is_nonascii(const char *str) return FALSE; } -static CURLcode check_telnet_options(struct Curl_easy *data) +static CURLcode check_telnet_options(struct Curl_easy *data, + struct TELNET *tn) { struct curl_slist *head; struct curl_slist *beg; - struct TELNET *tn = data->req.p.telnet; CURLcode result = CURLE_OK; /* Add the username as an environment variable if it @@ -862,22 +875,20 @@ static CURLcode check_telnet_options(struct Curl_easy *data) case 2: /* Window Size */ if(strncasecompare(option, "WS", 2)) { - char *p; - unsigned long x = strtoul(arg, &p, 10); - unsigned long y = 0; - if(x && (x <= 0xffff) && Curl_raw_tolower(*p) == 'x') { - p++; - y = strtoul(p, NULL, 10); - if(y && (y <= 0xffff)) { - tn->subopt_wsx = (unsigned short)x; - tn->subopt_wsy = (unsigned short)y; - tn->us_preferred[CURL_TELOPT_NAWS] = CURL_YES; - } - } - if(!y) { + const char *p = arg; + curl_off_t x = 0; + curl_off_t y = 0; + if(curlx_str_number(&p, &x, 0xffff) || + curlx_str_single(&p, 'x') || + curlx_str_number(&p, &y, 0xffff)) { failf(data, "Syntax error in telnet option: %s", head->data); result = CURLE_SETOPT_OPTION_SYNTAX; } + else { + tn->subopt_wsx = (unsigned short)x; + tn->subopt_wsy = (unsigned short)y; + tn->us_preferred[CURL_TELOPT_NAWS] = CURL_YES; + } } else result = CURLE_UNKNOWN_OPTION; @@ -922,14 +933,13 @@ static CURLcode check_telnet_options(struct Curl_easy *data) * side. */ -static void suboption(struct Curl_easy *data) +static void suboption(struct Curl_easy *data, struct TELNET *tn) { struct curl_slist *v; unsigned char temp[2048]; ssize_t bytes_written; size_t len; int err; - struct TELNET *tn = data->req.p.telnet; struct connectdata *conn = data->conn; printsub(data, '<', (unsigned char *)tn->subbuffer, CURL_SB_LEN(tn) + 2); @@ -1001,13 +1011,13 @@ static void suboption(struct Curl_easy *data) * Send suboption information to the server side. */ -static void sendsuboption(struct Curl_easy *data, int option) +static void sendsuboption(struct Curl_easy *data, + struct TELNET *tn, int option) { ssize_t bytes_written; int err; unsigned short x, y; unsigned char *uc1, *uc2; - struct TELNET *tn = data->req.p.telnet; struct connectdata *conn = data->conn; switch(option) { @@ -1044,7 +1054,7 @@ static void sendsuboption(struct Curl_easy *data, int option) } /* ... then the window size with the send_telnet_data() function to deal with 0xFF cases ... */ - send_telnet_data(data, (char *)tn->subbuffer + 3, 4); + send_telnet_data(data, tn, (char *)tn->subbuffer + 3, 4); /* ... and the footer */ bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer + 7, 2); if(bytes_written < 0) { @@ -1058,6 +1068,7 @@ static void sendsuboption(struct Curl_easy *data, int option) static CURLcode telrcv(struct Curl_easy *data, + struct TELNET *tn, const unsigned char *inbuf, /* Data received from socket */ ssize_t count) /* Number of bytes received */ { @@ -1065,17 +1076,16 @@ CURLcode telrcv(struct Curl_easy *data, CURLcode result; int in = 0; int startwrite = -1; - struct TELNET *tn = data->req.p.telnet; -#define startskipping() \ - if(startwrite >= 0) { \ - result = Curl_client_write(data, \ - CLIENTWRITE_BODY, \ - (char *)&inbuf[startwrite], \ - in-startwrite); \ - if(result) \ - return result; \ - } \ +#define startskipping() \ + if(startwrite >= 0) { \ + result = Curl_client_write(data, \ + CLIENTWRITE_BODY, \ + (const char *)&inbuf[startwrite], \ + in-startwrite); \ + if(result) \ + return result; \ + } \ startwrite = -1 #define writebyte() \ @@ -1145,28 +1155,28 @@ process_iac: case CURL_TS_WILL: printoption(data, "RCVD", CURL_WILL, c); tn->please_negotiate = 1; - rec_will(data, c); + rec_will(data, tn, c); tn->telrcv_state = CURL_TS_DATA; break; case CURL_TS_WONT: printoption(data, "RCVD", CURL_WONT, c); tn->please_negotiate = 1; - rec_wont(data, c); + rec_wont(data, tn, c); tn->telrcv_state = CURL_TS_DATA; break; case CURL_TS_DO: printoption(data, "RCVD", CURL_DO, c); tn->please_negotiate = 1; - rec_do(data, c); + rec_do(data, tn, c); tn->telrcv_state = CURL_TS_DATA; break; case CURL_TS_DONT: printoption(data, "RCVD", CURL_DONT, c); tn->please_negotiate = 1; - rec_dont(data, c); + rec_dont(data, tn, c); tn->telrcv_state = CURL_TS_DATA; break; @@ -1195,7 +1205,7 @@ process_iac: CURL_SB_TERM(tn); printoption(data, "In SUBOPTION processing, RCVD", CURL_IAC, c); - suboption(data); /* handle sub-option */ + suboption(data, tn); /* handle sub-option */ tn->telrcv_state = CURL_TS_IAC; goto process_iac; } @@ -1207,7 +1217,7 @@ process_iac: CURL_SB_ACCUM(tn, CURL_SE); tn->subpointer -= 2; CURL_SB_TERM(tn); - suboption(data); /* handle sub-option */ + suboption(data, tn); /* handle sub-option */ tn->telrcv_state = CURL_TS_DATA; } break; @@ -1220,6 +1230,7 @@ process_iac: /* Escape and send a telnet data block */ static CURLcode send_telnet_data(struct Curl_easy *data, + struct TELNET *tn, char *buffer, ssize_t nread) { size_t i, outlen; @@ -1228,7 +1239,6 @@ static CURLcode send_telnet_data(struct Curl_easy *data, size_t bytes_written; size_t total_written = 0; struct connectdata *conn = data->conn; - struct TELNET *tn = data->req.p.telnet; DEBUGASSERT(tn); DEBUGASSERT(nread > 0); @@ -1237,17 +1247,17 @@ static CURLcode send_telnet_data(struct Curl_easy *data, if(memchr(buffer, CURL_IAC, nread)) { /* only use the escape buffer when necessary */ - Curl_dyn_reset(&tn->out); + curlx_dyn_reset(&tn->out); for(i = 0; i < (size_t)nread && !result; i++) { - result = Curl_dyn_addn(&tn->out, &buffer[i], 1); + result = curlx_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); + result = curlx_dyn_addn(&tn->out, "\xff", 1); } - outlen = Curl_dyn_len(&tn->out); - outbuf = Curl_dyn_uptr(&tn->out); + outlen = curlx_dyn_len(&tn->out); + outbuf = curlx_dyn_uptr(&tn->out); } else { outlen = (size_t)nread; @@ -1278,16 +1288,9 @@ static CURLcode send_telnet_data(struct Curl_easy *data, static CURLcode telnet_done(struct Curl_easy *data, CURLcode status, bool premature) { - struct TELNET *tn = data->req.p.telnet; (void)status; /* unused */ (void)premature; /* not used */ - - if(!tn) - return CURLE_OK; - - curl_slist_free_all(tn->telnet_vars); - tn->telnet_vars = NULL; - Curl_dyn_free(&tn->out); + Curl_meta_remove(data, CURL_META_TELNET_EASY); return CURLE_OK; } @@ -1324,9 +1327,11 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) if(result) return result; - tn = data->req.p.telnet; + tn = Curl_meta_get(data, CURL_META_TELNET_EASY); + if(!tn) + return CURLE_FAILED_INIT; - result = check_telnet_options(data); + result = check_telnet_options(data, tn); if(result) return result; @@ -1419,7 +1424,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } } - result = send_telnet_data(data, buffer, readfile_read); + result = send_telnet_data(data, tn, buffer, readfile_read); if(result) { keepon = FALSE; break; @@ -1437,7 +1442,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) break; } - result = send_telnet_data(data, buffer, readfile_read); + result = send_telnet_data(data, tn, buffer, readfile_read); if(result) { keepon = FALSE; break; @@ -1450,7 +1455,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) events.lNetworkEvents = 0; if(WSAEnumNetworkEvents(sockfd, event_handle, &events) == SOCKET_ERROR) { err = SOCKERRNO; - if(err != EINPROGRESS) { + if(err != SOCKEINPROGRESS) { infof(data, "WSAEnumNetworkEvents failed (%d)", err); keepon = FALSE; result = CURLE_READ_ERROR; @@ -1475,7 +1480,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) break; } - result = telrcv(data, (unsigned char *) buffer, nread); + result = telrcv(data, tn, (unsigned char *) buffer, nread); if(result) { keepon = FALSE; break; @@ -1485,7 +1490,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) 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); + telnet_negotiate(data, tn); tn->already_negotiated = 1; } } @@ -1498,8 +1503,8 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } if(data->set.timeout) { - now = Curl_now(); - if(Curl_timediff(now, conn->created) >= data->set.timeout) { + now = curlx_now(); + if(curlx_timediff(now, conn->created) >= data->set.timeout) { failf(data, "Time-out"); result = CURLE_OPERATION_TIMEDOUT; keepon = FALSE; @@ -1555,7 +1560,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) /* In test 1452, macOS sees a ECONNRESET sometimes? Is this the * telnet test server not shutting down the socket in a clean way? * Seems to be timing related, happens more on slow debug build */ - if(data->state.os_errno == ECONNRESET) { + if(data->state.os_errno == SOCKECONNRESET) { DEBUGF(infof(data, "telnet_do, unexpected ECONNRESET on recv")); } break; @@ -1570,7 +1575,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) total_dl += nread; result = Curl_pgrsSetDownloadCounter(data, total_dl); if(!result) - result = telrcv(data, (unsigned char *)buffer, nread); + result = telrcv(data, tn, (unsigned char *)buffer, nread); if(result) { keepon = FALSE; break; @@ -1580,7 +1585,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) 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); + telnet_negotiate(data, tn); tn->already_negotiated = 1; } } @@ -1604,7 +1609,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } if(nread > 0) { - result = send_telnet_data(data, buffer, nread); + result = send_telnet_data(data, tn, buffer, nread); if(result) { keepon = FALSE; break; @@ -1619,8 +1624,8 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } /* poll switch statement */ if(data->set.timeout) { - now = Curl_now(); - if(Curl_timediff(now, conn->created) >= data->set.timeout) { + now = curlx_now(); + if(curlx_timediff(now, conn->created) >= data->set.timeout) { failf(data, "Time-out"); result = CURLE_OPERATION_TIMEDOUT; keepon = FALSE; diff --git a/Utilities/cmcurl/lib/tftp.c b/Utilities/cmcurl/lib/tftp.c index 3f214d55b6..e9f4e68ecc 100644 --- a/Utilities/cmcurl/lib/tftp.c +++ b/Utilities/cmcurl/lib/tftp.c @@ -62,6 +62,7 @@ #include "speedcheck.h" #include "select.h" #include "escape.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -120,7 +121,10 @@ struct tftp_packet { unsigned char *data; }; -struct tftp_state_data { +/* meta key for storing protocol meta at connection */ +#define CURL_META_TFTP_CONN "meta:proto:tftp:conn" + +struct tftp_conn { tftp_state_t state; tftp_mode_t mode; tftp_error_t error; @@ -135,9 +139,9 @@ struct tftp_state_data { struct Curl_sockaddr_storage remote_addr; curl_socklen_t remote_addrlen; int rbytes; - int sbytes; - int blksize; - int requested_blksize; + size_t sbytes; + unsigned int blksize; + unsigned int requested_blksize; unsigned short block; struct tftp_packet rpacket; struct tftp_packet spacket; @@ -145,12 +149,9 @@ struct tftp_state_data { /* Forward declarations */ -static CURLcode tftp_rx(struct tftp_state_data *state, tftp_event_t event); -static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event); +static CURLcode tftp_rx(struct tftp_conn *state, tftp_event_t event); +static CURLcode tftp_tx(struct tftp_conn *state, tftp_event_t event); static CURLcode tftp_connect(struct Curl_easy *data, bool *done); -static CURLcode tftp_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection); static CURLcode tftp_do(struct Curl_easy *data, bool *done); static CURLcode tftp_done(struct Curl_easy *data, CURLcode, bool premature); @@ -180,7 +181,7 @@ const struct Curl_handler Curl_handler_tftp = { tftp_getsock, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ - tftp_disconnect, /* disconnect */ + ZERO_NULL, /* disconnect */ ZERO_NULL, /* write_resp */ ZERO_NULL, /* write_resp_hd */ ZERO_NULL, /* connection_check */ @@ -202,7 +203,7 @@ const struct Curl_handler Curl_handler_tftp = { * * **********************************************************/ -static CURLcode tftp_set_timeouts(struct tftp_state_data *state) +static CURLcode tftp_set_timeouts(struct tftp_conn *state) { time_t maxtime, timeout; timediff_t timeout_ms; @@ -309,7 +310,7 @@ static const char *tftp_option_get(const char *buf, size_t len, return &buf[loc]; } -static CURLcode tftp_parse_option_ack(struct tftp_state_data *state, +static CURLcode tftp_parse_option_ack(struct tftp_conn *state, const char *ptr, int len) { const char *tmp = ptr; @@ -330,19 +331,16 @@ static CURLcode tftp_parse_option_ack(struct tftp_state_data *state, infof(data, "got option=(%s) value=(%s)", option, value); if(checkprefix(TFTP_OPTION_BLKSIZE, option)) { - long blksize; - - blksize = strtol(value, NULL, 10); - - if(!blksize) { - failf(data, "invalid blocksize value in OACK packet"); - return CURLE_TFTP_ILLEGAL; - } - if(blksize > TFTP_BLKSIZE_MAX) { + curl_off_t blksize; + if(curlx_str_number(&value, &blksize, TFTP_BLKSIZE_MAX)) { failf(data, "%s (%d)", "blksize is larger than max supported", TFTP_BLKSIZE_MAX); return CURLE_TFTP_ILLEGAL; } + if(!blksize) { + failf(data, "invalid blocksize value in OACK packet"); + return CURLE_TFTP_ILLEGAL; + } else if(blksize < TFTP_BLKSIZE_MIN) { failf(data, "%s (%d)", "blksize is smaller than min supported", TFTP_BLKSIZE_MIN); @@ -352,28 +350,27 @@ static CURLcode tftp_parse_option_ack(struct tftp_state_data *state, /* 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)", - "server requested blksize larger than allocated", blksize); + failf(data, "server requested blksize larger than allocated (%" + CURL_FORMAT_CURL_OFF_T ")", blksize); return CURLE_TFTP_ILLEGAL; } state->blksize = (int)blksize; - infof(data, "%s (%d) %s (%d)", "blksize parsed from OACK", - state->blksize, "requested", state->requested_blksize); + infof(data, "blksize parsed from OACK (%d) requested (%d)", + state->blksize, state->requested_blksize); } else if(checkprefix(TFTP_OPTION_TSIZE, option)) { - long tsize = 0; - - tsize = strtol(value, NULL, 10); - infof(data, "%s (%ld)", "tsize parsed from OACK", tsize); - + curl_off_t tsize = 0; /* tsize should be ignored on upload: Who cares about the size of the remote file? */ - if(!data->state.upload) { + if(!data->state.upload && + !curlx_str_number(&value, &tsize, CURL_OFF_T_MAX)) { if(!tsize) { failf(data, "invalid tsize -:%s:- value in OACK packet", value); return CURLE_TFTP_ILLEGAL; } + infof(data, "tsize parsed from OACK (%" CURL_FORMAT_CURL_OFF_T ")", + tsize); Curl_pgrsSetDownloadSize(data, tsize); } } @@ -382,7 +379,7 @@ static CURLcode tftp_parse_option_ack(struct tftp_state_data *state, return CURLE_OK; } -static CURLcode tftp_option_add(struct tftp_state_data *state, size_t *csize, +static CURLcode tftp_option_add(struct tftp_conn *state, size_t *csize, char *buf, const char *option) { if(( strlen(option) + *csize + 1) > (size_t)state->blksize) @@ -392,7 +389,7 @@ static CURLcode tftp_option_add(struct tftp_state_data *state, size_t *csize, return CURLE_OK; } -static CURLcode tftp_connect_for_tx(struct tftp_state_data *state, +static CURLcode tftp_connect_for_tx(struct tftp_conn *state, tftp_event_t event) { CURLcode result; @@ -408,7 +405,7 @@ static CURLcode tftp_connect_for_tx(struct tftp_state_data *state, return tftp_tx(state, event); } -static CURLcode tftp_connect_for_rx(struct tftp_state_data *state, +static CURLcode tftp_connect_for_rx(struct tftp_conn *state, tftp_event_t event) { CURLcode result; @@ -424,7 +421,7 @@ static CURLcode tftp_connect_for_rx(struct tftp_state_data *state, return tftp_rx(state, event); } -static CURLcode tftp_send_first(struct tftp_state_data *state, +static CURLcode tftp_send_first(struct tftp_conn *state, tftp_event_t event) { size_t sbytes; @@ -523,9 +520,14 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, /* the typecase for the 3rd argument is mostly for systems that do not have a size_t argument, like older unixes that want an 'int' */ +#ifdef __AMIGA__ +#define CURL_SENDTO_ARG5(x) CURL_UNCONST(x) +#else +#define CURL_SENDTO_ARG5(x) (x) +#endif senddata = sendto(state->sockfd, (void *)state->spacket.data, (SEND_TYPE_ARG3)sbytes, 0, - (struct sockaddr *)&data->conn->remote_addr->curl_sa_addr, + CURL_SENDTO_ARG5(&data->conn->remote_addr->curl_sa_addr), (curl_socklen_t)data->conn->remote_addr->addrlen); if(senddata != (ssize_t)sbytes) { char buffer[STRERROR_LEN]; @@ -574,8 +576,7 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, * Event handler for the RX state * **********************************************************/ -static CURLcode tftp_rx(struct tftp_state_data *state, - tftp_event_t event) +static CURLcode tftp_rx(struct tftp_conn *state, tftp_event_t event) { ssize_t sbytes; int rblock; @@ -697,7 +698,7 @@ static CURLcode tftp_rx(struct tftp_state_data *state, * Event handler for the TX state * **********************************************************/ -static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) +static CURLcode tftp_tx(struct tftp_conn *state, tftp_event_t event) { struct Curl_easy *data = state->data; ssize_t sbytes; @@ -776,7 +777,7 @@ static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) &cb, &eos); if(result) return result; - state->sbytes += (int)cb; + state->sbytes += cb; bufptr += cb; } while(state->sbytes < state->blksize && cb); @@ -899,7 +900,7 @@ static CURLcode tftp_translate_code(tftp_error_t error) * The tftp state machine event dispatcher * **********************************************************/ -static CURLcode tftp_state_machine(struct tftp_state_data *state, +static CURLcode tftp_state_machine(struct tftp_conn *state, tftp_event_t event) { CURLcode result = CURLE_OK; @@ -931,28 +932,14 @@ static CURLcode tftp_state_machine(struct tftp_state_data *state, return result; } -/********************************************************** - * - * tftp_disconnect - * - * The disconnect callback - * - **********************************************************/ -static CURLcode tftp_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead_connection) +static void tftp_conn_dtor(void *key, size_t klen, void *entry) { - struct tftp_state_data *state = conn->proto.tftpc; - (void) data; - (void) dead_connection; - - /* done, free dynamically allocated pkt buffers */ - if(state) { - Curl_safefree(state->rpacket.data); - Curl_safefree(state->spacket.data); - free(state); - } - - return CURLE_OK; + struct tftp_conn *state = entry; + (void)key; + (void)klen; + Curl_safefree(state->rpacket.data); + Curl_safefree(state->spacket.data); + free(state); } /********************************************************** @@ -964,15 +951,16 @@ static CURLcode tftp_disconnect(struct Curl_easy *data, **********************************************************/ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) { - struct tftp_state_data *state; + struct tftp_conn *state; int blksize; int need_blksize; struct connectdata *conn = data->conn; blksize = TFTP_BLKSIZE_DEFAULT; - state = conn->proto.tftpc = calloc(1, sizeof(struct tftp_state_data)); - if(!state) + state = calloc(1, sizeof(*state)); + if(!state || + Curl_conn_meta_set(conn, CURL_META_TFTP_CONN, state, tftp_conn_dtor)) return CURLE_OUT_OF_MEMORY; /* alloc pkt buffers based on specified blksize */ @@ -1059,7 +1047,7 @@ static CURLcode tftp_done(struct Curl_easy *data, CURLcode status, { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct tftp_state_data *state = conn->proto.tftpc; + struct tftp_conn *state = Curl_conn_meta_get(conn, CURL_META_TFTP_CONN); (void)status; /* unused */ (void)premature; /* not used */ @@ -1096,12 +1084,11 @@ static int tftp_getsock(struct Curl_easy *data, * Called once select fires and data is ready on the socket * **********************************************************/ -static CURLcode tftp_receive_packet(struct Curl_easy *data) +static CURLcode tftp_receive_packet(struct Curl_easy *data, + struct tftp_conn *state) { curl_socklen_t fromlen; CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct tftp_state_data *state = conn->proto.tftpc; /* Receive the packet */ fromlen = sizeof(state->remote_addr); @@ -1180,12 +1167,10 @@ static CURLcode tftp_receive_packet(struct Curl_easy *data) * Check if timeouts have been reached * **********************************************************/ -static timediff_t tftp_state_timeout(struct Curl_easy *data, +static timediff_t tftp_state_timeout(struct tftp_conn *state, tftp_event_t *event) { time_t current; - struct connectdata *conn = data->conn; - struct tftp_state_data *state = conn->proto.tftpc; timediff_t timeout_ms; if(event) @@ -1220,11 +1205,14 @@ static CURLcode tftp_multi_statemach(struct Curl_easy *data, bool *done) tftp_event_t event; CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct tftp_state_data *state = conn->proto.tftpc; - timediff_t timeout_ms = tftp_state_timeout(data, &event); + struct tftp_conn *state = Curl_conn_meta_get(conn, CURL_META_TFTP_CONN); + timediff_t timeout_ms; *done = FALSE; + if(!state) + return CURLE_FAILED_INIT; + timeout_ms = tftp_state_timeout(state, &event); if(timeout_ms < 0) { failf(data, "TFTP response timeout"); return CURLE_OPERATION_TIMEDOUT; @@ -1250,7 +1238,7 @@ static CURLcode tftp_multi_statemach(struct Curl_easy *data, bool *done) state->event = TFTP_EVENT_ERROR; } else if(rc) { - result = tftp_receive_packet(data); + result = tftp_receive_packet(data, state); if(result) return result; result = tftp_state_machine(state, state->event); @@ -1289,7 +1277,7 @@ static CURLcode tftp_doing(struct Curl_easy *data, bool *dophase_done) if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(data, Curl_now()); + result = Curl_speedcheck(data, curlx_now()); } return result; } @@ -1305,9 +1293,11 @@ static CURLcode tftp_perform(struct Curl_easy *data, bool *dophase_done) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct tftp_state_data *state = conn->proto.tftpc; + struct tftp_conn *state = Curl_conn_meta_get(conn, CURL_META_TFTP_CONN); *dophase_done = FALSE; + if(!state) + return CURLE_FAILED_INIT; result = tftp_state_machine(state, TFTP_EVENT_INIT); @@ -1335,21 +1325,21 @@ static CURLcode tftp_perform(struct Curl_easy *data, bool *dophase_done) static CURLcode tftp_do(struct Curl_easy *data, bool *done) { - struct tftp_state_data *state; - CURLcode result; struct connectdata *conn = data->conn; + struct tftp_conn *state = Curl_conn_meta_get(conn, CURL_META_TFTP_CONN); + CURLcode result; *done = FALSE; - if(!conn->proto.tftpc) { + if(!state) { result = tftp_connect(data, done); if(result) return result; - } - state = conn->proto.tftpc; - if(!state) - return CURLE_TFTP_ILLEGAL; + state = Curl_conn_meta_get(conn, CURL_META_TFTP_CONN); + if(!state) + return CURLE_TFTP_ILLEGAL; + } result = tftp_perform(data, done); diff --git a/Utilities/cmcurl/lib/transfer.c b/Utilities/cmcurl/lib/transfer.c index 742828a763..ef7d7f19af 100644 --- a/Utilities/cmcurl/lib/transfer.c +++ b/Utilities/cmcurl/lib/transfer.c @@ -23,7 +23,6 @@ ***************************************************************************/ #include "curl_setup.h" -#include "strtoofft.h" #ifdef HAVE_NETINET_IN_H #include @@ -40,7 +39,9 @@ #ifdef HAVE_SYS_IOCTL_H #include #endif +#ifndef UNDER_CE #include +#endif #ifdef HAVE_SYS_PARAM_H #include @@ -469,13 +470,13 @@ CURLcode Curl_sendrecv(struct Curl_easy *data, struct curltime *nowp) 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), + curlx_timediff(*nowp, data->progress.t_startsingle), k->bytecount, k->size); } else { failf(data, "Operation timed out after %" FMT_TIMEDIFF_T " milliseconds with %" FMT_OFF_T " bytes received", - Curl_timediff(*nowp, data->progress.t_startsingle), + curlx_timediff(*nowp, data->progress.t_startsingle), k->bytecount); } result = CURLE_OPERATION_TIMEDOUT; @@ -571,8 +572,9 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) data->state.followlocation = 0; /* reset the location-follow counter */ data->state.this_is_a_follow = FALSE; /* reset this */ data->state.errorbuf = FALSE; /* no error has occurred */ - data->state.httpwant = data->set.httpwant; - data->state.httpversion = 0; +#ifndef CURL_DISABLE_HTTP + Curl_http_neg_init(data, &data->state.http_neg); +#endif data->state.authproblem = FALSE; data->state.authhost.want = data->set.httpauth; data->state.authproxy.want = data->set.proxyauth; @@ -654,7 +656,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) * protocol. */ if(data->set.str[STRING_USERAGENT]) { - Curl_safefree(data->state.aptr.uagent); + free(data->state.aptr.uagent); data->state.aptr.uagent = aprintf("User-Agent: %s\r\n", data->set.str[STRING_USERAGENT]); if(!data->state.aptr.uagent) @@ -880,6 +882,11 @@ CURLcode Curl_xfer_write_resp(struct Curl_easy *data, return result; } +bool Curl_xfer_write_is_paused(struct Curl_easy *data) +{ + return Curl_cwriter_is_paused(data); +} + CURLcode Curl_xfer_write_resp_hd(struct Curl_easy *data, const char *hd0, size_t hdlen, bool is_eos) { diff --git a/Utilities/cmcurl/lib/transfer.h b/Utilities/cmcurl/lib/transfer.h index b67f8a8947..2c355e0e9d 100644 --- a/Utilities/cmcurl/lib/transfer.h +++ b/Utilities/cmcurl/lib/transfer.h @@ -55,9 +55,11 @@ CURLcode Curl_xfer_write_resp(struct Curl_easy *data, const char *buf, size_t blen, bool is_eos); +bool Curl_xfer_write_is_paused(struct Curl_easy *data); + /** * Write a single "header" line from a server response. - * @param hd0 the 0-terminated, single header line + * @param hd0 the null-terminated, single header line * @param hdlen the length of the header line * @param is_eos TRUE iff this is the end of the response */ diff --git a/Utilities/cmcurl/lib/uint-bset.c b/Utilities/cmcurl/lib/uint-bset.c new file mode 100644 index 0000000000..2560b37363 --- /dev/null +++ b/Utilities/cmcurl/lib/uint-bset.c @@ -0,0 +1,238 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "uint-bset.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifdef DEBUGBUILD +#define CURL_UINT_BSET_MAGIC 0x62757473 +#endif + +void Curl_uint_bset_init(struct uint_bset *bset) +{ + memset(bset, 0, sizeof(*bset)); +#ifdef DEBUGBUILD + bset->init = CURL_UINT_BSET_MAGIC; +#endif +} + + +CURLcode Curl_uint_bset_resize(struct uint_bset *bset, unsigned int nmax) +{ + unsigned int nslots = (nmax + 63) / 64; + + DEBUGASSERT(bset->init == CURL_UINT_BSET_MAGIC); + if(nslots != bset->nslots) { + curl_uint64_t *slots = calloc(nslots, sizeof(curl_uint64_t)); + if(!slots) + return CURLE_OUT_OF_MEMORY; + + if(bset->slots) { + memcpy(slots, bset->slots, + (CURLMIN(nslots, bset->nslots) * sizeof(curl_uint64_t))); + free(bset->slots); + } + bset->slots = slots; + bset->nslots = nslots; + } + return CURLE_OK; +} + + +void Curl_uint_bset_destroy(struct uint_bset *bset) +{ + DEBUGASSERT(bset->init == CURL_UINT_BSET_MAGIC); + free(bset->slots); + memset(bset, 0, sizeof(*bset)); +} + + +unsigned int Curl_uint_bset_capacity(struct uint_bset *bset) +{ + return bset->nslots * 64; +} + + +unsigned int Curl_uint_bset_count(struct uint_bset *bset) +{ + unsigned int i; + unsigned int n = 0; + for(i = 0; i < bset->nslots; ++i) { + if(bset->slots[i]) + n += CURL_POPCOUNT64(bset->slots[i]); + } + return n; +} + + +bool Curl_uint_bset_empty(struct uint_bset *bset) +{ + unsigned int i; + for(i = 0; i < bset->nslots; ++i) { + if(bset->slots[i]) + return FALSE; + } + return TRUE; +} + + +void Curl_uint_bset_clear(struct uint_bset *bset) +{ + if(bset->nslots) + memset(bset->slots, 0, bset->nslots * sizeof(curl_uint64_t)); +} + + +bool Curl_uint_bset_add(struct uint_bset *bset, unsigned int i) +{ + unsigned int islot = i / 64; + if(islot >= bset->nslots) + return FALSE; + bset->slots[islot] |= ((curl_uint64_t)1 << (i % 64)); + return TRUE; +} + + +void Curl_uint_bset_remove(struct uint_bset *bset, unsigned int i) +{ + size_t islot = i / 64; + if(islot < bset->nslots) + bset->slots[islot] &= ~((curl_uint64_t)1 << (i % 64)); +} + + +bool Curl_uint_bset_contains(struct uint_bset *bset, unsigned int i) +{ + unsigned int islot = i / 64; + if(islot >= bset->nslots) + return FALSE; + return (bset->slots[islot] & ((curl_uint64_t)1 << (i % 64))) != 0; +} + + +bool Curl_uint_bset_first(struct uint_bset *bset, unsigned int *pfirst) +{ + unsigned int i; + for(i = 0; i < bset->nslots; ++i) { + if(bset->slots[i]) { + *pfirst = (i * 64) + CURL_CTZ64(bset->slots[i]); + return TRUE; + } + } + *pfirst = UINT_MAX; /* a value we cannot store */ + return FALSE; +} + +bool Curl_uint_bset_next(struct uint_bset *bset, unsigned int last, + unsigned int *pnext) +{ + unsigned int islot; + curl_uint64_t x; + + ++last; /* look for number one higher than last */ + islot = last / 64; /* the slot this would be in */ + if(islot < bset->nslots) { + /* shift away the bits we already iterated in this slot */ + x = (bset->slots[islot] >> (last % 64)); + if(x) { + /* more bits set, next is `last` + trailing0s of the shifted slot */ + *pnext = last + CURL_CTZ64(x); + return TRUE; + } + /* no more bits set in the last slot, scan forward */ + for(islot = islot + 1; islot < bset->nslots; ++islot) { + if(bset->slots[islot]) { + *pnext = (islot * 64) + CURL_CTZ64(bset->slots[islot]); + return TRUE; + } + } + } + *pnext = UINT_MAX; /* a value we cannot store */ + return FALSE; +} + +#ifdef CURL_POPCOUNT64_IMPLEMENT +unsigned int Curl_popcount64(curl_uint64_t x) +{ + /* Compute the "Hamming Distance" between 'x' and 0, + * which is the number of set bits in 'x'. + * See: https://en.wikipedia.org/wiki/Hamming_weight */ + const curl_uint64_t m1 = CURL_OFF_TU_C(0x5555555555555555); /* 0101+ */ + const curl_uint64_t m2 = CURL_OFF_TU_C(0x3333333333333333); /* 00110011+ */ + const curl_uint64_t m4 = CURL_OFF_TU_C(0x0f0f0f0f0f0f0f0f); /* 00001111+ */ + /* 1 + 256^1 + 256^2 + 256^3 + ... + 256^7 */ + const curl_uint64_t h01 = CURL_OFF_TU_C(0x0101010101010101); + x -= (x >> 1) & m1; /* replace every 2 bits with bits present */ + x = (x & m2) + ((x >> 2) & m2); /* replace every nibble with bits present */ + x = (x + (x >> 4)) & m4; /* replace every byte with bits present */ + /* top 8 bits of x + (x<<8) + (x<<16) + (x<<24) + ... which makes the + * top byte the sum of all individual 8 bytes, throw away the rest */ + return (unsigned int)((x * h01) >> 56); +} +#endif /* CURL_POPCOUNT64_IMPLEMENT */ + + +#ifdef CURL_CTZ64_IMPLEMENT +unsigned int Curl_ctz64(curl_uint64_t x) +{ + /* count trailing zeros in a curl_uint64_t. + * divide and conquer to find the number of lower 0 bits */ + const curl_uint64_t ml32 = CURL_OFF_TU_C(0xFFFFFFFF); /* lower 32 bits */ + const curl_uint64_t ml16 = CURL_OFF_TU_C(0x0000FFFF); /* lower 16 bits */ + const curl_uint64_t ml8 = CURL_OFF_TU_C(0x000000FF); /* lower 8 bits */ + const curl_uint64_t ml4 = CURL_OFF_TU_C(0x0000000F); /* lower 4 bits */ + const curl_uint64_t ml2 = CURL_OFF_TU_C(0x00000003); /* lower 2 bits */ + unsigned int n; + + if(!x) + return 64; + n = 1; + if(!(x & ml32)) { + n = n + 32; + x = x >> 32; + } + if(!(x & ml16)) { + n = n + 16; + x = x >> 16; + } + if(!(x & ml8)) { + n = n + 8; + x = x >> 8; + } + if(!(x & ml4)) { + n = n + 4; + x = x >> 4; + } + if(!(x & ml2)) { + n = n + 2; + x = x >> 2; + } + return n - (unsigned int)(x & 1); +} +#endif /* CURL_CTZ64_IMPLEMENT */ diff --git a/Utilities/cmcurl/lib/uint-bset.h b/Utilities/cmcurl/lib/uint-bset.h new file mode 100644 index 0000000000..d998dccdfe --- /dev/null +++ b/Utilities/cmcurl/lib/uint-bset.h @@ -0,0 +1,114 @@ +#ifndef HEADER_CURL_UINT_BSET_H +#define HEADER_CURL_UINT_BSET_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 + +/* A bitset for unsigned int values. + * It can hold the numbers from 0 - (nmax - 1), + * rounded to the next 64 multiple. + * + * Optimized for high efficiency in adding/removing numbers. + * Efficient storage when the set is (often) relatively full. + * + * If the set's cardinality is only expected to be a fraction of nmax, + * uint_spbset offers a "sparse" variant with more memory efficiency at + * the price of slightly slower operations. + */ + +struct uint_bset { + curl_uint64_t *slots; + unsigned int nslots; +#ifdef DEBUGBUILD + int init; +#endif +}; + +/* Initialize the bitset with capacity 0. */ +void Curl_uint_bset_init(struct uint_bset *bset); + +/* Resize the bitset capacity to hold numbers from 0 to `nmax`, + * which rounds up `nmax` to the next multiple of 64. */ +CURLcode Curl_uint_bset_resize(struct uint_bset *bset, unsigned int nmax); + +/* Destroy the bitset, freeing all resources. */ +void Curl_uint_bset_destroy(struct uint_bset *bset); + +/* Get the bitset capacity, e.g. can hold numbers from 0 to capacity - 1. */ +unsigned int Curl_uint_bset_capacity(struct uint_bset *bset); + +/* Get the cardinality of the bitset, e.g. numbers present in the set. */ +unsigned int Curl_uint_bset_count(struct uint_bset *bset); + +/* TRUE of bitset is empty */ +bool Curl_uint_bset_empty(struct uint_bset *bset); + +/* Clear the bitset, making it empty. */ +void Curl_uint_bset_clear(struct uint_bset *bset); + +/* Add the number `i` to the bitset. Return FALSE if the number is + * outside the set's capacity. + * Numbers can be added more than once, without making a difference. */ +bool Curl_uint_bset_add(struct uint_bset *bset, unsigned int i); + +/* Remove the number `i` from the bitset. */ +void Curl_uint_bset_remove(struct uint_bset *bset, unsigned int i); + +/* Return TRUE if the bitset contains number `i`. */ +bool Curl_uint_bset_contains(struct uint_bset *bset, unsigned int i); + +/* Get the first number in the bitset, e.g. the smallest. + * Returns FALSE when the bitset is empty. */ +bool Curl_uint_bset_first(struct uint_bset *bset, unsigned int *pfirst); + +/* Get the next number in the bitset, following `last` in natural order. + * Put another way, this is the smallest number greater than `last` in + * the bitset. `last` does not have to be present in the set. + * + * Returns FALSE when no such number is in the set. + * + * This allows to iterate the set while being modified: + * - added numbers higher than 'last' will be picked up by the iteration. + * - added numbers lower than 'last' will not show up. + * - removed numbers lower or equal to 'last' will not show up. + * - removed numbers higher than 'last' will not be visited. */ +bool Curl_uint_bset_next(struct uint_bset *bset, unsigned int last, + unsigned int *pnext); + + +#ifndef CURL_POPCOUNT64 +#define CURL_POPCOUNT64(x) Curl_popcount64(x) +#define CURL_POPCOUNT64_IMPLEMENT +unsigned int Curl_popcount64(curl_uint64_t x); +#endif /* !CURL_POPCOUNT64 */ + +#ifndef CURL_CTZ64 +#define CURL_CTZ64(x) Curl_ctz64(x) +#define CURL_CTZ64_IMPLEMENT +unsigned int Curl_ctz64(curl_uint64_t x); +#endif /* !CURL_CTZ64 */ + +#endif /* HEADER_CURL_UINT_BSET_H */ diff --git a/Utilities/cmcurl/lib/uint-hash.c b/Utilities/cmcurl/lib/uint-hash.c new file mode 100644 index 0000000000..afeb684d0c --- /dev/null +++ b/Utilities/cmcurl/lib/uint-hash.c @@ -0,0 +1,246 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "uint-hash.h" +#include "curl_memory.h" + +/* The last #include file should be: */ +#include "memdebug.h" + +/* random patterns for API verification */ +#ifdef DEBUGBUILD +#define CURL_UINTHASHINIT 0x7117e779 +#endif + +static unsigned int uint_hash_hash(unsigned int id, unsigned int slots) +{ + return (id % slots); +} + + +struct uint_hash_entry { + struct uint_hash_entry *next; + void *value; + unsigned int id; +}; + +void Curl_uint_hash_init(struct uint_hash *h, + unsigned int slots, + Curl_uint_hash_dtor *dtor) +{ + DEBUGASSERT(h); + DEBUGASSERT(slots); + + h->table = NULL; + h->dtor = dtor; + h->size = 0; + h->slots = slots; +#ifdef DEBUGBUILD + h->init = CURL_UINTHASHINIT; +#endif +} + +static struct uint_hash_entry *uint_hash_mk_entry(unsigned int id, void *value) +{ + struct uint_hash_entry *e; + + /* allocate the struct for the hash entry */ + e = malloc(sizeof(*e)); + if(e) { + e->id = id; + e->next = NULL; + e->value = value; + } + return e; +} + +static void uint_hash_entry_clear(struct uint_hash *h, + struct uint_hash_entry *e) +{ + DEBUGASSERT(h); + DEBUGASSERT(e); + if(e->value) { + if(h->dtor) + h->dtor(e->id, e->value); + e->value = NULL; + } +} + +static void uint_hash_entry_destroy(struct uint_hash *h, + struct uint_hash_entry *e) +{ + uint_hash_entry_clear(h, e); + free(e); +} + +static void uint_hash_entry_unlink(struct uint_hash *h, + struct uint_hash_entry **he_anchor, + struct uint_hash_entry *he) +{ + *he_anchor = he->next; + --h->size; +} + +static void uint_hash_elem_link(struct uint_hash *h, + struct uint_hash_entry **he_anchor, + struct uint_hash_entry *he) +{ + he->next = *he_anchor; + *he_anchor = he; + ++h->size; +} + +#define CURL_UINT_HASH_SLOT(h,id) h->table[uint_hash_hash(id, h->slots)] +#define CURL_UINT_HASH_SLOT_ADDR(h,id) &CURL_UINT_HASH_SLOT(h,id) + +bool Curl_uint_hash_set(struct uint_hash *h, unsigned int id, void *value) +{ + struct uint_hash_entry *he, **slot; + + DEBUGASSERT(h); + DEBUGASSERT(h->slots); + DEBUGASSERT(h->init == CURL_UINTHASHINIT); + if(!h->table) { + h->table = calloc(h->slots, sizeof(*he)); + if(!h->table) + return FALSE; /* OOM */ + } + + slot = CURL_UINT_HASH_SLOT_ADDR(h, id); + for(he = *slot; he; he = he->next) { + if(he->id == id) { + /* existing key entry, overwrite by clearing old pointer */ + uint_hash_entry_clear(h, he); + he->value = value; + return TRUE; + } + } + + he = uint_hash_mk_entry(id, value); + if(!he) + return FALSE; /* OOM */ + + uint_hash_elem_link(h, slot, he); + return TRUE; +} + +bool Curl_uint_hash_remove(struct uint_hash *h, unsigned int id) +{ + DEBUGASSERT(h); + DEBUGASSERT(h->slots); + DEBUGASSERT(h->init == CURL_UINTHASHINIT); + if(h->table) { + struct uint_hash_entry *he, **he_anchor; + + he_anchor = CURL_UINT_HASH_SLOT_ADDR(h, id); + while(*he_anchor) { + he = *he_anchor; + if(id == he->id) { + uint_hash_entry_unlink(h, he_anchor, he); + uint_hash_entry_destroy(h, he); + return TRUE; + } + he_anchor = &he->next; + } + } + return FALSE; +} + +void *Curl_uint_hash_get(struct uint_hash *h, unsigned int id) +{ + DEBUGASSERT(h); + DEBUGASSERT(h->init == CURL_UINTHASHINIT); + if(h->table) { + struct uint_hash_entry *he; + DEBUGASSERT(h->slots); + he = CURL_UINT_HASH_SLOT(h, id); + while(he) { + if(id == he->id) { + return he->value; + } + he = he->next; + } + } + return NULL; +} + +static void uint_hash_clear(struct uint_hash *h) +{ + if(h && h->table) { + struct uint_hash_entry *he, **he_anchor; + size_t i; + DEBUGASSERT(h->init == CURL_UINTHASHINIT); + for(i = 0; i < h->slots; ++i) { + he_anchor = &h->table[i]; + while(*he_anchor) { + he = *he_anchor; + uint_hash_entry_unlink(h, he_anchor, he); + uint_hash_entry_destroy(h, he); + } + } + } +} + +void Curl_uint_hash_clear(struct uint_hash *h) +{ + uint_hash_clear(h); +} + +void Curl_uint_hash_destroy(struct uint_hash *h) +{ + DEBUGASSERT(h->init == CURL_UINTHASHINIT); + if(h->table) { + uint_hash_clear(h); + Curl_safefree(h->table); + } + DEBUGASSERT(h->size == 0); + h->slots = 0; +} + +unsigned int Curl_uint_hash_count(struct uint_hash *h) +{ + DEBUGASSERT(h->init == CURL_UINTHASHINIT); + return h->size; +} + +void Curl_uint_hash_visit(struct uint_hash *h, + Curl_uint_hash_visit_cb *cb, + void *user_data) +{ + if(h && h->table && cb) { + struct uint_hash_entry *he; + size_t i; + DEBUGASSERT(h->init == CURL_UINTHASHINIT); + for(i = 0; i < h->slots; ++i) { + for(he = h->table[i]; he; he = he->next) { + if(!cb(he->id, he->value, user_data)) + return; + } + } + } +} diff --git a/Utilities/cmcurl/lib/uint-hash.h b/Utilities/cmcurl/lib/uint-hash.h new file mode 100644 index 0000000000..1b52dba4c4 --- /dev/null +++ b/Utilities/cmcurl/lib/uint-hash.h @@ -0,0 +1,68 @@ +#ifndef HEADER_CURL_UINT_HASH_H +#define HEADER_CURL_UINT_HASH_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 + +#include "llist.h" + +/* A version with unsigned int as key */ +typedef void Curl_uint_hash_dtor(unsigned int id, void *value); +struct uint_hash_entry; + +/* Hash for `unsigned int` as key */ +struct uint_hash { + struct uint_hash_entry **table; + Curl_uint_hash_dtor *dtor; + unsigned int slots; + unsigned int size; +#ifdef DEBUGBUILD + int init; +#endif +}; + + +void Curl_uint_hash_init(struct uint_hash *h, + unsigned int slots, + Curl_uint_hash_dtor *dtor); +void Curl_uint_hash_destroy(struct uint_hash *h); +void Curl_uint_hash_clear(struct uint_hash *h); + +bool Curl_uint_hash_set(struct uint_hash *h, unsigned int id, void *value); +bool Curl_uint_hash_remove(struct uint_hash *h, unsigned int id); +void *Curl_uint_hash_get(struct uint_hash *h, unsigned int id); +unsigned int Curl_uint_hash_count(struct uint_hash *h); + + +typedef bool Curl_uint_hash_visit_cb(unsigned int id, void *value, + void *user_data); + +void Curl_uint_hash_visit(struct uint_hash *h, + Curl_uint_hash_visit_cb *cb, + void *user_data); + +#endif /* HEADER_CURL_UINT_HASH_H */ diff --git a/Utilities/cmcurl/lib/uint-spbset.c b/Utilities/cmcurl/lib/uint-spbset.c new file mode 100644 index 0000000000..578b9bd07c --- /dev/null +++ b/Utilities/cmcurl/lib/uint-spbset.c @@ -0,0 +1,273 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "uint-bset.h" +#include "uint-spbset.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifdef DEBUGBUILD +#define CURL_UINT_SPBSET_MAGIC 0x70737362 +#endif + +void Curl_uint_spbset_init(struct uint_spbset *bset) +{ + memset(bset, 0, sizeof(*bset)); +#ifdef DEBUGBUILD + bset->init = CURL_UINT_SPBSET_MAGIC; +#endif +} + +void Curl_uint_spbset_destroy(struct uint_spbset *bset) +{ + DEBUGASSERT(bset->init == CURL_UINT_SPBSET_MAGIC); + Curl_uint_spbset_clear(bset); +} + +unsigned int Curl_uint_spbset_count(struct uint_spbset *bset) +{ + struct uint_spbset_chunk *chunk; + unsigned int i, n = 0; + + for(chunk = &bset->head; chunk; chunk = chunk->next) { + for(i = 0; i < CURL_UINT_SPBSET_CH_SLOTS; ++i) { + if(chunk->slots[i]) + n += CURL_POPCOUNT64(chunk->slots[i]); + } + } + return n; +} + +bool Curl_uint_spbset_empty(struct uint_spbset *bset) +{ + struct uint_spbset_chunk *chunk; + unsigned int i; + + for(chunk = &bset->head; chunk; chunk = chunk->next) { + for(i = 0; i < CURL_UINT_SPBSET_CH_SLOTS; ++i) { + if(chunk->slots[i]) + return FALSE; + } + } + return TRUE; +} + +void Curl_uint_spbset_clear(struct uint_spbset *bset) +{ + struct uint_spbset_chunk *next, *chunk; + + for(chunk = bset->head.next; chunk; chunk = next) { + next = chunk->next; + free(chunk); + } + memset(&bset->head, 0, sizeof(bset->head)); +} + + +static struct uint_spbset_chunk * +uint_spbset_get_chunk(struct uint_spbset *bset, unsigned int i, bool grow) +{ + struct uint_spbset_chunk *chunk, **panchor = NULL; + unsigned int i_offset = (i & ~CURL_UINT_SPBSET_CH_MASK); + + if(!bset) + return NULL; + + for(chunk = &bset->head; chunk; + panchor = &chunk->next, chunk = chunk->next) { + if(chunk->offset == i_offset) { + return chunk; + } + else if(chunk->offset > i_offset) { + /* need new chunk here */ + chunk = NULL; + break; + } + } + + if(!grow) + return NULL; + + /* need a new one */ + chunk = calloc(1, sizeof(*chunk)); + if(!chunk) + return NULL; + + if(panchor) { /* insert between panchor and *panchor */ + chunk->next = *panchor; + *panchor = chunk; + } + else { /* prepend to head, switching places */ + memcpy(chunk, &bset->head, sizeof(*chunk)); + memset(&bset->head, 0, sizeof(bset->head)); + bset->head.next = chunk; + } + chunk->offset = i_offset; + return chunk; +} + + +bool Curl_uint_spbset_add(struct uint_spbset *bset, unsigned int i) +{ + struct uint_spbset_chunk *chunk; + unsigned int i_chunk; + + chunk = uint_spbset_get_chunk(bset, i, TRUE); + if(!chunk) + return FALSE; + + DEBUGASSERT(i >= chunk->offset); + i_chunk = (i - chunk->offset); + DEBUGASSERT((i_chunk / 64) < CURL_UINT_SPBSET_CH_SLOTS); + chunk->slots[(i_chunk / 64)] |= ((curl_uint64_t)1 << (i_chunk % 64)); + return TRUE; +} + + +void Curl_uint_spbset_remove(struct uint_spbset *bset, unsigned int i) +{ + struct uint_spbset_chunk *chunk; + unsigned int i_chunk; + + chunk = uint_spbset_get_chunk(bset, i, FALSE); + if(chunk) { + DEBUGASSERT(i >= chunk->offset); + i_chunk = (i - chunk->offset); + DEBUGASSERT((i_chunk / 64) < CURL_UINT_SPBSET_CH_SLOTS); + chunk->slots[(i_chunk / 64)] &= ~((curl_uint64_t)1 << (i_chunk % 64)); + } +} + + +bool Curl_uint_spbset_contains(struct uint_spbset *bset, unsigned int i) +{ + struct uint_spbset_chunk *chunk; + unsigned int i_chunk; + + chunk = uint_spbset_get_chunk(bset, i, FALSE); + if(chunk) { + DEBUGASSERT(i >= chunk->offset); + i_chunk = (i - chunk->offset); + DEBUGASSERT((i_chunk / 64) < CURL_UINT_SPBSET_CH_SLOTS); + return (chunk->slots[i_chunk / 64] & + ((curl_uint64_t)1 << (i_chunk % 64))) != 0; + } + return FALSE; +} + +bool Curl_uint_spbset_first(struct uint_spbset *bset, unsigned int *pfirst) +{ + struct uint_spbset_chunk *chunk; + unsigned int i; + + for(chunk = &bset->head; chunk; chunk = chunk->next) { + for(i = 0; i < CURL_UINT_SPBSET_CH_SLOTS; ++i) { + if(chunk->slots[i]) { + *pfirst = chunk->offset + ((i * 64) + CURL_CTZ64(chunk->slots[i])); + return TRUE; + } + } + } + *pfirst = 0; /* give it a defined value even if it should not be used */ + return FALSE; +} + + +static bool uint_spbset_chunk_first(struct uint_spbset_chunk *chunk, + unsigned int *pfirst) +{ + unsigned int i; + for(i = 0; i < CURL_UINT_SPBSET_CH_SLOTS; ++i) { + if(chunk->slots[i]) { + *pfirst = chunk->offset + ((i * 64) + CURL_CTZ64(chunk->slots[i])); + return TRUE; + } + } + *pfirst = UINT_MAX; /* a value we cannot store */ + return FALSE; +} + + +static bool uint_spbset_chunk_next(struct uint_spbset_chunk *chunk, + unsigned int last, + unsigned int *pnext) +{ + if(chunk->offset <= last) { + curl_uint64_t x; + unsigned int i = ((last - chunk->offset) / 64); + if(i < CURL_UINT_SPBSET_CH_SLOTS) { + x = (chunk->slots[i] >> (last % 64)); + if(x) { + /* more bits set, next is `last` + trailing0s of the shifted slot */ + *pnext = last + CURL_CTZ64(x); + return TRUE; + } + /* no more bits set in the last slot, scan forward */ + for(i = i + 1; i < CURL_UINT_SPBSET_CH_SLOTS; ++i) { + if(chunk->slots[i]) { + *pnext = chunk->offset + ((i * 64) + CURL_CTZ64(chunk->slots[i])); + return TRUE; + } + } + } + } + *pnext = UINT_MAX; + return FALSE; +} + +bool Curl_uint_spbset_next(struct uint_spbset *bset, unsigned int last, + unsigned int *pnext) +{ + struct uint_spbset_chunk *chunk; + unsigned int last_offset; + + ++last; /* look for the next higher number */ + last_offset = (last & ~CURL_UINT_SPBSET_CH_MASK); + + for(chunk = &bset->head; chunk; chunk = chunk->next) { + if(chunk->offset >= last_offset) { + break; + } + } + + if(chunk && (chunk->offset == last_offset)) { + /* is there a number higher than last in this chunk? */ + if(uint_spbset_chunk_next(chunk, last, pnext)) + return TRUE; + /* not in this chunk */ + chunk = chunk->next; + } + /* look for the first in the "higher" chunks, if there are any. */ + while(chunk) { + if(uint_spbset_chunk_first(chunk, pnext)) + return TRUE; + chunk = chunk->next; + } + *pnext = UINT_MAX; + return FALSE; +} diff --git a/Utilities/cmcurl/lib/uint-spbset.h b/Utilities/cmcurl/lib/uint-spbset.h new file mode 100644 index 0000000000..571d56753c --- /dev/null +++ b/Utilities/cmcurl/lib/uint-spbset.h @@ -0,0 +1,99 @@ +#ifndef HEADER_CURL_UINT_SPBSET_H +#define HEADER_CURL_UINT_SPBSET_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 + +/* A "sparse" bitset for unsigned int values. + * It can hold any unsigned int value. + * + * Optimized for the case where only a small set of numbers need + * to be kept, especially when "close" together. Then storage space + * is most efficient, deteriorating when many number are far apart. + */ + +/* 4 slots = 256 bits, keep this a 2^n value. */ +#define CURL_UINT_SPBSET_CH_SLOTS 4 +#define CURL_UINT_SPBSET_CH_MASK ((CURL_UINT_SPBSET_CH_SLOTS * 64) - 1) + +/* store the uint value from offset to + * (offset + (CURL_UINT_SPBSET_CHUNK_SLOTS * 64) - 1 */ +struct uint_spbset_chunk { + struct uint_spbset_chunk *next; + curl_uint64_t slots[CURL_UINT_SPBSET_CH_SLOTS]; + unsigned int offset; +}; + +struct uint_spbset { + struct uint_spbset_chunk head; +#ifdef DEBUGBUILD + int init; +#endif +}; + +void Curl_uint_spbset_init(struct uint_spbset *bset); + +void Curl_uint_spbset_destroy(struct uint_spbset *bset); + +/* Get the cardinality of the bitset, e.g. numbers present in the set. */ +unsigned int Curl_uint_spbset_count(struct uint_spbset *bset); + +/* TRUE of bitset is empty */ +bool Curl_uint_spbset_empty(struct uint_spbset *bset); + +/* Clear the bitset, making it empty. */ +void Curl_uint_spbset_clear(struct uint_spbset *bset); + +/* Add the number `i` to the bitset. + * Numbers can be added more than once, without making a difference. + * Returns FALSE if allocations failed. */ +bool Curl_uint_spbset_add(struct uint_spbset *bset, unsigned int i); + +/* Remove the number `i` from the bitset. */ +void Curl_uint_spbset_remove(struct uint_spbset *bset, unsigned int i); + +/* Return TRUE if the bitset contains number `i`. */ +bool Curl_uint_spbset_contains(struct uint_spbset *bset, unsigned int i); + +/* Get the first number in the bitset, e.g. the smallest. + * Returns FALSE when the bitset is empty. */ +bool Curl_uint_spbset_first(struct uint_spbset *bset, unsigned int *pfirst); + +/* Get the next number in the bitset, following `last` in natural order. + * Put another way, this is the smallest number greater than `last` in + * the bitset. `last` does not have to be present in the set. + * + * Returns FALSE when no such number is in the set. + * + * This allows to iterate the set while being modified: + * - added numbers higher than 'last' will be picked up by the iteration. + * - added numbers lower than 'last' will not show up. + * - removed numbers lower or equal to 'last' will not show up. + * - removed numbers higher than 'last' will not be visited. */ +bool Curl_uint_spbset_next(struct uint_spbset *bset, unsigned int last, + unsigned int *pnext); + +#endif /* HEADER_CURL_UINT_SPBSET_H */ diff --git a/Utilities/cmcurl/lib/uint-table.c b/Utilities/cmcurl/lib/uint-table.c new file mode 100644 index 0000000000..d8de1b128a --- /dev/null +++ b/Utilities/cmcurl/lib/uint-table.c @@ -0,0 +1,214 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 "uint-table.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifdef DEBUGBUILD +#define CURL_UINT_TBL_MAGIC 0x62757473 +#endif + +void Curl_uint_tbl_init(struct uint_tbl *tbl, + Curl_uint_tbl_entry_dtor *entry_dtor) +{ + memset(tbl, 0, sizeof(*tbl)); + tbl->entry_dtor = entry_dtor; + tbl->last_key_added = UINT_MAX; +#ifdef DEBUGBUILD + tbl->init = CURL_UINT_TBL_MAGIC; +#endif +} + + +static void uint_tbl_clear_rows(struct uint_tbl *tbl, + unsigned int from, + unsigned int upto_excluding) +{ + unsigned int i, end; + + end = CURLMIN(upto_excluding, tbl->nrows); + for(i = from; i < end; ++i) { + if(tbl->rows[i]) { + if(tbl->entry_dtor) + tbl->entry_dtor(i, tbl->rows[i]); + tbl->rows[i] = NULL; + tbl->nentries--; + } + } +} + + +CURLcode Curl_uint_tbl_resize(struct uint_tbl *tbl, unsigned int nrows) +{ + /* we use `tbl->nrows + 1` during iteration, want that to work */ + DEBUGASSERT(tbl->init == CURL_UINT_TBL_MAGIC); + if(!nrows || (nrows == UINT_MAX)) + return CURLE_BAD_FUNCTION_ARGUMENT; + if(nrows != tbl->nrows) { + void **rows = calloc(nrows, sizeof(void *)); + if(!rows) + return CURLE_OUT_OF_MEMORY; + if(tbl->rows) { + memcpy(rows, tbl->rows, (CURLMIN(nrows, tbl->nrows) * sizeof(void *))); + if(nrows < tbl->nrows) + uint_tbl_clear_rows(tbl, nrows, tbl->nrows); + free(tbl->rows); + } + tbl->rows = rows; + tbl->nrows = nrows; + } + return CURLE_OK; +} + + +void Curl_uint_tbl_destroy(struct uint_tbl *tbl) +{ + DEBUGASSERT(tbl->init == CURL_UINT_TBL_MAGIC); + Curl_uint_tbl_clear(tbl); + free(tbl->rows); + memset(tbl, 0, sizeof(*tbl)); +} + + +void Curl_uint_tbl_clear(struct uint_tbl *tbl) +{ + DEBUGASSERT(tbl->init == CURL_UINT_TBL_MAGIC); + uint_tbl_clear_rows(tbl, 0, tbl->nrows); + DEBUGASSERT(!tbl->nentries); + tbl->last_key_added = UINT_MAX; +} + + +unsigned int Curl_uint_tbl_capacity(struct uint_tbl *tbl) +{ + return tbl->nrows; +} + + +unsigned int Curl_uint_tbl_count(struct uint_tbl *tbl) +{ + return tbl->nentries; +} + + +void *Curl_uint_tbl_get(struct uint_tbl *tbl, unsigned int key) +{ + return (key < tbl->nrows) ? tbl->rows[key] : NULL; +} + + +bool Curl_uint_tbl_add(struct uint_tbl *tbl, void *entry, unsigned int *pkey) +{ + unsigned int key, start_pos; + + DEBUGASSERT(tbl->init == CURL_UINT_TBL_MAGIC); + if(!entry || !pkey) + return FALSE; + *pkey = UINT_MAX; /* always invalid */ + if(tbl->nentries == tbl->nrows) /* full */ + return FALSE; + + start_pos = CURLMIN(tbl->last_key_added, tbl->nrows) + 1; + for(key = start_pos; key < tbl->nrows; ++key) { + if(!tbl->rows[key]) { + tbl->rows[key] = entry; + tbl->nentries++; + tbl->last_key_added = key; + *pkey = key; + return TRUE; + } + } + /* no free entry at or above tbl->maybe_next_key, wrap around */ + for(key = 0; key < start_pos; ++key) { + if(!tbl->rows[key]) { + tbl->rows[key] = entry; + tbl->nentries++; + tbl->last_key_added = key; + *pkey = key; + return TRUE; + } + } + /* Did not find any free row? Should not happen */ + DEBUGASSERT(0); + return FALSE; +} + + +void Curl_uint_tbl_remove(struct uint_tbl *tbl, unsigned int key) +{ + uint_tbl_clear_rows(tbl, key, key + 1); +} + + +bool Curl_uint_tbl_contains(struct uint_tbl *tbl, unsigned int key) +{ + return (key < tbl->nrows) ? !!tbl->rows[key] : FALSE; +} + + +static bool uint_tbl_next_at(struct uint_tbl *tbl, unsigned int key, + unsigned int *pkey, void **pentry) +{ + for(; key < tbl->nrows; ++key) { + if(tbl->rows[key]) { + *pkey = key; + *pentry = tbl->rows[key]; + return TRUE; + } + } + *pkey = UINT_MAX; /* always invalid */ + *pentry = NULL; + return FALSE; +} + +bool Curl_uint_tbl_first(struct uint_tbl *tbl, + unsigned int *pkey, void **pentry) +{ + if(!pkey || !pentry) + return FALSE; + if(tbl->nentries && uint_tbl_next_at(tbl, 0, pkey, pentry)) + return TRUE; + DEBUGASSERT(!tbl->nentries); + *pkey = UINT_MAX; /* always invalid */ + *pentry = NULL; + return FALSE; +} + + +bool Curl_uint_tbl_next(struct uint_tbl *tbl, unsigned int last_key, + unsigned int *pkey, void **pentry) +{ + if(!pkey || !pentry) + return FALSE; + if(uint_tbl_next_at(tbl, last_key + 1, pkey, pentry)) + return TRUE; + *pkey = UINT_MAX; /* always invalid */ + *pentry = NULL; + return FALSE; +} diff --git a/Utilities/cmcurl/lib/uint-table.h b/Utilities/cmcurl/lib/uint-table.h new file mode 100644 index 0000000000..2c05b1de1a --- /dev/null +++ b/Utilities/cmcurl/lib/uint-table.h @@ -0,0 +1,101 @@ +#ifndef HEADER_CURL_UINT_TABLE_H +#define HEADER_CURL_UINT_TABLE_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 + +/* Destructor for a single table entry */ +typedef void Curl_uint_tbl_entry_dtor(unsigned int key, void *entry); + +struct uint_tbl { + void **rows; /* array of void* holding entries */ + Curl_uint_tbl_entry_dtor *entry_dtor; + unsigned int nrows; /* length of `rows` array */ + unsigned int nentries; /* entries in table */ + unsigned int last_key_added; /* UINT_MAX or last key added */ +#ifdef DEBUGBUILD + int init; +#endif +}; + +/* Initialize the table with 0 capacity. + * The optional `entry_dtor` is called when a table entry is removed, + * Passing NULL means no action is taken on removal. */ +void Curl_uint_tbl_init(struct uint_tbl *tbl, + Curl_uint_tbl_entry_dtor *entry_dtor); + +/* Resize the table to change capacity `nmax`. When `nmax` is reduced, + * all present entries with key equal or larger to `nmax` are removed. */ +CURLcode Curl_uint_tbl_resize(struct uint_tbl *tbl, unsigned int nmax); + +/* Destroy the table, freeing all entries. */ +void Curl_uint_tbl_destroy(struct uint_tbl *tbl); + +/* Get the table capacity. */ +unsigned int Curl_uint_tbl_capacity(struct uint_tbl *tbl); + +/* Get the number of entries in the table. */ +unsigned int Curl_uint_tbl_count(struct uint_tbl *tbl); + +/* Clear the table, making it empty. */ +void Curl_uint_tbl_clear(struct uint_tbl *tbl); + +/* Get the entry for key or NULL if not present */ +void *Curl_uint_tbl_get(struct uint_tbl *tbl, unsigned int key); + +/* Add a new entry to the table and assign it a free key. + * Returns FALSE if the table is full. + * + * Keys are assigned in a round-robin manner. + * No matter the capacity, UINT_MAX is never assigned. */ +bool Curl_uint_tbl_add(struct uint_tbl *tbl, void *entry, unsigned int *pkey); + +/* Remove the entry with `key`. */ +void Curl_uint_tbl_remove(struct uint_tbl *tbl, unsigned int key); + +/* Return TRUE if the table contains an tryn with that keys. */ +bool Curl_uint_tbl_contains(struct uint_tbl *tbl, unsigned int key); + +/* Get the first entry in the table (with the smallest `key`). + * Returns FALSE if the table is empty. */ +bool Curl_uint_tbl_first(struct uint_tbl *tbl, + unsigned int *pkey, void **pentry); + +/* Get the next key in the table, following `last_key` in natural order. + * Put another way, this is the smallest key greater than `last_key` in + * the table. `last_key` does not have to be present in the table. + * + * Returns FALSE when no such entry is in the table. + * + * This allows to iterate the table while being modified: + * - added keys higher than 'last_key' will be picked up by the iteration. + * - added keys lower than 'last_key' will not show up. + * - removed keys lower or equal to 'last_key' will not show up. + * - removed keys higher than 'last_key' will not be visited. */ +bool Curl_uint_tbl_next(struct uint_tbl *tbl, unsigned int last_key, + unsigned int *pkey, void **pentry); + +#endif /* HEADER_CURL_UINT_TABLE_H */ diff --git a/Utilities/cmcurl/lib/url.c b/Utilities/cmcurl/lib/url.c index 516ee08a1a..10e37ec67f 100644 --- a/Utilities/cmcurl/lib/url.c +++ b/Utilities/cmcurl/lib/url.c @@ -75,7 +75,6 @@ #include "strcase.h" #include "strerror.h" #include "escape.h" -#include "strtok.h" #include "share.h" #include "content_encoding.h" #include "http_digest.h" @@ -84,13 +83,15 @@ #include "multiif.h" #include "easyif.h" #include "speedcheck.h" -#include "warnless.h" +#include "curlx/warnless.h" #include "getinfo.h" +#include "pop3.h" #include "urlapi-int.h" #include "system_win32.h" #include "hsts.h" #include "noproxy.h" #include "cfilters.h" +#include "curl_krb5.h" #include "idn.h" /* And now for the protocols */ @@ -117,9 +118,9 @@ #include "strdup.h" #include "setopt.h" #include "altsvc.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "headers.h" - +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -271,7 +272,7 @@ CURLcode Curl_close(struct Curl_easy **datap) data->state.referer = NULL; up_free(data); - Curl_dyn_free(&data->state.headerb); + curlx_dyn_free(&data->state.headerb); Curl_flush_cookies(data, TRUE); #ifndef CURL_DISABLE_ALTSVC Curl_altsvc_save(data, data->asi, data->set.str[STRING_ALTSVC]); @@ -286,12 +287,14 @@ CURLcode Curl_close(struct Curl_easy **datap) #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH) Curl_http_auth_cleanup_digest(data); #endif + Curl_safefree(data->state.most_recent_ftp_entrypath); Curl_safefree(data->info.contenttype); Curl_safefree(data->info.wouldredirect); - /* this destroys the channel and we cannot use it anymore after this */ - Curl_resolver_cancel(data); - Curl_resolver_cleanup(data->state.async.resolver); + /* release any resolve information this transfer kept */ + Curl_async_destroy(data); + Curl_resolv_unlink(data, &data->state.dns[0]); /* done with this */ + Curl_resolv_unlink(data, &data->state.dns[1]); data_priority_cleanup(data); @@ -302,6 +305,7 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); } + Curl_hash_destroy(&data->meta_hash); #ifndef CURL_DISABLE_PROXY Curl_safefree(data->state.aptr.proxyuserpwd); #endif @@ -391,9 +395,6 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) set->socks5auth = CURLAUTH_BASIC | CURLAUTH_GSSAPI; #endif - /* make libcurl quiet by default: */ - set->hide_progress = TRUE; /* CURLOPT_NOPROGRESS changes these */ - Curl_mime_initpart(&set->mimepost); Curl_ssl_easy_config_init(data); @@ -428,7 +429,7 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) */ if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL && Curl_ssl_backend() != CURLSSLBACKEND_SECURETRANSPORT) { -#if defined(CURL_CA_BUNDLE) +#ifdef CURL_CA_BUNDLE result = Curl_setstropt(&set->str[STRING_SSL_CAFILE], CURL_CA_BUNDLE); if(result) return result; @@ -439,7 +440,7 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) return result; #endif #endif -#if defined(CURL_CA_PATH) +#ifdef CURL_CA_PATH result = Curl_setstropt(&set->str[STRING_SSL_CAPATH], CURL_CA_PATH); if(result) return result; @@ -474,19 +475,31 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) set->maxage_conn = 118; set->maxlifetime_conn = 0; set->http09_allowed = FALSE; -#ifdef USE_HTTP2 - set->httpwant = CURL_HTTP_VERSION_2TLS -#else - set->httpwant = CURL_HTTP_VERSION_1_1 -#endif + set->httpwant = CURL_HTTP_VERSION_NONE ; #if defined(USE_HTTP2) || defined(USE_HTTP3) memset(&set->priority, 0, sizeof(set->priority)); #endif set->quick_exit = 0L; +#ifndef CURL_DISABLE_WEBSOCKETS + set->ws_raw_mode = FALSE; + set->ws_no_auto_pong = FALSE; +#endif + return result; } +/* easy->meta_hash destructor. Should never be called as elements + * MUST be added with their own destructor */ +static void easy_meta_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); +} + /** * Curl_open() * @@ -509,45 +522,34 @@ CURLcode Curl_open(struct Curl_easy **curl) } data->magic = CURLEASY_MAGIC_NUMBER; + /* most recent connection is not yet defined */ + data->state.lastconnect_id = -1; + data->state.recent_conn_id = -1; + /* and not assigned an id yet */ + data->id = -1; + data->mid = UINT_MAX; + data->master_mid = UINT_MAX; + data->progress.hide = TRUE; + data->state.current_speed = -1; /* init to negative == impossible */ + Curl_hash_init(&data->meta_hash, 23, + Curl_hash_str, curlx_str_key_compare, easy_meta_freeentry); + curlx_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER); 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; - } + Curl_initinfo(data); +#ifndef CURL_DISABLE_HTTP + Curl_llist_init(&data->state.httphdrs, NULL); +#endif + Curl_netrc_init(&data->state.netrc); result = Curl_init_userdefined(data); - if(!result) { - Curl_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER); - Curl_initinfo(data); - - /* most recent connection is not yet defined */ - data->state.lastconnect_id = -1; - 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); + curlx_dyn_free(&data->state.headerb); Curl_freeset(data); Curl_req_free(&data->req, data); + Curl_hash_destroy(&data->meta_hash); + data->magic = 0; free(data); data = NULL; } @@ -562,6 +564,10 @@ void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn) DEBUGASSERT(conn); + if(conn->handler && conn->handler->disconnect && + !conn->bits.shutdown_handler) + conn->handler->disconnect(data, conn, TRUE); + for(i = 0; i < CURL_ARRAYSIZE(conn->cfilter); ++i) { Curl_conn_cf_discard_all(data, conn, (int)i); } @@ -578,6 +584,7 @@ void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn) Curl_safefree(conn->http_proxy.host.rawalloc); /* http proxy name buffer */ Curl_safefree(conn->socks_proxy.host.rawalloc); /* socks proxy name buffer */ #endif + Curl_sec_conn_destroy(conn); Curl_safefree(conn->user); Curl_safefree(conn->passwd); Curl_safefree(conn->sasl_authzid); @@ -594,56 +601,12 @@ void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn) Curl_safefree(conn->unix_domain_socket); #endif Curl_safefree(conn->destination); + Curl_uint_spbset_destroy(&conn->xfers_attached); + Curl_hash_destroy(&conn->meta_hash); 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 pool or - * killing of a dead old connection. - * - * A connection needs an easy handle when closing down. We support this passed - * in separately since the connection to get closed here is often already - * disassociated from an easy handle. - * - * This function MUST NOT reset state in the Curl_easy struct if that - * is not strictly bound to the life-time of *this* particular 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 pool */ - DEBUGASSERT(!conn->bits.in_cpool); - - /* there must be an associated transfer */ - DEBUGASSERT(data); - - /* the transfer must be detached from the connection */ - DEBUGASSERT(!data->conn); - - DEBUGF(infof(data, "Curl_disconnect(conn #%" FMT_OFF_T ", aborted=%d)", - conn->connection_id, aborted)); - - 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->connect_only) - /* treat the connection as aborted in CONNECT_ONLY situations */ - aborted = TRUE; - - return aborted; -} - /* * xfer_may_multiplex() * @@ -653,15 +616,20 @@ bool Curl_on_disconnect(struct Curl_easy *data, static bool xfer_may_multiplex(const struct Curl_easy *data, const struct connectdata *conn) { +#ifndef CURL_DISABLE_HTTP /* 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(data->multi) && - (data->state.httpwant >= CURL_HTTP_VERSION_2)) + (data->state.http_neg.allowed & (CURL_HTTP_V2x|CURL_HTTP_V3x))) /* allows HTTP/2 or newer */ return TRUE; } +#else + (void)data; + (void)conn; +#endif return FALSE; } @@ -712,7 +680,7 @@ static bool conn_maxage(struct Curl_easy *data, { timediff_t idletime, lifetime; - idletime = Curl_timediff(now, conn->lastused); + idletime = curlx_timediff(now, conn->lastused); idletime /= 1000; /* integer seconds is fine */ if(idletime > data->set.maxage_conn) { @@ -721,7 +689,7 @@ static bool conn_maxage(struct Curl_easy *data, return TRUE; } - lifetime = Curl_timediff(now, conn->created); + lifetime = curlx_timediff(now, conn->created); lifetime /= 1000; /* integer seconds is fine */ if(data->set.maxlifetime_conn && lifetime > data->set.maxlifetime_conn) { @@ -749,7 +717,7 @@ bool Curl_conn_seems_dead(struct connectdata *conn, bool dead; struct curltime now; if(!pnow) { - now = Curl_now(); + now = curlx_now(); pnow = &now; } @@ -807,7 +775,7 @@ CURLcode Curl_conn_upkeep(struct Curl_easy *data, struct curltime *now) { CURLcode result = CURLE_OK; - if(Curl_timediff(*now, conn->keepalive) <= data->set.upkeep_interval_ms) + if(curlx_timediff(*now, conn->keepalive) <= data->set.upkeep_interval_ms) return result; /* briefly attach for action */ @@ -833,11 +801,13 @@ CURLcode Curl_conn_upkeep(struct Curl_easy *data, static bool ssh_config_matches(struct connectdata *one, struct connectdata *two) { - return Curl_safecmp(one->proto.sshc.rsa, two->proto.sshc.rsa) && - Curl_safecmp(one->proto.sshc.rsa_pub, two->proto.sshc.rsa_pub); + struct ssh_conn *sshc1, *sshc2; + + sshc1 = Curl_conn_meta_get(one, CURL_META_SSH_CONN); + sshc2 = Curl_conn_meta_get(two, CURL_META_SSH_CONN); + return (sshc1 && sshc2 && Curl_safecmp(sshc1->rsa, sshc2->rsa) && + Curl_safecmp(sshc1->rsa_pub, sshc2->rsa_pub)); } -#else -#define ssh_config_matches(x,y) FALSE #endif struct url_conn_match { @@ -855,25 +825,19 @@ struct url_conn_match { BIT(seen_multiplex_conn); }; -static bool url_match_conn(struct connectdata *conn, void *userdata) +static bool url_match_connect_config(struct connectdata *conn, + struct url_conn_match *m) { - struct url_conn_match *match = userdata; - struct Curl_easy *data = match->data; - struct connectdata *needle = match->needle; - - /* Check if `conn` can be used for transfer `data` */ - + /* connect-only or to-be-closed connections will not be reused */ 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 != conn->ip_version) { - /* skip because the connection is not via the requested IP version */ + /* ip_version must match */ + if(m->data->set.ipver != CURL_IPRESOLVE_WHATEVER + && m->data->set.ipver != conn->ip_version) return FALSE; - } - if(needle->localdev || needle->localport) { + if(m->needle->localdev || m->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. @@ -885,105 +849,155 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) 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)))) + if((conn->localport != m->needle->localport) || + (conn->localportrange != m->needle->localportrange) || + (m->needle->localdev && + (!conn->localdev || strcmp(conn->localdev, m->needle->localdev)))) return FALSE; } - if(needle->bits.conn_to_host != conn->bits.conn_to_host) + if(m->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(needle->bits.conn_to_port != conn->bits.conn_to_port) + if(m->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_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(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(m->needle->unix_domain_socket) { if(!conn->unix_domain_socket) return FALSE; - if(strcmp(needle->unix_domain_socket, conn->unix_domain_socket)) + if(strcmp(m->needle->unix_domain_socket, conn->unix_domain_socket)) return FALSE; - if(needle->bits.abstract_unix_socket != conn->bits.abstract_unix_socket) + if(m->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) != - !Curl_conn_is_ssl(conn, FIRSTSOCKET)) && - !(get_protocol_family(conn->handler) == needle->handler->protocol && - conn->bits.tls_upgraded)) - /* Deny `conn` if it is not fit for `needle`'s SSL needs, - * UNLESS `conn` is the same protocol family and was upgraded to SSL. */ + return TRUE; +} + +static bool url_match_fully_connected(struct connectdata *conn, + struct url_conn_match *m) +{ + 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(m->may_multiplex) { + m->seen_pending_conn = TRUE; + /* Do not pick a connection that has not connected yet */ + infof(m->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; + } + return TRUE; +} + +static bool url_match_multi(struct connectdata *conn, + struct url_conn_match *m) +{ + if(CONN_INUSE(conn)) { + DEBUGASSERT(conn->attached_multi); + if(conn->attached_multi != m->data->multi) return FALSE; + } + return TRUE; +} + +static bool url_match_multiplex_needs(struct connectdata *conn, + struct url_conn_match *m) +{ + if(CONN_INUSE(conn)) { + if(!conn->bits.multiplex) { + /* conn busy and conn cannot take more transfers */ + m->seen_single_use_conn = TRUE; + return FALSE; + } + m->seen_multiplex_conn = TRUE; + if(!m->may_multiplex || !url_match_multi(conn, m)) + /* conn busy and transfer cannot be multiplexed */ + return FALSE; + } + return TRUE; +} + +static bool url_match_multiplex_limits(struct connectdata *conn, + struct url_conn_match *m) +{ + if(CONN_INUSE(conn) && m->may_multiplex) { + DEBUGASSERT(conn->bits.multiplex); + /* If multiplexed, make sure we do not go over concurrency limit */ + if(CONN_ATTACHED(conn) >= + Curl_multi_max_concurrent_streams(m->data->multi)) { + infof(m->data, "client side MAX_CONCURRENT_STREAMS reached" + ", skip (%u)", CONN_ATTACHED(conn)); + return FALSE; + } + if(CONN_ATTACHED(conn) >= + Curl_conn_get_max_concurrent(m->data, conn, FIRSTSOCKET)) { + infof(m->data, "MAX_CONCURRENT_STREAMS reached, skip (%u)", + CONN_ATTACHED(conn)); + return FALSE; + } + /* When not multiplexed, we have a match here! */ + infof(m->data, "Multiplexed connection found"); + } + return TRUE; +} + +static bool url_match_ssl_use(struct connectdata *conn, + struct url_conn_match *m) +{ + if(m->needle->handler->flags&PROTOPT_SSL) { + /* We are looking for SSL, if `conn` does not do it, not a match. */ + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) + return FALSE; + } + else if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + /* We are not *requiring* SSL, however `conn` has it. If the + * protocol *family* is not the same, not a match. */ + if(get_protocol_family(conn->handler) != m->needle->handler->protocol) + return FALSE; + } + return TRUE; +} #ifndef CURL_DISABLE_PROXY - if(needle->bits.httpproxy != conn->bits.httpproxy || - needle->bits.socksproxy != conn->bits.socksproxy) +static bool url_match_proxy_use(struct connectdata *conn, + struct url_conn_match *m) +{ + if(m->needle->bits.httpproxy != conn->bits.httpproxy || + m->needle->bits.socksproxy != conn->bits.socksproxy) return FALSE; - if(needle->bits.socksproxy && - !socks_proxy_info_matches(&needle->socks_proxy, + if(m->needle->bits.socksproxy && + !socks_proxy_info_matches(&m->needle->socks_proxy, &conn->socks_proxy)) return FALSE; - if(needle->bits.httpproxy) { - if(needle->bits.tunnel_proxy != conn->bits.tunnel_proxy) + if(m->needle->bits.httpproxy) { + if(m->needle->bits.tunnel_proxy != conn->bits.tunnel_proxy) return FALSE; - if(!proxy_info_matches(&needle->http_proxy, &conn->http_proxy)) + if(!proxy_info_matches(&m->needle->http_proxy, &conn->http_proxy)) return FALSE; - if(IS_HTTPS_PROXY(needle->http_proxy.proxytype)) { + if(IS_HTTPS_PROXY(m->needle->http_proxy.proxytype)) { /* https proxies come in different types, http/1.1, h2, ... */ - if(needle->http_proxy.proxytype != conn->http_proxy.proxytype) + if(m->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, + if(!Curl_ssl_conn_config_match(m->data, conn, TRUE)) { + DEBUGF(infof(m->data, "Connection #%" FMT_OFF_T " has different SSL proxy parameters, cannot reuse", conn->connection_id)); @@ -993,132 +1007,187 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) * further below */ } } + return TRUE; +} +#else +#define url_match_proxy_use(c,m) ((void)c, (void)m, TRUE) #endif - if(match->may_multiplex && - (data->state.httpwant == CURL_HTTP_VERSION_2_0) && - (needle->handler->protocol & CURLPROTO_HTTP) && +#ifndef CURL_DISABLE_HTTP +static bool url_match_http_multiplex(struct connectdata *conn, + struct url_conn_match *m) +{ + if(m->may_multiplex && + (m->data->state.http_neg.allowed & (CURL_HTTP_V2x|CURL_HTTP_V3x)) && + (m->needle->handler->protocol & CURLPROTO_HTTP) && !conn->httpversion_seen) { - if(data->set.pipewait) { - infof(data, "Server upgrade does not support multiplex yet, wait"); - match->found = NULL; - match->wait_pipe = TRUE; + if(m->data->set.pipewait) { + infof(m->data, "Server upgrade does not support multiplex yet, wait"); + m->found = NULL; + m->wait_pipe = TRUE; return TRUE; /* stop searching, we want to wait */ } - infof(data, "Server upgrade cannot be used"); + infof(m->data, "Server upgrade cannot be used"); return FALSE; } + return TRUE; +} - 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; +static bool url_match_http_version(struct connectdata *conn, + struct url_conn_match *m) +{ + /* If looking for HTTP and the HTTP versions allowed do not include + * the HTTP version of conn, continue looking. */ + if((m->needle->handler->protocol & PROTO_FAMILY_HTTP)) { + switch(Curl_conn_http_version(m->data, conn)) { + case 30: + if(!(m->data->state.http_neg.allowed & CURL_HTTP_V3x)) { + DEBUGF(infof(m->data, "not reusing conn #%" CURL_FORMAT_CURL_OFF_T + ", we do not want h3", conn->connection_id)); + return FALSE; + } + break; + case 20: + if(!(m->data->state.http_neg.allowed & CURL_HTTP_V2x)) { + DEBUGF(infof(m->data, "not reusing conn #%" CURL_FORMAT_CURL_OFF_T + ", we do not want h2", conn->connection_id)); + return FALSE; + } + break; + default: + if(!(m->data->state.http_neg.allowed & CURL_HTTP_V1x)) { + DEBUGF(infof(m->data, "not reusing conn #%" CURL_FORMAT_CURL_OFF_T + ", we do not want h1", conn->connection_id)); + return FALSE; + } + break; } } - -#ifdef HAVE_GSSAPI - /* 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; + return TRUE; +} +#else +#define url_match_http_multiplex(c,m) ((void)c, (void)m, TRUE) +#define url_match_http_version(c,m) ((void)c, (void)m, TRUE) #endif - /* 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)) { - unsigned char httpversion = Curl_conn_http_version(data); - if((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, httpversion)); - } - if((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, httpversion)); - return FALSE; - } - } +static bool url_match_proto_config(struct connectdata *conn, + struct url_conn_match *m) +{ + if(!url_match_http_version(conn, m)) + return FALSE; + #ifdef USE_SSH - else if(get_protocol_family(needle->handler) & PROTO_FAMILY_SSH) { - if(!ssh_config_matches(needle, conn)) + if(get_protocol_family(m->needle->handler) & PROTO_FAMILY_SSH) { + if(!ssh_config_matches(m->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, - 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)) + else if(get_protocol_family(m->needle->handler) & PROTO_FAMILY_FTP) { + if(!ftp_conns_match(m->needle, conn)) return FALSE; } #endif + return TRUE; +} - /* 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 - ) { - /* 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)); +static bool url_match_auth(struct connectdata *conn, + struct url_conn_match *m) +{ + if(!(m->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(m->needle->user, conn->user) || + Curl_timestrcmp(m->needle->passwd, conn->passwd) || + Curl_timestrcmp(m->needle->sasl_authzid, conn->sasl_authzid) || + Curl_timestrcmp(m->needle->oauth_bearer, conn->oauth_bearer)) { + /* one of them was different */ return FALSE; } } +#ifdef HAVE_GSSAPI + /* GSS delegation differences do not actually affect every connection + and auth method, but this check takes precaution before efficiency */ + if(m->needle->gssapi_delegation != conn->gssapi_delegation) + return FALSE; +#endif -#if defined(USE_NTLM) + return TRUE; +} + +static bool url_match_destination(struct connectdata *conn, + struct url_conn_match *m) +{ + /* Additional match requirements if talking TLS OR + * not talking to an HTTP proxy OR using a tunnel through a proxy */ + if((m->needle->handler->flags&PROTOPT_SSL) +#ifndef CURL_DISABLE_PROXY + || !m->needle->bits.httpproxy || m->needle->bits.tunnel_proxy +#endif + ) { + if(!strcasecompare(m->needle->handler->scheme, conn->handler->scheme)) { + /* `needle` and `conn` do not have the same scheme... */ + if(get_protocol_family(conn->handler) != m->needle->handler->protocol) { + /* and `conn`s protocol family is not the protocol `needle` wants. + * IMAPS would work for IMAP, but no vice versa. */ + return FALSE; + } + /* We are in an IMAPS vs IMAP like case. We expect `conn` to have SSL */ + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + DEBUGF(infof(m->data, + "Connection #%" FMT_OFF_T " has compatible protocol family, " + "but no SSL, no match", conn->connection_id)); + return FALSE; + } + } + + /* If needle has "conn_to_*" set, conn must match this */ + if((m->needle->bits.conn_to_host && !strcasecompare( + m->needle->conn_to_host.name, conn->conn_to_host.name)) || + (m->needle->bits.conn_to_port && + m->needle->conn_to_port != conn->conn_to_port)) + return FALSE; + + /* hostname and port must match */ + if(!strcasecompare(m->needle->host.name, conn->host.name) || + m->needle->remote_port != conn->remote_port) + return FALSE; + } + return TRUE; +} + +static bool url_match_ssl_config(struct connectdata *conn, + struct url_conn_match *m) +{ + /* If talking TLS, conn needs to use the same SSL options. */ + if((m->needle->handler->flags & PROTOPT_SSL) && + !Curl_ssl_conn_config_match(m->data, conn, FALSE)) { + DEBUGF(infof(m->data, + "Connection #%" FMT_OFF_T + " has different SSL parameters, cannot reuse", + conn->connection_id)); + return FALSE; + } + return TRUE; +} + +#ifdef USE_NTLM +static bool url_match_auth_ntlm(struct connectdata *conn, + struct url_conn_match *m) +{ /* 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)) { + if(m->want_ntlm_http) { + if(Curl_timestrcmp(m->needle->user, conn->user) || + Curl_timestrcmp(m->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; + m->found = conn; return FALSE; } } @@ -1129,15 +1198,15 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) #ifndef CURL_DISABLE_PROXY /* Same for Proxy NTLM authentication */ - if(match->want_proxy_ntlm_http) { + if(m->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, + if(Curl_timestrcmp(m->needle->http_proxy.user, conn->http_proxy.user) || - Curl_timestrcmp(needle->http_proxy.passwd, + Curl_timestrcmp(m->needle->http_proxy.passwd, conn->http_proxy.passwd)) return FALSE; } @@ -1146,53 +1215,83 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) return FALSE; } #endif - if(match->want_ntlm_http || match->want_proxy_ntlm_http) { + if(m->want_ntlm_http || m->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; + m->found = conn; - if((match->want_ntlm_http && + if((m->want_ntlm_http && (conn->http_ntlm_state != NTLMSTATE_NONE)) || - (match->want_proxy_ntlm_http && + (m->want_proxy_ntlm_http && (conn->proxy_ntlm_state != NTLMSTATE_NONE))) { /* We must use this connection, no other */ - match->force_reuse = TRUE; + m->force_reuse = TRUE; return TRUE; } /* Continue look up for a better connection */ return FALSE; } + return TRUE; +} +#else +#define url_match_auth_ntlm(c,m) ((void)c, (void)m, TRUE) #endif - 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); +static bool url_match_conn(struct connectdata *conn, void *userdata) +{ + struct url_conn_match *m = userdata; + /* Check if `conn` can be used for transfer `m->data` */ + + /* general connect config setting match? */ + if(!url_match_connect_config(conn, m)) + return FALSE; + + if(!url_match_destination(conn, m)) + return FALSE; + + if(!url_match_fully_connected(conn, m)) + return FALSE; + + if(!url_match_multiplex_needs(conn, m)) + return FALSE; + + if(!url_match_ssl_use(conn, m)) + return FALSE; + if(!url_match_proxy_use(conn, m)) + return FALSE; + if(!url_match_ssl_config(conn, m)) + return FALSE; + + if(!url_match_http_multiplex(conn, m)) + return FALSE; + else if(m->wait_pipe) + /* we decided to wait on PIPELINING */ + return TRUE; + + if(!url_match_auth(conn, m)) + return FALSE; + + if(!url_match_proto_config(conn, m)) + return FALSE; + + if(!url_match_auth_ntlm(conn, m)) + return FALSE; + else if(m->force_reuse) + return TRUE; + + if(!url_match_multiplex_limits(conn, m)) + return FALSE; + + if(!CONN_INUSE(conn) && Curl_conn_seems_dead(conn, m->data, NULL)) { + /* remove and disconnect. */ + Curl_conn_terminate(m->data, conn, FALSE); return FALSE; } - /* We have found a connection. Let's stop searching. */ - match->found = conn; + /* conn matches our needs. */ + m->found = conn; return TRUE; } @@ -1260,7 +1359,7 @@ ConnectionExists(struct Curl_easy *data, /* 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, + result = Curl_cpool_find(data, needle->destination, url_match_conn, url_match_result, &match); /* wait_pipe is TRUE if we encounter a bundle that is undecided. There @@ -1285,7 +1384,7 @@ void Curl_verboseconnect(struct Curl_easy *data, infof(data, "Connected to %s (%s) port %u", CURL_CONN_HOST_DISPNAME(conn), conn->primary.remote_ip, conn->primary.remote_port); -#if !defined(CURL_DISABLE_HTTP) +#ifndef CURL_DISABLE_HTTP if(conn->handler->protocol & PROTO_FAMILY_HTTP) { switch(conn->alpn) { case CURL_HTTP_VERSION_3: @@ -1328,7 +1427,7 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) connclose(conn, "Default to force-close"); /* Store creation time to help future close decision making */ - conn->created = Curl_now(); + conn->created = curlx_now(); /* Store current time to give a baseline to keepalive connection times. */ conn->keepalive = conn->created; @@ -1364,8 +1463,8 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) conn->connect_only = data->set.connect_only; conn->transport = TRNSPRT_TCP; /* most of them are TCP streams */ - /* Initialize the easy handle list */ - Curl_llist_init(&conn->easyq, NULL); + /* Initialize the attached xfers bitset */ + Curl_uint_spbset_init(&conn->xfers_attached); #ifdef HAVE_GSSAPI conn->data_prot = PROT_CLEAR; @@ -1440,7 +1539,7 @@ const struct Curl_handler *Curl_getn_scheme_handler(const char *scheme, #else NULL, #endif -#if defined(USE_SSH) +#ifdef USE_SSH &Curl_handler_sftp, #else NULL, @@ -1682,12 +1781,12 @@ static void zonefrom_url(CURLU *uh, struct Curl_easy *data, #endif if(!uc && zoneid) { - char *endp; - unsigned long scope = strtoul(zoneid, &endp, 10); - if(!*endp && (scope < UINT_MAX)) + const char *p = zoneid; + curl_off_t scope; + if(!curlx_str_number(&p, &scope, UINT_MAX)) /* A plain number, use it directly as a scope id. */ conn->scope_id = (unsigned int)scope; -#if defined(HAVE_IF_NAMETOINDEX) +#ifdef HAVE_IF_NAMETOINDEX else { #elif defined(_WIN32) else if(Curl_if_nametoindex) { @@ -1696,7 +1795,7 @@ static void zonefrom_url(CURLU *uh, struct Curl_easy *data, #if defined(HAVE_IF_NAMETOINDEX) || defined(_WIN32) /* Zone identifier is not numeric */ unsigned int scopeidx = 0; -#if defined(_WIN32) +#ifdef _WIN32 scopeidx = Curl_if_nametoindex(zoneid); #else scopeidx = if_nametoindex(zoneid); @@ -1921,10 +2020,17 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } else { - unsigned long port = strtoul(data->state.up.port, NULL, 10); - conn->primary.remote_port = conn->remote_port = - (data->set.use_port && data->state.allow_port) ? - data->set.use_port : curlx_ultous(port); + curl_off_t port; + bool valid = TRUE; + if(data->set.use_port && data->state.allow_port) + port = data->set.use_port; + else { + const char *p = data->state.up.port; + if(curlx_str_number(&p, &port, 0xffff)) + valid = FALSE; + } + if(valid) + conn->primary.remote_port = conn->remote_port = (unsigned short)port; } (void)curl_url_get(uh, CURLUPART_QUERY, &data->state.up.query, 0); @@ -2029,9 +2135,8 @@ static CURLcode setup_connection_internals(struct Curl_easy *data, if(!conn->destination) return CURLE_OUT_OF_MEMORY; - conn->destination_len = strlen(conn->destination) + 1; Curl_strntolower(conn->destination, conn->destination, - conn->destination_len - 1); + strlen(conn->destination)); return CURLE_OK; } @@ -2068,7 +2173,7 @@ static char *detect_proxy(struct Curl_easy *data, * checked if the lowercase versions do not exist. */ char proxy_env[20]; - char *envp = proxy_env; + const char *envp = proxy_env; #ifdef CURL_DISABLE_VERBOSE_STRINGS (void)data; #endif @@ -2109,10 +2214,10 @@ static char *detect_proxy(struct Curl_easy *data, } if(!proxy) { #endif - envp = (char *)"all_proxy"; + envp = "all_proxy"; proxy = curl_getenv(envp); /* default proxy to use */ if(!proxy) { - envp = (char *)"ALL_PROXY"; + envp = "ALL_PROXY"; proxy = curl_getenv(envp); } #ifndef CURL_DISABLE_WEBSOCKETS @@ -2228,7 +2333,7 @@ static CURLcode parse_proxy(struct Curl_easy *data, goto error; if(proxyuser || proxypasswd) { - Curl_safefree(proxyinfo->user); + free(proxyinfo->user); proxyinfo->user = proxyuser; result = Curl_setstropt(&data->state.aptr.proxyuser, proxyuser); proxyuser = NULL; @@ -2253,7 +2358,10 @@ static CURLcode parse_proxy(struct Curl_easy *data, (void)curl_url_get(uhp, CURLUPART_PORT, &portptr, 0); if(portptr) { - port = (int)strtol(portptr, NULL, 10); + curl_off_t num; + const char *p = portptr; + if(!curlx_str_number(&p, &num, 0xffff)) + port = (int)num; free(portptr); } else { @@ -2297,7 +2405,7 @@ static CURLcode parse_proxy(struct Curl_easy *data, result = CURLE_OUT_OF_MEMORY; goto error; } - Curl_safefree(proxyinfo->host.rawalloc); + free(proxyinfo->host.rawalloc); proxyinfo->host.rawalloc = host; proxyinfo->host.name = host; host = NULL; @@ -2306,7 +2414,7 @@ static CURLcode parse_proxy(struct Curl_easy *data, if(!is_unix_proxy) { #endif - Curl_safefree(proxyinfo->host.rawalloc); + free(proxyinfo->host.rawalloc); proxyinfo->host.rawalloc = host; if(host[0] == '[') { /* this is a numerical IPv6, strip off the brackets */ @@ -2493,7 +2601,7 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, if(!conn->socks_proxy.user) { conn->socks_proxy.user = conn->http_proxy.user; conn->http_proxy.user = NULL; - Curl_safefree(conn->socks_proxy.passwd); + free(conn->socks_proxy.passwd); conn->socks_proxy.passwd = conn->http_proxy.passwd; conn->http_proxy.passwd = NULL; } @@ -2728,7 +2836,7 @@ static CURLcode override_login(struct Curl_easy *data, } } if(url_provided) { - Curl_safefree(conn->user); + free(conn->user); conn->user = strdup(*userp); if(!conn->user) return CURLE_OUT_OF_MEMORY; @@ -2836,7 +2944,7 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, int port = -1; CURLcode result = CURLE_OK; -#if defined(CURL_DISABLE_VERBOSE_STRINGS) +#ifdef CURL_DISABLE_VERBOSE_STRINGS (void) data; #endif @@ -2891,19 +2999,18 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, /* Get port number off server.com:1080 */ host_portno = strchr(portptr, ':'); if(host_portno) { - char *endp = NULL; *host_portno = '\0'; /* cut off number from hostname */ host_portno++; if(*host_portno) { - long portparse = strtol(host_portno, &endp, 10); - if((endp && *endp) || (portparse < 0) || (portparse > 65535)) { + curl_off_t portparse; + const char *p = host_portno; + if(curlx_str_number(&p, &portparse, 0xffff)) { failf(data, "No valid port number in connect to host string (%s)", host_portno); result = CURLE_SETOPT_OPTION_SYNTAX; goto error; } - else - port = (int)portparse; /* we know it will fit */ + port = (int)portparse; /* we know it will fit */ } } @@ -2974,12 +3081,11 @@ static CURLcode parse_connect_to_string(struct Curl_easy *data, /* check whether the URL's port matches */ char *ptr_next = strchr(ptr, ':'); if(ptr_next) { - char *endp = NULL; - long port_to_match = strtol(ptr, &endp, 10); - if((endp == ptr_next) && (port_to_match == conn->remote_port)) { + curl_off_t port_to_match; + if(!curlx_str_number(&ptr, &port_to_match, 0xffff) && + (port_to_match == (curl_off_t)conn->remote_port)) port_match = TRUE; - ptr = ptr_next + 1; - } + ptr = ptr_next + 1; } } } @@ -3049,75 +3155,54 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, )) { /* no connect_to match, try alt-svc! */ 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 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; + int allowed_alpns = ALPN_none; + struct http_negotiation *neg = &data->state.http_neg; - 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; + DEBUGF(infof(data, "Alt-svc check wanted=%x, allowed=%x", + neg->wanted, neg->allowed)); +#ifdef USE_HTTP3 + if(neg->allowed & CURL_HTTP_V3x) + allowed_alpns |= ALPN_h3; +#endif +#ifdef USE_HTTP2 + if(neg->allowed & CURL_HTTP_V2x) + allowed_alpns |= ALPN_h2; +#endif + if(neg->allowed & CURL_HTTP_V1x) + allowed_alpns |= ALPN_h1; + allowed_alpns &= (int)data->asi->flags; host = conn->host.rawalloc; 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 < CURL_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 */ +#ifdef USE_HTTP3 + if(!hit && (neg->wanted & CURL_HTTP_V3x)) { + srcalpnid = ALPN_h3; hit = Curl_altsvc_lookup(data->asi, - srcalpnid, host, conn->remote_port, /* from */ + ALPN_h3, host, conn->remote_port, /* from */ &as /* to */, - allowed_versions); + allowed_alpns); + } + #endif + #ifdef USE_HTTP2 + if(!hit && (neg->wanted & CURL_HTTP_V2x) && + !neg->h2_prior_knowledge) { + srcalpnid = ALPN_h2; + hit = Curl_altsvc_lookup(data->asi, + ALPN_h2, host, conn->remote_port, /* from */ + &as /* to */, + allowed_alpns); + } + #endif + if(!hit && (neg->wanted & CURL_HTTP_V1x) && + !neg->only_10) { + srcalpnid = ALPN_h1; + hit = Curl_altsvc_lookup(data->asi, + ALPN_h1, host, conn->remote_port, /* from */ + &as /* to */, + allowed_alpns); } - if(hit) { char *hostd = strdup((char *)as->dst.host); @@ -3136,14 +3221,15 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, /* protocol version switch */ switch(as->dst.alpnid) { case ALPN_h1: - data->state.httpwant = CURL_HTTP_VERSION_1_1; + neg->wanted = neg->allowed = CURL_HTTP_V1x; + neg->only_10 = FALSE; break; case ALPN_h2: - data->state.httpwant = CURL_HTTP_VERSION_2_0; + neg->wanted = neg->allowed = CURL_HTTP_V2x; break; case ALPN_h3: conn->transport = TRNSPRT_QUIC; - data->state.httpwant = CURL_HTTP_VERSION_3; + neg->wanted = neg->allowed = CURL_HTTP_V3x; break; default: /* should not be possible */ break; @@ -3159,13 +3245,14 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, #ifdef USE_UNIX_SOCKETS static CURLcode resolve_unix(struct Curl_easy *data, struct connectdata *conn, - char *unix_path) + char *unix_path, + struct Curl_dns_entry **pdns) { - struct Curl_dns_entry *hostaddr = NULL; + struct Curl_dns_entry *hostaddr; bool longpath = FALSE; DEBUGASSERT(unix_path); - DEBUGASSERT(conn->dns_entry == NULL); + *pdns = NULL; /* Unix domain sockets are local. The host gets ignored, just use the * specified domain socket address. Do not cache "DNS entries". There is @@ -3185,7 +3272,7 @@ static CURLcode resolve_unix(struct Curl_easy *data, } hostaddr->refcount = 1; /* connection is the only one holding this */ - conn->dns_entry = hostaddr; + *pdns = hostaddr; return CURLE_OK; } #endif @@ -3195,31 +3282,35 @@ static CURLcode resolve_unix(struct Curl_easy *data, *************************************************************/ static CURLcode resolve_server(struct Curl_easy *data, struct connectdata *conn, - bool *async) + bool *async, + struct Curl_dns_entry **pdns) { struct hostname *ehost; timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); const char *peertype = "host"; - int rc; + CURLcode result; + + *pdns = NULL; + #ifdef USE_UNIX_SOCKETS - char *unix_path = conn->unix_domain_socket; + { + char *unix_path = conn->unix_domain_socket; #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; + 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 - if(unix_path) { - /* This only works if previous transport is TRNSPRT_TCP. Check it? */ - conn->transport = TRNSPRT_UNIX; - return resolve_unix(data, conn, unix_path); + if(unix_path) { + /* This only works if previous transport is TRNSPRT_TCP. Check it? */ + conn->transport = TRNSPRT_UNIX; + return resolve_unix(data, conn, unix_path, pdns); + } } #endif - DEBUGASSERT(conn->dns_entry == NULL); - #ifndef CURL_DISABLE_PROXY if(CONN_IS_PROXIED(conn)) { ehost = conn->bits.socksproxy ? &conn->socks_proxy.host : @@ -3241,22 +3332,25 @@ static CURLcode resolve_server(struct Curl_easy *data, if(!conn->hostname_resolve) return CURLE_OUT_OF_MEMORY; - rc = Curl_resolv_timeout(data, conn->hostname_resolve, - conn->primary.remote_port, - &conn->dns_entry, timeout_ms); - if(rc == CURLRESOLV_PENDING) + result = Curl_resolv_timeout(data, conn->hostname_resolve, + conn->primary.remote_port, conn->ip_version, + pdns, timeout_ms); + DEBUGASSERT(!result || !*pdns); + if(result == CURLE_AGAIN) { *async = TRUE; - else if(rc == CURLRESOLV_TIMEDOUT) { + return CURLE_OK; + } + else if(result == CURLE_OPERATION_TIMEDOUT) { 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)); + curlx_timediff(curlx_now(), data->progress.t_startsingle)); return CURLE_OPERATION_TIMEDOUT; } - else if(!conn->dns_entry) { + else if(result) { failf(data, "Could not resolve %s: %s", peertype, ehost->dispname); - return CURLE_COULDNT_RESOLVE_HOST; + return result; } - + DEBUGASSERT(*pdns); return CURLE_OK; } @@ -3273,8 +3367,8 @@ static void reuse_conn(struct Curl_easy *data, * be new for this request even when we reuse an existing connection */ if(temp->user) { /* use the new username and password though */ - Curl_safefree(existing->user); - Curl_safefree(existing->passwd); + free(existing->user); + free(existing->passwd); existing->user = temp->user; existing->passwd = temp->passwd; temp->user = NULL; @@ -3285,10 +3379,10 @@ static void reuse_conn(struct Curl_easy *data, existing->bits.proxy_user_passwd = temp->bits.proxy_user_passwd; if(existing->bits.proxy_user_passwd) { /* 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); - Curl_safefree(existing->socks_proxy.passwd); + free(existing->http_proxy.user); + free(existing->socks_proxy.user); + free(existing->http_proxy.passwd); + free(existing->socks_proxy.passwd); existing->http_proxy.user = temp->http_proxy.user; existing->socks_proxy.user = temp->socks_proxy.user; existing->http_proxy.passwd = temp->http_proxy.passwd; @@ -3325,8 +3419,7 @@ static void reuse_conn(struct Curl_easy *data, temp->conn_to_host.rawalloc = NULL; existing->conn_to_port = temp->conn_to_port; existing->remote_port = temp->remote_port; - Curl_safefree(existing->hostname_resolve); - + free(existing->hostname_resolve); existing->hostname_resolve = temp->hostname_resolve; temp->hostname_resolve = NULL; @@ -3336,6 +3429,15 @@ static void reuse_conn(struct Curl_easy *data, Curl_conn_free(data, temp); } +static void conn_meta_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); +} + /** * create_conn() sets up a new connectdata struct, or reuses an already * existing one, and resolves hostname. @@ -3346,14 +3448,14 @@ static void reuse_conn(struct Curl_easy *data, * * @param data The sessionhandle pointer * @param in_connect is set to the next connection data pointer - * @param async is set TRUE when an async DNS resolution is pending + * @param reusedp is set to to TRUE if connection was reused * @see Curl_setup_conn() * */ static CURLcode create_conn(struct Curl_easy *data, struct connectdata **in_connect, - bool *async) + bool *reusedp) { CURLcode result = CURLE_OK; struct connectdata *conn; @@ -3363,7 +3465,7 @@ static CURLcode create_conn(struct Curl_easy *data, bool force_reuse = FALSE; bool waitpipe = FALSE; - *async = FALSE; + *reusedp = FALSE; *in_connect = NULL; /************************************************************* @@ -3390,6 +3492,13 @@ static CURLcode create_conn(struct Curl_easy *data, any failure */ *in_connect = conn; + /* Do the unfailable inits first, before checks that may early return */ + Curl_hash_init(&conn->meta_hash, 23, + Curl_hash_str, curlx_str_key_compare, conn_meta_freeentry); + + /* GSSAPI related inits */ + Curl_sec_conn_init(conn); + result = parseurlandfillconn(data, conn); if(result) goto out; @@ -3530,30 +3639,26 @@ static CURLcode create_conn(struct Curl_easy *data, /* conn_protocol can only provide "old" protocols */ data->info.conn_protocol = (conn->handler->protocol) & CURLPROTO_MASK; result = conn->handler->connect_it(data, &done); + if(result) + goto out; /* Setup a "faked" transfer that will do nothing */ + Curl_attach_connection(data, conn); + result = Curl_cpool_add(data, conn); if(!result) { - Curl_attach_connection(data, conn); - result = Curl_cpool_add_conn(data, conn); - if(result) - goto out; - - /* - * Setup whatever necessary for a resumed transfer - */ + /* Setup whatever necessary for a resumed transfer */ result = setup_range(data); - if(result) { - DEBUGASSERT(conn->handler->done); - /* we ignore the return code for the protocol-specific DONE */ - (void)conn->handler->done(data, result, FALSE); - goto out; + if(!result) { + Curl_xfer_setup_nop(data); + result = Curl_init_do(data, conn); } - Curl_xfer_setup_nop(data); } - /* since we skip do_init() */ - Curl_init_do(data, conn); - + if(result) { + DEBUGASSERT(conn->handler->done); + /* we ignore the return code for the protocol-specific DONE */ + (void)conn->handler->done(data, result, FALSE); + } goto out; } #endif @@ -3597,18 +3702,25 @@ static CURLcode create_conn(struct Curl_easy *data, * `existing` and thus we need to cleanup the one we just * allocated before we can move along and use `existing`. */ + bool tls_upgraded = (!(conn->given->flags & PROTOPT_SSL) && + Curl_conn_is_ssl(conn, FIRSTSOCKET)); + reuse_conn(data, conn, existing); conn = existing; *in_connect = conn; #ifndef CURL_DISABLE_PROXY - infof(data, "Re-using existing connection with %s %s", + infof(data, "Re-using existing %s: connection%s with %s %s", + conn->given->scheme, + tls_upgraded ? " (upgraded to SSL)" : "", 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); #else - infof(data, "Re-using existing connection with host %s", + infof(data, "Re-using existing %s: connection%s with host %s", + conn->given->scheme, + tls_upgraded ? " (upgraded to SSL)" : "", conn->host.dispname); #endif } @@ -3624,10 +3736,12 @@ static CURLcode create_conn(struct Curl_easy *data, conn->bits.tls_enable_alpn = TRUE; } - if(waitpipe) + if(waitpipe) { /* There is a connection that *might* become usable for multiplexing "soon", and we wait for that */ + infof(data, "Waiting on connection to negotiate possible multiplexing."); connections_available = FALSE; + } else { switch(Curl_cpool_check_limits(data, conn)) { case CPOOL_LIMIT_DEST: @@ -3635,13 +3749,12 @@ static CURLcode create_conn(struct Curl_easy *data, 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"); + if(data->master_mid != UINT_MAX) + CURL_TRC_M(data, "Allowing sub-requests (like DoH) to override " + "max connection limit"); + else { + infof(data, "No connections available, total of %ld reached.", + data->multi->max_total_connections); connections_available = FALSE; } break; @@ -3651,8 +3764,6 @@ static CURLcode create_conn(struct Curl_easy *data, } if(!connections_available) { - infof(data, "No connections available."); - Curl_conn_free(data, conn); *in_connect = NULL; @@ -3671,12 +3782,12 @@ static CURLcode create_conn(struct Curl_easy *data, } Curl_attach_connection(data, conn); - result = Curl_cpool_add_conn(data, conn); + result = Curl_cpool_add(data, conn); if(result) goto out; } -#if defined(USE_NTLM) +#ifdef USE_NTLM /* 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. */ @@ -3697,7 +3808,9 @@ static CURLcode create_conn(struct Curl_easy *data, } /* Setup and init stuff before DO starts, in preparing for the transfer. */ - Curl_init_do(data, conn); + result = Curl_init_do(data, conn); + if(result) + goto out; /* * Setup whatever necessary for a resumed transfer @@ -3712,15 +3825,7 @@ static CURLcode create_conn(struct Curl_easy *data, /* 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; + *reusedp = TRUE; } /* persist the scheme and handler the transfer is using */ @@ -3749,24 +3854,17 @@ out: * Curl_setup_conn() also handles reused connections */ CURLcode Curl_setup_conn(struct Curl_easy *data, + struct Curl_dns_entry *dns, bool *protocol_done) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; + DEBUGASSERT(dns); Curl_pgrsTime(data, TIMER_NAMELOOKUP); - if(conn->handler->flags & PROTOPT_NONETWORK) { - /* nothing to setup when not using a network */ - *protocol_done = TRUE; - return result; - } - - /* 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, + result = Curl_conn_setup(data, conn, FIRSTSOCKET, dns, CURL_CF_SSL_DEFAULT); if(!result) result = Curl_headers_init(data); @@ -3782,35 +3880,56 @@ CURLcode Curl_connect(struct Curl_easy *data, { CURLcode result; struct connectdata *conn; + bool reused = FALSE; *asyncp = FALSE; /* assume synchronous resolves by default */ + *protocol_done = FALSE; /* 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); + result = create_conn(data, &conn, &reused); + + if(result == CURLE_NO_CONNECTION_AVAILABLE) { + DEBUGASSERT(!conn); + return result; + } if(!result) { - if(CONN_INUSE(conn) > 1) - /* multiplexed */ + DEBUGASSERT(conn); + if(reused) { + if(CONN_ATTACHED(conn) > 1) + /* multiplexed */ + *protocol_done = TRUE; + } + else if(conn->handler->flags & PROTOPT_NONETWORK) { + *asyncp = FALSE; + Curl_pgrsTime(data, TIMER_NAMELOOKUP); *protocol_done = TRUE; - else if(!*asyncp) { - /* 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); + } + else { + /************************************************************* + * Resolve the address of the server or proxy + *************************************************************/ + struct Curl_dns_entry *dns; + result = resolve_server(data, conn, asyncp, &dns); + if(!result) { + *asyncp = !dns; + if(dns) + /* 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, dns, protocol_done); + } } } - if(result == CURLE_NO_CONNECTION_AVAILABLE) { - return result; - } - else if(result && conn) { + if(result && conn) { /* We are not allowed to return failure with memory left allocated in the connectdata struct, free those here */ Curl_detach_connection(data); - Curl_cpool_disconnect(data, conn, TRUE); + Curl_conn_terminate(data, conn, TRUE); } return result; @@ -3953,3 +4072,25 @@ void Curl_data_priority_clear_state(struct Curl_easy *data) } #endif /* defined(USE_HTTP2) || defined(USE_HTTP3) */ + + +CURLcode Curl_conn_meta_set(struct connectdata *conn, const char *key, + void *meta_data, Curl_meta_dtor *meta_dtor) +{ + if(!Curl_hash_add2(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1, + meta_data, meta_dtor)) { + meta_dtor(CURL_UNCONST(key), strlen(key) + 1, meta_data); + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; +} + +void Curl_conn_meta_remove(struct connectdata *conn, const char *key) +{ + Curl_hash_delete(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1); +} + +void *Curl_conn_meta_get(struct connectdata *conn, const char *key) +{ + return Curl_hash_pick(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1); +} diff --git a/Utilities/cmcurl/lib/url.h b/Utilities/cmcurl/lib/url.h index 47c1db44f3..7aba98dbb9 100644 --- a/Utilities/cmcurl/lib/url.h +++ b/Utilities/cmcurl/lib/url.h @@ -37,15 +37,36 @@ 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); -bool Curl_on_disconnect(struct Curl_easy *data, - struct connectdata *, bool aborted); CURLcode Curl_setup_conn(struct Curl_easy *data, + struct Curl_dns_entry *dns, bool *protocol_done); 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); +/* Attach/Clear/Get meta data for an easy handle. Needs to provide + * a destructor, will be automatically called when the easy handle + * is reset or closed. */ +typedef void Curl_meta_dtor(void *key, size_t key_len, void *meta_data); + +/* Set the transfer meta data for the key. Any existing entry for that + * key will be destroyed. + * Takes ownership of `meta_data` and destroys it when the call fails. */ +CURLcode Curl_meta_set(struct Curl_easy *data, const char *key, + void *meta_data, Curl_meta_dtor *meta_dtor); +void Curl_meta_remove(struct Curl_easy *data, const char *key); +void *Curl_meta_get(struct Curl_easy *data, const char *key); +void Curl_meta_reset(struct Curl_easy *data); + +/* Set connection meta data for the key. Any existing entry for that + * key will be destroyed. + * Takes ownership of `meta_data` and destroys it when the call fails. */ +CURLcode Curl_conn_meta_set(struct connectdata *conn, const char *key, + void *meta_data, Curl_meta_dtor *meta_dtor); +void Curl_conn_meta_remove(struct connectdata *conn, const char *key); +void *Curl_conn_meta_get(struct connectdata *conn, const char *key); + /* Get protocol handler for a URI scheme * @param scheme URI scheme, case-insensitive * @return NULL of handler not found diff --git a/Utilities/cmcurl/lib/urlapi-int.h b/Utilities/cmcurl/lib/urlapi-int.h index fcffab2e95..fbce1837ff 100644 --- a/Utilities/cmcurl/lib/urlapi-int.h +++ b/Utilities/cmcurl/lib/urlapi-int.h @@ -30,6 +30,8 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, CURLUcode Curl_url_set_authority(CURLU *u, const char *authority); +CURLUcode Curl_junkscan(const char *url, size_t *urllen, bool allowspace); + #ifdef UNITTESTS UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, bool has_scheme); diff --git a/Utilities/cmcurl/lib/urlapi.c b/Utilities/cmcurl/lib/urlapi.c index 2368bd79b5..6a7ab1a575 100644 --- a/Utilities/cmcurl/lib/urlapi.c +++ b/Utilities/cmcurl/lib/urlapi.c @@ -30,10 +30,12 @@ #include "url.h" #include "escape.h" #include "curl_ctype.h" -#include "inet_pton.h" +#include "curlx/inet_pton.h" #include "inet_ntop.h" #include "strdup.h" #include "idn.h" +#include "curlx/strparse.h" +#include "curl_memrchr.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -109,37 +111,24 @@ static void free_urlhandle(struct Curl_URL *u) */ static const char *find_host_sep(const char *url) { - const char *sep; - const char *query; - /* Find the start of the hostname */ - sep = strstr(url, "//"); + const char *sep = strstr(url, "//"); if(!sep) sep = url; else sep += 2; - query = strchr(sep, '?'); - sep = strchr(sep, '/'); + /* Find first / or ? */ + while(*sep && *sep != '/' && *sep != '?') + sep++; - if(!sep) - sep = url + strlen(url); - - if(!query) - query = url + strlen(url); - - return sep < query ? sep : query; + return sep; } /* 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. - */ -#define urlchar_needs_escaping(c) (!(ISCNTRL(c) || ISSPACE(c) || ISGRAPH(c))) -static const char hexdigits[] = "0123456789abcdef"; /* urlencode_str() writes data into an output dynbuf and URL-encodes the * spaces in the source URL accordingly. * @@ -154,46 +143,39 @@ 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; + CURLcode result = CURLE_OK; - if(!relative) + if(!relative) { + size_t n; host_sep = (const unsigned char *) find_host_sep(url); - for(iptr = (unsigned char *)url; /* read from here */ - len; iptr++, len--) { - - if(iptr < host_sep) { - result = Curl_dyn_addn(o, iptr, 1); - if(result) - return cc2cu(result); - continue; - } - - if(*iptr == ' ') { - if(left) - result = Curl_dyn_addn(o, "%20", 3); - else - result = Curl_dyn_addn(o, "+", 1); - if(result) - return cc2cu(result); - continue; - } - - if(*iptr == '?') - left = FALSE; - - if(urlchar_needs_escaping(*iptr)) { - char out[3]={'%'}; - out[1] = hexdigits[*iptr >> 4]; - out[2] = hexdigits[*iptr & 0xf]; - result = Curl_dyn_addn(o, out, 3); - } - else - result = Curl_dyn_addn(o, iptr, 1); - if(result) - return cc2cu(result); + /* output the first piece as-is */ + n = (const char *)host_sep - url; + result = curlx_dyn_addn(o, url, n); + len -= n; } + for(iptr = host_sep; len && !result; iptr++, len--) { + if(*iptr == ' ') { + if(left) + result = curlx_dyn_addn(o, "%20", 3); + else + result = curlx_dyn_addn(o, "+", 1); + } + else if((*iptr < ' ') || (*iptr >= 0x7f)) { + unsigned char out[3]={'%'}; + Curl_hexbyte(&out[1], *iptr, TRUE); + result = curlx_dyn_addn(o, out, 3); + } + else { + result = curlx_dyn_addn(o, iptr, 1); + if(*iptr == '?') + left = FALSE; + } + } + + if(result) + return cc2cu(result); return CURLUE_OK; } @@ -246,113 +228,95 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, } /* - * Concatenate a relative URL to a base URL making it absolute. - * - * Note that this function destroys the 'base' string. + * Concatenate a relative URL onto a base URL making it absolute. */ -static CURLUcode redirect_url(char *base, const char *relurl, +static CURLUcode redirect_url(const char *base, const char *relurl, CURLU *u, unsigned int flags) { struct dynbuf urlbuf; bool host_changed = FALSE; const char *useurl = relurl; - CURLcode result = CURLE_OK; + const char *cutoff = NULL; + size_t prelen; CURLUcode uc; - /* protsep points to the start of the hostname */ - char *protsep = strstr(base, "//"); - DEBUGASSERT(protsep); - if(!protsep) - protsep = base; - else - protsep += 2; /* pass the slashes */ - if(('/' != relurl[0]) && ('#' != relurl[0])) { - /* First we need to find out if there is a ?-letter in the original URL, - and cut it and the right-side of that off */ - char *pathsep = strchr(protsep, '?'); - if(pathsep) - *pathsep = 0; - else { - /* if not, cut off the potential fragment */ - pathsep = strchr(protsep, '#'); - if(pathsep) - *pathsep = 0; - } - - /* if the redirect-to piece is not just a query, cut the path after the - last slash */ - if(useurl[0] != '?') { - pathsep = strrchr(protsep, '/'); - if(pathsep) - pathsep[1] = 0; /* leave the slash */ - } - } - else if('/' == relurl[0]) { - /* We got a new absolute path for this server */ + /* protsep points to the start of the hostname, after [scheme]:// */ + const char *protsep = base + strlen(u->scheme) + 3; + DEBUGASSERT(base && relurl && u); /* all set here */ + if(!base) + return CURLUE_MALFORMED_INPUT; /* should never happen */ + /* handle different relative URL types */ + switch(relurl[0]) { + case '/': if(relurl[1] == '/') { - /* the new URL starts with //, just keep the protocol part from the - original one */ - *protsep = 0; - useurl = &relurl[2]; /* we keep the slashes from the original, so we - skip the new ones */ + /* protocol-relative URL: //example.com/path */ + cutoff = protsep; + useurl = &relurl[2]; host_changed = TRUE; } - else { - /* cut the original URL at first slash */ - char *pathsep = strchr(protsep, '/'); - if(pathsep) - *pathsep = 0; + else + /* absolute /path */ + cutoff = strchr(protsep, '/'); + break; + + case '#': + /* fragment-only change */ + if(u->fragment) + cutoff = strchr(protsep, '#'); + break; + + default: + /* path or query-only change */ + if(u->query && u->query[0]) + /* remove existing query */ + cutoff = strchr(protsep, '?'); + else if(u->fragment && u->fragment[0]) + /* Remove existing fragment */ + cutoff = strchr(protsep, '#'); + + if(relurl[0] != '?') { + /* append a relative path after the last slash */ + cutoff = memrchr(protsep, '/', + cutoff ? (size_t)(cutoff - protsep) : strlen(protsep)); + if(cutoff) + cutoff++; /* truncate after last slash */ } - } - else { - /* the relative piece starts with '#' */ - - /* If there is a fragment in the original URL, cut it off */ - char *pathsep = strchr(protsep, '#'); - if(pathsep) - *pathsep = 0; + break; } - Curl_dyn_init(&urlbuf, CURL_MAX_INPUT_LENGTH); + prelen = cutoff ? (size_t)(cutoff - base) : strlen(base); - /* copy over the root URL part */ - result = Curl_dyn_add(&urlbuf, base); - if(result) - return cc2cu(result); + /* build new URL */ + curlx_dyn_init(&urlbuf, CURL_MAX_INPUT_LENGTH); - /* then append the new piece on the right side */ - uc = urlencode_str(&urlbuf, useurl, strlen(useurl), !host_changed, - FALSE); - if(!uc) - uc = parseurl_and_replace(Curl_dyn_ptr(&urlbuf), u, - flags&~CURLU_PATH_AS_IS); - Curl_dyn_free(&urlbuf); + if(!curlx_dyn_addn(&urlbuf, base, prelen) && + !urlencode_str(&urlbuf, useurl, strlen(useurl), !host_changed, FALSE)) { + uc = parseurl_and_replace(curlx_dyn_ptr(&urlbuf), u, + flags & ~CURLU_PATH_AS_IS); + } + else + uc = CURLUE_OUT_OF_MEMORY; + + curlx_dyn_free(&urlbuf); return uc; } /* scan for byte values <= 31, 127 and sometimes space */ -static CURLUcode junkscan(const char *url, size_t *urllen, unsigned int flags) +CURLUcode Curl_junkscan(const char *url, size_t *urllen, bool allowspace) { - static const char badbytes[]={ - /* */ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, - 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, - 0x7f, 0x00 /* null-terminate */ - }; size_t n = strlen(url); - size_t nfine; - + size_t i; + unsigned char control; + const unsigned char *p = (const unsigned char *)url; if(n > CURL_MAX_INPUT_LENGTH) - /* excessive input length */ - return CURLUE_MALFORMED_INPUT; - - nfine = strcspn(url, badbytes); - if((nfine != n) || - (!(flags & CURLU_ALLOW_SPACE) && strchr(url, ' '))) return CURLUE_MALFORMED_INPUT; + control = allowspace ? 0x1f : 0x20; + for(i = 0; i < n; i++) { + if(p[i] <= control || p[i] == 127) + return CURLUE_MALFORMED_INPUT; + } *urllen = n; return CURLUE_OK; } @@ -452,8 +416,8 @@ out: UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, bool has_scheme) { - char *portptr; - char *hostname = Curl_dyn_ptr(host); + const char *portptr; + char *hostname = curlx_dyn_ptr(host); /* * Find the end of an IPv6 address on the ']' ending bracket. */ @@ -474,8 +438,7 @@ UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, portptr = strchr(hostname, ':'); if(portptr) { - char *rest = NULL; - unsigned long port; + curl_off_t port; size_t keep = portptr - hostname; /* Browser behavior adaptation. If there is a colon with no digits after, @@ -485,24 +448,18 @@ UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, 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); + curlx_dyn_setlen(host, keep); portptr++; if(!*portptr) return has_scheme ? CURLUE_OK : CURLUE_BAD_PORT_NUMBER; - if(!ISDIGIT(*portptr)) - return CURLUE_BAD_PORT_NUMBER; - - errno = 0; - port = strtoul(portptr, &rest, 10); /* Port number must be decimal */ - - if(errno || (port > 0xffff) || *rest) + if(curlx_str_number(&portptr, &port, 0xffff) || *portptr) return CURLUE_BAD_PORT_NUMBER; 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); + u->port = aprintf("%" CURL_FORMAT_CURL_OFF_T, port); if(!u->port) return CURLUE_OUT_OF_MEMORY; } @@ -554,7 +511,7 @@ static CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, { char dest[16]; /* fits a binary IPv6 address */ hostname[hlen] = 0; /* end the address there */ - if(1 != Curl_inet_pton(AF_INET6, hostname, dest)) + if(1 != curlx_inet_pton(AF_INET6, hostname, dest)) return CURLUE_BAD_IPV6; if(Curl_inet_ntop(AF_INET6, dest, hostname, hlen)) { hlen = strlen(hostname); /* might be shorter now */ @@ -608,31 +565,31 @@ static int ipv4_normalize(struct dynbuf *host) { bool done = FALSE; int n = 0; - const char *c = Curl_dyn_ptr(host); - unsigned long parts[4] = {0, 0, 0, 0}; + const char *c = curlx_dyn_ptr(host); + unsigned int parts[4] = {0, 0, 0, 0}; CURLcode result = CURLE_OK; if(*c == '[') return HOST_IPV6; - errno = 0; /* for strtoul */ while(!done) { - char *endp = NULL; - unsigned long l; - if(!ISDIGIT(*c)) - /* 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 + int rc; + curl_off_t l; + if(*c == '0') { + if(c[1] == 'x') { + c += 2; /* skip the prefix */ + rc = curlx_str_hex(&c, &l, UINT_MAX); + } + else + rc = curlx_str_octal(&c, &l, UINT_MAX); + } + else + rc = curlx_str_number(&c, &l, UINT_MAX); - parts[n] = l; - c = endp; + if(rc) + return HOST_NAME; + + parts[n] = (unsigned int)l; switch(*c) { case '.': @@ -653,44 +610,44 @@ static int ipv4_normalize(struct dynbuf *host) switch(n) { case 0: /* a -- 32 bits */ - Curl_dyn_reset(host); + curlx_dyn_reset(host); - result = Curl_dyn_addf(host, "%u.%u.%u.%u", - (unsigned int)(parts[0] >> 24), - (unsigned int)((parts[0] >> 16) & 0xff), - (unsigned int)((parts[0] >> 8) & 0xff), - (unsigned int)(parts[0] & 0xff)); + result = curlx_dyn_addf(host, "%u.%u.%u.%u", + (parts[0] >> 24), + ((parts[0] >> 16) & 0xff), + ((parts[0] >> 8) & 0xff), + (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", - (unsigned int)(parts[0]), - (unsigned int)((parts[1] >> 16) & 0xff), - (unsigned int)((parts[1] >> 8) & 0xff), - (unsigned int)(parts[1] & 0xff)); + curlx_dyn_reset(host); + result = curlx_dyn_addf(host, "%u.%u.%u.%u", + (parts[0]), + ((parts[1] >> 16) & 0xff), + ((parts[1] >> 8) & 0xff), + (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", - (unsigned int)(parts[0]), - (unsigned int)(parts[1]), - (unsigned int)((parts[2] >> 8) & 0xff), - (unsigned int)(parts[2] & 0xff)); + curlx_dyn_reset(host); + result = curlx_dyn_addf(host, "%u.%u.%u.%u", + (parts[0]), + (parts[1]), + ((parts[2] >> 8) & 0xff), + (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) || (parts[3] > 0xff)) return HOST_NAME; - Curl_dyn_reset(host); - result = Curl_dyn_addf(host, "%u.%u.%u.%u", - (unsigned int)(parts[0]), - (unsigned int)(parts[1]), - (unsigned int)(parts[2]), - (unsigned int)(parts[3])); + curlx_dyn_reset(host); + result = curlx_dyn_addf(host, "%u.%u.%u.%u", + (parts[0]), + (parts[1]), + (parts[2]), + (parts[3])); break; } if(result) @@ -702,7 +659,7 @@ static int ipv4_normalize(struct dynbuf *host) static CURLUcode urldecode_host(struct dynbuf *host) { char *per = NULL; - const char *hostname = Curl_dyn_ptr(host); + const char *hostname = curlx_dyn_ptr(host); per = strchr(hostname, '%'); if(!per) /* nothing to decode */ @@ -715,8 +672,8 @@ static CURLUcode urldecode_host(struct dynbuf *host) REJECT_CTRL); if(result) return CURLUE_BAD_HOSTNAME; - Curl_dyn_reset(host); - result = Curl_dyn_addn(host, decoded, dlen); + curlx_dyn_reset(host); + result = curlx_dyn_addn(host, decoded, dlen); free(decoded); if(result) return cc2cu(result); @@ -742,7 +699,7 @@ static CURLUcode parse_authority(struct Curl_URL *u, if(uc) goto out; - result = Curl_dyn_addn(host, auth + offset, authlen - offset); + result = curlx_dyn_addn(host, auth + offset, authlen - offset); if(result) { uc = cc2cu(result); goto out; @@ -752,19 +709,19 @@ static CURLUcode parse_authority(struct Curl_URL *u, if(uc) goto out; - if(!Curl_dyn_len(host)) + if(!curlx_dyn_len(host)) return CURLUE_NO_HOST; switch(ipv4_normalize(host)) { case HOST_IPV4: break; case HOST_IPV6: - uc = ipv6_parse(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); + uc = ipv6_parse(u, curlx_dyn_ptr(host), curlx_dyn_len(host)); break; case HOST_NAME: uc = urldecode_host(host); if(!uc) - uc = hostname_check(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); + uc = hostname_check(u, curlx_dyn_ptr(host), curlx_dyn_len(host)); break; case HOST_ERROR: uc = CURLUE_OUT_OF_MEMORY; @@ -785,15 +742,15 @@ CURLUcode Curl_url_set_authority(CURLU *u, const char *authority) struct dynbuf host; DEBUGASSERT(authority); - Curl_dyn_init(&host, CURL_MAX_INPUT_LENGTH); + curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH); result = parse_authority(u, authority, strlen(authority), CURLU_DISALLOW_USER, &host, !!u->scheme); if(result) - Curl_dyn_free(&host); + curlx_dyn_free(&host); else { free(u->host); - u->host = Curl_dyn_ptr(&host); + u->host = curlx_dyn_ptr(&host); } return result; } @@ -803,6 +760,25 @@ CURLUcode Curl_url_set_authority(CURLU *u, const char *authority) * https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4 */ +static bool is_dot(const char **str, size_t *clen) +{ + const char *p = *str; + if(*p == '.') { + (*str)++; + (*clen)--; + return TRUE; + } + else if((*clen >= 3) && + (p[0] == '%') && (p[1] == '2') && ((p[2] | 0x20) == 'e')) { + *str += 3; + *clen -= 3; + return TRUE; + } + return FALSE; +} + +#define ISSLASH(x) ((x) == '/') + /* * dedotdotify() * @unittest: 1395 @@ -811,8 +787,7 @@ CURLUcode Curl_url_set_authority(CURLU *u, const char *authority) * passed in and strips them off according to the rules in RFC 3986 section * 5.2.4. * - * The function handles a query part ('?' + stuff) appended but it expects - * that fragments ('#' + stuff) have already been cut off. + * The function handles a path. It should not contain the query nor fragment. * * RETURNS * @@ -821,112 +796,109 @@ CURLUcode Curl_url_set_authority(CURLU *u, const char *authority) UNITTEST int dedotdotify(const char *input, size_t clen, char **outp); UNITTEST int dedotdotify(const char *input, size_t clen, char **outp) { - char *outptr; - const char *endp = &input[clen]; - char *out; + struct dynbuf out; + CURLcode result = CURLE_OK; *outp = NULL; /* the path always starts with a slash, and a slash has not dot */ - if((clen < 2) || !memchr(input, '.', clen)) + if(clen < 2) return 0; - out = malloc(clen + 1); - if(!out) - return 1; /* out of memory */ + curlx_dyn_init(&out, clen + 1); - *out = 0; /* null-terminates, for inputs like "./" */ - outptr = out; + /* A. If the input buffer begins with a prefix of "../" or "./", then + remove that prefix from the input buffer; otherwise, */ + if(is_dot(&input, &clen)) { + const char *p = input; + size_t blen = clen; - do { - bool dotdot = TRUE; - if(*input == '.') { - /* A. If the input buffer begins with a prefix of "../" or "./", then - remove that prefix from the input buffer; otherwise, */ - - if(!strncmp("./", input, 2)) { - input += 2; - clen -= 2; - } - else if(!strncmp("../", input, 3)) { - input += 3; - clen -= 3; - } - /* D. if the input buffer consists only of "." or "..", then remove - that from the input buffer; otherwise, */ - - else if(!strcmp(".", input) || !strcmp("..", input) || - !strncmp(".?", input, 2) || !strncmp("..?", input, 3)) { - *out = 0; - break; - } - else - dotdot = FALSE; + if(!clen) + /* . [end] */ + goto end; + else if(ISSLASH(*p)) { + /* one dot followed by a slash */ + input = p + 1; + clen--; } - else if(*input == '/') { + + /* D. if the input buffer consists only of "." or "..", then remove + that from the input buffer; otherwise, */ + else if(is_dot(&p, &blen)) { + if(!blen) + /* .. [end] */ + goto end; + else if(ISSLASH(*p)) { + /* ../ */ + input = p + 1; + clen = blen - 1; + } + } + } + + while(clen && !result) { /* until end of path content */ + if(ISSLASH(*input)) { + const char *p = &input[1]; + size_t blen = clen - 1; /* 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)) { - input += 2; - clen -= 2; - } - else if(!strcmp("/.", input) || !strncmp("/.?", input, 3)) { - *outptr++ = '/'; - *outptr = 0; - break; - } - - /* 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, */ - - else if(!strncmp("/../", input, 4)) { - input += 3; - clen -= 3; - /* remove the last segment from the output buffer */ - while(outptr > out) { - outptr--; - if(*outptr == '/') - break; + if(is_dot(&p, &blen)) { + if(!blen) { /* /. */ + result = curlx_dyn_addn(&out, "/", 1); + break; } - *outptr = 0; /* null-terminate where it stops */ - } - else if(!strcmp("/..", input) || !strncmp("/..?", input, 4)) { - /* remove the last segment from the output buffer */ - while(outptr > out) { - outptr--; - if(*outptr == '/') - break; + else if(ISSLASH(*p)) { /* /./ */ + input = p; + clen = blen; + continue; + } + + /* 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, */ + else if(is_dot(&p, &blen) && (ISSLASH(*p) || !blen)) { + /* remove the last segment from the output buffer */ + size_t len = curlx_dyn_len(&out); + if(len) { + char *ptr = curlx_dyn_ptr(&out); + char *last = memrchr(ptr, '/', len); + if(last) + /* trim the output at the slash */ + curlx_dyn_setlen(&out, last - ptr); + } + + if(blen) { /* /../ */ + input = p; + clen = blen; + continue; + } + result = curlx_dyn_addn(&out, "/", 1); + break; } - *outptr++ = '/'; - *outptr = 0; /* null-terminate where it stops */ - break; } - else - dotdot = FALSE; - } - else - dotdot = FALSE; - - if(!dotdot) { - /* 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. */ - - do { - *outptr++ = *input++; - clen--; - } while(*input && (*input != '/') && (*input != '?')); - *outptr = 0; } - /* continue until end of path */ - } while(input < endp); + /* 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. */ - *outp = out; - return 0; /* success */ + result = curlx_dyn_addn(&out, input, 1); + input++; + clen--; + } +end: + if(!result) { + if(curlx_dyn_len(&out)) + *outp = curlx_dyn_ptr(&out); + else { + *outp = strdup(""); + if(!*outp) + return 1; + } + } + return result ? 1 : 0; /* success */ } static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) @@ -944,9 +916,9 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) DEBUGASSERT(url); - Curl_dyn_init(&host, CURL_MAX_INPUT_LENGTH); + curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH); - result = junkscan(url, &urllen, flags); + result = Curl_junkscan(url, &urllen, !!(flags & CURLU_ALLOW_SPACE)); if(result) goto fail; @@ -964,7 +936,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) } /* path has been allocated large enough to hold this */ - path = (char *)&url[5]; + path = &url[5]; pathlen = urllen - 5; u->scheme = strdup("file"); @@ -1010,7 +982,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) ptr += 9; /* now points to the slash after the host */ } else { -#if defined(_WIN32) +#ifdef _WIN32 size_t len; /* the hostname, NetBIOS computer name, can not contain disallowed @@ -1024,7 +996,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) len = path - ptr; if(len) { - CURLcode code = Curl_dyn_addn(&host, ptr, len); + CURLcode code = curlx_dyn_addn(&host, ptr, len); if(code) { result = cc2cu(code); goto fail; @@ -1048,7 +1020,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) if(!uncpath) /* no host for file: URLs by default */ - Curl_dyn_reset(&host); + curlx_dyn_reset(&host); #if !defined(_WIN32) && !defined(MSDOS) && !defined(__CYGWIN__) /* Do not allow Windows drive letters when not in Windows. @@ -1134,7 +1106,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) goto fail; if((flags & CURLU_GUESS_SCHEME) && !schemep) { - const char *hostname = Curl_dyn_ptr(&host); + const char *hostname = curlx_dyn_ptr(&host); /* legacy curl-style guess based on hostname */ if(checkprefix("ftp.", hostname)) schemep = "ftp"; @@ -1161,7 +1133,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) } else if(flags & CURLU_NO_AUTHORITY) { /* allowed to be empty. */ - if(Curl_dyn_add(&host, "")) { + if(curlx_dyn_add(&host, "")) { result = CURLUE_OUT_OF_MEMORY; goto fail; } @@ -1180,11 +1152,11 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) /* 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); + curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); result = urlencode_str(&enc, fragment + 1, fraglen - 1, TRUE, FALSE); if(result) goto fail; - u->fragment = Curl_dyn_ptr(&enc); + u->fragment = curlx_dyn_ptr(&enc); } else { u->fragment = Curl_memdup0(fragment + 1, fraglen - 1); @@ -1207,12 +1179,12 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) if(qlen > 1) { if(flags & CURLU_URLENCODE) { struct dynbuf enc; - Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); /* skip the leading question mark */ result = urlencode_str(&enc, query + 1, qlen - 1, TRUE, TRUE); if(result) goto fail; - u->query = Curl_dyn_ptr(&enc); + u->query = curlx_dyn_ptr(&enc); } else { u->query = Curl_memdup0(query + 1, qlen - 1); @@ -1234,12 +1206,12 @@ 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); + curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); result = urlencode_str(&enc, path, pathlen, TRUE, FALSE); if(result) goto fail; - pathlen = Curl_dyn_len(&enc); - path = u->path = Curl_dyn_ptr(&enc); + pathlen = curlx_dyn_len(&enc); + path = u->path = curlx_dyn_ptr(&enc); } if(pathlen <= 1) { @@ -1262,7 +1234,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) if(!(flags & CURLU_PATH_AS_IS)) { /* remove ../ and ./ sequences according to RFC3986 */ char *dedot; - int err = dedotdotify((char *)path, pathlen, &dedot); + int err = dedotdotify(path, pathlen, &dedot); if(err) { result = CURLUE_OUT_OF_MEMORY; goto fail; @@ -1274,11 +1246,11 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) } } - u->host = Curl_dyn_ptr(&host); + u->host = curlx_dyn_ptr(&host); return result; fail: - Curl_dyn_free(&host); + curlx_dyn_free(&host); free_urlhandle(u); return result; } @@ -1440,7 +1412,7 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, break; case CURLUPART_URL: { char *url; - char *scheme; + const char *scheme; char *options = u->options; char *port = u->port; char *allochost = NULL; @@ -1452,8 +1424,10 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, 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", + url = aprintf("file://%s%s%s%s%s", u->path, + show_query ? "?": "", + u->query ? u->query : "", show_fragment ? "#": "", u->fragment ? u->fragment : ""); } @@ -1465,7 +1439,7 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, if(u->scheme) scheme = u->scheme; else if(flags & CURLU_DEFAULT_SCHEME) - scheme = (char *) DEFAULT_SCHEME; + scheme = DEFAULT_SCHEME; else return CURLUE_NO_SCHEME; @@ -1494,11 +1468,11 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, /* make it '[ host %25 zoneid ]' */ struct dynbuf enc; size_t hostlen = strlen(u->host); - Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); - if(Curl_dyn_addf(&enc, "%.*s%%25%s]", (int)hostlen - 1, u->host, - u->zoneid)) + curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + if(curlx_dyn_addf(&enc, "%.*s%%25%s]", (int)hostlen - 1, u->host, + u->zoneid)) return CURLUE_OUT_OF_MEMORY; - allochost = Curl_dyn_ptr(&enc); + allochost = curlx_dyn_ptr(&enc); } } else if(urlencode) { @@ -1519,7 +1493,7 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, } } else if(depunyfy) { - if(Curl_is_ASCII_name(u->host) && !strncmp("xn--", u->host, 4)) { + if(Curl_is_ASCII_name(u->host)) { #ifndef USE_IDN return CURLUE_LACKS_IDN; #else @@ -1595,12 +1569,12 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, if(urlencode) { struct dynbuf enc; CURLUcode uc; - Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); uc = urlencode_str(&enc, *part, partlen, TRUE, what == CURLUPART_QUERY); if(uc) return uc; free(*part); - *part = Curl_dyn_ptr(&enc); + *part = curlx_dyn_ptr(&enc); } else if(punycode) { if(!Curl_is_ASCII_name(u->host)) { @@ -1618,7 +1592,7 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, } } else if(depunyfy) { - if(Curl_is_ASCII_name(u->host) && !strncmp("xn--", u->host, 4)) { + if(Curl_is_ASCII_name(u->host)) { #ifndef USE_IDN return CURLUE_LACKS_IDN; #else @@ -1639,6 +1613,89 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, return ifmissing; } +static CURLUcode set_url_scheme(CURLU *u, const char *scheme, + unsigned int flags) +{ + size_t plen = strlen(scheme); + const char *s = scheme; + if((plen > MAX_SCHEME_LEN) || (plen < 1)) + /* too long or too short */ + return CURLUE_BAD_SCHEME; + /* verify that it is a fine scheme */ + if(!(flags & CURLU_NON_SUPPORT_SCHEME) && !Curl_get_scheme_handler(scheme)) + return CURLUE_UNSUPPORTED_SCHEME; + if(ISALPHA(*s)) { + /* ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */ + while(--plen) { + if(ISALNUM(*s) || (*s == '+') || (*s == '-') || (*s == '.')) + s++; /* fine */ + else + return CURLUE_BAD_SCHEME; + } + } + else + return CURLUE_BAD_SCHEME; + u->guessed_scheme = FALSE; + return CURLUE_OK; +} + +static CURLUcode set_url_port(CURLU *u, const char *provided_port) +{ + char *tmp; + curl_off_t port; + if(!ISDIGIT(provided_port[0])) + /* not a number */ + return CURLUE_BAD_PORT_NUMBER; + if(curlx_str_number(&provided_port, &port, 0xffff) || *provided_port) + /* weirdly provided number, not good! */ + return CURLUE_BAD_PORT_NUMBER; + tmp = aprintf("%" CURL_FORMAT_CURL_OFF_T, port); + if(!tmp) + return CURLUE_OUT_OF_MEMORY; + free(u->port); + u->port = tmp; + u->portnum = (unsigned short)port; + return CURLUE_OK; +} + +static CURLUcode set_url(CURLU *u, const char *url, size_t part_size, + unsigned int flags) +{ + /* + * Allow a new URL to replace the existing (if any) contents. + * + * If the existing contents is enough for a URL, allow a relative URL to + * replace it. + */ + CURLUcode uc; + char *oldurl = NULL; + + if(!part_size) { + /* a blank URL is not a valid URL unless we already have a complete one + and this is a redirect */ + if(!curl_url_get(u, CURLUPART_URL, &oldurl, flags)) { + /* success, meaning the "" is a fine relative URL, but nothing + changes */ + free(oldurl); + return CURLUE_OK; + } + 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'), then replace the existing with the new. */ + if(Curl_is_absolute_url(url, NULL, 0, + flags & (CURLU_GUESS_SCHEME|CURLU_DEFAULT_SCHEME)) + || curl_url_get(u, CURLUPART_URL, &oldurl, flags)) { + return parseurl_and_replace(url, u, flags); + } + DEBUGASSERT(oldurl); /* it is set here */ + /* apply the relative part to create a new URL */ + uc = redirect_url(oldurl, url, u, flags); + free(oldurl); + return uc; +} + CURLUcode curl_url_set(CURLU *u, CURLUPart what, const char *part, unsigned int flags) { @@ -1712,28 +1769,11 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, switch(what) { case CURLUPART_SCHEME: { - size_t plen = strlen(part); - const char *s = part; - if((plen > MAX_SCHEME_LEN) || (plen < 1)) - /* too long or too short */ - return CURLUE_BAD_SCHEME; - /* verify that it is a fine scheme */ - if(!(flags & CURLU_NON_SUPPORT_SCHEME) && !Curl_get_scheme_handler(part)) - return CURLUE_UNSUPPORTED_SCHEME; + CURLUcode status = set_url_scheme(u, part, flags); + if(status) + return status; storep = &u->scheme; urlencode = FALSE; /* never */ - if(ISALPHA(*s)) { - /* ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */ - while(--plen) { - if(ISALNUM(*s) || (*s == '+') || (*s == '-') || (*s == '.')) - s++; /* fine */ - else - return CURLUE_BAD_SCHEME; - } - } - else - return CURLUE_BAD_SCHEME; - u->guessed_scheme = FALSE; break; } case CURLUPART_USER: @@ -1753,26 +1793,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, storep = &u->zoneid; break; case CURLUPART_PORT: - if(!ISDIGIT(part[0])) - /* not a number */ - return CURLUE_BAD_PORT_NUMBER; - 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; - } + return set_url_port(u, part); case CURLUPART_PATH: urlskipslash = TRUE; leadingslash = TRUE; /* enforce */ @@ -1790,31 +1811,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, u->fragment_present = TRUE; break; case CURLUPART_URL: { - /* - * Allow a new URL to replace the existing (if any) contents. - * - * If the existing contents is enough for a URL, allow a relative URL to - * replace it. - */ - CURLUcode uc; - char *oldurl; - - if(!nalloc) - /* a blank URL is not a valid URL */ - 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'), then replace the existing with the new. */ - if(Curl_is_absolute_url(part, NULL, 0, - flags & (CURLU_GUESS_SCHEME|CURLU_DEFAULT_SCHEME)) - || curl_url_get(u, CURLUPART_URL, &oldurl, flags)) { - return parseurl_and_replace(part, u, flags); - } - - /* apply the relative part to create a new URL */ - uc = redirect_url(oldurl, part, u, flags); - free(oldurl); - return uc; + return set_url(u, part, nalloc, flags); } default: return CURLUE_UNKNOWN_PART; @@ -1823,10 +1820,10 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, { const char *newp; struct dynbuf enc; - Curl_dyn_init(&enc, nalloc * 3 + 1 + leadingslash); + curlx_dyn_init(&enc, nalloc * 3 + 1 + leadingslash); if(leadingslash && (part[0] != '/')) { - CURLcode result = Curl_dyn_addn(&enc, "/", 1); + CURLcode result = curlx_dyn_addn(&enc, "/", 1); if(result) return cc2cu(result); } @@ -1836,7 +1833,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, for(i = (const unsigned char *)part; *i; i++) { CURLcode result; if((*i == ' ') && plusencode) { - result = Curl_dyn_addn(&enc, "+", 1); + result = curlx_dyn_addn(&enc, "+", 1); if(result) return CURLUE_OUT_OF_MEMORY; } @@ -1846,15 +1843,14 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, if((*i == '=') && equalsencode) /* only skip the first equals sign */ equalsencode = FALSE; - result = Curl_dyn_addn(&enc, i, 1); + result = curlx_dyn_addn(&enc, i, 1); if(result) return cc2cu(result); } else { - char out[3]={'%'}; - out[1] = hexdigits[*i >> 4]; - out[2] = hexdigits[*i & 0xf]; - result = Curl_dyn_addn(&enc, out, 3); + unsigned char out[3]={'%'}; + Curl_hexbyte(&out[1], *i, TRUE); + result = curlx_dyn_addn(&enc, out, 3); if(result) return cc2cu(result); } @@ -1862,10 +1858,10 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, } else { char *p; - CURLcode result = Curl_dyn_add(&enc, part); + CURLcode result = curlx_dyn_add(&enc, part); if(result) return cc2cu(result); - p = Curl_dyn_ptr(&enc); + p = curlx_dyn_ptr(&enc); while(*p) { /* make sure percent encoded are lower case */ if((*p == '%') && ISXDIGIT(p[1]) && ISXDIGIT(p[2]) && @@ -1878,7 +1874,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, p++; } } - newp = Curl_dyn_ptr(&enc); + newp = curlx_dyn_ptr(&enc); if(appendquery && newp) { /* Append the 'newp' string onto the old query. Add a '&' separator if @@ -1888,29 +1884,29 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, bool addamperand = querylen && (u->query[querylen -1] != '&'); if(querylen) { struct dynbuf qbuf; - Curl_dyn_init(&qbuf, CURL_MAX_INPUT_LENGTH); + curlx_dyn_init(&qbuf, CURL_MAX_INPUT_LENGTH); - if(Curl_dyn_addn(&qbuf, u->query, querylen)) /* add original query */ + if(curlx_dyn_addn(&qbuf, u->query, querylen)) /* add original query */ goto nomem; if(addamperand) { - if(Curl_dyn_addn(&qbuf, "&", 1)) + if(curlx_dyn_addn(&qbuf, "&", 1)) goto nomem; } - if(Curl_dyn_add(&qbuf, newp)) + if(curlx_dyn_add(&qbuf, newp)) goto nomem; - Curl_dyn_free(&enc); + curlx_dyn_free(&enc); free(*storep); - *storep = Curl_dyn_ptr(&qbuf); + *storep = curlx_dyn_ptr(&qbuf); return CURLUE_OK; nomem: - Curl_dyn_free(&enc); + curlx_dyn_free(&enc); return CURLUE_OUT_OF_MEMORY; } } else if(what == CURLUPART_HOST) { - size_t n = Curl_dyn_len(&enc); + size_t n = curlx_dyn_len(&enc); if(!n && (flags & CURLU_NO_AUTHORITY)) { /* Skip hostname check, it is allowed to be empty. */ } @@ -1929,17 +1925,17 @@ nomem: bad = TRUE; free(decoded); } - else if(hostname_check(u, (char *)newp, n)) + else if(hostname_check(u, (char *)CURL_UNCONST(newp), n)) bad = TRUE; if(bad) { - Curl_dyn_free(&enc); + curlx_dyn_free(&enc); return CURLUE_BAD_HOSTNAME; } } } free(*storep); - *storep = (char *)newp; + *storep = (char *)CURL_UNCONST(newp); } return CURLUE_OK; } diff --git a/Utilities/cmcurl/lib/urldata.h b/Utilities/cmcurl/lib/urldata.h index d9acb2b7bf..45052e84b1 100644 --- a/Utilities/cmcurl/lib/urldata.h +++ b/Utilities/cmcurl/lib/urldata.h @@ -95,13 +95,6 @@ typedef unsigned int curl_prot_t; in the API */ #define CURLPROTO_MASK (0x3ffffff) -#define DICT_MATCH "/MATCH:" -#define DICT_MATCH2 "/M:" -#define DICT_MATCH3 "/FIND:" -#define DICT_DEFINE "/DEFINE:" -#define DICT_DEFINE2 "/D:" -#define DICT_DEFINE3 "/LOOKUP:" - #define CURL_DEFAULT_USER "anonymous" #define CURL_DEFAULT_PASSWORD "ftp@example.com" @@ -152,7 +145,7 @@ typedef unsigned int curl_prot_t; #include #endif -#include "timeval.h" +#include "curlx/timeval.h" #include @@ -160,7 +153,7 @@ typedef unsigned int curl_prot_t; #include "hostip.h" #include "hash.h" #include "splay.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "dynhds.h" #include "request.h" #include "netrc.h" @@ -182,7 +175,6 @@ typedef ssize_t (Curl_recv)(struct Curl_easy *data, /* transfer */ #include "mime.h" #include "imap.h" -#include "pop3.h" #include "smtp.h" #include "ftp.h" #include "file.h" @@ -272,6 +264,7 @@ struct ssl_primary_config { char *clientcert; char *cipher_list; /* list of ciphers to use */ char *cipher_list13; /* list of TLS 1.3 cipher suites to use */ + char *signature_algorithms; /* list of signature algorithms to use */ char *pinned_key; char *CRLfile; /* CRL to check certificate revocation */ struct curl_blob *cert_blob; @@ -282,8 +275,8 @@ struct ssl_primary_config { char *password; /* TLS password (for, e.g., SRP) */ #endif char *curves; /* list of curves to use */ - unsigned char ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ unsigned int version_max; /* max supported version the client wants to use */ + unsigned char ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ unsigned char version; /* what version the client wants to use */ BIT(verifypeer); /* set TRUE if this is desired */ BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ @@ -513,7 +506,6 @@ struct ConnectBits { #ifdef USE_UNIX_SOCKETS BIT(abstract_unix_socket); #endif - BIT(tls_upgraded); BIT(sock_accepted); /* TRUE if the SECONDARYSOCKET was created with accept() */ BIT(parallel_connect); /* set TRUE when a parallel connect attempt has @@ -563,21 +555,6 @@ struct hostname { #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 { - char *hostname; - struct Curl_dns_entry *dns; - struct thread_data *tdata; - void *resolver; /* resolver state, if it is used in the URL state - - ares_channel e.g. */ - int port; - int status; /* if done is TRUE, this is the status from the callback */ - BIT(done); /* set TRUE when the lookup is complete */ -}; - -#endif - #define FIRSTSOCKET 0 #define SECONDARYSOCKET 1 @@ -742,8 +719,6 @@ struct proxy_info { char *passwd; /* proxy password string, allocated */ }; -struct ldapconninfo; - #define TRNSPRT_TCP 3 #define TRNSPRT_UDP 4 #define TRNSPRT_QUIC 5 @@ -755,6 +730,7 @@ struct ldapconninfo; */ struct connectdata { struct Curl_llist_node cpool_node; /* conncache lists */ + struct Curl_llist_node cshutdn_node; /* cshutdn list */ curl_closesocket_callback fclosesocket; /* function closing the socket(s) */ void *closesocket_client; @@ -763,19 +739,19 @@ struct connectdata { 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) Curl_llist_count(&(c)->easyq) +#define CONN_INUSE(c) (!Curl_uint_spbset_empty(&(c)->xfers_attached)) +#define CONN_ATTACHED(c) Curl_uint_spbset_count(&(c)->xfers_attached) /**** 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 - multi_done(). This entry will be NULL if the connection is reused as then - there is no name resolve done. */ - struct Curl_dns_entry *dns_entry; + /* `meta_hash` is a general key-value store for implementations + * with the lifetime of the connection. + * Elements need to be added with their own destructor to be invoked when + * the connection is cleaned up (see Curl_hash_add2()).*/ + struct Curl_hash meta_hash; /* 'remote_addr' is the particular IP we connected to. it is owned, set * and NULLed by the connected socket filter (if there is one). */ @@ -803,7 +779,6 @@ struct connectdata { 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 poolas idle */ curl_socket_t sock[2]; /* two sockets, the second is used for the data @@ -815,9 +790,6 @@ struct connectdata { 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 @@ -855,7 +827,14 @@ struct connectdata { struct kerberos5data krb5; /* variables into the structure definition, */ #endif /* however, some of them are ftp specific. */ - struct Curl_llist easyq; /* List of easy handles using this connection */ + struct uint_spbset xfers_attached; /* mids of attached transfers */ + /* A connection cache from a SHARE might be used in several multi handles. + * We MUST not reuse connections that are running in another multi, + * for concurrency reasons. That multi might run in another thread. + * `attached_multi` is set by the first transfer attached and cleared + * when the last one is detached. + * NEVER call anything on this multi, just check for equality. */ + struct Curl_multi *attached_multi; /*************** Request - specific items ************/ #if defined(USE_WINDOWS_SSPI) && defined(SECPKG_ATTR_ENDPOINT_BINDINGS) @@ -884,46 +863,6 @@ struct connectdata { struct negotiatedata proxyneg; /* state data for proxy Negotiate auth */ #endif - union { -#ifndef CURL_DISABLE_FTP - struct ftp_conn ftpc; -#endif -#ifdef USE_SSH - struct ssh_conn sshc; -#endif -#ifndef CURL_DISABLE_TFTP - struct tftp_state_data *tftpc; -#endif -#ifndef CURL_DISABLE_IMAP - struct imap_conn imapc; -#endif -#ifndef CURL_DISABLE_POP3 - struct pop3_conn pop3c; -#endif -#ifndef CURL_DISABLE_SMTP - struct smtp_conn smtpc; -#endif -#ifndef CURL_DISABLE_RTSP - struct rtsp_conn rtspc; -#endif -#ifndef CURL_DISABLE_SMB - struct smb_conn smbc; -#endif -#ifdef USE_LIBRTMP - void *rtmp; -#endif -#ifdef USE_OPENLDAP - struct ldapconninfo *ldapc; -#endif -#ifndef CURL_DISABLE_MQTT - struct mqtt_conn mqtt; -#endif -#ifndef CURL_DISABLE_WEBSOCKETS - struct websocket *ws; -#endif - unsigned int unused:1; /* avoids empty union */ - } proto; - #ifdef USE_UNIX_SOCKETS char *unix_domain_socket; #endif @@ -1039,9 +978,6 @@ struct Progress { 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; timediff_t t_postqueue; @@ -1063,7 +999,11 @@ struct Progress { curl_off_t speeder[ CURR_TIME ]; struct curltime speeder_time[ CURR_TIME ]; - int speeder_c; + unsigned char speeder_c; + BIT(hide); + BIT(ul_size_known); + BIT(dl_size_known); + BIT(headers_out); /* when the headers have been written */ BIT(callback); /* set when progress callback is used */ BIT(is_t_startransfer_set); }; @@ -1138,6 +1078,7 @@ typedef enum { EXPIRE_QUIC, EXPIRE_FTP_ACCEPT, EXPIRE_ALPN_EYEBALLS, + EXPIRE_SHUTDOWN, EXPIRE_LAST /* not an actual timer, used as a marker only */ } expire_id; @@ -1211,6 +1152,8 @@ struct UrlState { #endif struct auth authhost; /* auth details for host */ struct auth authproxy; /* auth details for proxy */ + + struct Curl_dns_entry *dns[2]; /* DNS to connect FIRST/SECONDARY */ #ifdef USE_CURL_ASYNC struct Curl_async async; /* asynchronous name resolver data */ #endif @@ -1218,10 +1161,13 @@ struct UrlState { #if defined(USE_OPENSSL) /* void instead of ENGINE to avoid bleeding OpenSSL into this header */ void *engine; - /* this is just a flag -- we do not need to reference the provider in any - * way as OpenSSL takes care of that */ - BIT(provider); - BIT(provider_failed); + /* void instead of OSSL_PROVIDER */ + void *provider; + void *baseprov; + void *libctx; + char *propq; /* for a provider */ + + BIT(provider_loaded); #endif /* USE_OPENSSL */ struct curltime expiretime; /* set this with Curl_expire() only */ struct Curl_tree timenode; /* for the splay stuff */ @@ -1312,10 +1258,9 @@ struct UrlState { 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 */ +#ifndef CURL_DISABLE_HTTP + struct http_negotiation http_neg; +#endif unsigned char httpreq; /* Curl_HttpReq; what kind of HTTP request (if any) is this */ unsigned char select_bits; /* != 0 -> bitmask of socket events for this @@ -1360,6 +1305,7 @@ struct UrlState { BIT(internal); /* internal: true if this easy handle was created for internal use and the user does not have ownership of the handle. */ + BIT(http_ignorecustom); /* ignore custom method from now */ }; /* @@ -1496,6 +1442,7 @@ enum dupstring { #endif STRING_ECH_CONFIG, /* CURLOPT_ECH_CONFIG */ STRING_ECH_PUBLIC, /* CURLOPT_ECH_PUBLIC */ + STRING_SSL_SIGNATURE_ALGORITHMS, /* CURLOPT_SSL_SIGNATURE_ALGORITHMS */ /* -- end of null-terminated strings -- */ @@ -1522,10 +1469,6 @@ enum dupblob { 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 - DoH. */ -typedef int (*multidone_func)(struct Curl_easy *easy, CURLcode result); struct UserDefined { FILE *err; /* the stderr user data goes here */ @@ -1681,10 +1624,6 @@ struct UserDefined { before resolver start */ void *resolver_start_client; /* pointer to pass to resolver start callback */ long upkeep_interval_ms; /* Time between calls for connection upkeep. */ - multidone_func fmultidone; -#ifndef CURL_DISABLE_DOH - 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 void *trailer_data; /* pointer to pass to trailer data callback */ @@ -1694,6 +1633,9 @@ struct UserDefined { struct curl_slist *mail_rcpt; /* linked list of mail recipients */ #endif unsigned int maxconnects; /* Max idle connections in the connection cache */ +#ifdef USE_ECH + int tls_ech; /* TLS ECH configuration */ +#endif unsigned short use_port; /* which port to use (when not using default) */ #ifndef CURL_DISABLE_BINDLOCAL unsigned short localport; /* local port number to bind to */ @@ -1721,11 +1663,13 @@ struct UserDefined { to be used in the library's request(s) */ unsigned char ipver; /* the CURL_IPRESOLVE_* defines in the public header file 0 - whatever, 1 - v2, 2 - v6 */ + unsigned char upload_flags; /* flags set by CURLOPT_UPLOAD_FLAGS */ #ifdef HAVE_GSSAPI /* GSS-API credential delegation, see the documentation of CURLOPT_GSSAPI_DELEGATION */ unsigned char gssapi_delegation; #endif + unsigned char http_follow_mode; /* follow HTTP redirects */ BIT(connect_only); /* make connection/request, then let application use the socket */ BIT(connect_only_ws); /* special websocket connect-only level */ @@ -1774,10 +1718,8 @@ struct UserDefined { us */ BIT(wildcard_enabled); /* enable wildcard matching */ #endif - 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 */ BIT(http_transfer_encoding); /* request compressed HTTP transfer-encoding */ BIT(allow_auth_to_other_hosts); BIT(include_header); /* include received protocol headers in data output */ @@ -1830,9 +1772,7 @@ struct UserDefined { BIT(http09_allowed); /* allow HTTP/0.9 responses */ #ifndef CURL_DISABLE_WEBSOCKETS BIT(ws_raw_mode); -#endif -#ifdef USE_ECH - int tls_ech; /* TLS ECH configuration */ + BIT(ws_no_auto_pong); #endif }; @@ -1842,14 +1782,11 @@ struct UserDefined { #define IS_MIME_POST(a) FALSE #endif -struct Names { - struct Curl_hash *hostcache; - enum { - HCACHE_NONE, /* not pointing to anything */ - HCACHE_MULTI, /* points to a shared one in the multi handle */ - HCACHE_SHARED /* points to a shared one in a shared object */ - } hostcachetype; -}; +/* callback that gets called when a sub easy (data->master_mid set) is + DONE. Called on the master easy. */ +typedef void multi_sub_xfer_done_cb(struct Curl_easy *master_easy, + struct Curl_easy *sub_easy, + CURLcode result); /* * The 'connectdata' struct MUST have all the connection oriented stuff as we @@ -1875,24 +1812,17 @@ struct Curl_easy { /* 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; + unsigned int mid; + unsigned int master_mid; /* if set, this transfer belongs to a master */ + multi_sub_xfer_done_cb *sub_xfer_done; struct connectdata *conn; - 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 */ struct Curl_message msg; /* A single posted message. */ - /* Array with the plain socket numbers this handle takes care of, in no - 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(). */ - struct easy_pollset last_poll; - - struct Names dns; struct Curl_multi *multi; /* if non-NULL, points to the multi handle struct to which this "belongs" when used by the multi interface */ @@ -1900,6 +1830,13 @@ struct Curl_easy { struct to which this "belongs" when used by the easy interface */ struct Curl_share *share; /* Share, handles global variable mutexing */ + + /* `meta_hash` is a general key-value store for implementations + * with the lifetime of the easy handle. + * Elements need to be added with their own destructor to be invoked when + * the easy handle is cleaned up (see Curl_hash_add2()).*/ + struct Curl_hash meta_hash; + #ifdef USE_LIBPSL struct PslCache *psl; /* The associated PSL cache. */ #endif diff --git a/Utilities/cmcurl/lib/vauth/cleartext.c b/Utilities/cmcurl/lib/vauth/cleartext.c index cf8108ac5b..719abd1960 100644 --- a/Utilities/cmcurl/lib/vauth/cleartext.c +++ b/Utilities/cmcurl/lib/vauth/cleartext.c @@ -25,24 +25,23 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \ !defined(CURL_DISABLE_POP3) || \ (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP)) #include -#include "urldata.h" +#include "../urldata.h" -#include "vauth/vauth.h" -#include "warnless.h" -#include "strtok.h" -#include "sendf.h" -#include "curl_printf.h" +#include "vauth.h" +#include "../curlx/warnless.h" +#include "../sendf.h" +#include "../curl_printf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Curl_auth_create_plain_message() diff --git a/Utilities/cmcurl/lib/vauth/cram.c b/Utilities/cmcurl/lib/vauth/cram.c index c51c7285b4..3586e1012d 100644 --- a/Utilities/cmcurl/lib/vauth/cram.c +++ b/Utilities/cmcurl/lib/vauth/cram.c @@ -24,22 +24,22 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifndef CURL_DISABLE_DIGEST_AUTH #include -#include "urldata.h" +#include "../urldata.h" -#include "vauth/vauth.h" -#include "curl_hmac.h" -#include "curl_md5.h" -#include "warnless.h" -#include "curl_printf.h" +#include "vauth.h" +#include "../curl_hmac.h" +#include "../curl_md5.h" +#include "../curlx/warnless.h" +#include "../curl_printf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* diff --git a/Utilities/cmcurl/lib/vauth/digest.c b/Utilities/cmcurl/lib/vauth/digest.c index 0acfcace1d..ec4e82256c 100644 --- a/Utilities/cmcurl/lib/vauth/digest.c +++ b/Utilities/cmcurl/lib/vauth/digest.c @@ -25,31 +25,32 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifndef CURL_DISABLE_DIGEST_AUTH #include -#include "vauth/vauth.h" -#include "vauth/digest.h" -#include "urldata.h" -#include "curl_base64.h" -#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" -#include "strcase.h" -#include "curl_printf.h" -#include "rand.h" +#include "vauth.h" +#include "digest.h" +#include "../urldata.h" +#include "../curlx/base64.h" +#include "../curl_hmac.h" +#include "../curl_md5.h" +#include "../curl_sha256.h" +#include "../curl_sha512_256.h" +#include "../vtls/vtls.h" +#include "../curlx/warnless.h" +#include "../curlx/strparse.h" +#include "../strcase.h" +#include "../curl_printf.h" +#include "../rand.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" +#ifndef USE_WINDOWS_SSPI #define SESSION_ALGO 1 /* for algos with this bit set */ #define ALGO_MD5 0 @@ -59,7 +60,6 @@ #define ALGO_SHA512_256 4 #define ALGO_SHA512_256SESS (ALGO_SHA512_256 | SESSION_ALGO) -#if !defined(USE_WINDOWS_SSPI) #define DIGEST_QOP_VALUE_AUTH (1 << 0) #define DIGEST_QOP_VALUE_AUTH_INT (1 << 1) #define DIGEST_QOP_VALUE_AUTH_CONF (1 << 2) @@ -141,8 +141,8 @@ bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, return TRUE; } -#if !defined(USE_WINDOWS_SSPI) -/* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ASCII string */ +#ifndef USE_WINDOWS_SSPI +/* 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 */ { @@ -165,7 +165,7 @@ static char *auth_digest_string_quoted(const char *source) { char *dest; const char *s = source; - size_t n = 1; /* null terminator */ + size_t n = 1; /* null-terminator */ /* Calculate size needed */ while(*s) { @@ -219,33 +219,21 @@ static bool auth_digest_get_key_value(const char *chlg, static CURLcode auth_digest_get_qop_values(const char *options, int *value) { - char *tmp; - char *token; - char *tok_buf = NULL; - + struct Curl_str out; /* Initialise the output */ *value = 0; - /* Tokenise the list of qop values. Use a temporary clone of the buffer since - Curl_strtok_r() ruins it. */ - tmp = strdup(options); - if(!tmp) - return CURLE_OUT_OF_MEMORY; - - token = Curl_strtok_r(tmp, ",", &tok_buf); - while(token) { - if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) + while(!curlx_str_until(&options, &out, 32, ',')) { + if(curlx_str_casecompare(&out, DIGEST_QOP_VALUE_STRING_AUTH)) *value |= DIGEST_QOP_VALUE_AUTH; - else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) + else if(curlx_str_casecompare(&out, DIGEST_QOP_VALUE_STRING_AUTH_INT)) *value |= DIGEST_QOP_VALUE_AUTH_INT; - else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF)) + else if(curlx_str_casecompare(&out, DIGEST_QOP_VALUE_STRING_AUTH_CONF)) *value |= DIGEST_QOP_VALUE_AUTH_CONF; - - token = Curl_strtok_r(NULL, ",", &tok_buf); + if(curlx_str_single(&options, ',')) + break; } - free(tmp); - return CURLE_OK; } @@ -504,10 +492,6 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, struct digestdata *digest) { bool before = FALSE; /* got a nonce before */ - bool foundAuth = FALSE; - bool foundAuthInt = FALSE; - char *token = NULL; - char *tmp = NULL; /* If we already have received a nonce, keep that in mind */ if(digest->nonce) @@ -551,29 +535,25 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, return CURLE_OUT_OF_MEMORY; } else if(strcasecompare(value, "qop")) { - char *tok_buf = NULL; - /* Tokenize the list and choose auth if possible, use a temporary - clone of the buffer since Curl_strtok_r() ruins it */ - tmp = strdup(content); - if(!tmp) - return CURLE_OUT_OF_MEMORY; - - token = Curl_strtok_r(tmp, ",", &tok_buf); - while(token) { - /* Pass additional spaces here */ + const char *token = content; + struct Curl_str out; + bool foundAuth = FALSE; + bool foundAuthInt = FALSE; + /* Pass leading spaces */ + while(*token && ISBLANK(*token)) + token++; + while(!curlx_str_until(&token, &out, 32, ',')) { + if(curlx_str_casecompare(&out, DIGEST_QOP_VALUE_STRING_AUTH)) + foundAuth = TRUE; + else if(curlx_str_casecompare(&out, + DIGEST_QOP_VALUE_STRING_AUTH_INT)) + foundAuthInt = TRUE; + if(curlx_str_single(&token, ',')) + break; while(*token && ISBLANK(*token)) token++; - if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) { - foundAuth = TRUE; - } - else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) { - foundAuthInt = TRUE; - } - token = Curl_strtok_r(NULL, ",", &tok_buf); } - free(tmp); - /* Select only auth or auth-int. Otherwise, ignore */ if(foundAuth) { free(digest->qop); @@ -719,8 +699,8 @@ static CURLcode auth_create_digest_http_message( if(result) return result; - result = Curl_base64_encode(cnoncebuf, sizeof(cnoncebuf), - &cnonce, &cnonce_sz); + result = curlx_base64_encode(cnoncebuf, sizeof(cnoncebuf), + &cnonce, &cnonce_sz); if(result) return result; diff --git a/Utilities/cmcurl/lib/vauth/digest_sspi.c b/Utilities/cmcurl/lib/vauth/digest_sspi.c index c06ee1064f..2761c59965 100644 --- a/Utilities/cmcurl/lib/vauth/digest_sspi.c +++ b/Utilities/cmcurl/lib/vauth/digest_sspi.c @@ -25,25 +25,25 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_WINDOWS_SSPI) && !defined(CURL_DISABLE_DIGEST_AUTH) #include -#include "vauth/vauth.h" -#include "vauth/digest.h" -#include "urldata.h" -#include "warnless.h" -#include "curl_multibyte.h" -#include "sendf.h" -#include "strdup.h" -#include "strcase.h" -#include "strerror.h" +#include "vauth.h" +#include "digest.h" +#include "../urldata.h" +#include "../curlx/warnless.h" +#include "../curlx/multibyte.h" +#include "../sendf.h" +#include "../strdup.h" +#include "../strcase.h" +#include "../strerror.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Curl_auth_is_digest_supported() @@ -61,8 +61,9 @@ bool Curl_auth_is_digest_supported(void) /* Query the security package for Digest */ status = - Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), - &SecurityPackage); + Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + &SecurityPackage); /* Release the package buffer as it is not required anymore */ if(status == SEC_E_OK) { @@ -121,8 +122,9 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, /* Query the security package for DigestSSP */ status = - Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), - &SecurityPackage); + Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + &SecurityPackage); if(status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; @@ -163,10 +165,10 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, /* Acquire our credentials handle */ status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *) TEXT(SP_NAME_DIGEST), - SECPKG_CRED_OUTBOUND, NULL, - p_identity, NULL, NULL, - &credentials, &expiry); + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + SECPKG_CRED_OUTBOUND, NULL, + p_identity, NULL, NULL, + &credentials, &expiry); if(status != SEC_E_OK) { Curl_sspi_free_identity(p_identity); @@ -180,7 +182,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, chlg_desc.cBuffers = 1; chlg_desc.pBuffers = &chlg_buf; chlg_buf.BufferType = SECBUFFER_TOKEN; - chlg_buf.pvBuffer = (void *) Curl_bufref_ptr(chlg); + chlg_buf.pvBuffer = CURL_UNCONST(Curl_bufref_ptr(chlg)); chlg_buf.cbBuffer = curlx_uztoul(Curl_bufref_len(chlg)); /* Setup the response "output" security buffer */ @@ -240,7 +242,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, /* * Curl_override_sspi_http_realm() * - * This is used to populate the domain in a SSPI identity structure + * This is used to populate the domain in an SSPI identity structure * The realm is extracted from the challenge message and used as the * domain if it is not already explicitly set. * @@ -271,7 +273,7 @@ CURLcode Curl_override_sspi_http_realm(const char *chlg, if(strcasecompare(value, "realm")) { /* Setup identity's domain and length */ - domain.tchar_ptr = curlx_convert_UTF8_to_tchar((char *) content); + domain.tchar_ptr = curlx_convert_UTF8_to_tchar(content); if(!domain.tchar_ptr) return CURLE_OUT_OF_MEMORY; @@ -413,8 +415,9 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, /* Query the security package for DigestSSP */ status = - Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), - &SecurityPackage); + Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + &SecurityPackage); if(status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; @@ -454,10 +457,10 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, chlg_buf[0].pvBuffer = NULL; chlg_buf[0].cbBuffer = 0; chlg_buf[1].BufferType = SECBUFFER_PKG_PARAMS; - chlg_buf[1].pvBuffer = (void *) request; + chlg_buf[1].pvBuffer = CURL_UNCONST(request); chlg_buf[1].cbBuffer = curlx_uztoul(strlen((const char *) request)); chlg_buf[2].BufferType = SECBUFFER_PKG_PARAMS; - chlg_buf[2].pvBuffer = (void *) uripath; + chlg_buf[2].pvBuffer = CURL_UNCONST(uripath); chlg_buf[2].cbBuffer = curlx_uztoul(strlen((const char *) uripath)); chlg_buf[3].BufferType = SECBUFFER_PKG_PARAMS; chlg_buf[3].pvBuffer = NULL; @@ -534,10 +537,10 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, /* Acquire our credentials handle */ status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *) TEXT(SP_NAME_DIGEST), - SECPKG_CRED_OUTBOUND, NULL, - p_identity, NULL, NULL, - &credentials, &expiry); + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_DIGEST)), + SECPKG_CRED_OUTBOUND, NULL, + p_identity, NULL, NULL, + &credentials, &expiry); if(status != SEC_E_OK) { Curl_sspi_free_identity(p_identity); free(output_token); @@ -553,7 +556,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, chlg_buf[0].pvBuffer = digest->input_token; chlg_buf[0].cbBuffer = curlx_uztoul(digest->input_token_len); chlg_buf[1].BufferType = SECBUFFER_PKG_PARAMS; - chlg_buf[1].pvBuffer = (void *) request; + chlg_buf[1].pvBuffer = CURL_UNCONST(request); chlg_buf[1].cbBuffer = curlx_uztoul(strlen((const char *) request)); chlg_buf[2].BufferType = SECBUFFER_PKG_PARAMS; chlg_buf[2].pvBuffer = NULL; @@ -567,7 +570,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, resp_buf.pvBuffer = output_token; resp_buf.cbBuffer = curlx_uztoul(token_max); - spn = curlx_convert_UTF8_to_tchar((char *) uripath); + spn = curlx_convert_UTF8_to_tchar((const char *) uripath); if(!spn) { Curl_pSecFn->FreeCredentialsHandle(&credentials); @@ -579,8 +582,12 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, /* Allocate our new context handle */ digest->http_context = calloc(1, sizeof(CtxtHandle)); - if(!digest->http_context) + if(!digest->http_context) { + curlx_unicodefree(spn); + Curl_sspi_free_identity(p_identity); + free(output_token); return CURLE_OUT_OF_MEMORY; + } /* Generate our response message */ status = Curl_pSecFn->InitializeSecurityContext(&credentials, NULL, diff --git a/Utilities/cmcurl/lib/vauth/gsasl.c b/Utilities/cmcurl/lib/vauth/gsasl.c index ee11b6039d..3684c8f4b2 100644 --- a/Utilities/cmcurl/lib/vauth/gsasl.c +++ b/Utilities/cmcurl/lib/vauth/gsasl.c @@ -24,22 +24,22 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_GSASL #include -#include "vauth/vauth.h" -#include "urldata.h" -#include "sendf.h" +#include "vauth.h" +#include "../urldata.h" +#include "../sendf.h" #include /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" bool Curl_auth_gsasl_is_supported(struct Curl_easy *data, const char *mech, diff --git a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c index beaf027297..b559040617 100644 --- a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c +++ b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c @@ -25,22 +25,22 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(HAVE_GSSAPI) && defined(USE_KERBEROS5) #include -#include "vauth/vauth.h" -#include "curl_sasl.h" -#include "urldata.h" -#include "curl_gssapi.h" -#include "sendf.h" -#include "curl_printf.h" +#include "vauth.h" +#include "../curl_sasl.h" +#include "../urldata.h" +#include "../curl_gssapi.h" +#include "../sendf.h" +#include "../curl_printf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" #if defined(__GNUC__) && defined(__APPLE__) #pragma GCC diagnostic push @@ -133,7 +133,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, infof(data, "GSSAPI handshake failure (empty challenge message)"); return CURLE_BAD_CONTENT_ENCODING; } - input_token.value = (void *) Curl_bufref_ptr(chlg); + input_token.value = CURL_UNCONST(Curl_bufref_ptr(chlg)); input_token.length = Curl_bufref_len(chlg); } @@ -210,7 +210,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, } /* Setup the challenge "input" security buffer */ - input_token.value = (void *) Curl_bufref_ptr(chlg); + input_token.value = CURL_UNCONST(Curl_bufref_ptr(chlg)); input_token.length = Curl_bufref_len(chlg); /* Decrypt the inbound challenge and obtain the qop */ diff --git a/Utilities/cmcurl/lib/vauth/krb5_sspi.c b/Utilities/cmcurl/lib/vauth/krb5_sspi.c index 00a5db125d..a29358569d 100644 --- a/Utilities/cmcurl/lib/vauth/krb5_sspi.c +++ b/Utilities/cmcurl/lib/vauth/krb5_sspi.c @@ -24,21 +24,21 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_WINDOWS_SSPI) && defined(USE_KERBEROS5) #include -#include "vauth/vauth.h" -#include "urldata.h" -#include "warnless.h" -#include "curl_multibyte.h" -#include "sendf.h" +#include "vauth.h" +#include "../urldata.h" +#include "../curlx/warnless.h" +#include "../curlx/multibyte.h" +#include "../sendf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Curl_auth_is_gssapi_supported() @@ -55,9 +55,9 @@ bool Curl_auth_is_gssapi_supported(void) SECURITY_STATUS status; /* Query the security package for Kerberos */ - status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) - TEXT(SP_NAME_KERBEROS), - &SecurityPackage); + status = Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), + &SecurityPackage); /* Release the package buffer as it is not required anymore */ if(status == SEC_E_OK) { @@ -118,9 +118,9 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, if(!krb5->output_token) { /* Query the security package for Kerberos */ - status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) - TEXT(SP_NAME_KERBEROS), - &SecurityPackage); + status = Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), + &SecurityPackage); if(status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; @@ -159,11 +159,10 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, /* Acquire our credentials handle */ status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *) - TEXT(SP_NAME_KERBEROS), - SECPKG_CRED_OUTBOUND, NULL, - krb5->p_identity, NULL, NULL, - krb5->credentials, &expiry); + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_KERBEROS)), + SECPKG_CRED_OUTBOUND, NULL, + krb5->p_identity, NULL, NULL, + krb5->credentials, &expiry); if(status != SEC_E_OK) return CURLE_LOGIN_DENIED; @@ -184,7 +183,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, chlg_desc.cBuffers = 1; chlg_desc.pBuffers = &chlg_buf; chlg_buf.BufferType = SECBUFFER_TOKEN; - chlg_buf.pvBuffer = (void *) Curl_bufref_ptr(chlg); + chlg_buf.pvBuffer = CURL_UNCONST(Curl_bufref_ptr(chlg)); chlg_buf.cbBuffer = curlx_uztoul(Curl_bufref_len(chlg)); } @@ -297,7 +296,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, input_desc.cBuffers = 2; input_desc.pBuffers = input_buf; input_buf[0].BufferType = SECBUFFER_STREAM; - input_buf[0].pvBuffer = (void *) Curl_bufref_ptr(chlg); + input_buf[0].pvBuffer = CURL_UNCONST(Curl_bufref_ptr(chlg)); input_buf[0].cbBuffer = curlx_uztoul(Curl_bufref_len(chlg)); input_buf[1].BufferType = SECBUFFER_DATA; input_buf[1].pvBuffer = NULL; diff --git a/Utilities/cmcurl/lib/vauth/ntlm.c b/Utilities/cmcurl/lib/vauth/ntlm.c index f8f6aea0e9..5cda790dda 100644 --- a/Utilities/cmcurl/lib/vauth/ntlm.c +++ b/Utilities/cmcurl/lib/vauth/ntlm.c @@ -22,7 +22,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_NTLM) && !defined(USE_WINDOWS_SSPI) @@ -35,26 +35,130 @@ #define DEBUG_ME 0 -#include "urldata.h" -#include "sendf.h" -#include "curl_ntlm_core.h" -#include "curl_gethostname.h" -#include "curl_multibyte.h" -#include "curl_md5.h" -#include "warnless.h" -#include "rand.h" -#include "vtls/vtls.h" -#include "strdup.h" +#include "../urldata.h" +#include "../sendf.h" +#include "../curl_ntlm_core.h" +#include "../curl_gethostname.h" +#include "../curlx/multibyte.h" +#include "../curl_md5.h" +#include "../curlx/warnless.h" +#include "../rand.h" +#include "../vtls/vtls.h" +#include "../strdup.h" -#define BUILDING_CURL_NTLM_MSGS_C -#include "vauth/vauth.h" -#include "vauth/ntlm.h" -#include "curl_endian.h" -#include "curl_printf.h" +#include "vauth.h" +#include "../curl_endian.h" +#include "../curl_printf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" + + +/* NTLM buffer fixed size, large enough for long user + host + domain */ +#define NTLM_BUFSIZE 1024 + +/* Flag bits definitions based on + https://davenport.sourceforge.net/ntlm.html */ + +#define NTLMFLAG_NEGOTIATE_UNICODE (1<<0) +/* Indicates that Unicode strings are supported for use in security buffer + data. */ + +#define NTLMFLAG_NEGOTIATE_OEM (1<<1) +/* Indicates that OEM strings are supported for use in security buffer data. */ + +#define NTLMFLAG_REQUEST_TARGET (1<<2) +/* Requests that the server's authentication realm be included in the Type 2 + message. */ + +/* unknown (1<<3) */ +#define NTLMFLAG_NEGOTIATE_SIGN (1<<4) +/* Specifies that authenticated communication between the client and server + should carry a digital signature (message integrity). */ + +#define NTLMFLAG_NEGOTIATE_SEAL (1<<5) +/* Specifies that authenticated communication between the client and server + should be encrypted (message confidentiality). */ + +#define NTLMFLAG_NEGOTIATE_DATAGRAM_STYLE (1<<6) +/* Indicates that datagram authentication is being used. */ + +#define NTLMFLAG_NEGOTIATE_LM_KEY (1<<7) +/* Indicates that the LAN Manager session key should be used for signing and + sealing authenticated communications. */ + +#define NTLMFLAG_NEGOTIATE_NTLM_KEY (1<<9) +/* Indicates that NTLM authentication is being used. */ + +/* unknown (1<<10) */ + +#define NTLMFLAG_NEGOTIATE_ANONYMOUS (1<<11) +/* Sent by the client in the Type 3 message to indicate that an anonymous + context has been established. This also affects the response fields. */ + +#define NTLMFLAG_NEGOTIATE_DOMAIN_SUPPLIED (1<<12) +/* Sent by the client in the Type 1 message to indicate that a desired + authentication realm is included in the message. */ + +#define NTLMFLAG_NEGOTIATE_WORKSTATION_SUPPLIED (1<<13) +/* Sent by the client in the Type 1 message to indicate that the client + workstation's name is included in the message. */ + +#define NTLMFLAG_NEGOTIATE_LOCAL_CALL (1<<14) +/* Sent by the server to indicate that the server and client are on the same + machine. Implies that the client may use a pre-established local security + context rather than responding to the challenge. */ + +#define NTLMFLAG_NEGOTIATE_ALWAYS_SIGN (1<<15) +/* Indicates that authenticated communication between the client and server + should be signed with a "dummy" signature. */ + +#define NTLMFLAG_TARGET_TYPE_DOMAIN (1<<16) +/* Sent by the server in the Type 2 message to indicate that the target + authentication realm is a domain. */ + +#define NTLMFLAG_TARGET_TYPE_SERVER (1<<17) +/* Sent by the server in the Type 2 message to indicate that the target + authentication realm is a server. */ + +#define NTLMFLAG_TARGET_TYPE_SHARE (1<<18) +/* Sent by the server in the Type 2 message to indicate that the target + authentication realm is a share. Presumably, this is for share-level + authentication. Usage is unclear. */ + +#define NTLMFLAG_NEGOTIATE_NTLM2_KEY (1<<19) +/* Indicates that the NTLM2 signing and sealing scheme should be used for + protecting authenticated communications. */ + +#define NTLMFLAG_REQUEST_INIT_RESPONSE (1<<20) +/* unknown purpose */ + +#define NTLMFLAG_REQUEST_ACCEPT_RESPONSE (1<<21) +/* unknown purpose */ + +#define NTLMFLAG_REQUEST_NONNT_SESSION_KEY (1<<22) +/* unknown purpose */ + +#define NTLMFLAG_NEGOTIATE_TARGET_INFO (1<<23) +/* Sent by the server in the Type 2 message to indicate that it is including a + Target Information block in the message. */ + +/* unknown (1<24) */ +/* unknown (1<25) */ +/* unknown (1<26) */ +/* unknown (1<27) */ +/* unknown (1<28) */ + +#define NTLMFLAG_NEGOTIATE_128 (1<<29) +/* Indicates that 128-bit encryption is supported. */ + +#define NTLMFLAG_NEGOTIATE_KEY_EXCHANGE (1<<30) +/* Indicates that the client will provide an encrypted master key in + the "Session Key" field of the Type 3 message. */ + +#define NTLMFLAG_NEGOTIATE_56 (1<<31) +/* Indicates that 56-bit encryption is supported. */ /* "NTLMSSP" signature is always in ASCII regardless of the platform */ #define NTLMSSP_SIGNATURE "\x4e\x54\x4c\x4d\x53\x53\x50" diff --git a/Utilities/cmcurl/lib/vauth/ntlm.h b/Utilities/cmcurl/lib/vauth/ntlm.h deleted file mode 100644 index 31ce921cd1..0000000000 --- a/Utilities/cmcurl/lib/vauth/ntlm.h +++ /dev/null @@ -1,143 +0,0 @@ -#ifndef HEADER_VAUTH_NTLM_H -#define HEADER_VAUTH_NTLM_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" - -#ifdef USE_NTLM - -/* NTLM buffer fixed size, large enough for long user + host + domain */ -#define NTLM_BUFSIZE 1024 - -/* Stuff only required for curl_ntlm_msgs.c */ -#ifdef BUILDING_CURL_NTLM_MSGS_C - -/* Flag bits definitions based on - https://davenport.sourceforge.net/ntlm.html */ - -#define NTLMFLAG_NEGOTIATE_UNICODE (1<<0) -/* Indicates that Unicode strings are supported for use in security buffer - data. */ - -#define NTLMFLAG_NEGOTIATE_OEM (1<<1) -/* Indicates that OEM strings are supported for use in security buffer data. */ - -#define NTLMFLAG_REQUEST_TARGET (1<<2) -/* Requests that the server's authentication realm be included in the Type 2 - message. */ - -/* unknown (1<<3) */ -#define NTLMFLAG_NEGOTIATE_SIGN (1<<4) -/* Specifies that authenticated communication between the client and server - should carry a digital signature (message integrity). */ - -#define NTLMFLAG_NEGOTIATE_SEAL (1<<5) -/* Specifies that authenticated communication between the client and server - should be encrypted (message confidentiality). */ - -#define NTLMFLAG_NEGOTIATE_DATAGRAM_STYLE (1<<6) -/* Indicates that datagram authentication is being used. */ - -#define NTLMFLAG_NEGOTIATE_LM_KEY (1<<7) -/* Indicates that the LAN Manager session key should be used for signing and - sealing authenticated communications. */ - -#define NTLMFLAG_NEGOTIATE_NTLM_KEY (1<<9) -/* Indicates that NTLM authentication is being used. */ - -/* unknown (1<<10) */ - -#define NTLMFLAG_NEGOTIATE_ANONYMOUS (1<<11) -/* Sent by the client in the Type 3 message to indicate that an anonymous - context has been established. This also affects the response fields. */ - -#define NTLMFLAG_NEGOTIATE_DOMAIN_SUPPLIED (1<<12) -/* Sent by the client in the Type 1 message to indicate that a desired - authentication realm is included in the message. */ - -#define NTLMFLAG_NEGOTIATE_WORKSTATION_SUPPLIED (1<<13) -/* Sent by the client in the Type 1 message to indicate that the client - workstation's name is included in the message. */ - -#define NTLMFLAG_NEGOTIATE_LOCAL_CALL (1<<14) -/* Sent by the server to indicate that the server and client are on the same - machine. Implies that the client may use a pre-established local security - context rather than responding to the challenge. */ - -#define NTLMFLAG_NEGOTIATE_ALWAYS_SIGN (1<<15) -/* Indicates that authenticated communication between the client and server - should be signed with a "dummy" signature. */ - -#define NTLMFLAG_TARGET_TYPE_DOMAIN (1<<16) -/* Sent by the server in the Type 2 message to indicate that the target - authentication realm is a domain. */ - -#define NTLMFLAG_TARGET_TYPE_SERVER (1<<17) -/* Sent by the server in the Type 2 message to indicate that the target - authentication realm is a server. */ - -#define NTLMFLAG_TARGET_TYPE_SHARE (1<<18) -/* Sent by the server in the Type 2 message to indicate that the target - authentication realm is a share. Presumably, this is for share-level - authentication. Usage is unclear. */ - -#define NTLMFLAG_NEGOTIATE_NTLM2_KEY (1<<19) -/* Indicates that the NTLM2 signing and sealing scheme should be used for - protecting authenticated communications. */ - -#define NTLMFLAG_REQUEST_INIT_RESPONSE (1<<20) -/* unknown purpose */ - -#define NTLMFLAG_REQUEST_ACCEPT_RESPONSE (1<<21) -/* unknown purpose */ - -#define NTLMFLAG_REQUEST_NONNT_SESSION_KEY (1<<22) -/* unknown purpose */ - -#define NTLMFLAG_NEGOTIATE_TARGET_INFO (1<<23) -/* Sent by the server in the Type 2 message to indicate that it is including a - Target Information block in the message. */ - -/* unknown (1<24) */ -/* unknown (1<25) */ -/* unknown (1<26) */ -/* unknown (1<27) */ -/* unknown (1<28) */ - -#define NTLMFLAG_NEGOTIATE_128 (1<<29) -/* Indicates that 128-bit encryption is supported. */ - -#define NTLMFLAG_NEGOTIATE_KEY_EXCHANGE (1<<30) -/* Indicates that the client will provide an encrypted master key in - the "Session Key" field of the Type 3 message. */ - -#define NTLMFLAG_NEGOTIATE_56 (1<<31) -/* Indicates that 56-bit encryption is supported. */ - -#endif /* BUILDING_CURL_NTLM_MSGS_C */ - -#endif /* USE_NTLM */ - -#endif /* HEADER_VAUTH_NTLM_H */ diff --git a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c index 6421c6ac1b..86b4bccfdf 100644 --- a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c +++ b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c @@ -22,23 +22,23 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_WINDOWS_SSPI) && defined(USE_NTLM) #include -#include "vauth/vauth.h" -#include "urldata.h" -#include "curl_ntlm_core.h" -#include "warnless.h" -#include "curl_multibyte.h" -#include "sendf.h" -#include "strdup.h" +#include "vauth.h" +#include "../urldata.h" +#include "../curl_ntlm_core.h" +#include "../curlx/warnless.h" +#include "../curlx/multibyte.h" +#include "../sendf.h" +#include "../strdup.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Curl_auth_is_ntlm_supported() @@ -55,8 +55,9 @@ bool Curl_auth_is_ntlm_supported(void) SECURITY_STATUS status; /* Query the security package for NTLM */ - status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), - &SecurityPackage); + status = Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NTLM)), + &SecurityPackage); /* Release the package buffer as it is not required anymore */ if(status == SEC_E_OK) { @@ -103,8 +104,9 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, Curl_auth_cleanup_ntlm(ntlm); /* Query the security package for NTLM */ - status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), - &SecurityPackage); + status = Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NTLM)), + &SecurityPackage); if(status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; @@ -142,10 +144,10 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, /* Acquire our credentials handle */ status = Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *) TEXT(SP_NAME_NTLM), - SECPKG_CRED_OUTBOUND, NULL, - ntlm->p_identity, NULL, NULL, - ntlm->credentials, &expiry); + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NTLM)), + SECPKG_CRED_OUTBOUND, NULL, + ntlm->p_identity, NULL, NULL, + ntlm->credentials, &expiry); if(status != SEC_E_OK) return CURLE_LOGIN_DENIED; diff --git a/Utilities/cmcurl/lib/vauth/oauth2.c b/Utilities/cmcurl/lib/vauth/oauth2.c index dc94afa365..76e77c6a2f 100644 --- a/Utilities/cmcurl/lib/vauth/oauth2.c +++ b/Utilities/cmcurl/lib/vauth/oauth2.c @@ -24,22 +24,22 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \ !defined(CURL_DISABLE_POP3) || \ (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP)) #include -#include "urldata.h" +#include "../urldata.h" -#include "vauth/vauth.h" -#include "warnless.h" -#include "curl_printf.h" +#include "vauth.h" +#include "../curlx/warnless.h" +#include "../curl_printf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Curl_auth_create_oauth_bearer_message() diff --git a/Utilities/cmcurl/lib/vauth/spnego_gssapi.c b/Utilities/cmcurl/lib/vauth/spnego_gssapi.c index 55232a8e4a..b17ee46d17 100644 --- a/Utilities/cmcurl/lib/vauth/spnego_gssapi.c +++ b/Utilities/cmcurl/lib/vauth/spnego_gssapi.c @@ -24,23 +24,23 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(HAVE_GSSAPI) && defined(USE_SPNEGO) #include -#include "vauth/vauth.h" -#include "urldata.h" -#include "curl_base64.h" -#include "curl_gssapi.h" -#include "warnless.h" -#include "curl_multibyte.h" -#include "sendf.h" +#include "vauth.h" +#include "../urldata.h" +#include "../curlx/base64.h" +#include "../curl_gssapi.h" +#include "../curlx/warnless.h" +#include "../curlx/multibyte.h" +#include "../sendf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" #if defined(__GNUC__) && defined(__APPLE__) #pragma GCC diagnostic push @@ -139,7 +139,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(chlg64 && *chlg64) { /* Decode the base-64 encoded challenge message */ if(*chlg64 != '=') { - result = Curl_base64_decode(chlg64, &chlg, &chlglen); + result = curlx_base64_decode(chlg64, &chlg, &chlglen); if(result) return result; } @@ -156,10 +156,10 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, } /* Set channel binding data if available */ - if(nego->channel_binding_data.leng > 0) { + if(curlx_dyn_len(&nego->channel_binding_data)) { 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.application_data.length = curlx_dyn_len(&nego->channel_binding_data); + chan.application_data.value = curlx_dyn_ptr(&nego->channel_binding_data); chan_bindings = &chan; } @@ -228,9 +228,9 @@ CURLcode Curl_auth_create_spnego_message(struct negotiatedata *nego, OM_uint32 minor_status; /* Base64 encode the already generated response */ - result = Curl_base64_encode(nego->output_token.value, - nego->output_token.length, - outptr, outlen); + result = curlx_base64_encode(nego->output_token.value, + nego->output_token.length, + outptr, outlen); if(result) { gss_release_buffer(&minor_status, &nego->output_token); diff --git a/Utilities/cmcurl/lib/vauth/spnego_sspi.c b/Utilities/cmcurl/lib/vauth/spnego_sspi.c index 2439e7319f..c19a1ff423 100644 --- a/Utilities/cmcurl/lib/vauth/spnego_sspi.c +++ b/Utilities/cmcurl/lib/vauth/spnego_sspi.c @@ -24,23 +24,23 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_WINDOWS_SSPI) && defined(USE_SPNEGO) #include -#include "vauth/vauth.h" -#include "urldata.h" -#include "curl_base64.h" -#include "warnless.h" -#include "curl_multibyte.h" -#include "sendf.h" -#include "strerror.h" +#include "vauth.h" +#include "../urldata.h" +#include "../curlx/base64.h" +#include "../curlx/warnless.h" +#include "../curlx/multibyte.h" +#include "../sendf.h" +#include "../strerror.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Curl_auth_is_spnego_supported() @@ -57,9 +57,9 @@ bool Curl_auth_is_spnego_supported(void) SECURITY_STATUS status; /* Query the security package for Negotiate */ - status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) - TEXT(SP_NAME_NEGOTIATE), - &SecurityPackage); + status = Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), + &SecurityPackage); /* Release the package buffer as it is not required anymore */ if(status == SEC_E_OK) { @@ -128,9 +128,9 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(!nego->output_token) { /* Query the security package for Negotiate */ - nego->status = (DWORD)Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) - TEXT(SP_NAME_NEGOTIATE), - &SecurityPackage); + nego->status = (DWORD)Curl_pSecFn->QuerySecurityPackageInfo( + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), + &SecurityPackage); if(nego->status != SEC_E_OK) { failf(data, "SSPI: could not get auth info"); return CURLE_AUTH_ERROR; @@ -170,10 +170,10 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, /* Acquire our credentials handle */ nego->status = (DWORD) Curl_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *)TEXT(SP_NAME_NEGOTIATE), - SECPKG_CRED_OUTBOUND, NULL, - nego->p_identity, NULL, NULL, - nego->credentials, &expiry); + (TCHAR *)CURL_UNCONST(TEXT(SP_NAME_NEGOTIATE)), + SECPKG_CRED_OUTBOUND, NULL, + nego->p_identity, NULL, NULL, + nego->credentials, &expiry); if(nego->status != SEC_E_OK) return CURLE_AUTH_ERROR; @@ -186,7 +186,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, if(chlg64 && *chlg64) { /* Decode the base-64 encoded challenge message */ if(*chlg64 != '=') { - result = Curl_base64_decode(chlg64, &chlg, &chlglen); + result = curlx_base64_decode(chlg64, &chlg, &chlglen); if(result) return result; } @@ -308,9 +308,9 @@ CURLcode Curl_auth_create_spnego_message(struct negotiatedata *nego, char **outptr, size_t *outlen) { /* Base64 encode the already generated response */ - CURLcode result = Curl_base64_encode((const char *) nego->output_token, - nego->output_token_length, outptr, - outlen); + CURLcode result = curlx_base64_encode((const char *) nego->output_token, + nego->output_token_length, outptr, + outlen); if(!result && (!*outptr || !*outlen)) { free(*outptr); result = CURLE_REMOTE_ACCESS_DENIED; diff --git a/Utilities/cmcurl/lib/vauth/vauth.c b/Utilities/cmcurl/lib/vauth/vauth.c index 171e53fb13..e2872b2522 100644 --- a/Utilities/cmcurl/lib/vauth/vauth.c +++ b/Utilities/cmcurl/lib/vauth/vauth.c @@ -22,24 +22,24 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #include #include "vauth.h" -#include "urldata.h" -#include "strcase.h" -#include "curl_multibyte.h" -#include "curl_printf.h" +#include "../urldata.h" +#include "../strcase.h" +#include "../curlx/multibyte.h" +#include "../curl_printf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Curl_auth_build_spn() * - * This is used to build a SPN string in the following formats: + * This is used to build an SPN string in the following formats: * * service/host@realm (Not currently used) * service/host (Not used by GSS-API) diff --git a/Utilities/cmcurl/lib/vauth/vauth.h b/Utilities/cmcurl/lib/vauth/vauth.h index 7e823484f6..58b13f582c 100644 --- a/Utilities/cmcurl/lib/vauth/vauth.h +++ b/Utilities/cmcurl/lib/vauth/vauth.h @@ -26,7 +26,7 @@ #include -#include "bufref.h" +#include "../bufref.h" struct Curl_easy; @@ -60,7 +60,7 @@ struct gsasldata; */ bool Curl_auth_allowed_to_host(struct Curl_easy *data); -/* This is used to build a SPN string */ +/* This is used to build an SPN string */ #if !defined(USE_WINDOWS_SSPI) char *Curl_auth_build_spn(const char *service, const char *host, const char *realm); @@ -167,6 +167,8 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, /* This is used to clean up the NTLM specific data */ void Curl_auth_cleanup_ntlm(struct ntlmdata *ntlm); +#else +#define Curl_auth_is_ntlm_supported() FALSE #endif /* USE_NTLM */ /* This is used to generate a base64 encoded OAuth 2.0 message */ @@ -207,6 +209,8 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, /* This is used to clean up the GSSAPI specific data */ void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5); +#else +#define Curl_auth_is_gssapi_supported() FALSE #endif /* USE_KERBEROS5 */ #if defined(USE_SPNEGO) diff --git a/Utilities/cmcurl/lib/version.c b/Utilities/cmcurl/lib/version.c index 1cfabaa722..4451d3e534 100644 --- a/Utilities/cmcurl/lib/version.c +++ b/Utilities/cmcurl/lib/version.c @@ -422,8 +422,6 @@ static int idn_present(curl_version_info_data *info) return info->libidn != NULL; #endif } -#else -#define idn_present NULL #endif #if defined(USE_SSL) && !defined(CURL_DISABLE_PROXY) && \ diff --git a/Utilities/cmcurl/lib/vquic/curl_msh3.c b/Utilities/cmcurl/lib/vquic/curl_msh3.c index e0b5949cfd..cbeb650516 100644 --- a/Utilities/cmcurl/lib/vquic/curl_msh3.c +++ b/Utilities/cmcurl/lib/vquic/curl_msh3.c @@ -22,30 +22,32 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_MSH3 -#include "urldata.h" -#include "hash.h" -#include "timeval.h" -#include "multiif.h" -#include "sendf.h" -#include "curl_trc.h" -#include "cfilters.h" -#include "cf-socket.h" -#include "connect.h" -#include "progress.h" -#include "http1.h" +#include "../urldata.h" +#include "../hash.h" +#include "../uint-hash.h" +#include "../curlx/timeval.h" +#include "../multiif.h" +#include "../sendf.h" +#include "../curl_trc.h" +#include "../cfilters.h" +#include "../cf-socket.h" +#include "../connect.h" +#include "../progress.h" +#include "../http1.h" #include "curl_msh3.h" -#include "socketpair.h" -#include "vtls/vtls.h" -#include "vquic/vquic.h" +#include "../socketpair.h" +#include "../vtls/vtls.h" +#include "vquic.h" +#include "vquic_int.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" #ifdef CURL_DISABLE_SOCKETPAIR #error "MSH3 cannot be build with CURL_DISABLE_SOCKETPAIR set" @@ -119,18 +121,18 @@ 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` */ + struct uint_hash streams; /* hash `data->mid` to `stream_ctx` */ /* Flags written by msh3/msquic thread */ - bool handshake_complete; - bool handshake_succeeded; - bool connected; + BIT(handshake_complete); + BIT(handshake_succeeded); + BIT(connected); BIT(initialized); /* Flags written by curl thread */ BIT(verbose); BIT(active); }; -static void h3_stream_hash_free(void *stream); +static void h3_stream_hash_free(unsigned int id, void *stream); static CURLcode cf_msh3_ctx_init(struct cf_msh3_ctx *ctx, const struct Curl_addrinfo *ai) @@ -138,7 +140,7 @@ static CURLcode cf_msh3_ctx_init(struct cf_msh3_ctx *ctx, CURLcode result; DEBUGASSERT(!ctx->initialized); - Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free); + Curl_uint_hash_init(&ctx->streams, 63, h3_stream_hash_free); result = Curl_sock_assign_addr(&ctx->addr, ai, TRNSPRT_QUIC); if(result) @@ -154,7 +156,7 @@ static CURLcode cf_msh3_ctx_init(struct cf_msh3_ctx *ctx, static void cf_msh3_ctx_free(struct cf_msh3_ctx *ctx) { if(ctx && ctx->initialized) { - Curl_hash_destroy(&ctx->streams); + Curl_uint_hash_destroy(&ctx->streams); } free(ctx); } @@ -169,7 +171,7 @@ static struct cf_msh3_ctx *h3_get_msh3_ctx(struct Curl_easy *data); /** * All about the H3 internals of a stream */ -struct stream_ctx { +struct h3_stream_ctx { struct MSH3_REQUEST *req; struct bufq recvbuf; /* h3 response */ #ifdef _WIN32 @@ -180,33 +182,31 @@ struct stream_ctx { uint64_t error3; /* HTTP/3 stream error code */ int status_code; /* HTTP status code */ CURLcode recv_error; - bool closed; - bool reset; - bool upload_done; - bool firstheader; /* FALSE until headers arrive */ - bool recv_header_complete; + BIT(closed); + BIT(reset); + BIT(upload_done); + BIT(firstheader); /* FALSE until headers arrive */ + BIT(recv_header_complete); }; -#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) +static void h3_stream_ctx_free(struct h3_stream_ctx *stream) { Curl_bufq_free(&stream->recvbuf); free(stream); } -static void h3_stream_hash_free(void *stream) +static void h3_stream_hash_free(unsigned int id, void *stream) { + (void)id; DEBUGASSERT(stream); - h3_stream_ctx_free((struct stream_ctx *)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_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream) return CURLE_OK; @@ -221,7 +221,7 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, 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)) { + if(!Curl_uint_hash_set(&ctx->streams, data->mid, stream)) { h3_stream_ctx_free(stream); return CURLE_OUT_OF_MEMORY; } @@ -232,17 +232,17 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)cf; if(stream) { CURL_TRC_CF(data, cf, "easy handle is done"); - Curl_hash_offt_remove(&ctx->streams, data->mid); + Curl_uint_hash_remove(&ctx->streams, data->mid); } } static void drain_stream_from_other_thread(struct Curl_easy *data, - struct stream_ctx *stream) + struct h3_stream_ctx *stream) { unsigned char bits; @@ -260,7 +260,7 @@ static void h3_drain_stream(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); unsigned char bits; (void)cf; @@ -359,7 +359,7 @@ static CURLcode write_resp_raw(struct Curl_easy *data, const void *mem, size_t memlen) { struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result = CURLE_OK; ssize_t nwritten; @@ -386,7 +386,7 @@ static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, { struct Curl_easy *data = userp; struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result; (void)Request; @@ -398,7 +398,7 @@ static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, msh3_lock_acquire(&stream->recv_lock); if((hd->NameLength == 7) && - !strncmp(HTTP_PSEUDO_STATUS, (char *)hd->Name, 7)) { + !strncmp(HTTP_PSEUDO_STATUS, (const char *)hd->Name, 7)) { char line[14]; /* status line is always 13 characters long */ size_t ncopy; @@ -437,7 +437,7 @@ static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, { struct Curl_easy *data = IfContext; struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result; bool rv = FALSE; @@ -477,7 +477,7 @@ static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext, { struct Curl_easy *data = IfContext; struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)Request; if(!stream) @@ -497,7 +497,7 @@ static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request, { struct Curl_easy *data = IfContext; struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(!stream) return; @@ -510,7 +510,7 @@ static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request, { struct Curl_easy *data = IfContext; struct cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(!stream) return; (void)Request; @@ -523,7 +523,7 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, CURLcode *err) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; if(!stream) { @@ -557,7 +557,7 @@ out: static void set_quic_expire(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_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 @@ -575,7 +575,7 @@ static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; struct cf_call_data save; @@ -627,7 +627,7 @@ static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode *err) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct h1_req_parser h1; struct dynhds h2_headers; MSH3_HEADER *nva = NULL; @@ -723,7 +723,7 @@ static void cf_msh3_adjust_pollset(struct Curl_cfilter *cf, struct easy_pollset *ps) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; CF_DATA_SAVE(save, cf, data); @@ -743,7 +743,7 @@ static bool cf_msh3_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; bool pending = FALSE; @@ -752,12 +752,13 @@ static bool cf_msh3_data_pending(struct Curl_cfilter *cf, (void)cf; if(stream && stream->req) { msh3_lock_acquire(&stream->recv_lock); - CURL_TRC_CF((struct Curl_easy *)data, cf, "data pending = %zu", + CURL_TRC_CF((struct Curl_easy *)CURL_UNCONST(data), cf, + "data pending = %zu", Curl_bufq_len(&stream->recvbuf)); pending = !Curl_bufq_is_empty(&stream->recvbuf); msh3_lock_release(&stream->recv_lock); if(pending) - h3_drain_stream(cf, (struct Curl_easy *)data); + h3_drain_stream(cf, (struct Curl_easy *)CURL_UNCONST(data)); } CF_DATA_RESTORE(cf, save); @@ -780,7 +781,7 @@ static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf, int event, int arg1, void *arg2) { struct cf_msh3_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; CURLcode result = CURLE_OK; @@ -838,16 +839,10 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, MSH3_SET_PORT(&addr, (uint16_t)cf->conn->remote_port); if(verify && (conn_config->CAfile || conn_config->CApath)) { - /* Need a way to provide trust anchors to MSH3 */ -#ifdef DEBUGBUILD - /* we need this for our test cases to run */ - CURL_TRC_CF(data, cf, "non-standard CA not supported, " - "switching off verifypeer in DEBUG mode"); - verify = 0; -#else + /* Note there's currently no way to provide trust anchors to MSH3 and + that causes tests to fail. */ CURL_TRC_CF(data, cf, "non-standard CA not supported, " "attempting with built-in verification"); -#endif } CURL_TRC_CF(data, cf, "connecting to %s:%d (verify=%d)", @@ -883,13 +878,12 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, static CURLcode cf_msh3_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_msh3_ctx *ctx = cf->ctx; struct cf_call_data save; CURLcode result = CURLE_OK; - (void)blocking; if(cf->connected) { *done = TRUE; return CURLE_OK; @@ -907,14 +901,14 @@ static CURLcode cf_msh3_connect(struct Curl_cfilter *cf, *done = FALSE; if(!ctx->qconn) { - ctx->connect_started = Curl_now(); + ctx->connect_started = curlx_now(); result = cf_connect_start(cf, data); if(result) goto out; } if(ctx->handshake_complete) { - ctx->handshake_at = Curl_now(); + ctx->handshake_at = curlx_now(); if(ctx->handshake_succeeded) { CURL_TRC_CF(data, cf, "handshake succeeded"); cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ diff --git a/Utilities/cmcurl/lib/vquic/curl_msh3.h b/Utilities/cmcurl/lib/vquic/curl_msh3.h index 33931f59bb..d2862c2d89 100644 --- a/Utilities/cmcurl/lib/vquic/curl_msh3.h +++ b/Utilities/cmcurl/lib/vquic/curl_msh3.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_MSH3 diff --git a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c index cc9d560d70..f529f7e4fa 100644 --- a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c +++ b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c @@ -22,7 +22,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) #include @@ -32,49 +32,51 @@ #include #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) #include +#elif defined(OPENSSL_QUIC_API2) +#include #else #include #endif -#include "vtls/openssl.h" +#include "../vtls/openssl.h" #elif defined(USE_GNUTLS) #include -#include "vtls/gtls.h" +#include "../vtls/gtls.h" #elif defined(USE_WOLFSSL) #include -#include "vtls/wolfssl.h" +#include "../vtls/wolfssl.h" #endif -#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 "transfer.h" +#include "../urldata.h" +#include "../uint-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 "../curlx/dynbuf.h" +#include "../http1.h" +#include "../select.h" +#include "../curlx/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 "vtls/vtls_scache.h" +#include "../vtls/keylog.h" +#include "../vtls/vtls.h" +#include "../vtls/vtls_scache.h" #include "curl_ngtcp2.h" -#include "warnless.h" +#include "../curlx/warnless.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" #define QUIC_MAX_STREAMS (256*1024) @@ -87,6 +89,10 @@ * 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) +#if H3_STREAM_CHUNK_SIZE < NGTCP2_MAX_UDP_PAYLOAD_SIZE +#error H3_STREAM_CHUNK_SIZE smaller than NGTCP2_MAX_UDP_PAYLOAD_SIZE +#endif + /* 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 @@ -117,6 +123,9 @@ struct cf_ngtcp2_ctx { struct cf_quic_ctx q; struct ssl_peer peer; struct curl_tls_ctx tls; +#ifdef OPENSSL_QUIC_API2 + ngtcp2_crypto_ossl_ctx *ossl_ctx; +#endif ngtcp2_path connected_path; ngtcp2_conn *qconn; ngtcp2_cid dcid; @@ -133,9 +142,8 @@ struct cf_ngtcp2_ctx { struct curltime handshake_at; /* time connect handshake finished */ 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` */ + struct uint_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 */ size_t earlydata_max; /* max amount of early data supported by @@ -156,7 +164,7 @@ 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 h3_stream_hash_free(unsigned int id, void *stream); static void cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx) { @@ -164,11 +172,10 @@ static void cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx) 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); + curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); + Curl_uint_hash_init(&ctx->streams, 63, h3_stream_hash_free); ctx->initialized = TRUE; } @@ -178,14 +185,51 @@ static void cf_ngtcp2_ctx_free(struct cf_ngtcp2_ctx *ctx) Curl_vquic_tls_cleanup(&ctx->tls); vquic_ctx_free(&ctx->q); Curl_bufcp_free(&ctx->stream_bufcp); - Curl_dyn_free(&ctx->scratch); - Curl_hash_clean(&ctx->streams); - Curl_hash_destroy(&ctx->streams); + curlx_dyn_free(&ctx->scratch); + Curl_uint_hash_destroy(&ctx->streams); Curl_ssl_peer_cleanup(&ctx->peer); } free(ctx); } +static void cf_ngtcp2_setup_keep_alive(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + const ngtcp2_transport_params *rp; + /* Peer should have sent us its transport parameters. If it + * announces a positive `max_idle_timeout` it will close the + * connection when it does not hear from us for that time. + * + * Some servers use this as a keep-alive timer at a rather low + * value. We are doing HTTP/3 here and waiting for the response + * to a request may take a considerable amount of time. We need + * to prevent the peer's QUIC stack from closing in this case. + */ + if(!ctx->qconn) + return; + + rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); + if(!rp || !rp->max_idle_timeout) { + ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, UINT64_MAX); + CURL_TRC_CF(data, cf, "no peer idle timeout, unset keep-alive"); + } + else if(!Curl_uint_hash_count(&ctx->streams)) { + ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, UINT64_MAX); + CURL_TRC_CF(data, cf, "no active streams, unset keep-alive"); + } + else { + ngtcp2_duration keep_ns; + keep_ns = (rp->max_idle_timeout > 1) ? (rp->max_idle_timeout / 2) : 1; + ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, keep_ns); + CURL_TRC_CF(data, cf, "peer idle timeout is %" FMT_PRIu64 "ms, " + "set keep-alive to %" FMT_PRIu64 " ms.", + (curl_uint64_t)(rp->max_idle_timeout / NGTCP2_MILLISECONDS), + (curl_uint64_t)(keep_ns / NGTCP2_MILLISECONDS)); + } +} + + struct pkt_io_ctx; static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -206,18 +250,13 @@ struct h3_stream_ctx { 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(resp_hds_complete); /* we have a complete, final response */ + BIT(closed); /* TRUE on stream close */ + BIT(reset); /* TRUE on stream reset */ + BIT(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)) -#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); @@ -225,8 +264,9 @@ static void h3_stream_ctx_free(struct h3_stream_ctx *stream) free(stream); } -static void h3_stream_hash_free(void *stream) +static void h3_stream_hash_free(unsigned int id, void *stream) { + (void)id; DEBUGASSERT(stream); h3_stream_ctx_free((struct h3_stream_ctx *)stream); } @@ -254,11 +294,14 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, stream->sendbuf_len_in_flight = 0; Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - if(!Curl_hash_offt_set(&ctx->streams, data->mid, stream)) { + if(!Curl_uint_hash_set(&ctx->streams, data->mid, stream)) { h3_stream_ctx_free(stream); return CURLE_OUT_OF_MEMORY; } + if(Curl_uint_hash_count(&ctx->streams) == 1) + cf_ngtcp2_setup_keep_alive(cf, data); + return CURLE_OK; } @@ -293,42 +336,12 @@ static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) 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); + Curl_uint_hash_remove(&ctx->streams, data->mid); + if(!Curl_uint_hash_count(&ctx->streams)) + cf_ngtcp2_setup_keep_alive(cf, data); } } -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) { @@ -447,7 +460,7 @@ 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 = (ctx->max_idle_ms * NGTCP2_MILLISECONDS); + t->max_idle_timeout = 0; /* no idle timeout from our side */ if(ctx->qlogfd != -1) { s->qlog_write = qlog_callback; } @@ -469,29 +482,44 @@ static int cf_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data) if(!ctx || !data) return NGHTTP3_ERR_CALLBACK_FAILURE; - ctx->handshake_at = Curl_now(); + ctx->handshake_at = curlx_now(); ctx->tls_handshake_complete = TRUE; cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ ctx->tls_vrfy_result = Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); CURL_TRC_CF(data, cf, "handshake complete after %dms", - (int)Curl_timediff(ctx->handshake_at, ctx->started_at)); + (int)curlx_timediff(ctx->handshake_at, ctx->started_at)); /* In case of earlydata, where we simulate being connected, update * the handshake time when we really did connect */ if(ctx->use_earlydata) Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at); -#ifdef USE_GNUTLS if(ctx->use_earlydata) { +#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) + ctx->earlydata_accepted = + (SSL_get_early_data_status(ctx->tls.ossl.ssl) != + SSL_EARLY_DATA_REJECTED); +#endif +#ifdef USE_GNUTLS int flags = gnutls_session_get_flags(ctx->tls.gtls.session); ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA); +#endif +#ifdef USE_WOLFSSL +#ifdef WOLFSSL_EARLY_DATA + ctx->earlydata_accepted = + (wolfSSL_get_early_data_status(ctx->tls.wssl.ssl) != + WOLFSSL_EARLY_DATA_REJECTED); +#else + DEBUGASSERT(0); /* should not come here if ED is disabled. */ + ctx->earlydata_accepted = FALSE; +#endif /* WOLFSSL_EARLY_DATA */ +#endif CURL_TRC_CF(data, cf, "server did%s accept %zu bytes of early data", ctx->earlydata_accepted ? "" : " not", ctx->earlydata_skip); Curl_pgrsEarlyData(data, ctx->earlydata_accepted ? (curl_off_t)ctx->earlydata_skip : -(curl_off_t)ctx->earlydata_skip); } -#endif return 0; } @@ -563,7 +591,7 @@ static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read_stream(len=%zu) -> %zd", stream_id, buflen, nconsumed); if(nconsumed < 0) { - struct h3_stream_ctx *stream = H3_STREAM_CTX_ID(ctx, stream_id); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(data && stream) { CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] error on known stream, " "reset=%d, closed=%d", @@ -695,28 +723,26 @@ static int cb_extend_max_local_streams_bidi(ngtcp2_conn *tconn, return 0; } -static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t sid, +static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id, 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 Curl_easy *s_data = stream_user_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 && 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 = H3_STREAM_CTX(ctx, s_data); + if(stream && stream->quic_flow_blocked) { + CURL_TRC_CF(s_data, cf, "[%" FMT_PRId64 "] unblock quic flow", + (curl_int64_t)stream_id); stream->quic_flow_blocked = FALSE; h3_drain_stream(cf, s_data); } @@ -996,7 +1022,7 @@ static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, if(!stream) return NGHTTP3_ERR_CALLBACK_FAILURE; - h3_xfer_write_resp(cf, data, stream, (char *)buf, blen, FALSE); + h3_xfer_write_resp(cf, data, stream, (const char *)buf, blen, FALSE); if(blen) { CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] ACK %zu bytes of DATA", stream->id, blen); @@ -1079,18 +1105,18 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t sid, (const char *)h3val.base, h3val.len); if(result) return -1; - Curl_dyn_reset(&ctx->scratch); - result = Curl_dyn_addn(&ctx->scratch, STRCONST("HTTP/3 ")); + curlx_dyn_reset(&ctx->scratch); + result = curlx_dyn_addn(&ctx->scratch, STRCONST("HTTP/3 ")); if(!result) - result = Curl_dyn_addn(&ctx->scratch, - (const char *)h3val.base, h3val.len); + result = curlx_dyn_addn(&ctx->scratch, + (const char *)h3val.base, h3val.len); if(!result) - result = Curl_dyn_addn(&ctx->scratch, STRCONST(" \r\n")); + result = curlx_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); + h3_xfer_write_resp_hd(cf, data, stream, curlx_dyn_ptr(&ctx->scratch), + curlx_dyn_len(&ctx->scratch), FALSE); CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] status: %s", - stream_id, Curl_dyn_ptr(&ctx->scratch)); + stream_id, curlx_dyn_ptr(&ctx->scratch)); if(result) { return -1; } @@ -1100,19 +1126,19 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t sid, CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] header: %.*s: %.*s", stream_id, (int)h3name.len, h3name.base, (int)h3val.len, h3val.base); - Curl_dyn_reset(&ctx->scratch); - result = Curl_dyn_addn(&ctx->scratch, - (const char *)h3name.base, h3name.len); + curlx_dyn_reset(&ctx->scratch); + result = curlx_dyn_addn(&ctx->scratch, + (const char *)h3name.base, h3name.len); if(!result) - result = Curl_dyn_addn(&ctx->scratch, STRCONST(": ")); + result = curlx_dyn_addn(&ctx->scratch, STRCONST(": ")); if(!result) - result = Curl_dyn_addn(&ctx->scratch, - (const char *)h3val.base, h3val.len); + result = curlx_dyn_addn(&ctx->scratch, + (const char *)h3val.base, h3val.len); if(!result) - result = Curl_dyn_addn(&ctx->scratch, STRCONST("\r\n")); + result = curlx_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); + h3_xfer_write_resp_hd(cf, data, stream, curlx_dyn_ptr(&ctx->scratch), + curlx_dyn_len(&ctx->scratch), FALSE); } return 0; } @@ -1400,7 +1426,7 @@ cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, while(nvecs < veccnt && Curl_bufq_peek_at(&stream->sendbuf, stream->sendbuf_len_in_flight, - (const unsigned char **)&vec[nvecs].base, + CURL_UNCONST(&vec[nvecs].base), &vec[nvecs].len)) { stream->sendbuf_len_in_flight += vec[nvecs].len; nwritten += vec[nvecs].len; @@ -1759,7 +1785,7 @@ static ssize_t read_pkt_to_send(void *userp, if(ctx->h3conn && ngtcp2_conn_get_max_data_left(ctx->qconn)) { veccnt = nghttp3_conn_writev_stream(ctx->h3conn, &stream_id, &fin, vec, - sizeof(vec) / sizeof(vec[0])); + CURL_ARRAYSIZE(vec)); if(veccnt < 0) { failf(x->data, "nghttp3_conn_writev_stream returned error: %s", nghttp3_strerror((int)veccnt)); @@ -2033,10 +2059,20 @@ static void cf_ngtcp2_ctx_close(struct cf_ngtcp2_ctx *ctx) ctx->qlogfd = -1; Curl_vquic_tls_cleanup(&ctx->tls); vquic_ctx_free(&ctx->q); - if(ctx->h3conn) + if(ctx->h3conn) { nghttp3_conn_del(ctx->h3conn); - if(ctx->qconn) + ctx->h3conn = NULL; + } + if(ctx->qconn) { ngtcp2_conn_del(ctx->qconn); + ctx->qconn = NULL; + } +#ifdef OPENSSL_QUIC_API2 + if(ctx->ossl_ctx) { + ngtcp2_crypto_ossl_ctx_del(ctx->ossl_ctx); + ctx->ossl_ctx = NULL; + } +#endif ctx->call_data = save; } @@ -2076,6 +2112,7 @@ static CURLcode cf_ngtcp2_shutdown(struct Curl_cfilter *cf, } } + DEBUGASSERT(Curl_bufq_is_empty(&ctx->q.sendbuf)); ctx->shutdown_started = TRUE; nwritten = ngtcp2_conn_write_connection_close( ctx->qconn, NULL, /* path */ @@ -2085,14 +2122,21 @@ static CURLcode cf_ngtcp2_shutdown(struct Curl_cfilter *cf, 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); + /* there are cases listed in ngtcp2 documentation where this call + * may fail. Since we are doing a connection shutdown as graceful + * as we can, such an error is ignored here. */ if(nwritten > 0) { - Curl_bufq_write(&ctx->q.sendbuf, (const unsigned char *)buffer, - (size_t)nwritten, &result); + /* Ignore amount written. sendbuf was empty and has always room for + * NGTCP2_MAX_UDP_PAYLOAD_SIZE. It can only completely fail, in which + * case `result` is set non zero. */ + (void)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; @@ -2152,6 +2196,7 @@ static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { CURL_TRC_CF(data, cf, "destroy"); if(cf->ctx) { + cf_ngtcp2_close(cf, data); cf_ngtcp2_ctx_free(cf->ctx); cf->ctx = NULL; } @@ -2173,8 +2218,24 @@ static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) ctx = cf ? cf->ctx : NULL; data = cf ? CF_DATA_CURRENT(cf) : NULL; if(cf && data && ctx) { + unsigned char *quic_tp = NULL; + size_t quic_tp_len = 0; +#ifdef HAVE_OPENSSL_EARLYDATA + ngtcp2_ssize tplen; + uint8_t tpbuf[256]; + + tplen = ngtcp2_conn_encode_0rtt_transport_params(ctx->qconn, tpbuf, + sizeof(tpbuf)); + if(tplen < 0) + CURL_TRC_CF(data, cf, "error encoding 0RTT transport data: %s", + ngtcp2_strerror((int)tplen)); + else { + quic_tp = (unsigned char *)tpbuf; + quic_tp_len = (size_t)tplen; + } +#endif Curl_ossl_add_session(cf, data, ctx->peer.scache_key, ssl_sessionid, - SSL_version(ssl), "h3"); + SSL_version(ssl), "h3", quic_tp, quic_tp_len); return 1; } return 0; @@ -2257,8 +2318,23 @@ static int wssl_quic_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) struct Curl_easy *data = CF_DATA_CURRENT(cf); DEBUGASSERT(data); if(data && ctx) { + ngtcp2_ssize tplen; + uint8_t tpbuf[256]; + unsigned char *quic_tp = NULL; + size_t quic_tp_len = 0; + + tplen = ngtcp2_conn_encode_0rtt_transport_params(ctx->qconn, tpbuf, + sizeof(tpbuf)); + if(tplen < 0) + CURL_TRC_CF(data, cf, "error encoding 0RTT transport data: %s", + ngtcp2_strerror((int)tplen)); + else { + quic_tp = (unsigned char *)tpbuf; + quic_tp_len = (size_t)tplen; + } (void)Curl_wssl_cache_session(cf, data, ctx->peer.scache_key, - session, wolfSSL_version(ssl), "h3"); + session, wolfSSL_version(ssl), + "h3", quic_tp, quic_tp_len); } } return 0; @@ -2279,6 +2355,8 @@ static CURLcode cf_ngtcp2_tls_ctx_setup(struct Curl_cfilter *cf, failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); return CURLE_FAILED_INIT; } +#elif defined(OPENSSL_QUIC_API2) + /* nothing to do */ #else if(ngtcp2_crypto_quictls_configure_client_context(ctx->ossl.ssl_ctx) != 0) { failf(data, "ngtcp2_crypto_quictls_configure_client_context failed"); @@ -2308,13 +2386,13 @@ static CURLcode cf_ngtcp2_tls_ctx_setup(struct Curl_cfilter *cf, } #elif defined(USE_WOLFSSL) - if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->wssl.ctx) != 0) { + if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->wssl.ssl_ctx) != 0) { failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); return CURLE_FAILED_INIT; } 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); + wolfSSL_CTX_sess_set_new_cb(ctx->wssl.ssl_ctx, wssl_quic_new_session_cb); } #endif return CURLE_OK; @@ -2322,6 +2400,7 @@ static CURLcode cf_ngtcp2_tls_ctx_setup(struct Curl_cfilter *cf, static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf, struct Curl_easy *data, + struct alpn_spec *alpns, struct Curl_ssl_session *scs, bool *do_early_data) { @@ -2329,13 +2408,26 @@ static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf, CURLcode result = CURLE_OK; *do_early_data = FALSE; +#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) + ctx->earlydata_max = scs->earlydata_max; +#endif #ifdef USE_GNUTLS ctx->earlydata_max = gnutls_record_get_max_early_data_size(ctx->tls.gtls.session); +#endif +#ifdef USE_WOLFSSL +#ifdef WOLFSSL_EARLY_DATA + ctx->earlydata_max = scs->earlydata_max; +#else + ctx->earlydata_max = 0; +#endif /* WOLFSSL_EARLY_DATA */ +#endif +#if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ + (defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA)) if((!ctx->earlydata_max)) { CURL_TRC_CF(data, cf, "SSL session does not allow earlydata"); } - else if(strcmp("h3", scs->alpn)) { + else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) { CURL_TRC_CF(data, cf, "SSL session from different ALPN, no early data"); } else if(!scs->quic_tp || !scs->quic_tp_len) { @@ -2344,7 +2436,7 @@ static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf, else { int rv; rv = ngtcp2_conn_decode_and_set_0rtt_transport_params( - ctx->qconn, (uint8_t *)scs->quic_tp, scs->quic_tp_len); + ctx->qconn, (const uint8_t *)scs->quic_tp, scs->quic_tp_len); if(rv) CURL_TRC_CF(data, cf, "no early data, failed to set 0RTT transport " "parameters: %s", ngtcp2_strerror(rv)); @@ -2359,10 +2451,11 @@ static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf, } } } -#else /* USE_GNUTLS */ +#else /* not supported in the TLS backend */ (void)data; (void)ctx; (void)scs; + (void)alpns; #endif return result; } @@ -2380,6 +2473,9 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, CURLcode result; const struct Curl_sockaddr_ex *sockaddr = NULL; int qfd; +static const struct alpn_spec ALPN_SPEC_H3 = { + { "h3", "h3-29" }, 2 +}; DEBUGASSERT(ctx->initialized); ctx->dcid.datalen = NGTCP2_MAX_CIDLEN; @@ -2423,37 +2519,45 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, if(rc) return CURLE_QUIC_CONNECT_ERROR; -#define H3_ALPN "\x2h3\x5h3-29" - result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, - H3_ALPN, sizeof(H3_ALPN) - 1, + ctx->conn_ref.get_conn = get_conn; + ctx->conn_ref.user_data = cf; + + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, &ALPN_SPEC_H3, cf_ngtcp2_tls_ctx_setup, &ctx->tls, &ctx->conn_ref, cf_ngtcp2_on_session_reuse); if(result) return result; -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL) && defined(OPENSSL_QUIC_API2) + if(ngtcp2_crypto_ossl_ctx_new(&ctx->ossl_ctx, ctx->tls.ossl.ssl) != 0) { + failf(data, "ngtcp2_crypto_ossl_ctx_new failed"); + return CURLE_FAILED_INIT; + } + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ossl_ctx); + if(ngtcp2_crypto_ossl_configure_client_session(ctx->tls.ossl.ssl) != 0) { + failf(data, "ngtcp2_crypto_ossl_configure_client_session failed"); + return CURLE_FAILED_INIT; + } +#elif defined(USE_OPENSSL) SSL_set_quic_use_legacy_codepoint(ctx->tls.ossl.ssl, 0); 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); + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.wssl.ssl); #else #error "ngtcp2 TLS backend not defined" #endif ngtcp2_ccerr_default(&ctx->last_error); - ctx->conn_ref.get_conn = get_conn; - ctx->conn_ref.user_data = cf; - return CURLE_OK; } static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_ngtcp2_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; @@ -2468,13 +2572,13 @@ static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf, /* Connect the UDP filter first */ if(!cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, blocking, done); + result = Curl_conn_cf_connect(cf->next, data, done); if(result || !*done) return result; } *done = FALSE; - now = Curl_now(); + now = curlx_now(); pktx_init(&pktx, cf, data); CF_DATA_SAVE(save, cf, data); @@ -2561,7 +2665,7 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, } else if(ctx->max_bidi_streams) { uint64_t avail_bidi_streams = 0; - uint64_t max_streams = CONN_INUSE(cf->conn); + uint64_t max_streams = CONN_ATTACHED(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; @@ -2570,14 +2674,14 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, 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)); + "MAX_CONCURRENT -> %d (%u in use)", + cf->conn->connection_id, *pres1, CONN_ATTACHED(cf->conn)); CF_DATA_RESTORE(cf, save); return CURLE_OK; } case CF_QUERY_CONNECT_REPLY_MS: if(ctx->q.got_first_byte) { - timediff_t ms = Curl_timediff(ctx->q.first_byte_at, ctx->started_at); + timediff_t ms = curlx_timediff(ctx->q.first_byte_at, ctx->started_at); *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; } else @@ -2620,21 +2724,12 @@ static bool cf_ngtcp2_conn_is_alive(struct Curl_cfilter *cf, 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. */ + /* We do not announce a max idle timeout, but when the peer does + * it will close the connection when it expires. */ 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) + if(rp && rp->max_idle_timeout) { + timediff_t idletime = curlx_timediff(curlx_now(), ctx->q.last_io); + if(idletime > 0 && (uint64_t)idletime > rp->max_idle_timeout) goto out; } diff --git a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h index db3e611bd0..884662523f 100644 --- a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h +++ b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) @@ -33,6 +33,9 @@ #endif #include +#ifdef OPENSSL_QUIC_API2 +#include +#endif #include #ifdef USE_OPENSSL #include @@ -44,7 +47,7 @@ struct Curl_cfilter; -#include "urldata.h" +#include "../urldata.h" void Curl_ngtcp2_ver(char *p, size_t len); diff --git a/Utilities/cmcurl/lib/vquic/curl_osslq.c b/Utilities/cmcurl/lib/vquic/curl_osslq.c index 4fd4fee11a..d5af10b06b 100644 --- a/Utilities/cmcurl/lib/vquic/curl_osslq.c +++ b/Utilities/cmcurl/lib/vquic/curl_osslq.c @@ -22,7 +22,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) @@ -31,36 +31,36 @@ #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 "../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 "../curlx/dynbuf.h" +#include "../http1.h" +#include "../select.h" +#include "../curlx/inet_pton.h" +#include "../uint-hash.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 "../vtls/keylog.h" +#include "../vtls/vtls.h" +#include "../vtls/openssl.h" #include "curl_osslq.h" - -#include "warnless.h" +#include "../curlx/warnless.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#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 @@ -171,7 +171,7 @@ static CURLcode make_bio_addr(BIO_ADDR **pbio_addr, switch(addr->family) { case AF_INET: { struct sockaddr_in * const sin = - (struct sockaddr_in * const)(void *)&addr->curl_sa_addr; + (struct sockaddr_in * const)CURL_UNCONST(&addr->curl_sa_addr); if(!BIO_ADDR_rawmake(ba, AF_INET, &sin->sin_addr, sizeof(sin->sin_addr), sin->sin_port)) { goto out; @@ -182,7 +182,7 @@ static CURLcode make_bio_addr(BIO_ADDR **pbio_addr, #ifdef USE_IPV6 case AF_INET6: { struct sockaddr_in6 * const sin = - (struct sockaddr_in6 * const)(void *)&addr->curl_sa_addr; + (struct sockaddr_in6 * const)CURL_UNCONST(&addr->curl_sa_addr); if(!BIO_ADDR_rawmake(ba, AF_INET6, &sin->sin6_addr, sizeof(sin->sin6_addr), sin->sin6_port)) { } @@ -285,12 +285,12 @@ struct cf_osslq_ctx { 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` */ + struct uint_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 */ SSL_POLL_ITEM *poll_items; /* Array for polling on writable state */ struct Curl_easy **curl_items; /* Array of easy objs */ - size_t item_count; /* count of elements in poll/curl_items */ + size_t items_max; /* max elements in poll/curl_items */ BIT(initialized); BIT(got_first_byte); /* if first byte was received */ BIT(x509_store_setup); /* if x509 store has been set up */ @@ -299,17 +299,17 @@ struct cf_osslq_ctx { BIT(need_send); /* QUIC connection needs to send */ }; -static void h3_stream_hash_free(void *stream); +static void h3_stream_hash_free(unsigned int id, 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); + Curl_uint_hash_init(&ctx->streams, 63, h3_stream_hash_free); ctx->poll_items = NULL; ctx->curl_items = NULL; - ctx->item_count = 0; + ctx->items_max = 0; ctx->initialized = TRUE; } @@ -317,8 +317,7 @@ 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_uint_hash_destroy(&ctx->streams); Curl_ssl_peer_cleanup(&ctx->peer); free(ctx->poll_items); free(ctx->curl_items); @@ -584,16 +583,13 @@ struct h3_stream_ctx { 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(resp_hds_complete); /* we have a complete, final response */ + BIT(closed); /* TRUE on stream close */ + BIT(reset); /* TRUE on stream reset */ + BIT(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); @@ -603,8 +599,9 @@ static void h3_stream_ctx_free(struct h3_stream_ctx *stream) free(stream); } -static void h3_stream_hash_free(void *stream) +static void h3_stream_hash_free(unsigned int id, void *stream) { + (void)id; DEBUGASSERT(stream); h3_stream_ctx_free((struct h3_stream_ctx *)stream); } @@ -637,7 +634,7 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, 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)) { + if(!Curl_uint_hash_set(&ctx->streams, data->mid, stream)) { h3_stream_ctx_free(stream); return CURLE_OUT_OF_MEMORY; } @@ -654,7 +651,7 @@ static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) if(stream) { CURL_TRC_CF(data, cf, "[%"FMT_PRId64"] easy handle is done", stream->s.id); - if(ctx->h3.conn && !stream->closed) { + if(ctx->h3.conn && (stream->s.id >= 0) && !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); @@ -662,10 +659,28 @@ static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) stream->closed = TRUE; } - Curl_hash_offt_remove(&ctx->streams, data->mid); + Curl_uint_hash_remove(&ctx->streams, data->mid); } } +struct cf_ossq_find_ctx { + curl_int64_t stream_id; + struct h3_stream_ctx *stream; +}; + +static bool cf_osslq_find_stream(unsigned int mid, void *val, void *user_data) +{ + struct h3_stream_ctx *stream = val; + struct cf_ossq_find_ctx *fctx = user_data; + + (void)mid; + if(stream && stream->s.id == fctx->stream_id) { + fctx->stream = stream; + return FALSE; /* stop iterating */ + } + return TRUE; +} + static struct cf_osslq_stream *cf_osslq_get_qstream(struct Curl_cfilter *cf, struct Curl_easy *data, int64_t stream_id) @@ -686,17 +701,12 @@ static struct cf_osslq_stream *cf_osslq_get_qstream(struct Curl_cfilter *cf, 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; - } - } + struct cf_ossq_find_ctx fctx; + fctx.stream_id = stream_id; + fctx.stream = NULL; + Curl_uint_hash_visit(&ctx->streams, cf_osslq_find_stream, &fctx); + if(fctx.stream) + return &fctx.stream->s; } return NULL; } @@ -819,7 +829,7 @@ static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, return NGHTTP3_ERR_CALLBACK_FAILURE; } stream->download_recvd += (curl_off_t)buflen; - CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] DATA len=%zu, total=%zd", + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] DATA len=%zu, total=%" FMT_OFF_T, stream->s.id, buflen, stream->download_recvd); h3_drain_stream(cf, data); return 0; @@ -1012,7 +1022,7 @@ cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, while(nvecs < veccnt && Curl_bufq_peek_at(&stream->sendbuf, stream->sendbuf_len_in_flight, - (const unsigned char **)&vec[nvecs].base, + CURL_UNCONST(&vec[nvecs].base), &vec[nvecs].len)) { stream->sendbuf_len_in_flight += vec[nvecs].len; nwritten += vec[nvecs].len; @@ -1163,13 +1173,15 @@ static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf, const struct Curl_sockaddr_ex *peer_addr = NULL; BIO *bio = NULL; BIO_ADDR *baddr = NULL; +static const struct alpn_spec ALPN_SPEC_H3 = { + { "h3" }, 1 +}; DEBUGASSERT(ctx->initialized); #define H3_ALPN "\x2h3" result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, - H3_ALPN, sizeof(H3_ALPN) - 1, - NULL, NULL, NULL, NULL); + &ALPN_SPEC_H3, NULL, NULL, NULL, NULL); if(result) goto out; @@ -1224,17 +1236,6 @@ static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf, 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); @@ -1399,6 +1400,29 @@ out: return result; } +struct cf_ossq_recv_ctx { + struct Curl_cfilter *cf; + struct Curl_multi *multi; + CURLcode result; +}; + +static bool cf_osslq_iter_recv(unsigned int mid, void *val, void *user_data) +{ + struct h3_stream_ctx *stream = val; + struct cf_ossq_recv_ctx *rctx = user_data; + + (void)mid; + if(stream && !stream->closed && !Curl_bufq_is_full(&stream->recvbuf)) { + struct Curl_easy *sdata = Curl_multi_get_easy(rctx->multi, mid); + if(sdata) { + rctx->result = cf_osslq_stream_recv(&stream->s, rctx->cf, sdata); + if(rctx->result) + return FALSE; /* abort iteration */ + } + } + return TRUE; +} + static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -1435,22 +1459,14 @@ static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, } if(ctx->h3.conn) { - struct Curl_llist_node *e; - struct h3_stream_ctx *stream; - /* PULL all open streams */ + struct cf_ossq_recv_ctx rctx; + 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; - } - } - } + rctx.cf = cf; + rctx.multi = data->multi; + rctx.result = CURLE_OK; + Curl_uint_hash_visit(&ctx->streams, cf_osslq_iter_recv, &rctx); + result = rctx.result; } out: @@ -1458,13 +1474,41 @@ out: return result; } +struct cf_ossq_fill_ctx { + struct cf_osslq_ctx *ctx; + struct Curl_multi *multi; + size_t n; +}; + +static bool cf_osslq_collect_block_send(unsigned int mid, void *val, + void *user_data) +{ + struct h3_stream_ctx *stream = val; + struct cf_ossq_fill_ctx *fctx = user_data; + struct cf_osslq_ctx *ctx = fctx->ctx; + + if(fctx->n >= ctx->items_max) /* should not happen, prevent mayhem */ + return FALSE; + + if(stream && stream->s.ssl && stream->s.send_blocked) { + struct Curl_easy *sdata = Curl_multi_get_easy(fctx->multi, mid); + if(sdata) { + ctx->poll_items[fctx->n].desc = SSL_as_poll_descriptor(stream->s.ssl); + ctx->poll_items[fctx->n].events = SSL_POLL_EVENT_W; + ctx->curl_items[fctx->n] = sdata; + fctx->n++; + } + } + return TRUE; +} + /* 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; - size_t poll_count = 0; + size_t poll_count; size_t result_count = 0; size_t idx_count = 0; CURLcode res = CURLE_OK; @@ -1472,68 +1516,63 @@ static CURLcode cf_osslq_check_and_unblock(struct Curl_cfilter *cf, void *tmpptr; if(ctx->h3.conn) { - struct Curl_llist_node *e; + struct cf_ossq_fill_ctx fill_ctx; - res = CURLE_OUT_OF_MEMORY; - - if(ctx->item_count < Curl_llist_count(&data->multi->process)) { - ctx->item_count = 0; - tmpptr = realloc(ctx->poll_items, - Curl_llist_count(&data->multi->process) * - sizeof(SSL_POLL_ITEM)); + if(ctx->items_max < Curl_uint_hash_count(&ctx->streams)) { + size_t nmax = Curl_uint_hash_count(&ctx->streams); + ctx->items_max = 0; + tmpptr = realloc(ctx->poll_items, nmax * sizeof(SSL_POLL_ITEM)); if(!tmpptr) { free(ctx->poll_items); ctx->poll_items = NULL; + res = CURLE_OUT_OF_MEMORY; goto out; } ctx->poll_items = tmpptr; - tmpptr = realloc(ctx->curl_items, - Curl_llist_count(&data->multi->process) * - sizeof(struct Curl_easy *)); + tmpptr = realloc(ctx->curl_items, nmax * sizeof(struct Curl_easy *)); if(!tmpptr) { free(ctx->curl_items); ctx->curl_items = NULL; + res = CURLE_OUT_OF_MEMORY; goto out; } ctx->curl_items = tmpptr; - - ctx->item_count = Curl_llist_count(&data->multi->process); + ctx->items_max = nmax; } - 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) { - ctx->poll_items[poll_count].desc = - SSL_as_poll_descriptor(stream->s.ssl); - ctx->poll_items[poll_count].events = SSL_POLL_EVENT_W; - ctx->curl_items[poll_count] = sdata; - poll_count++; + fill_ctx.ctx = ctx; + fill_ctx.multi = data->multi; + fill_ctx.n = 0; + Curl_uint_hash_visit(&ctx->streams, cf_osslq_collect_block_send, + &fill_ctx); + poll_count = fill_ctx.n; + if(poll_count) { + CURL_TRC_CF(data, cf, "polling %zu blocked streams", poll_count); + + memset(&timeout, 0, sizeof(struct timeval)); + res = CURLE_UNRECOVERABLE_POLL; + if(!SSL_poll(ctx->poll_items, poll_count, sizeof(SSL_POLL_ITEM), + &timeout, 0, &result_count)) + goto out; + + res = CURLE_OK; + + for(idx_count = 0; idx_count < poll_count && result_count > 0; + idx_count++) { + if(ctx->poll_items[idx_count].revents & SSL_POLL_EVENT_W) { + stream = H3_STREAM_CTX(ctx, ctx->curl_items[idx_count]); + DEBUGASSERT(stream); /* should still exist */ + if(stream) { + nghttp3_conn_unblock_stream(ctx->h3.conn, stream->s.id); + stream->s.send_blocked = FALSE; + h3_drain_stream(cf, ctx->curl_items[idx_count]); + CURL_TRC_CF(ctx->curl_items[idx_count], cf, "unblocked"); + } + result_count--; } } } - - memset(&timeout, 0, sizeof(struct timeval)); - res = CURLE_UNRECOVERABLE_POLL; - if(!SSL_poll(ctx->poll_items, poll_count, sizeof(SSL_POLL_ITEM), &timeout, - 0, &result_count)) - goto out; - - res = CURLE_OK; - - for(idx_count = 0; idx_count < poll_count && result_count > 0; - idx_count++) { - if(ctx->poll_items[idx_count].revents & SSL_POLL_EVENT_W) { - stream = H3_STREAM_CTX(ctx, ctx->curl_items[idx_count]); - nghttp3_conn_unblock_stream(ctx->h3.conn, stream->s.id); - stream->s.send_blocked = FALSE; - h3_drain_stream(cf, ctx->curl_items[idx_count]); - CURL_TRC_CF(ctx->curl_items[idx_count], cf, "unblocked"); - result_count--; - } - } } out: @@ -1625,7 +1664,7 @@ static CURLcode h3_send_streams(struct Curl_cfilter *cf, 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(); + ctx->q.last_io = curlx_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", @@ -1718,7 +1757,7 @@ out: static CURLcode cf_osslq_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_osslq_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; @@ -1733,13 +1772,13 @@ static CURLcode cf_osslq_connect(struct Curl_cfilter *cf, /* Connect the UDP filter first */ if(!cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, blocking, done); + result = Curl_conn_cf_connect(cf->next, data, done); if(result || !*done) return result; } *done = FALSE; - now = Curl_now(); + now = curlx_now(); CF_DATA_SAVE(save, cf, data); if(!ctx->tls.ossl.ssl) { @@ -1753,7 +1792,7 @@ static CURLcode cf_osslq_connect(struct Curl_cfilter *cf, 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(); + ctx->first_byte_at = curlx_now(); } } @@ -1773,7 +1812,7 @@ static CURLcode cf_osslq_connect(struct Curl_cfilter *cf, 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)); + (int)curlx_timediff(now, ctx->started_at)); result = cf_osslq_verify_peer(cf, data); if(!result) { CURL_TRC_CF(data, cf, "peer verified"); @@ -2249,7 +2288,7 @@ static bool cf_osslq_conn_is_alive(struct Curl_cfilter *cf, goto out; } CURL_TRC_CF(data, cf, "negotiated idle timeout: %zums", (size_t)idle_ms); - idletime = Curl_timediff(Curl_now(), ctx->q.last_io); + idletime = curlx_timediff(curlx_now(), ctx->q.last_io); if(idletime > 0 && (uint64_t)idletime > idle_ms) goto out; } @@ -2326,17 +2365,17 @@ static CURLcode cf_osslq_query(struct Curl_cfilter *cf, return CURLE_HTTP3; } /* we report avail + in_use */ - v += CONN_INUSE(cf->conn); + v += CONN_ATTACHED(cf->conn); *pres1 = (v > INT_MAX) ? INT_MAX : (int)v; #else *pres1 = 100; #endif - CURL_TRC_CF(data, cf, "query max_conncurrent -> %d", *pres1); + CURL_TRC_CF(data, cf, "query max_concurrent -> %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); + timediff_t ms = curlx_timediff(ctx->first_byte_at, ctx->started_at); *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; } else diff --git a/Utilities/cmcurl/lib/vquic/curl_osslq.h b/Utilities/cmcurl/lib/vquic/curl_osslq.h index 0e12d7023e..a2809c92bc 100644 --- a/Utilities/cmcurl/lib/vquic/curl_osslq.h +++ b/Utilities/cmcurl/lib/vquic/curl_osslq.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) @@ -34,7 +34,7 @@ struct Curl_cfilter; -#include "urldata.h" +#include "../urldata.h" void Curl_osslq_ver(char *p, size_t len); diff --git a/Utilities/cmcurl/lib/vquic/curl_quiche.c b/Utilities/cmcurl/lib/vquic/curl_quiche.c index 679cba3641..8806532889 100644 --- a/Utilities/cmcurl/lib/vquic/curl_quiche.c +++ b/Utilities/cmcurl/lib/vquic/curl_quiche.c @@ -22,40 +22,40 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_QUICHE #include #include #include -#include "bufq.h" -#include "hash.h" -#include "urldata.h" -#include "cfilters.h" -#include "cf-socket.h" -#include "sendf.h" -#include "strdup.h" -#include "rand.h" -#include "strcase.h" -#include "multiif.h" -#include "connect.h" -#include "progress.h" -#include "strerror.h" -#include "http1.h" +#include "../bufq.h" +#include "../uint-hash.h" +#include "../urldata.h" +#include "../cfilters.h" +#include "../cf-socket.h" +#include "../sendf.h" +#include "../strdup.h" +#include "../rand.h" +#include "../strcase.h" +#include "../multiif.h" +#include "../connect.h" +#include "../progress.h" +#include "../strerror.h" +#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" -#include "vtls/openssl.h" -#include "vtls/keylog.h" -#include "vtls/vtls.h" +#include "../transfer.h" +#include "../curlx/inet_pton.h" +#include "../vtls/openssl.h" +#include "../vtls/keylog.h" +#include "../vtls/vtls.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* HTTP/3 error values defined in RFC 9114, ch. 8.1 */ #define CURL_H3_NO_ERROR (0x0100) @@ -97,7 +97,7 @@ struct cf_quiche_ctx { struct curltime started_at; /* time the current attempt started */ struct curltime handshake_at; /* time connect handshake finished */ struct bufc_pool stream_bufcp; /* chunk pool for streams */ - struct Curl_hash streams; /* hash `data->mid` to `stream_ctx` */ + struct uint_hash streams; /* hash `data->mid` to `stream_ctx` */ curl_off_t data_recvd; BIT(initialized); BIT(goaway); /* got GOAWAY from server */ @@ -115,7 +115,7 @@ static void quiche_debug_log(const char *line, void *argp) } #endif -static void h3_stream_hash_free(void *stream); +static void h3_stream_hash_free(unsigned int id, void *stream); static void cf_quiche_ctx_init(struct cf_quiche_ctx *ctx) { @@ -128,7 +128,7 @@ static void cf_quiche_ctx_init(struct cf_quiche_ctx *ctx) #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); + Curl_uint_hash_init(&ctx->streams, 63, h3_stream_hash_free); ctx->data_recvd = 0; ctx->initialized = TRUE; } @@ -142,8 +142,7 @@ static void cf_quiche_ctx_free(struct cf_quiche_ctx *ctx) 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); + Curl_uint_hash_destroy(&ctx->streams); } free(ctx); } @@ -166,7 +165,7 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf, /** * All about the H3 internals of a stream */ -struct stream_ctx { +struct h3_stream_ctx { curl_uint64_t id; /* HTTP/3 protocol stream identifier */ struct bufq recvbuf; /* h3 response */ struct h1_req_parser h1; /* h1 request parsing */ @@ -180,47 +179,87 @@ struct stream_ctx { BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ }; -#define H3_STREAM_CTX(ctx,data) ((struct stream_ctx *)(\ - data? Curl_hash_offt_get(&(ctx)->streams, (data)->mid) : NULL)) - -static void h3_stream_ctx_free(struct stream_ctx *stream) +static void h3_stream_ctx_free(struct h3_stream_ctx *stream) { Curl_bufq_free(&stream->recvbuf); Curl_h1_req_parse_free(&stream->h1); free(stream); } -static void h3_stream_hash_free(void *stream) +static void h3_stream_hash_free(unsigned int id, void *stream) { + (void)id; DEBUGASSERT(stream); - h3_stream_ctx_free((struct stream_ctx *)stream); + h3_stream_ctx_free((struct h3_stream_ctx *)stream); } -static void check_resumes(struct Curl_cfilter *cf, - struct Curl_easy *data) +typedef bool cf_quiche_svisit(struct Curl_cfilter *cf, + struct Curl_easy *sdata, + struct h3_stream_ctx *stream, + void *user_data); + +struct cf_quiche_visit_ctx { + struct Curl_cfilter *cf; + struct Curl_multi *multi; + cf_quiche_svisit *cb; + void *user_data; +}; + +static bool cf_quiche_stream_do(unsigned int mid, void *val, void *user_data) +{ + struct cf_quiche_visit_ctx *vctx = user_data; + struct h3_stream_ctx *stream = val; + struct Curl_easy *sdata = Curl_multi_get_easy(vctx->multi, mid); + if(sdata) + return vctx->cb(vctx->cf, sdata, stream, vctx->user_data); + return TRUE; +} + +static void cf_quiche_for_all_streams(struct Curl_cfilter *cf, + struct Curl_multi *multi, + cf_quiche_svisit *do_cb, + void *user_data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct Curl_llist_node *e; + struct cf_quiche_visit_ctx vctx; + vctx.cf = cf; + vctx.multi = multi; + vctx.cb = do_cb; + vctx.user_data = user_data; + Curl_uint_hash_visit(&ctx->streams, cf_quiche_stream_do, &vctx); +} - 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); - } - } +static bool cf_quiche_do_resume(struct Curl_cfilter *cf, + struct Curl_easy *sdata, + struct h3_stream_ctx *stream, + void *user_data) +{ + (void)user_data; + if(stream->quic_flow_blocked) { + stream->quic_flow_blocked = FALSE; + Curl_expire(sdata, 0, EXPIRE_RUN_NOW); + CURL_TRC_CF(sdata, cf, "[%"FMT_PRIu64"] unblock", stream->id); } + return TRUE; +} + +static bool cf_quiche_do_expire(struct Curl_cfilter *cf, + struct Curl_easy *sdata, + struct h3_stream_ctx *stream, + void *user_data) +{ + (void)stream; + (void)user_data; + CURL_TRC_CF(sdata, cf, "conn closed, expire transfer"); + Curl_expire(sdata, 0, EXPIRE_RUN_NOW); + return TRUE; } 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(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream) return CURLE_OK; @@ -234,7 +273,7 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, 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)) { + if(!Curl_uint_hash_set(&ctx->streams, data->mid, stream)) { h3_stream_ctx_free(stream); return CURLE_OUT_OF_MEMORY; } @@ -245,7 +284,7 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, 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(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result; (void)cf; @@ -264,7 +303,7 @@ static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) if(result) CURL_TRC_CF(data, cf, "data_done, flush egress -> %d", result); } - Curl_hash_offt_remove(&ctx->streams, data->mid); + Curl_uint_hash_remove(&ctx->streams, data->mid); } } @@ -272,7 +311,7 @@ static void h3_drain_stream(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); unsigned char bits; (void)cf; @@ -285,52 +324,12 @@ static void h3_drain_stream(struct Curl_cfilter *cf, } } -static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_uint64_t stream_id, - struct stream_ctx **pstream) -{ - struct cf_quiche_ctx *ctx = cf->ctx; - struct 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 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); - } + cf_quiche_for_all_streams(cf, data->multi, cf_quiche_do_expire, NULL); } /* @@ -343,7 +342,7 @@ static CURLcode write_resp_raw(struct Curl_cfilter *cf, const void *mem, size_t memlen) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result = CURLE_OK; ssize_t nwritten; @@ -374,7 +373,7 @@ static int cb_each_header(uint8_t *name, size_t name_len, { struct cb_ctx *x = argp; struct cf_quiche_ctx *ctx = x->cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, x->data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, x->data); CURLcode result; if(!stream) @@ -414,7 +413,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(ctx, x->data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, x->data); ssize_t nread; if(!stream) { @@ -438,7 +437,7 @@ static CURLcode cf_recv_body(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nwritten; struct cb_ctx cb_ctx; CURLcode result = CURLE_OK; @@ -496,7 +495,7 @@ static const char *cf_ev_name(quiche_h3_event *ev) static CURLcode h3_process_event(struct Curl_cfilter *cf, struct Curl_easy *data, - struct stream_ctx *stream, + struct h3_stream_ctx *stream, quiche_h3_event *ev) { struct cb_ctx cb_ctx; @@ -557,14 +556,48 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf, return result; } +static CURLcode cf_quiche_ev_process(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + quiche_h3_event *ev) +{ + CURLcode result = h3_process_event(cf, data, stream, ev); + h3_drain_stream(cf, data); + if(result) + CURL_TRC_CF(data, cf, "error processing event %s " + "for [%"FMT_PRIu64"] -> %d", cf_ev_name(ev), + stream->id, result); + return result; +} + +struct cf_quich_disp_ctx { + curl_uint64_t stream_id; + struct Curl_cfilter *cf; + struct Curl_multi *multi; + quiche_h3_event *ev; + CURLcode result; +}; + +static bool cf_quiche_disp_event(unsigned int mid, void *val, void *user_data) +{ + struct cf_quich_disp_ctx *dctx = user_data; + struct h3_stream_ctx *stream = val; + + if(stream->id == dctx->stream_id) { + struct Curl_easy *sdata = Curl_multi_get_easy(dctx->multi, mid); + if(sdata) + dctx->result = cf_quiche_ev_process(dctx->cf, sdata, stream, dctx->ev); + return FALSE; /* stop iterating */ + } + return TRUE; +} + static CURLcode cf_poll_events(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = NULL; - struct Curl_easy *sdata; + struct h3_stream_ctx *stream = NULL; quiche_h3_event *ev; - CURLcode result; /* Take in the events and distribute them to the transfers. */ while(ctx->h3c) { @@ -576,28 +609,27 @@ static CURLcode cf_poll_events(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "error poll: %"FMT_PRId64, stream3_id); return CURLE_HTTP3; } - - 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, stream, ev); - h3_drain_stream(cf, sdata); - if(result) { - 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 - * transfer we were called with. Otherwise we fail a transfer - * due to a problem in another one. */ - quiche_h3_event_free(ev); + struct cf_quich_disp_ctx dctx; + dctx.stream_id = (curl_uint64_t)stream3_id; + dctx.cf = cf; + dctx.multi = data->multi; + dctx.ev = ev; + dctx.result = CURLE_OK; + stream = H3_STREAM_CTX(ctx, data); + if(stream && stream->id == dctx.stream_id) { + /* event for calling transfer */ + CURLcode result = cf_quiche_ev_process(cf, data, stream, ev); + quiche_h3_event_free(ev); + if(result) return result; - } } - quiche_h3_event_free(ev); + else { + /* another transfer, do not return errors, as they are not for + * the calling transfer */ + Curl_uint_hash_visit(&ctx->streams, cf_quiche_disp_event, &dctx); + quiche_h3_event_free(ev); + } } } return CURLE_OK; @@ -627,7 +659,8 @@ static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, recv_info.from = (struct sockaddr *)remote_addr; recv_info.from_len = remote_addrlen; - nread = quiche_conn_recv(ctx->qconn, (unsigned char *)pkt, pktlen, + nread = quiche_conn_recv(ctx->qconn, + (unsigned char *)CURL_UNCONST(pkt), pktlen, &recv_info); if(nread < 0) { if(QUICHE_ERR_DONE == nread) { @@ -686,7 +719,8 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf, if(rctx.pkts > 0) { /* quiche digested ingress packets. It might have opened flow control * windows again. */ - check_resumes(cf, data); + DEBUGASSERT(data->multi); + cf_quiche_for_all_streams(cf, data->multi, cf_quiche_do_resume, NULL); } return cf_poll_events(cf, data); } @@ -811,7 +845,7 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, CURLcode *err) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; DEBUGASSERT(stream); @@ -843,7 +877,7 @@ 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(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); ssize_t nread = -1; CURLcode result; @@ -916,7 +950,7 @@ out: static ssize_t cf_quiche_send_body(struct Curl_cfilter *cf, struct Curl_easy *data, - struct stream_ctx *stream, + struct h3_stream_ctx *stream, const void *buf, size_t len, bool eos, CURLcode *err) { @@ -924,7 +958,7 @@ static ssize_t cf_quiche_send_body(struct Curl_cfilter *cf, ssize_t nwritten; nwritten = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->id, - (uint8_t *)buf, len, eos); + (uint8_t *)CURL_UNCONST(buf), len, eos); if(nwritten == QUICHE_H3_ERR_DONE || (nwritten == 0 && len > 0)) { /* Blocked on flow control and should HOLD sending. But when do we open * again? */ @@ -975,7 +1009,7 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf, CURLcode *err) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); size_t nheader, i; curl_int64_t stream3_id; struct dynhds h2_headers; @@ -1095,7 +1129,7 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode *err) { struct cf_quiche_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); CURLcode result; ssize_t nwritten; @@ -1154,7 +1188,7 @@ 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(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); return stream && (quiche_conn_stream_writable( ctx->qconn, (curl_uint64_t)stream->id, 1) > 0); @@ -1172,7 +1206,7 @@ static void cf_quiche_adjust_pollset(struct Curl_cfilter *cf, 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); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); bool c_exhaust, s_exhaust; c_exhaust = FALSE; /* Have not found any call in quiche that tells @@ -1195,7 +1229,7 @@ static bool cf_quiche_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - const struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + const struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); (void)cf; return stream && !Curl_bufq_is_empty(&stream->recvbuf); } @@ -1232,7 +1266,7 @@ static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf, h3_data_done(cf, data); break; case CF_CTRL_DATA_DONE_SEND: { - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream && !stream->send_closed) { unsigned char body[1]; ssize_t sent; @@ -1246,7 +1280,7 @@ static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf, break; } case CF_CTRL_DATA_IDLE: { - struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); if(stream && !stream->closed) { result = cf_flush_egress(cf, data); if(result) @@ -1267,6 +1301,9 @@ static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf, int rv; CURLcode result; const struct Curl_sockaddr_ex *sockaddr; +static const struct alpn_spec ALPN_SPEC_H3 = { + { "h3" }, 1 +}; DEBUGASSERT(ctx->q.sockfd != CURL_SOCKET_BAD); DEBUGASSERT(ctx->initialized); @@ -1281,7 +1318,6 @@ static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf, return CURLE_FAILED_INIT; } 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); @@ -1298,15 +1334,12 @@ static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf, 10 * QUIC_MAX_STREAMS * H3_STREAM_WINDOW_SIZE); quiche_config_set_max_stream_window(ctx->cfg, 10 * H3_STREAM_WINDOW_SIZE); quiche_config_set_application_protos(ctx->cfg, - (uint8_t *) - QUICHE_H3_APPLICATION_PROTOCOL, + (uint8_t *)CURL_UNCONST(QUICHE_H3_APPLICATION_PROTOCOL), sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1); result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, - QUICHE_H3_APPLICATION_PROTOCOL, - sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1, - NULL, NULL, cf, NULL); + &ALPN_SPEC_H3, NULL, NULL, cf, NULL); if(result) return result; @@ -1378,7 +1411,7 @@ static CURLcode cf_quiche_verify_peer(struct Curl_cfilter *cf, static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct cf_quiche_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; @@ -1390,7 +1423,7 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, /* Connect the UDP filter first */ if(!cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, blocking, done); + result = Curl_conn_cf_connect(cf->next, data, done); if(result || !*done) return result; } @@ -1419,7 +1452,7 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, 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(ctx->handshake_at, ctx->started_at)); + (int)curlx_timediff(ctx->handshake_at, ctx->started_at)); result = cf_quiche_verify_peer(cf, data); if(!result) { CURL_TRC_CF(data, cf, "peer verified"); @@ -1535,19 +1568,19 @@ static CURLcode cf_quiche_query(struct Curl_cfilter *cf, switch(query) { case CF_QUERY_MAX_CONCURRENT: { - curl_uint64_t max_streams = CONN_INUSE(cf->conn); + curl_uint64_t max_streams = CONN_ATTACHED(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 conn[%" FMT_OFF_T "]: " - "MAX_CONCURRENT -> %d (%zu in use)", - cf->conn->connection_id, *pres1, CONN_INUSE(cf->conn)); + "MAX_CONCURRENT -> %d (%u in use)", + cf->conn->connection_id, *pres1, CONN_ATTACHED(cf->conn)); return CURLE_OK; } case CF_QUERY_CONNECT_REPLY_MS: if(ctx->q.got_first_byte) { - timediff_t ms = Curl_timediff(ctx->q.first_byte_at, ctx->started_at); + timediff_t ms = curlx_timediff(ctx->q.first_byte_at, ctx->started_at); *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; } else diff --git a/Utilities/cmcurl/lib/vquic/curl_quiche.h b/Utilities/cmcurl/lib/vquic/curl_quiche.h index bce781c1bc..9832687bdc 100644 --- a/Utilities/cmcurl/lib/vquic/curl_quiche.h +++ b/Utilities/cmcurl/lib/vquic/curl_quiche.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_QUICHE diff --git a/Utilities/cmcurl/lib/vquic/vquic-tls.c b/Utilities/cmcurl/lib/vquic/vquic-tls.c index ff2445d45f..2a5be138fc 100644 --- a/Utilities/cmcurl/lib/vquic/vquic-tls.c +++ b/Utilities/cmcurl/lib/vquic/vquic-tls.c @@ -22,214 +22,47 @@ * ***************************************************************************/ -#include "curl_setup.h" +#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" +#include "../vtls/openssl.h" #elif defined(USE_GNUTLS) #include #include #include #include #include -#include "vtls/gtls.h" +#include "../vtls/gtls.h" #elif defined(USE_WOLFSSL) #include #include #include -#include "vtls/wolfssl.h" +#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 "vtls/vtls_scache.h" +#include "../urldata.h" +#include "../curl_trc.h" +#include "../cfilters.h" +#include "../multiif.h" +#include "../vtls/keylog.h" +#include "../vtls/vtls.h" +#include "../vtls/vtls_scache.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" - -#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)Curl_wssl_setup_session(cf, data, &ctx->wssl, peer->scache_key); - } - - return CURLE_OK; -} -#endif /* defined(USE_WOLFSSL) */ +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" 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, + const struct alpn_spec *alpns, Curl_vquic_tls_ctx_setup *cb_setup, void *cb_user_data, void *ssl_user_data, Curl_vquic_session_reuse_cb *session_reuse_cb) @@ -254,21 +87,17 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, #ifdef USE_OPENSSL (void)result; - return Curl_ossl_ctx_init(&ctx->ossl, cf, data, peer, - (const unsigned char *)alpn, alpn_len, - cb_setup, cb_user_data, NULL, ssl_user_data); + return Curl_ossl_ctx_init(&ctx->ossl, cf, data, peer, alpns, + cb_setup, cb_user_data, NULL, ssl_user_data, + session_reuse_cb); #elif defined(USE_GNUTLS) - return Curl_gtls_ctx_init(&ctx->gtls, cf, data, peer, - (const unsigned char *)alpn, alpn_len, + return Curl_gtls_ctx_init(&ctx->gtls, cf, data, peer, alpns, cb_setup, cb_user_data, ssl_user_data, session_reuse_cb); #elif defined(USE_WOLFSSL) - result = wssl_init_ctx(ctx, cf, data, cb_setup, cb_user_data); - if(result) - return result; - - (void)session_reuse_cb; - return wssl_init_ssl(ctx, cf, data, peer, alpn, alpn_len, ssl_user_data); + return Curl_wssl_ctx_init(&ctx->wssl, cf, data, peer, alpns, + cb_setup, cb_user_data, + ssl_user_data, session_reuse_cb); #else #error "no TLS lib in used, should not happen" return CURLE_FAILED_INIT; @@ -287,10 +116,10 @@ void Curl_vquic_tls_cleanup(struct curl_tls_ctx *ctx) 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); + if(ctx->wssl.ssl) + wolfSSL_free(ctx->wssl.ssl); + if(ctx->wssl.ssl_ctx) + wolfSSL_CTX_free(ctx->wssl.ssl_ctx); #endif memset(ctx, 0, sizeof(*ctx)); } @@ -350,16 +179,16 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, #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); + char *snihost = peer->sni ? peer->sni : peer->hostname; + WOLFSSL_X509* cert = wolfSSL_get_peer_certificate(ctx->wssl.ssl); + if(wolfSSL_X509_check_host(cert, snihost, strlen(snihost), 0, NULL) + == WOLFSSL_FAILURE) { + result = CURLE_PEER_FAILED_VERIFICATION; } - + wolfSSL_X509_free(cert); } + if(!result) + result = Curl_wssl_verify_pinned(cf, data, &ctx->wssl); #endif /* on error, remove any session we might have in the pool */ if(result) diff --git a/Utilities/cmcurl/lib/vquic/vquic-tls.h b/Utilities/cmcurl/lib/vquic/vquic-tls.h index c0706b0eb8..bf29eecc91 100644 --- a/Utilities/cmcurl/lib/vquic/vquic-tls.h +++ b/Utilities/cmcurl/lib/vquic/vquic-tls.h @@ -24,15 +24,16 @@ * ***************************************************************************/ -#include "curl_setup.h" -#include "bufq.h" -#include "vtls/vtls.h" -#include "vtls/openssl.h" +#include "../curl_setup.h" +#include "../bufq.h" +#include "../vtls/vtls.h" +#include "../vtls/vtls_int.h" +#include "../vtls/openssl.h" #if defined(USE_HTTP3) && \ (defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL)) -#include "vtls/wolfssl.h" +#include "../vtls/wolfssl.h" struct ssl_peer; struct Curl_ssl_session; @@ -43,7 +44,7 @@ struct curl_tls_ctx { #elif defined(USE_GNUTLS) struct gtls_ctx gtls; #elif defined(USE_WOLFSSL) - struct wolfssl_ctx wssl; + struct wssl_ctx wssl; #endif }; @@ -60,6 +61,7 @@ typedef CURLcode Curl_vquic_tls_ctx_setup(struct Curl_cfilter *cf, typedef CURLcode Curl_vquic_session_reuse_cb(struct Curl_cfilter *cf, struct Curl_easy *data, + struct alpn_spec *alpns, struct Curl_ssl_session *scs, bool *do_early_data); @@ -70,9 +72,7 @@ typedef CURLcode Curl_vquic_session_reuse_cb(struct Curl_cfilter *cf, * @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 alpns the ALPN specifications to negotiate, may be NULL * @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 @@ -82,7 +82,7 @@ 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, + const struct alpn_spec *alpns, Curl_vquic_tls_ctx_setup *cb_setup, void *cb_user_data, void *ssl_user_data, diff --git a/Utilities/cmcurl/lib/vquic/vquic.c b/Utilities/cmcurl/lib/vquic/vquic.c index 69de5c1a8f..b5dc44f8aa 100644 --- a/Utilities/cmcurl/lib/vquic/vquic.c +++ b/Utilities/cmcurl/lib/vquic/vquic.c @@ -22,7 +22,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef HAVE_NETINET_UDP_H #include @@ -30,25 +30,26 @@ #ifdef HAVE_FCNTL_H #include #endif -#include "urldata.h" -#include "bufq.h" -#include "dynbuf.h" -#include "cfilters.h" -#include "curl_trc.h" +#include "../urldata.h" +#include "../bufq.h" +#include "../curlx/dynbuf.h" +#include "../cfilters.h" +#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 "../multiif.h" +#include "../rand.h" #include "vquic.h" #include "vquic_int.h" -#include "strerror.h" +#include "../strerror.h" +#include "../curlx/strparse.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" #ifdef USE_HTTP3 @@ -57,6 +58,16 @@ #define NW_SEND_CHUNKS 2 +int Curl_vquic_init(void) +{ +#if defined(USE_NGTCP2) && defined(OPENSSL_QUIC_API2) + if(ngtcp2_crypto_ossl_init()) + return 0; +#endif + + return 1; +} + void Curl_quic_ver(char *p, size_t len) { #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) @@ -81,10 +92,10 @@ CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx) #endif #ifdef DEBUGBUILD { - char *p = getenv("CURL_DBG_QUIC_WBLOCK"); + const char *p = getenv("CURL_DBG_QUIC_WBLOCK"); if(p) { - long l = strtol(p, NULL, 10); - if(l >= 0 && l <= 100) + curl_off_t l; + if(!curlx_str_number(&p, &l, 100)) qctx->wblock_percent = (int)l; } } @@ -101,7 +112,7 @@ void vquic_ctx_free(struct cf_quic_ctx *qctx) void vquic_ctx_update_time(struct cf_quic_ctx *qctx) { - qctx->last_op = Curl_now(); + qctx->last_op = curlx_now(); } static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, @@ -126,7 +137,7 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, #endif *psent = 0; - msg_iov.iov_base = (uint8_t *)pkt; + msg_iov.iov_base = (uint8_t *)CURL_UNCONST(pkt); msg_iov.iov_len = pktlen; msg.msg_iov = &msg_iov; msg.msg_iovlen = 1; @@ -147,17 +158,18 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, #endif - while((sent = sendmsg(qctx->sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR) + while((sent = sendmsg(qctx->sockfd, &msg, 0)) == -1 && + SOCKERRNO == SOCKEINTR) ; if(sent == -1) { switch(SOCKERRNO) { case EAGAIN: -#if EAGAIN != EWOULDBLOCK - case EWOULDBLOCK: +#if EAGAIN != SOCKEWOULDBLOCK + case SOCKEWOULDBLOCK: #endif return CURLE_AGAIN; - case EMSGSIZE: + case SOCKEMSGSIZE: /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */ break; case EIO: @@ -185,16 +197,16 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, while((sent = send(qctx->sockfd, (const char *)pkt, (SEND_TYPE_ARG3)pktlen, 0)) == -1 && - SOCKERRNO == EINTR) + SOCKERRNO == SOCKEINTR) ; if(sent == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { return CURLE_AGAIN; } else { failf(data, "send() returned %zd (errno %d)", sent, SOCKERRNO); - if(SOCKERRNO != EMSGSIZE) { + if(SOCKERRNO != SOCKEMSGSIZE) { return CURLE_SEND_ERROR; } /* UDP datagram is too large; caused by PMTUD. Just let it be @@ -377,7 +389,7 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, total_nread = 0; while(pkts < max_pkts) { - n = (int)CURLMIN(MMSG_NUM, max_pkts); + n = (int)CURLMIN(CURLMIN(MMSG_NUM, IOV_MAX), max_pkts); memset(&mmsg, 0, sizeof(mmsg)); for(i = 0; i < n; ++i) { msg_iov[i].iov_base = bufs[i]; @@ -391,14 +403,14 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, } while((mcount = recvmmsg(qctx->sockfd, mmsg, n, 0, NULL)) == -1 && - SOCKERRNO == EINTR) + SOCKERRNO == SOCKEINTR) ; if(mcount == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { CURL_TRC_CF(data, cf, "ingress, recvmmsg -> EAGAIN"); goto out; } - if(!cf->connected && SOCKERRNO == ECONNREFUSED) { + if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { struct ip_quadruple ip; Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); failf(data, "QUIC: connection to %s port %u refused", @@ -469,27 +481,28 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, size_t pktlen; size_t offset, to; - msg_iov.iov_base = buf; - msg_iov.iov_len = (int)sizeof(buf); - - 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;) { + /* fully initialise this on each call to `recvmsg()`. There seem to + * operating systems out there that mess with `msg_iov.iov_len`. */ + memset(&msg, 0, sizeof(msg)); + msg_iov.iov_base = buf; + msg_iov.iov_len = (int)sizeof(buf); + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + msg.msg_control = msg_ctrl; 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) + SOCKERRNO == SOCKEINTR) ; if(nread == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { goto out; } - if(!cf->connected && SOCKERRNO == ECONNREFUSED) { + if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { struct ip_quadruple ip; Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); failf(data, "QUIC: connection to %s port %u refused", @@ -557,14 +570,14 @@ static CURLcode recvfrom_packets(struct Curl_cfilter *cf, while((nread = recvfrom(qctx->sockfd, (char *)buf, bufsize, 0, (struct sockaddr *)&remote_addr, &remote_addrlen)) == -1 && - SOCKERRNO == EINTR) + SOCKERRNO == SOCKEINTR) ; if(nread == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == SOCKEWOULDBLOCK) { CURL_TRC_CF(data, cf, "ingress, recvfrom -> EAGAIN"); goto out; } - if(!cf->connected && SOCKERRNO == ECONNREFUSED) { + if(!cf->connected && SOCKERRNO == SOCKECONNREFUSED) { struct ip_quadruple ip; Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); failf(data, "QUIC: connection to %s port %u refused", @@ -638,25 +651,25 @@ CURLcode Curl_qlogdir(struct Curl_easy *data, struct dynbuf fname; CURLcode result; unsigned int i; - Curl_dyn_init(&fname, DYN_QLOG_NAME); - result = Curl_dyn_add(&fname, qlog_dir); + curlx_dyn_init(&fname, DYN_QLOG_NAME); + result = curlx_dyn_add(&fname, qlog_dir); if(!result) - result = Curl_dyn_add(&fname, "/"); + result = curlx_dyn_add(&fname, "/"); for(i = 0; (i < scidlen) && !result; i++) { char hex[3]; msnprintf(hex, 3, "%02x", scid[i]); - result = Curl_dyn_add(&fname, hex); + result = curlx_dyn_add(&fname, hex); } if(!result) - result = Curl_dyn_add(&fname, ".sqlog"); + result = curlx_dyn_add(&fname, ".sqlog"); if(!result) { - int qlogfd = open(Curl_dyn_ptr(&fname), O_WRONLY|O_CREAT|CURL_O_BINARY, + int qlogfd = open(curlx_dyn_ptr(&fname), O_WRONLY|O_CREAT|CURL_O_BINARY, data->set.new_file_perms); if(qlogfd != -1) *qlogfdp = qlogfd; } - Curl_dyn_free(&fname); + curlx_dyn_free(&fname); if(result) return result; } diff --git a/Utilities/cmcurl/lib/vquic/vquic.h b/Utilities/cmcurl/lib/vquic/vquic.h index 1cd3e258f4..dbf63b1f6f 100644 --- a/Utilities/cmcurl/lib/vquic/vquic.h +++ b/Utilities/cmcurl/lib/vquic/vquic.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_HTTP3 struct Curl_cfilter; @@ -33,6 +33,7 @@ struct connectdata; struct Curl_addrinfo; void Curl_quic_ver(char *p, size_t len); +int Curl_vquic_init(void); CURLcode Curl_qlogdir(struct Curl_easy *data, unsigned char *scid, @@ -48,6 +49,8 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, extern struct Curl_cftype Curl_cft_http3; +#else +#define Curl_vquic_init() 1 #endif /* !USE_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, diff --git a/Utilities/cmcurl/lib/vquic/vquic_int.h b/Utilities/cmcurl/lib/vquic/vquic_int.h index 754e1f5910..4641c3125b 100644 --- a/Utilities/cmcurl/lib/vquic/vquic_int.h +++ b/Utilities/cmcurl/lib/vquic/vquic_int.h @@ -24,15 +24,13 @@ * ***************************************************************************/ -#include "curl_setup.h" -#include "bufq.h" +#include "../curl_setup.h" +#include "../bufq.h" #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 */ @@ -53,6 +51,9 @@ struct cf_quic_ctx { BIT(no_gso); /* do not use gso on sending */ }; +#define H3_STREAM_CTX(ctx,data) \ + (data ? Curl_uint_hash_get(&(ctx)->streams, (data)->mid) : NULL) + CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx); void vquic_ctx_free(struct cf_quic_ctx *qctx); diff --git a/Utilities/cmcurl/lib/vssh/curl_path.c b/Utilities/cmcurl/lib/vssh/curl_path.c index 61452a4252..117d2e6009 100644 --- a/Utilities/cmcurl/lib/vssh/curl_path.c +++ b/Utilities/cmcurl/lib/vssh/curl_path.c @@ -22,15 +22,15 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_SSH) #include "curl_path.h" #include -#include "curl_memory.h" -#include "escape.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../escape.h" +#include "../memdebug.h" #define MAX_SSHPATH_LEN 100000 /* arbitrary */ @@ -50,13 +50,13 @@ CURLcode Curl_getworkingpath(struct Curl_easy *data, return result; /* new path to switch to in case we need to */ - Curl_dyn_init(&npath, MAX_SSHPATH_LEN); + curlx_dyn_init(&npath, MAX_SSHPATH_LEN); /* Check for /~/, indicating relative to the user's home directory */ if((data->conn->handler->protocol & CURLPROTO_SCP) && (working_path_len > 3) && (!memcmp(working_path, "/~/", 3))) { /* It is referenced to the home directory, so strip the leading '/~/' */ - if(Curl_dyn_addn(&npath, &working_path[3], working_path_len - 3)) { + if(curlx_dyn_addn(&npath, &working_path[3], working_path_len - 3)) { free(working_path); return CURLE_OUT_OF_MEMORY; } @@ -64,7 +64,7 @@ CURLcode Curl_getworkingpath(struct Curl_easy *data, else if((data->conn->handler->protocol & CURLPROTO_SFTP) && (!strcmp("/~", working_path) || ((working_path_len > 2) && !memcmp(working_path, "/~/", 3)))) { - if(Curl_dyn_add(&npath, homedir)) { + if(curlx_dyn_add(&npath, homedir)) { free(working_path); return CURLE_OUT_OF_MEMORY; } @@ -73,24 +73,24 @@ CURLcode Curl_getworkingpath(struct Curl_easy *data, const char *p; int copyfrom = 3; /* Copy a separating '/' if homedir does not end with one */ - len = Curl_dyn_len(&npath); - p = Curl_dyn_ptr(&npath); + len = curlx_dyn_len(&npath); + p = curlx_dyn_ptr(&npath); if(len && (p[len-1] != '/')) copyfrom = 2; - if(Curl_dyn_addn(&npath, - &working_path[copyfrom], working_path_len - copyfrom)) { + if(curlx_dyn_addn(&npath, &working_path[copyfrom], + working_path_len - copyfrom)) { free(working_path); return CURLE_OUT_OF_MEMORY; } } } - if(Curl_dyn_len(&npath)) { + if(curlx_dyn_len(&npath)) { free(working_path); /* store the pointer for the caller to receive */ - *path = Curl_dyn_ptr(&npath); + *path = curlx_dyn_ptr(&npath); } else *path = working_path; @@ -133,7 +133,7 @@ CURLcode Curl_get_pathname(const char **cpp, char **path, const char *homedir) if(!*cp || !homedir) return CURLE_QUOTE_ERROR; - Curl_dyn_init(&out, MAX_PATHLENGTH); + curlx_dyn_init(&out, MAX_PATHLENGTH); /* Ignore leading whitespace */ cp += strspn(cp, WHITESPACE); @@ -158,12 +158,12 @@ CURLcode Curl_get_pathname(const char **cpp, char **path, const char *homedir) goto fail; } } - result = Curl_dyn_addn(&out, &cp[i], 1); + result = curlx_dyn_addn(&out, &cp[i], 1); if(result) return result; } - if(!Curl_dyn_len(&out)) + if(!curlx_dyn_len(&out)) goto fail; /* return pointer to second parameter if it exists */ @@ -180,23 +180,23 @@ CURLcode Curl_get_pathname(const char **cpp, char **path, const char *homedir) /* Handling for relative path - prepend home directory */ if(cp[0] == '/' && cp[1] == '~' && cp[2] == '/') { - result = Curl_dyn_add(&out, homedir); + result = curlx_dyn_add(&out, homedir); if(!result) - result = Curl_dyn_addn(&out, "/", 1); + result = curlx_dyn_addn(&out, "/", 1); if(result) return result; cp += 3; } /* Copy path name up until first "whitespace" */ - result = Curl_dyn_addn(&out, cp, (end - cp)); + result = curlx_dyn_addn(&out, cp, (end - cp)); if(result) return result; } - *path = Curl_dyn_ptr(&out); + *path = curlx_dyn_ptr(&out); return CURLE_OK; fail: - Curl_dyn_free(&out); + curlx_dyn_free(&out); return CURLE_QUOTE_ERROR; } diff --git a/Utilities/cmcurl/lib/vssh/curl_path.h b/Utilities/cmcurl/lib/vssh/curl_path.h index 8e984174d7..1c167f9660 100644 --- a/Utilities/cmcurl/lib/vssh/curl_path.h +++ b/Utilities/cmcurl/lib/vssh/curl_path.h @@ -24,9 +24,9 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #include -#include "urldata.h" +#include "../urldata.h" CURLcode Curl_getworkingpath(struct Curl_easy *data, char *homedir, diff --git a/Utilities/cmcurl/lib/vssh/libssh.c b/Utilities/cmcurl/lib/vssh/libssh.c index 2390967d91..86f1e1a568 100644 --- a/Utilities/cmcurl/lib/vssh/libssh.c +++ b/Utilities/cmcurl/lib/vssh/libssh.c @@ -25,7 +25,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_LIBSSH @@ -46,29 +46,29 @@ #endif #include -#include "urldata.h" -#include "sendf.h" -#include "hostip.h" -#include "progress.h" -#include "transfer.h" -#include "escape.h" -#include "http.h" /* for HTTP proxy tunnel stuff */ +#include "../urldata.h" +#include "../sendf.h" +#include "../hostip.h" +#include "../progress.h" +#include "../transfer.h" +#include "../escape.h" +#include "../http.h" /* for HTTP proxy tunnel stuff */ #include "ssh.h" -#include "url.h" -#include "speedcheck.h" -#include "getinfo.h" -#include "strdup.h" -#include "strcase.h" -#include "vtls/vtls.h" -#include "cfilters.h" -#include "connect.h" -#include "inet_ntop.h" -#include "parsedate.h" /* for the week day and month names */ -#include "sockaddr.h" /* required for Curl_sockaddr_storage */ -#include "strtoofft.h" -#include "multiif.h" -#include "select.h" -#include "warnless.h" +#include "../url.h" +#include "../speedcheck.h" +#include "../getinfo.h" +#include "../strdup.h" +#include "../strcase.h" +#include "../vtls/vtls.h" +#include "../cfilters.h" +#include "../connect.h" +#include "../inet_ntop.h" +#include "../parsedate.h" /* for the week day and month names */ +#include "../sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "../curlx/strparse.h" +#include "../multiif.h" +#include "../select.h" +#include "../curlx/warnless.h" #include "curl_path.h" #ifdef HAVE_SYS_STAT_H @@ -82,9 +82,9 @@ #endif /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* A recent macro provided by libssh. Or make our own. */ #ifndef SSH_STRING_FREE_CHAR @@ -130,14 +130,19 @@ CURLcode sftp_perform(struct Curl_easy *data, bool *connected, bool *dophase_done); -static void sftp_quote(struct Curl_easy *data); -static void sftp_quote_stat(struct Curl_easy *data); +static void sftp_quote(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp); +static void sftp_quote_stat(struct Curl_easy *data, struct ssh_conn *sshc); 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 void myssh_block2waitfor(struct connectdata *conn, + struct ssh_conn *sshc, + bool block); static CURLcode myssh_setup_connection(struct Curl_easy *data, struct connectdata *conn); +static void sshc_cleanup(struct ssh_conn *sshc); /* * SCP protocol handler. @@ -223,23 +228,23 @@ static CURLcode sftp_error_to_CURLE(int err) } #ifndef DEBUGBUILD -#define state(x,y) mystate(x,y) +#define myssh_state(x,y,z) myssh_set_state(x,y,z) #else -#define state(x,y) mystate(x,y, __LINE__) +#define myssh_state(x,y,z) myssh_set_state(x,y,z, __LINE__) #endif /* * SSH State machine related code */ /* This is the ONLY way to change SSH state! */ -static void mystate(struct Curl_easy *data, sshstate nowstate +static void myssh_set_state(struct Curl_easy *data, + struct ssh_conn *sshc, + sshstate nowstate #ifdef DEBUGBUILD - , int lineno + , int lineno #endif ) { - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char *const names[] = { @@ -312,7 +317,7 @@ static void mystate(struct Curl_easy *data, sshstate nowstate lineno); } #endif - + (void)data; sshc->state = nowstate; } @@ -326,11 +331,9 @@ static void mystate(struct Curl_easy *data, sshstate nowstate * * Returns SSH_OK or SSH_ERROR. */ -static int myssh_is_known(struct Curl_easy *data) +static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc) { int rc; - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; ssh_key pubkey; size_t hlen; unsigned char *hash = NULL; @@ -516,14 +519,14 @@ cleanup: return rc; } -#define MOVE_TO_ERROR_STATE(_r) do { \ - state(data, SSH_SESSION_DISCONNECT); \ - sshc->actualcode = _r; \ - rc = SSH_ERROR; \ +#define MOVE_TO_ERROR_STATE(_r) do { \ + myssh_state(data, sshc, SSH_SESSION_DISCONNECT); \ + sshc->actualcode = _r; \ + rc = SSH_ERROR; \ } while(0) #define MOVE_TO_SFTP_CLOSE_STATE() do { \ - state(data, SSH_SFTP_CLOSE); \ + myssh_state(data, sshc, SSH_SFTP_CLOSE); \ sshc->actualcode = \ sftp_error_to_CURLE(sftp_get_error(sshc->sftp_session)); \ rc = SSH_ERROR; \ @@ -532,7 +535,7 @@ cleanup: #define MOVE_TO_PASSWD_AUTH do { \ if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) { \ rc = SSH_OK; \ - state(data, SSH_AUTH_PASS_INIT); \ + myssh_state(data, sshc, SSH_AUTH_PASS_INIT); \ } \ else { \ MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); \ @@ -542,7 +545,7 @@ cleanup: #define MOVE_TO_KEY_AUTH do { \ if(sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { \ rc = SSH_OK; \ - state(data, SSH_AUTH_KEY_INIT); \ + myssh_state(data, sshc, SSH_AUTH_KEY_INIT); \ } \ else { \ MOVE_TO_PASSWD_AUTH; \ @@ -552,7 +555,7 @@ cleanup: #define MOVE_TO_GSSAPI_AUTH do { \ if(sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { \ rc = SSH_OK; \ - state(data, SSH_AUTH_GSSAPI); \ + myssh_state(data, sshc, SSH_AUTH_GSSAPI); \ } \ else { \ MOVE_TO_KEY_AUTH; \ @@ -560,10 +563,10 @@ cleanup: } while(0) static -int myssh_auth_interactive(struct connectdata *conn) +int myssh_auth_interactive(struct connectdata *conn, + struct ssh_conn *sshc) { int rc; - struct ssh_conn *sshc = &conn->proto.sshc; int nprompts; restart: @@ -624,21 +627,464 @@ restart: return rc; } +static void myssh_state_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + sshc->secondCreateDirs = 0; + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_OK; + +#if 0 + ssh_set_log_level(SSH_LOG_PROTOCOL); +#endif + + /* Set libssh to non-blocking, since everything internally is + non-blocking */ + ssh_set_blocking(sshc->ssh_session, 0); + + myssh_state(data, sshc, SSH_S_STARTUP); +} + +static int myssh_state_startup(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + struct connectdata *conn = data->conn; + int rc = ssh_connect(sshc->ssh_session); + + myssh_block2waitfor(conn, sshc, (rc == SSH_AGAIN)); + if(rc == SSH_AGAIN) { + DEBUGF(infof(data, "ssh_connect -> EAGAIN")); + } + else if(rc != SSH_OK) { + failf(data, "Failure establishing ssh session"); + MOVE_TO_ERROR_STATE(CURLE_FAILED_INIT); + } + else + myssh_state(data, sshc, SSH_HOSTKEY); + + return rc; +} + +static int myssh_state_authlist(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc; + sshc->authed = FALSE; + + rc = ssh_userauth_none(sshc->ssh_session, NULL); + if(rc == SSH_AUTH_AGAIN) + return SSH_AGAIN; + + if(rc == SSH_AUTH_SUCCESS) { + sshc->authed = TRUE; + infof(data, "Authenticated with none"); + myssh_state(data, sshc, SSH_AUTH_DONE); + return rc; + } + else if(rc == SSH_AUTH_ERROR) { + MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); + return rc; + } + + 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 ? + "public key, ": "", + sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC ? + "GSSAPI, " : "", + sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE ? + "keyboard-interactive, " : "", + sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD ? + "password": ""); + if(sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { + myssh_state(data, sshc, SSH_AUTH_PKEY_INIT); + infof(data, "Authentication using SSH public key file"); + } + else if(sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { + myssh_state(data, sshc, SSH_AUTH_GSSAPI); + } + else if(sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { + myssh_state(data, sshc, SSH_AUTH_KEY_INIT); + } + else if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) { + myssh_state(data, sshc, SSH_AUTH_PASS_INIT); + } + else { /* unsupported authentication method */ + MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); + } + return rc; +} + +static int myssh_state_auth_pkey_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc; + if(!(data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY)) { + MOVE_TO_GSSAPI_AUTH; + return 0; + } + + /* Two choices, (1) private key was given on CMD, + * (2) use the "default" keys. */ + if(data->set.str[STRING_SSH_PRIVATE_KEY]) { + if(sshc->pubkey && !data->set.ssl.key_passwd) { + rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, + sshc->pubkey); + if(rc == SSH_AUTH_AGAIN) + return SSH_AGAIN; + + if(rc != SSH_OK) { + MOVE_TO_GSSAPI_AUTH; + return rc; + } + } + + rc = ssh_pki_import_privkey_file(data-> + set.str[STRING_SSH_PRIVATE_KEY], + data->set.ssl.key_passwd, NULL, + NULL, &sshc->privkey); + if(rc != SSH_OK) { + failf(data, "Could not load private key file %s", + data->set.str[STRING_SSH_PRIVATE_KEY]); + MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); + return rc; + } + + myssh_state(data, sshc, SSH_AUTH_PKEY); + } + else { + rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL, + data->set.ssl.key_passwd); + if(rc == SSH_AUTH_AGAIN) + return SSH_AGAIN; + + if(rc == SSH_AUTH_SUCCESS) { + rc = SSH_OK; + sshc->authed = TRUE; + infof(data, "Completed public key authentication"); + myssh_state(data, sshc, SSH_AUTH_DONE); + return rc; + } + + MOVE_TO_GSSAPI_AUTH; + } + return rc; +} + +static int myssh_state_upload_init(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + int flags; + int rc = 0; + + if(data->state.resume_from) { + sftp_attributes attrs; + + if(data->state.resume_from < 0) { + attrs = sftp_stat(sshc->sftp_session, sshp->path); + if(attrs) { + curl_off_t size = attrs->size; + if(size < 0) { + failf(data, "Bad file size (%" FMT_OFF_T ")", size); + MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); + return rc; + } + data->state.resume_from = attrs->size; + + sftp_attributes_free(attrs); + } + else { + data->state.resume_from = 0; + } + } + } + + if(data->set.remote_append) + /* Try to open for append, but create if nonexisting */ + flags = O_WRONLY|O_CREAT|O_APPEND; + else if(data->state.resume_from > 0) + /* If we have restart position then open for append */ + flags = O_WRONLY|O_APPEND; + else + /* Clear file before writing (normal behavior) */ + flags = O_WRONLY|O_CREAT|O_TRUNC; + + if(sshc->sftp_file) + sftp_close(sshc->sftp_file); + sshc->sftp_file = + sftp_open(sshc->sftp_session, sshp->path, + flags, (mode_t)data->set.new_file_perms); + if(!sshc->sftp_file) { + int err = sftp_get_error(sshc->sftp_session); + + if(((err == SSH_FX_NO_SUCH_FILE || err == SSH_FX_FAILURE || + err == SSH_FX_NO_SUCH_PATH)) && + (data->set.ftp_create_missing_dirs && + (strlen(sshp->path) > 1))) { + /* try to create the path remotely */ + rc = 0; + sshc->secondCreateDirs = 1; + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS_INIT); + return rc; + } + else { + MOVE_TO_SFTP_CLOSE_STATE(); + return rc; + } + } + + /* 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"); + MOVE_TO_ERROR_STATE(CURLE_FTP_COULDNT_USE_REST); + return rc; + } + /* 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 = + data->state.fread_func(scratch, 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, "Failed to read data"); + MOVE_TO_ERROR_STATE(CURLE_FTP_COULDNT_USE_REST); + return rc; + } + } 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); + } + + rc = sftp_seek64(sshc->sftp_file, data->state.resume_from); + if(rc) { + MOVE_TO_SFTP_CLOSE_STATE(); + return rc; + } + } + 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 libssh 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); +#if LIBSSH_VERSION_INT > SSH_VERSION_INT(0, 11, 0) + sshc->sftp_send_state = 0; +#endif + myssh_state(data, sshc, SSH_STOP); + return rc; +} + +static int myssh_state_sftp_download_stat(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + curl_off_t size; + int rc = 0; + sftp_attributes attrs = sftp_fstat(sshc->sftp_file); + if(!attrs || + !(attrs->flags & SSH_FILEXFER_ATTR_SIZE) || + (attrs->size == 0)) { + /* + * 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; + data->req.maxdownload = -1; + Curl_pgrsSetDownloadSize(data, -1); + size = 0; + if(attrs) + sftp_attributes_free(attrs); + } + else { + size = attrs->size; + + sftp_attributes_free(attrs); + + if(size < 0) { + failf(data, "Bad file size (%" FMT_OFF_T ")", size); + MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); + return rc; + } + if(data->state.use_range) { + curl_off_t from, to; + const char *p = data->state.range; + int from_t, to_t; + + from_t = curlx_str_number(&p, &from, CURL_OFF_T_MAX); + if(from_t == STRE_OVERFLOW) { + MOVE_TO_ERROR_STATE(CURLE_RANGE_ERROR); + return rc; + } + curlx_str_passblanks(&p); + (void)curlx_str_single(&p, '-'); + + to_t = curlx_str_numblanks(&p, &to); + if(to_t == STRE_OVERFLOW) + return CURLE_RANGE_ERROR; + + if((to_t == STRE_NO_NUM) || (to >= size)) { + to = size - 1; + to_t = STRE_OK; + } + + if(from_t == STRE_NO_NUM) { + /* from is relative to end of file */ + from = size - to; + to = size - 1; + from_t = STRE_OK; + } + if(from > size) { + failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%" + FMT_OFF_T ")", from, size); + MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); + return rc; + } + if(from > to) { + from = to; + size = 0; + } + else { + if((to - from) == CURL_OFF_T_MAX) { + MOVE_TO_ERROR_STATE(CURLE_RANGE_ERROR); + return rc; + } + size = to - from + 1; + } + + rc = sftp_seek64(sshc->sftp_file, from); + if(rc) { + MOVE_TO_SFTP_CLOSE_STATE(); + return rc; + } + } + 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)size < -data->state.resume_from) { + failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%" + FMT_OFF_T ")", data->state.resume_from, size); + MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); + return rc; + } + /* download from where? */ + data->state.resume_from += size; + } + else { + if((curl_off_t)size < data->state.resume_from) { + failf(data, "Offset (%" FMT_OFF_T + ") was beyond file size (%" FMT_OFF_T ")", + data->state.resume_from, size); + MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); + return rc; + } + } + /* Now store the number of bytes we are expected to download */ + data->req.size = size - data->state.resume_from; + data->req.maxdownload = size - data->state.resume_from; + Curl_pgrsSetDownloadSize(data, + size - data->state.resume_from); + + rc = sftp_seek64(sshc->sftp_file, data->state.resume_from); + if(rc) { + MOVE_TO_SFTP_CLOSE_STATE(); + return rc; + } + } + + /* Setup the actual download */ + if(data->req.size == 0) { + /* no data to transfer */ + Curl_xfer_setup_nop(data); + infof(data, "File already completely downloaded"); + myssh_state(data, sshc, SSH_STOP); + return rc; + } + 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 libssh recv function will deal + with both accordingly */ + data->state.select_bits = CURL_CSELECT_IN; + + sshc->sftp_recv_state = 0; + myssh_state(data, sshc, SSH_STOP); + + return rc; +} + /* * 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 * 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 */ -static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) +static CURLcode myssh_statemach_act(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, + bool *block) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct SSHPROTO *protop = data->req.p.ssh; - struct ssh_conn *sshc = &conn->proto.sshc; curl_socket_t sock = conn->sock[FIRSTSOCKET]; int rc = SSH_NO_ERROR, err; - int seekerr = CURL_SEEKFUNC_OK; const char *err_msg; *block = 0; /* we are not blocking by default */ @@ -646,156 +1092,28 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) switch(sshc->state) { case SSH_INIT: - sshc->secondCreateDirs = 0; - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_OK; - -#if 0 - ssh_set_log_level(SSH_LOG_PROTOCOL); -#endif - - /* Set libssh to non-blocking, since everything internally is - non-blocking */ - ssh_set_blocking(sshc->ssh_session, 0); - - state(data, SSH_S_STARTUP); + myssh_state_init(data, sshc); FALLTHROUGH(); case SSH_S_STARTUP: - rc = ssh_connect(sshc->ssh_session); - - myssh_block2waitfor(conn, (rc == SSH_AGAIN)); - if(rc == SSH_AGAIN) { - DEBUGF(infof(data, "ssh_connect -> EAGAIN")); + rc = myssh_state_startup(data, sshc); + if(rc) break; - } - - if(rc != SSH_OK) { - failf(data, "Failure establishing ssh session"); - MOVE_TO_ERROR_STATE(CURLE_FAILED_INIT); - break; - } - - state(data, SSH_HOSTKEY); - FALLTHROUGH(); case SSH_HOSTKEY: - - rc = myssh_is_known(data); + rc = myssh_is_known(data, sshc); if(rc != SSH_OK) { MOVE_TO_ERROR_STATE(CURLE_PEER_FAILED_VERIFICATION); break; } - state(data, SSH_AUTHLIST); + myssh_state(data, sshc, SSH_AUTHLIST); FALLTHROUGH(); - case SSH_AUTHLIST:{ - sshc->authed = FALSE; - - rc = ssh_userauth_none(sshc->ssh_session, NULL); - if(rc == SSH_AUTH_AGAIN) { - rc = SSH_AGAIN; - break; - } - - if(rc == SSH_AUTH_SUCCESS) { - sshc->authed = TRUE; - infof(data, "Authenticated with none"); - state(data, SSH_AUTH_DONE); - break; - } - else if(rc == SSH_AUTH_ERROR) { - MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); - break; - } - - 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 ? - "public key, ": "", - sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC ? - "GSSAPI, " : "", - sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE ? - "keyboard-interactive, " : "", - sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD ? - "password": ""); - if(sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { - state(data, SSH_AUTH_PKEY_INIT); - infof(data, "Authentication using SSH public key file"); - } - else if(sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { - state(data, SSH_AUTH_GSSAPI); - } - else if(sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { - state(data, SSH_AUTH_KEY_INIT); - } - else if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) { - state(data, SSH_AUTH_PASS_INIT); - } - else { /* unsupported authentication method */ - MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); - break; - } - - break; - } + case SSH_AUTHLIST: + rc = myssh_state_authlist(data, sshc); + break; case SSH_AUTH_PKEY_INIT: - if(!(data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY)) { - MOVE_TO_GSSAPI_AUTH; - break; - } - - /* Two choices, (1) private key was given on CMD, - * (2) use the "default" keys. */ - if(data->set.str[STRING_SSH_PRIVATE_KEY]) { - if(sshc->pubkey && !data->set.ssl.key_passwd) { - rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, - sshc->pubkey); - if(rc == SSH_AUTH_AGAIN) { - rc = SSH_AGAIN; - break; - } - - if(rc != SSH_OK) { - MOVE_TO_GSSAPI_AUTH; - break; - } - } - - rc = ssh_pki_import_privkey_file(data-> - set.str[STRING_SSH_PRIVATE_KEY], - data->set.ssl.key_passwd, NULL, - NULL, &sshc->privkey); - if(rc != SSH_OK) { - failf(data, "Could not load private key file %s", - data->set.str[STRING_SSH_PRIVATE_KEY]); - MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); - break; - } - - state(data, SSH_AUTH_PKEY); - break; - - } - else { - rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL, - data->set.ssl.key_passwd); - if(rc == SSH_AUTH_AGAIN) { - rc = SSH_AGAIN; - break; - } - if(rc == SSH_AUTH_SUCCESS) { - rc = SSH_OK; - sshc->authed = TRUE; - infof(data, "Completed public key authentication"); - state(data, SSH_AUTH_DONE); - break; - } - - MOVE_TO_GSSAPI_AUTH; - } + rc = myssh_state_auth_pkey_init(data, sshc); break; case SSH_AUTH_PKEY: rc = ssh_userauth_publickey(sshc->ssh_session, NULL, sshc->privkey); @@ -807,7 +1125,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) if(rc == SSH_AUTH_SUCCESS) { sshc->authed = TRUE; infof(data, "Completed public key authentication"); - state(data, SSH_AUTH_DONE); + myssh_state(data, sshc, SSH_AUTH_DONE); break; } else { @@ -832,7 +1150,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) rc = SSH_OK; sshc->authed = TRUE; infof(data, "Completed gssapi authentication"); - state(data, SSH_AUTH_DONE); + myssh_state(data, sshc, SSH_AUTH_DONE); break; } @@ -841,7 +1159,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) case SSH_AUTH_KEY_INIT: if(data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) { - state(data, SSH_AUTH_KEY); + myssh_state(data, sshc, SSH_AUTH_KEY); } else { MOVE_TO_PASSWD_AUTH; @@ -850,14 +1168,14 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) case SSH_AUTH_KEY: /* keyboard-interactive authentication */ - rc = myssh_auth_interactive(conn); + rc = myssh_auth_interactive(conn, sshc); if(rc == SSH_AGAIN) { break; } if(rc == SSH_OK) { sshc->authed = TRUE; infof(data, "completed keyboard interactive authentication"); - state(data, SSH_AUTH_DONE); + myssh_state(data, sshc, SSH_AUTH_DONE); } else { MOVE_TO_PASSWD_AUTH; @@ -869,7 +1187,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); break; } - state(data, SSH_AUTH_PASS); + myssh_state(data, sshc, SSH_AUTH_PASS); FALLTHROUGH(); case SSH_AUTH_PASS: @@ -882,7 +1200,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) if(rc == SSH_AUTH_SUCCESS) { sshc->authed = TRUE; infof(data, "Completed password authentication"); - state(data, SSH_AUTH_DONE); + myssh_state(data, sshc, SSH_AUTH_DONE); } else { MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED); @@ -907,11 +1225,11 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) conn->writesockfd = CURL_SOCKET_BAD; if(conn->handler->protocol == CURLPROTO_SFTP) { - state(data, SSH_SFTP_INIT); + myssh_state(data, sshc, SSH_SFTP_INIT); break; } infof(data, "SSH CONNECT phase done"); - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; case SSH_SFTP_INIT: @@ -932,7 +1250,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) MOVE_TO_ERROR_STATE(sftp_error_to_CURLE(SSH_FX_FAILURE)); break; } - state(data, SSH_SFTP_REALPATH); + myssh_state(data, sshc, SSH_SFTP_REALPATH); FALLTHROUGH(); case SSH_SFTP_REALPATH: /* @@ -943,31 +1261,34 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) MOVE_TO_ERROR_STATE(CURLE_COULDNT_CONNECT); break; } - data->state.most_recent_ftp_entrypath = sshc->homedir; + free(data->state.most_recent_ftp_entrypath); + data->state.most_recent_ftp_entrypath = strdup(sshc->homedir); + if(!data->state.most_recent_ftp_entrypath) + return CURLE_OUT_OF_MEMORY; /* 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 working path will not. */ DEBUGF(infof(data, "SSH CONNECT phase done")); - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; case SSH_SFTP_QUOTE_INIT: - result = Curl_getworkingpath(data, sshc->homedir, &protop->path); + result = Curl_getworkingpath(data, sshc->homedir, &sshp->path); if(result) { sshc->actualcode = result; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } if(data->set.quote) { infof(data, "Sending quote commands"); sshc->quote_item = data->set.quote; - state(data, SSH_SFTP_QUOTE); + myssh_state(data, sshc, SSH_SFTP_QUOTE); } else { - state(data, SSH_SFTP_GETINFO); + myssh_state(data, sshc, SSH_SFTP_GETINFO); } break; @@ -975,16 +1296,16 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) if(data->set.postquote) { infof(data, "Sending quote commands"); sshc->quote_item = data->set.postquote; - state(data, SSH_SFTP_QUOTE); + myssh_state(data, sshc, SSH_SFTP_QUOTE); } else { - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); } break; case SSH_SFTP_QUOTE: /* Send any quote commands */ - sftp_quote(data); + sftp_quote(data, sshc, sshp); break; case SSH_SFTP_NEXT_QUOTE: @@ -994,21 +1315,21 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) sshc->quote_item = sshc->quote_item->next; if(sshc->quote_item) { - state(data, SSH_SFTP_QUOTE); + myssh_state(data, sshc, SSH_SFTP_QUOTE); } else { if(sshc->nextstate != SSH_NO_STATE) { - state(data, sshc->nextstate); + myssh_state(data, sshc, sshc->nextstate); sshc->nextstate = SSH_NO_STATE; } else { - state(data, SSH_SFTP_GETINFO); + myssh_state(data, sshc, SSH_SFTP_GETINFO); } } break; case SSH_SFTP_QUOTE_STAT: - sftp_quote_stat(data); + sftp_quote_stat(data, sshc); break; case SSH_SFTP_QUOTE_SETSTAT: @@ -1019,7 +1340,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->quote_path2); failf(data, "Attempt to set SFTP stats failed: %s", ssh_get_error(sshc->ssh_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; /* sshc->actualcode = sftp_error_to_CURLE(err); @@ -1027,7 +1348,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) * the error the libssh2 backend is returning */ break; } - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); break; case SSH_SFTP_QUOTE_SYMLINK: @@ -1038,12 +1359,12 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->quote_path2); failf(data, "symlink command failed: %s", ssh_get_error(sshc->ssh_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; break; } - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); break; case SSH_SFTP_QUOTE_MKDIR: @@ -1053,12 +1374,12 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->quote_path1); failf(data, "mkdir command failed: %s", ssh_get_error(sshc->ssh_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; break; } - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); break; case SSH_SFTP_QUOTE_RENAME: @@ -1069,12 +1390,12 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->quote_path2); failf(data, "rename command failed: %s", ssh_get_error(sshc->ssh_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; break; } - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); break; case SSH_SFTP_QUOTE_RMDIR: @@ -1083,12 +1404,12 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->quote_path1); failf(data, "rmdir command failed: %s", ssh_get_error(sshc->ssh_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; break; } - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); break; case SSH_SFTP_QUOTE_UNLINK: @@ -1097,12 +1418,12 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->quote_path1); failf(data, "rm command failed: %s", ssh_get_error(sshc->ssh_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; break; } - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); break; case SSH_SFTP_QUOTE_STATVFS: @@ -1114,7 +1435,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->quote_path1); failf(data, "statvfs command failed: %s", ssh_get_error(sshc->ssh_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; break; @@ -1147,7 +1468,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) if(!tmp) { result = CURLE_OUT_OF_MEMORY; - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; break; } @@ -1155,21 +1476,21 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); free(tmp); if(result) { - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = result; } } - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); break; } case SSH_SFTP_GETINFO: if(data->set.get_filetime) { - state(data, SSH_SFTP_FILETIME); + myssh_state(data, sshc, SSH_SFTP_FILETIME); } else { - state(data, SSH_SFTP_TRANS_INIT); + myssh_state(data, sshc, SSH_SFTP_TRANS_INIT); } break; @@ -1177,180 +1498,38 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) { sftp_attributes attrs; - attrs = sftp_stat(sshc->sftp_session, protop->path); + attrs = sftp_stat(sshc->sftp_session, sshp->path); if(attrs) { data->info.filetime = attrs->mtime; sftp_attributes_free(attrs); } - state(data, SSH_SFTP_TRANS_INIT); + myssh_state(data, sshc, SSH_SFTP_TRANS_INIT); break; } case SSH_SFTP_TRANS_INIT: if(data->state.upload) - state(data, SSH_SFTP_UPLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); else { - if(protop->path[strlen(protop->path)-1] == '/') - state(data, SSH_SFTP_READDIR_INIT); + if(sshp->path[strlen(sshp->path)-1] == '/') + myssh_state(data, sshc, SSH_SFTP_READDIR_INIT); else - state(data, SSH_SFTP_DOWNLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_DOWNLOAD_INIT); } break; case SSH_SFTP_UPLOAD_INIT: - { - int flags; - - if(data->state.resume_from) { - sftp_attributes attrs; - - if(data->state.resume_from < 0) { - attrs = sftp_stat(sshc->sftp_session, protop->path); - if(attrs) { - curl_off_t size = attrs->size; - if(size < 0) { - failf(data, "Bad file size (%" FMT_OFF_T ")", size); - MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME); - break; - } - data->state.resume_from = attrs->size; - - sftp_attributes_free(attrs); - } - else { - data->state.resume_from = 0; - } - } - } - - if(data->set.remote_append) - /* Try to open for append, but create if nonexisting */ - flags = O_WRONLY|O_CREAT|O_APPEND; - else if(data->state.resume_from > 0) - /* If we have restart position then open for append */ - flags = O_WRONLY|O_APPEND; - else - /* Clear file before writing (normal behavior) */ - flags = O_WRONLY|O_CREAT|O_TRUNC; - - if(sshc->sftp_file) - sftp_close(sshc->sftp_file); - sshc->sftp_file = - sftp_open(sshc->sftp_session, protop->path, - flags, (mode_t)data->set.new_file_perms); - if(!sshc->sftp_file) { - err = sftp_get_error(sshc->sftp_session); - - if(((err == SSH_FX_NO_SUCH_FILE || err == SSH_FX_FAILURE || - err == SSH_FX_NO_SUCH_PATH)) && - (data->set.ftp_create_missing_dirs && - (strlen(protop->path) > 1))) { - /* try to create the path remotely */ - rc = 0; - sshc->secondCreateDirs = 1; - state(data, SSH_SFTP_CREATE_DIRS_INIT); - break; - } - else { - MOVE_TO_SFTP_CLOSE_STATE(); - 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(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 = - data->state.fread_func(scratch, 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, "Failed to read data"); - MOVE_TO_ERROR_STATE(CURLE_FTP_COULDNT_USE_REST); - break; - } - } while(passed < data->state.resume_from); - if(rc) - break; - } - - /* 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); - } - - rc = sftp_seek64(sshc->sftp_file, data->state.resume_from); - if(rc) { - MOVE_TO_SFTP_CLOSE_STATE(); - break; - } - } - 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 */ - conn->sockfd = 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 libssh 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); -#if LIBSSH_VERSION_INT > SSH_VERSION_INT(0, 11, 0) - sshc->sftp_send_state = 0; -#endif - state(data, SSH_STOP); + rc = myssh_state_upload_init(data, sshc, sshp); break; - } case SSH_SFTP_CREATE_DIRS_INIT: - if(strlen(protop->path) > 1) { - sshc->slash_pos = protop->path + 1; /* ignore the leading '/' */ - state(data, SSH_SFTP_CREATE_DIRS); + if(strlen(sshp->path) > 1) { + sshc->slash_pos = sshp->path + 1; /* ignore the leading '/' */ + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS); } else { - state(data, SSH_SFTP_UPLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); } break; @@ -1359,16 +1538,16 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) if(sshc->slash_pos) { *sshc->slash_pos = 0; - infof(data, "Creating directory '%s'", protop->path); - state(data, SSH_SFTP_CREATE_DIRS_MKDIR); + infof(data, "Creating directory '%s'", sshp->path); + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS_MKDIR); break; } - state(data, SSH_SFTP_UPLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); break; case SSH_SFTP_CREATE_DIRS_MKDIR: /* 'mode' - parameter is preliminary - default to 0644 */ - rc = sftp_mkdir(sshc->sftp_session, protop->path, + rc = sftp_mkdir(sshc->sftp_session, sshp->path, (mode_t)data->set.new_directory_perms); *sshc->slash_pos = '/'; ++sshc->slash_pos; @@ -1387,13 +1566,13 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } rc = 0; /* clear rc and continue */ } - state(data, SSH_SFTP_CREATE_DIRS); + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS); break; case SSH_SFTP_READDIR_INIT: Curl_pgrsSetDownloadSize(data, -1); if(data->req.no_body) { - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } @@ -1402,18 +1581,18 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) * listing */ sshc->sftp_dir = sftp_opendir(sshc->sftp_session, - protop->path); + sshp->path); if(!sshc->sftp_dir) { failf(data, "Could not open directory for reading: %s", ssh_get_error(sshc->ssh_session)); MOVE_TO_SFTP_CLOSE_STATE(); break; } - state(data, SSH_SFTP_READDIR); + myssh_state(data, sshc, SSH_SFTP_READDIR); break; case SSH_SFTP_READDIR: - Curl_dyn_reset(&sshc->readdir_buf); + curlx_dyn_reset(&sshc->readdir_buf); if(sshc->readdir_attrs) sftp_attributes_free(sshc->readdir_attrs); @@ -1428,7 +1607,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) tmpLine = aprintf("%s\n", sshc->readdir_filename); if(!tmpLine) { - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->actualcode = CURLE_OUT_OF_MEMORY; break; } @@ -1437,39 +1616,39 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) free(tmpLine); if(result) { - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } } else { - if(Curl_dyn_add(&sshc->readdir_buf, sshc->readdir_longentry)) { + if(curlx_dyn_add(&sshc->readdir_buf, sshc->readdir_longentry)) { sshc->actualcode = CURLE_OUT_OF_MEMORY; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } if((sshc->readdir_attrs->flags & SSH_FILEXFER_ATTR_PERMISSIONS) && ((sshc->readdir_attrs->permissions & SSH_S_IFMT) == SSH_S_IFLNK)) { - sshc->readdir_linkPath = aprintf("%s%s", protop->path, + sshc->readdir_linkPath = aprintf("%s%s", sshp->path, sshc->readdir_filename); if(!sshc->readdir_linkPath) { - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->actualcode = CURLE_OUT_OF_MEMORY; break; } - state(data, SSH_SFTP_READDIR_LINK); + myssh_state(data, sshc, SSH_SFTP_READDIR_LINK); break; } - state(data, SSH_SFTP_READDIR_BOTTOM); + myssh_state(data, sshc, SSH_SFTP_READDIR_BOTTOM); break; } } else if(sftp_dir_eof(sshc->sftp_dir)) { - state(data, SSH_SFTP_READDIR_DONE); + myssh_state(data, sshc, SSH_SFTP_READDIR_DONE); break; } else { @@ -1511,8 +1690,8 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) Curl_safefree(sshc->readdir_linkPath); - if(Curl_dyn_addf(&sshc->readdir_buf, " -> %s", - sshc->readdir_filename)) { + if(curlx_dyn_addf(&sshc->readdir_buf, " -> %s", + sshc->readdir_filename)) { sshc->actualcode = CURLE_OUT_OF_MEMORY; break; } @@ -1522,24 +1701,24 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) sshc->readdir_filename = NULL; sshc->readdir_longentry = NULL; - state(data, SSH_SFTP_READDIR_BOTTOM); + myssh_state(data, sshc, SSH_SFTP_READDIR_BOTTOM); FALLTHROUGH(); case SSH_SFTP_READDIR_BOTTOM: - if(Curl_dyn_addn(&sshc->readdir_buf, "\n", 1)) + if(curlx_dyn_addn(&sshc->readdir_buf, "\n", 1)) result = CURLE_OUT_OF_MEMORY; else result = Curl_client_write(data, CLIENTWRITE_BODY, - Curl_dyn_ptr(&sshc->readdir_buf), - Curl_dyn_len(&sshc->readdir_buf)); + curlx_dyn_ptr(&sshc->readdir_buf), + curlx_dyn_len(&sshc->readdir_buf)); ssh_string_free_char(sshc->readdir_tmp); sshc->readdir_tmp = NULL; if(result) { - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); } else - state(data, SSH_SFTP_READDIR); + myssh_state(data, sshc, SSH_SFTP_READDIR); break; case SSH_SFTP_READDIR_DONE: @@ -1548,7 +1727,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) /* no data to transfer */ Curl_xfer_setup_nop(data); - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; case SSH_SFTP_DOWNLOAD_INIT: @@ -1558,7 +1737,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) if(sshc->sftp_file) sftp_close(sshc->sftp_file); - sshc->sftp_file = sftp_open(sshc->sftp_session, protop->path, + sshc->sftp_file = sftp_open(sshc->sftp_session, sshp->path, O_RDONLY, (mode_t)data->set.new_file_perms); if(!sshc->sftp_file) { failf(data, "Could not open remote file for reading: %s", @@ -1568,160 +1747,19 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) break; } sftp_file_set_nonblocking(sshc->sftp_file); - state(data, SSH_SFTP_DOWNLOAD_STAT); + myssh_state(data, sshc, SSH_SFTP_DOWNLOAD_STAT); break; case SSH_SFTP_DOWNLOAD_STAT: - { - sftp_attributes attrs; - curl_off_t size; - - attrs = sftp_fstat(sshc->sftp_file); - if(!attrs || - !(attrs->flags & SSH_FILEXFER_ATTR_SIZE) || - (attrs->size == 0)) { - /* - * 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; - data->req.maxdownload = -1; - Curl_pgrsSetDownloadSize(data, -1); - size = 0; - } - else { - size = attrs->size; - - sftp_attributes_free(attrs); - - 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, size); - 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; - } - - rc = sftp_seek64(sshc->sftp_file, from); - if(rc) { - MOVE_TO_SFTP_CLOSE_STATE(); - break; - } - } - 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)size < -data->state.resume_from) { - 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? */ - data->state.resume_from += size; - } - else { - if((curl_off_t)size < data->state.resume_from) { - failf(data, "Offset (%" FMT_OFF_T - ") was beyond file size (%" FMT_OFF_T ")", - data->state.resume_from, size); - return CURLE_BAD_DOWNLOAD_RESUME; - } - } - /* Now store the number of bytes we are expected to download */ - data->req.size = size - data->state.resume_from; - data->req.maxdownload = size - data->state.resume_from; - Curl_pgrsSetDownloadSize(data, - size - data->state.resume_from); - - rc = sftp_seek64(sshc->sftp_file, data->state.resume_from); - if(rc) { - MOVE_TO_SFTP_CLOSE_STATE(); - break; - } - } - } - - /* 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); + rc = myssh_state_sftp_download_stat(data, sshc); break; - } - Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE); - - /* 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 */ - data->state.select_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 { - sshc->sftp_recv_state = 0; - state(data, SSH_STOP); - } - break; case SSH_SFTP_CLOSE: if(sshc->sftp_file) { sftp_close(sshc->sftp_file); sshc->sftp_file = NULL; } - Curl_safefree(protop->path); + Curl_safefree(sshp->path); DEBUGF(infof(data, "SFTP DONE done")); @@ -1730,11 +1768,11 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) SSH_SFTP_CLOSE to pass the correct result back */ if(sshc->nextstate != SSH_NO_STATE && sshc->nextstate != SSH_SFTP_CLOSE) { - state(data, sshc->nextstate); + myssh_state(data, sshc, sshc->nextstate); sshc->nextstate = SSH_SFTP_CLOSE; } else { - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); result = sshc->actualcode; } break; @@ -1762,16 +1800,15 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } SSH_STRING_FREE_CHAR(sshc->homedir); - data->state.most_recent_ftp_entrypath = NULL; - state(data, SSH_SESSION_DISCONNECT); + myssh_state(data, sshc, SSH_SESSION_DISCONNECT); break; case SSH_SCP_TRANS_INIT: - result = Curl_getworkingpath(data, sshc->homedir, &protop->path); + result = Curl_getworkingpath(data, sshc->homedir, &sshp->path); if(result) { sshc->actualcode = result; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } @@ -1787,13 +1824,13 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } sshc->scp_session = - ssh_scp_new(sshc->ssh_session, SSH_SCP_WRITE, protop->path); - state(data, SSH_SCP_UPLOAD_INIT); + ssh_scp_new(sshc->ssh_session, SSH_SCP_WRITE, sshp->path); + myssh_state(data, sshc, SSH_SCP_UPLOAD_INIT); } else { sshc->scp_session = - ssh_scp_new(sshc->ssh_session, SSH_SCP_READ, protop->path); - state(data, SSH_SCP_DOWNLOAD_INIT); + ssh_scp_new(sshc->ssh_session, SSH_SCP_READ, sshp->path); + myssh_state(data, sshc, SSH_SCP_DOWNLOAD_INIT); } if(!sshc->scp_session) { @@ -1814,9 +1851,10 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) break; } - rc = ssh_scp_push_file(sshc->scp_session, protop->path, - (size_t)data->state.infilesize, - (int)data->set.new_file_perms); + rc = ssh_scp_push_file64(sshc->scp_session, sshp->path, + (uint64_t)data->state.infilesize, + (int)data->set.new_file_perms); + if(rc != SSH_OK) { err_msg = ssh_get_error(sshc->ssh_session); failf(data, "%s", err_msg); @@ -1839,7 +1877,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) with both accordingly */ data->state.select_bits = CURL_CSELECT_OUT; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; @@ -1852,7 +1890,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) MOVE_TO_ERROR_STATE(CURLE_COULDNT_CONNECT); break; } - state(data, SSH_SCP_DOWNLOAD); + myssh_state(data, sshc, SSH_SCP_DOWNLOAD); FALLTHROUGH(); case SSH_SCP_DOWNLOAD:{ @@ -1879,14 +1917,14 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) with both accordingly */ data->state.select_bits = CURL_CSELECT_IN; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } case SSH_SCP_DONE: if(data->state.upload) - state(data, SSH_SCP_SEND_EOF); + myssh_state(data, sshc, SSH_SCP_SEND_EOF); else - state(data, SSH_SCP_CHANNEL_FREE); + myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE); break; case SSH_SCP_SEND_EOF: @@ -1904,7 +1942,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } } - state(data, SSH_SCP_CHANNEL_FREE); + myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE); break; case SSH_SCP_CHANNEL_FREE: @@ -1916,7 +1954,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) ssh_set_blocking(sshc->ssh_session, 0); - state(data, SSH_SESSION_DISCONNECT); + myssh_state(data, sshc, SSH_SESSION_DISCONNECT); FALLTHROUGH(); case SSH_SESSION_DISCONNECT: @@ -1928,6 +1966,15 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) sshc->scp_session = NULL; } + if(sshc->sftp_file) { + sftp_close(sshc->sftp_file); + sshc->sftp_file = NULL; + } + if(sshc->sftp_session) { + sftp_free(sshc->sftp_session); + sshc->sftp_session = NULL; + } + 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, @@ -1937,64 +1984,25 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } SSH_STRING_FREE_CHAR(sshc->homedir); - data->state.most_recent_ftp_entrypath = NULL; - state(data, SSH_SESSION_FREE); + myssh_state(data, sshc, SSH_SESSION_FREE); FALLTHROUGH(); case SSH_SESSION_FREE: - if(sshc->ssh_session) { - ssh_free(sshc->ssh_session); - sshc->ssh_session = NULL; - } - - /* worst-case scenario cleanup */ - - DEBUGASSERT(sshc->ssh_session == NULL); - DEBUGASSERT(sshc->scp_session == NULL); - - if(sshc->readdir_tmp) { - ssh_string_free_char(sshc->readdir_tmp); - sshc->readdir_tmp = NULL; - } - - if(sshc->quote_attrs) - sftp_attributes_free(sshc->quote_attrs); - - if(sshc->readdir_attrs) - sftp_attributes_free(sshc->readdir_attrs); - - if(sshc->readdir_link_attrs) - sftp_attributes_free(sshc->readdir_link_attrs); - - if(sshc->privkey) - ssh_key_free(sshc->privkey); - if(sshc->pubkey) - ssh_key_free(sshc->pubkey); - - Curl_safefree(sshc->rsa_pub); - Curl_safefree(sshc->rsa); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - Curl_dyn_free(&sshc->readdir_buf); - Curl_safefree(sshc->readdir_linkPath); - SSH_STRING_FREE_CHAR(sshc->homedir); - + sshc_cleanup(sshc); /* the code we are about to return */ result = sshc->actualcode; - memset(sshc, 0, sizeof(struct ssh_conn)); - connclose(conn, "SSH session free"); sshc->state = SSH_SESSION_FREE; /* current */ sshc->nextstate = SSH_NO_STATE; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; case SSH_QUIT: default: /* internal error */ sshc->nextstate = SSH_NO_STATE; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } @@ -2034,10 +2042,10 @@ static int myssh_getsock(struct Curl_easy *data, return bitmap; } -static void myssh_block2waitfor(struct connectdata *conn, bool block) +static void myssh_block2waitfor(struct connectdata *conn, + struct ssh_conn *sshc, + bool block) { - struct ssh_conn *sshc = &conn->proto.sshc; - /* If it did not block, or nothing was returned by ssh_get_poll_flags * have the original set */ conn->waitfor = sshc->orig_waitfor; @@ -2058,30 +2066,35 @@ static CURLcode myssh_multi_statemach(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); bool block; /* we store the status and use that to provide a ssh_getsock() implementation */ - CURLcode result = myssh_statemach_act(data, &block); + CURLcode result; + if(!sshc || !sshp) + return CURLE_FAILED_INIT; + result = myssh_statemach_act(data, sshc, sshp, &block); *done = (sshc->state == SSH_STOP); - myssh_block2waitfor(conn, block); + myssh_block2waitfor(conn, sshc, block); return result; } static CURLcode myssh_block_statemach(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, bool disconnect) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; CURLcode result = CURLE_OK; while((sshc->state != SSH_STOP) && !result) { bool block; timediff_t left = 1000; - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); - result = myssh_statemach_act(data, &block); + result = myssh_statemach_act(data, sshc, sshp, &block); if(result) break; @@ -2112,19 +2125,46 @@ static CURLcode myssh_block_statemach(struct Curl_easy *data, return result; } +static void myssh_easy_dtor(void *key, size_t klen, void *entry) +{ + struct SSHPROTO *sshp = entry; + (void)key; + (void)klen; + Curl_safefree(sshp->path); + free(sshp); +} + +static void myssh_conn_dtor(void *key, size_t klen, void *entry) +{ + struct ssh_conn *sshc = entry; + (void)key; + (void)klen; + sshc_cleanup(sshc); + free(sshc); +} + /* * SSH setup connection */ static CURLcode myssh_setup_connection(struct Curl_easy *data, struct connectdata *conn) { - struct SSHPROTO *ssh; - struct ssh_conn *sshc = &conn->proto.sshc; + struct SSHPROTO *sshp; + struct ssh_conn *sshc; - data->req.p.ssh = ssh = calloc(1, sizeof(struct SSHPROTO)); - if(!ssh) + sshc = calloc(1, sizeof(*sshc)); + if(!sshc) + return CURLE_OUT_OF_MEMORY; + + curlx_dyn_init(&sshc->readdir_buf, CURL_PATH_MAX * 2); + sshc->initialised = TRUE; + if(Curl_conn_meta_set(conn, CURL_META_SSH_CONN, sshc, myssh_conn_dtor)) + return CURLE_OUT_OF_MEMORY; + + sshp = calloc(1, sizeof(*sshp)); + if(!sshp || + Curl_meta_set(data, CURL_META_SSH_EASY, sshp, myssh_easy_dtor)) return CURLE_OUT_OF_MEMORY; - Curl_dyn_init(&sshc->readdir_buf, CURL_PATH_MAX * 2); return CURLE_OK; } @@ -2138,15 +2178,15 @@ static Curl_send scp_send, sftp_send; */ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) { - struct ssh_conn *ssh; CURLcode result; struct connectdata *conn = data->conn; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *ssh = Curl_meta_get(data, CURL_META_SSH_EASY); curl_socket_t sock = conn->sock[FIRSTSOCKET]; int rc; - /* initialize per-handle data if not already */ - if(!data->req.p.ssh) - myssh_setup_connection(data, conn); + if(!sshc || !ssh) + return CURLE_FAILED_INIT; /* We default to persistent connections. We set this already in this connect function to make the reuse checks properly be able to check this bit. */ @@ -2161,10 +2201,8 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) conn->send[FIRSTSOCKET] = sftp_send; } - ssh = &conn->proto.sshc; - - ssh->ssh_session = ssh_new(); - if(!ssh->ssh_session) { + sshc->ssh_session = ssh_new(); + if(!sshc->ssh_session) { failf(data, "Failure initialising ssh session"); return CURLE_FAILED_INIT; } @@ -2172,23 +2210,23 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) if(conn->bits.ipv6_ip) { char ipv6[MAX_IPADR_LEN]; msnprintf(ipv6, sizeof(ipv6), "[%s]", conn->host.name); - rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_HOST, ipv6); + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_HOST, ipv6); } else - rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_HOST, conn->host.name); + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_HOST, conn->host.name); if(rc != SSH_OK) { failf(data, "Could not set remote host"); return CURLE_FAILED_INIT; } - rc = ssh_options_parse_config(ssh->ssh_session, NULL); + rc = ssh_options_parse_config(sshc->ssh_session, NULL); if(rc != SSH_OK) { infof(data, "Could not parse SSH configuration files"); /* ignore */ } - rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_FD, &sock); + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_FD, &sock); if(rc != SSH_OK) { failf(data, "Could not set socket"); return CURLE_FAILED_INIT; @@ -2196,7 +2234,7 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) if(conn->user && conn->user[0] != '\0') { infof(data, "User: %s", conn->user); - rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_USER, conn->user); + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_USER, conn->user); if(rc != SSH_OK) { failf(data, "Could not set user"); return CURLE_FAILED_INIT; @@ -2205,7 +2243,7 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) if(data->set.str[STRING_SSH_KNOWNHOSTS]) { infof(data, "Known hosts: %s", data->set.str[STRING_SSH_KNOWNHOSTS]); - rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_KNOWNHOSTS, + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_KNOWNHOSTS, data->set.str[STRING_SSH_KNOWNHOSTS]); if(rc != SSH_OK) { failf(data, "Could not set known hosts file path"); @@ -2214,7 +2252,7 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) } if(conn->remote_port) { - rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_PORT, + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_PORT, &conn->remote_port); if(rc != SSH_OK) { failf(data, "Could not set remote port"); @@ -2223,7 +2261,7 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) } if(data->set.ssh_compression) { - rc = ssh_options_set(ssh->ssh_session, SSH_OPTIONS_COMPRESSION, + rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_COMPRESSION, "zlib,zlib@openssh.com,none"); if(rc != SSH_OK) { failf(data, "Could not set compression"); @@ -2231,12 +2269,12 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) } } - ssh->privkey = NULL; - ssh->pubkey = NULL; + sshc->privkey = NULL; + sshc->pubkey = NULL; if(data->set.str[STRING_SSH_PUBLIC_KEY]) { rc = ssh_pki_import_pubkey_file(data->set.str[STRING_SSH_PUBLIC_KEY], - &ssh->pubkey); + &sshc->pubkey); if(rc != SSH_OK) { failf(data, "Could not load public key file"); return CURLE_FAILED_INIT; @@ -2246,7 +2284,7 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done) /* we do not verify here, we do it at the state machine, * after connection */ - state(data, SSH_INIT); + myssh_state(data, sshc, SSH_INIT); result = myssh_multi_statemach(data, done); @@ -2280,13 +2318,16 @@ CURLcode scp_perform(struct Curl_easy *data, bool *connected, bool *dophase_done) { CURLcode result = CURLE_OK; + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); DEBUGF(infof(data, "DO phase starts")); *dophase_done = FALSE; /* not done yet */ + if(!sshc) + return CURLE_FAILED_INIT; /* start the first command in the DO phase */ - state(data, SSH_SCP_TRANS_INIT); + myssh_state(data, sshc, SSH_SCP_TRANS_INIT); result = myssh_multi_statemach(data, dophase_done); @@ -2304,9 +2345,11 @@ static CURLcode myssh_do_it(struct Curl_easy *data, bool *done) CURLcode result; bool connected = FALSE; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); *done = FALSE; /* default to false */ + if(!sshc) + return CURLE_FAILED_INIT; data->req.size = -1; /* make sure this is unknown at this point */ @@ -2327,6 +2370,62 @@ static CURLcode myssh_do_it(struct Curl_easy *data, bool *done) return result; } +static void sshc_cleanup(struct ssh_conn *sshc) +{ + if(sshc->initialised) { + if(sshc->sftp_file) { + sftp_close(sshc->sftp_file); + sshc->sftp_file = NULL; + } + if(sshc->sftp_session) { + sftp_free(sshc->sftp_session); + sshc->sftp_session = NULL; + } + if(sshc->ssh_session) { + ssh_free(sshc->ssh_session); + sshc->ssh_session = NULL; + } + + /* worst-case scenario cleanup */ + DEBUGASSERT(sshc->ssh_session == NULL); + DEBUGASSERT(sshc->scp_session == NULL); + + if(sshc->readdir_tmp) { + ssh_string_free_char(sshc->readdir_tmp); + sshc->readdir_tmp = NULL; + } + if(sshc->quote_attrs) { + sftp_attributes_free(sshc->quote_attrs); + sshc->quote_attrs = NULL; + } + if(sshc->readdir_attrs) { + sftp_attributes_free(sshc->readdir_attrs); + sshc->readdir_attrs = NULL; + } + if(sshc->readdir_link_attrs) { + sftp_attributes_free(sshc->readdir_link_attrs); + sshc->readdir_link_attrs = NULL; + } + if(sshc->privkey) { + ssh_key_free(sshc->privkey); + sshc->privkey = NULL; + } + if(sshc->pubkey) { + ssh_key_free(sshc->pubkey); + sshc->pubkey = NULL; + } + + Curl_safefree(sshc->rsa_pub); + Curl_safefree(sshc->rsa); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + curlx_dyn_free(&sshc->readdir_buf); + Curl_safefree(sshc->readdir_linkPath); + SSH_STRING_FREE_CHAR(sshc->homedir); + sshc->initialised = FALSE; + } +} + /* BLOCKING, but the function is using the state machine so the only reason this is still blocking is that the multi interface code has no support for disconnecting operations that takes a while */ @@ -2335,15 +2434,16 @@ static CURLcode scp_disconnect(struct Curl_easy *data, bool dead_connection) { CURLcode result = CURLE_OK; - struct ssh_conn *ssh = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); (void) dead_connection; - if(ssh->ssh_session) { + if(sshc && sshc->ssh_session && sshp) { /* only if there is a session still around to use! */ - state(data, SSH_SESSION_DISCONNECT); + myssh_state(data, sshc, SSH_SESSION_DISCONNECT); - result = myssh_block_statemach(data, TRUE); + result = myssh_block_statemach(data, sshc, sshp, TRUE); } return result; @@ -2351,20 +2451,20 @@ static CURLcode scp_disconnect(struct Curl_easy *data, /* generic done function for both SCP and SFTP called from their specific done functions */ -static CURLcode myssh_done(struct Curl_easy *data, CURLcode status) +static CURLcode myssh_done(struct Curl_easy *data, + struct ssh_conn *sshc, + CURLcode status) { CURLcode result = CURLE_OK; - struct SSHPROTO *protop = data->req.p.ssh; + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); - if(!status) { + if(!status && sshp) { /* run the state-machine */ - result = myssh_block_statemach(data, FALSE); + result = myssh_block_statemach(data, sshc, sshp, FALSE); } else result = status; - if(protop) - Curl_safefree(protop->path); if(Curl_pgrsDone(data)) return CURLE_ABORTED_BY_CALLBACK; @@ -2376,13 +2476,15 @@ static CURLcode myssh_done(struct Curl_easy *data, CURLcode status) static CURLcode scp_done(struct Curl_easy *data, CURLcode status, bool premature) { + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); (void) premature; /* not used */ + if(!sshc) + return CURLE_FAILED_INIT; if(!status) - state(data, SSH_SCP_DONE); - - return myssh_done(data, status); + myssh_state(data, sshc, SSH_SCP_DONE); + return myssh_done(data, sshc, status); } static ssize_t scp_send(struct Curl_easy *data, int sockindex, @@ -2390,17 +2492,22 @@ static ssize_t scp_send(struct Curl_easy *data, int sockindex, { int rc; struct connectdata *conn = data->conn; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_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); + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } + + rc = ssh_scp_write(sshc->scp_session, mem, len); #if 0 /* 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)); + myssh_block2waitfor(conn, sshc, (rc == SSH_AGAIN)); if(rc == SSH_AGAIN) { *err = CURLE_AGAIN; @@ -2421,18 +2528,22 @@ static ssize_t scp_recv(struct Curl_easy *data, int sockindex, { ssize_t nread; struct connectdata *conn = data->conn; - (void) err; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void) sockindex; /* we only support SCP on the fixed known primary socket */ + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } /* libssh returns int */ - nread = ssh_scp_read(conn->proto.sshc.scp_session, mem, len); + nread = ssh_scp_read(sshc->scp_session, mem, len); #if 0 /* 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 SSH_OK or SSH_ERROR. */ - myssh_block2waitfor(conn, (nread == SSH_AGAIN)); + myssh_block2waitfor(conn, sshc, (nread == SSH_AGAIN)); if(nread == SSH_AGAIN) { *err = CURLE_AGAIN; nread = -1; @@ -2460,14 +2571,17 @@ CURLcode sftp_perform(struct Curl_easy *data, bool *connected, bool *dophase_done) { + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); CURLcode result = CURLE_OK; DEBUGF(infof(data, "DO phase starts")); *dophase_done = FALSE; /* not done yet */ + if(!sshc) + return CURLE_FAILED_INIT; /* start the first command in the DO phase */ - state(data, SSH_SFTP_QUOTE_INIT); + myssh_state(data, sshc, SSH_SFTP_QUOTE_INIT); /* run the state-machine */ result = myssh_multi_statemach(data, dophase_done); @@ -2499,38 +2613,40 @@ static CURLcode sftp_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); CURLcode result = CURLE_OK; (void) dead_connection; DEBUGF(infof(data, "SSH DISCONNECT starts now")); - if(conn->proto.sshc.ssh_session) { + if(sshc && sshc->ssh_session && sshp) { /* only if there is a session still around to use! */ - state(data, SSH_SFTP_SHUTDOWN); - result = myssh_block_statemach(data, TRUE); + myssh_state(data, sshc, SSH_SFTP_SHUTDOWN); + result = myssh_block_statemach(data, sshc, sshp, TRUE); } DEBUGF(infof(data, "SSH DISCONNECT is done")); - return result; - } static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, bool premature) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + if(!sshc) + return CURLE_FAILED_INIT; if(!status) { /* Post quote commands are executed after the SFTP_CLOSE state to avoid errors that could happen due to open file handles during POSTQUOTE operation */ if(!premature && data->set.postquote && !conn->bits.retry) sshc->nextstate = SSH_SFTP_POSTQUOTE_INIT; - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); } - return myssh_done(data, status); + return myssh_done(data, sshc, status); } /* return number of sent bytes */ @@ -2540,28 +2656,33 @@ static ssize_t sftp_send(struct Curl_easy *data, int sockindex, { ssize_t nwrite; struct connectdata *conn = data->conn; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void)sockindex; (void)eos; + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } /* 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; #if LIBSSH_VERSION_INT > SSH_VERSION_INT(0, 11, 0) - switch(conn->proto.sshc.sftp_send_state) { + switch(sshc->sftp_send_state) { case 0: - sftp_file_set_nonblocking(conn->proto.sshc.sftp_file); - if(sftp_aio_begin_write(conn->proto.sshc.sftp_file, mem, len, - &conn->proto.sshc.sftp_aio) == SSH_ERROR) { + sftp_file_set_nonblocking(sshc->sftp_file); + if(sftp_aio_begin_write(sshc->sftp_file, mem, len, + &sshc->sftp_aio) == SSH_ERROR) { *err = CURLE_SEND_ERROR; return -1; } - conn->proto.sshc.sftp_send_state = 1; + sshc->sftp_send_state = 1; FALLTHROUGH(); case 1: - nwrite = sftp_aio_wait_write(&conn->proto.sshc.sftp_aio); - myssh_block2waitfor(conn, (nwrite == SSH_AGAIN) ? TRUE : FALSE); + nwrite = sftp_aio_wait_write(&sshc->sftp_aio); + myssh_block2waitfor(conn, sshc, (nwrite == SSH_AGAIN) ? TRUE : FALSE); if(nwrite == SSH_AGAIN) { *err = CURLE_AGAIN; return 0; @@ -2570,20 +2691,20 @@ static ssize_t sftp_send(struct Curl_easy *data, int sockindex, *err = CURLE_SEND_ERROR; return -1; } - if(conn->proto.sshc.sftp_aio) { - sftp_aio_free(conn->proto.sshc.sftp_aio); - conn->proto.sshc.sftp_aio = NULL; + if(sshc->sftp_aio) { + sftp_aio_free(sshc->sftp_aio); + sshc->sftp_aio = NULL; } - conn->proto.sshc.sftp_send_state = 0; + sshc->sftp_send_state = 0; return nwrite; default: /* we never reach here */ return -1; } #else - nwrite = sftp_write(conn->proto.sshc.sftp_file, mem, len); + nwrite = sftp_write(sshc->sftp_file, mem, len); - myssh_block2waitfor(conn, FALSE); + myssh_block2waitfor(conn, sshc, FALSE); #if 0 /* not returned by libssh on write */ if(nwrite == SSH_AGAIN) { @@ -2610,29 +2731,31 @@ static ssize_t sftp_recv(struct Curl_easy *data, int sockindex, { ssize_t nread; struct connectdata *conn = data->conn; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void)sockindex; DEBUGASSERT(len < CURL_MAX_READ_SIZE); + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } - switch(conn->proto.sshc.sftp_recv_state) { + switch(sshc->sftp_recv_state) { case 0: - conn->proto.sshc.sftp_file_index = - sftp_async_read_begin(conn->proto.sshc.sftp_file, - (uint32_t)len); - if(conn->proto.sshc.sftp_file_index < 0) { + sshc->sftp_file_index = + sftp_async_read_begin(sshc->sftp_file, (uint32_t)len); + if(sshc->sftp_file_index < 0) { *err = CURLE_RECV_ERROR; return -1; } FALLTHROUGH(); case 1: - conn->proto.sshc.sftp_recv_state = 1; + sshc->sftp_recv_state = 1; + nread = sftp_async_read(sshc->sftp_file, mem, (uint32_t)len, + (uint32_t)sshc->sftp_file_index); - nread = sftp_async_read(conn->proto.sshc.sftp_file, - mem, (uint32_t)len, - (uint32_t)conn->proto.sshc.sftp_file_index); - - myssh_block2waitfor(conn, (nread == SSH_AGAIN)); + myssh_block2waitfor(conn, sshc, (nread == SSH_AGAIN)); if(nread == SSH_AGAIN) { *err = CURLE_AGAIN; @@ -2643,7 +2766,7 @@ static ssize_t sftp_recv(struct Curl_easy *data, int sockindex, return -1; } - conn->proto.sshc.sftp_recv_state = 0; + sshc->sftp_recv_state = 0; return nread; default: @@ -2652,12 +2775,11 @@ static ssize_t sftp_recv(struct Curl_easy *data, int sockindex, } } -static void sftp_quote(struct Curl_easy *data) +static void sftp_quote(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) { const char *cp; - struct connectdata *conn = data->conn; - struct SSHPROTO *protop = data->req.p.ssh; - struct ssh_conn *sshc = &conn->proto.sshc; CURLcode result; /* @@ -2678,15 +2800,14 @@ static void sftp_quote(struct Curl_easy *data) if(strcasecompare("pwd", cmd)) { /* output debug output if that is requested */ - char *tmp = aprintf("257 \"%s\" is current directory.\n", - protop->path); + char *tmp = aprintf("257 \"%s\" is current directory.\n", sshp->path); if(!tmp) { sshc->actualcode = CURLE_OUT_OF_MEMORY; - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; return; } - Curl_debug(data, CURLINFO_HEADER_OUT, (char *) "PWD\n", 4); + Curl_debug(data, CURLINFO_HEADER_OUT, "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 @@ -2695,12 +2816,12 @@ static void sftp_quote(struct Curl_easy *data) result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); free(tmp); if(result) { - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = result; } else - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); return; } @@ -2711,7 +2832,7 @@ static void sftp_quote(struct Curl_easy *data) cp = strchr(cmd, ' '); if(!cp) { failf(data, "Syntax error in SFTP command. Supply parameter(s)"); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; return; @@ -2727,7 +2848,7 @@ static void sftp_quote(struct Curl_easy *data) failf(data, "Out of memory"); else failf(data, "Syntax error: Bad first parameter"); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = result; return; @@ -2739,11 +2860,11 @@ static void sftp_quote(struct Curl_easy *data) * OpenSSH's sftp program and call the appropriate libssh * functions. */ - if(strncasecompare(cmd, "chgrp ", 6) || - strncasecompare(cmd, "chmod ", 6) || - strncasecompare(cmd, "chown ", 6) || - strncasecompare(cmd, "atime ", 6) || - strncasecompare(cmd, "mtime ", 6)) { + if(!strncmp(cmd, "chgrp ", 6) || + !strncmp(cmd, "chmod ", 6) || + !strncmp(cmd, "chown ", 6) || + !strncmp(cmd, "atime ", 6) || + !strncmp(cmd, "mtime ", 6)) { /* attribute change */ /* sshc->quote_path1 contains the mode to set */ @@ -2756,17 +2877,17 @@ static void sftp_quote(struct Curl_easy *data) failf(data, "Syntax error in chgrp/chmod/chown/atime/mtime: " "Bad second parameter"); Curl_safefree(sshc->quote_path1); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = result; return; } sshc->quote_attrs = NULL; - state(data, SSH_SFTP_QUOTE_STAT); + myssh_state(data, sshc, SSH_SFTP_QUOTE_STAT); return; } - if(strncasecompare(cmd, "ln ", 3) || - strncasecompare(cmd, "symlink ", 8)) { + if(!strncmp(cmd, "ln ", 3) || + !strncmp(cmd, "symlink ", 8)) { /* symbolic linking */ /* sshc->quote_path1 is the source */ /* get the destination */ @@ -2777,20 +2898,20 @@ static void sftp_quote(struct Curl_easy *data) else failf(data, "Syntax error in ln/symlink: Bad second parameter"); Curl_safefree(sshc->quote_path1); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = result; return; } - state(data, SSH_SFTP_QUOTE_SYMLINK); + myssh_state(data, sshc, SSH_SFTP_QUOTE_SYMLINK); return; } - else if(strncasecompare(cmd, "mkdir ", 6)) { + else if(!strncmp(cmd, "mkdir ", 6)) { /* create dir */ - state(data, SSH_SFTP_QUOTE_MKDIR); + myssh_state(data, sshc, SSH_SFTP_QUOTE_MKDIR); return; } - else if(strncasecompare(cmd, "rename ", 7)) { + else if(!strncmp(cmd, "rename ", 7)) { /* rename file */ /* first param is the source path */ /* second param is the dest. path */ @@ -2801,26 +2922,26 @@ static void sftp_quote(struct Curl_easy *data) else failf(data, "Syntax error in rename: Bad second parameter"); Curl_safefree(sshc->quote_path1); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = result; return; } - state(data, SSH_SFTP_QUOTE_RENAME); + myssh_state(data, sshc, SSH_SFTP_QUOTE_RENAME); return; } - else if(strncasecompare(cmd, "rmdir ", 6)) { + else if(!strncmp(cmd, "rmdir ", 6)) { /* delete dir */ - state(data, SSH_SFTP_QUOTE_RMDIR); + myssh_state(data, sshc, SSH_SFTP_QUOTE_RMDIR); return; } - else if(strncasecompare(cmd, "rm ", 3)) { - state(data, SSH_SFTP_QUOTE_UNLINK); + else if(!strncmp(cmd, "rm ", 3)) { + myssh_state(data, sshc, SSH_SFTP_QUOTE_UNLINK); return; } #ifdef HAS_STATVFS_SUPPORT - else if(strncasecompare(cmd, "statvfs ", 8)) { - state(data, SSH_SFTP_QUOTE_STATVFS); + else if(!strncmp(cmd, "statvfs ", 8)) { + myssh_state(data, sshc, SSH_SFTP_QUOTE_STATVFS); return; } #endif @@ -2828,15 +2949,14 @@ static void sftp_quote(struct Curl_easy *data) failf(data, "Unknown SFTP command"); Curl_safefree(sshc->quote_path1); Curl_safefree(sshc->quote_path2); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; } -static void sftp_quote_stat(struct Curl_easy *data) +static void sftp_quote_stat(struct Curl_easy *data, + struct ssh_conn *sshc) { - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; char *cmd = sshc->quote_item->data; sshc->acceptfail = FALSE; @@ -2863,59 +2983,63 @@ static void sftp_quote_stat(struct Curl_easy *data) Curl_safefree(sshc->quote_path2); failf(data, "Attempt to get SFTP stats failed: %d", sftp_get_error(sshc->sftp_session)); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; return; } /* Now set the new attributes... */ - if(strncasecompare(cmd, "chgrp", 5)) { - sshc->quote_attrs->gid = (uint32_t)strtoul(sshc->quote_path1, NULL, 10); + if(!strncmp(cmd, "chgrp", 5)) { + const char *p = sshc->quote_path1; + curl_off_t gid; + (void)curlx_str_number(&p, &gid, UINT_MAX); + sshc->quote_attrs->gid = (uint32_t)gid; if(sshc->quote_attrs->gid == 0 && !ISDIGIT(sshc->quote_path1[0]) && - !sshc->acceptfail) { + !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); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; return; } sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_UIDGID; } - else if(strncasecompare(cmd, "chmod", 5)) { - mode_t perms; - perms = (mode_t)strtoul(sshc->quote_path1, NULL, 8); - /* permissions are octal */ - if(perms == 0 && !ISDIGIT(sshc->quote_path1[0])) { + else if(!strncmp(cmd, "chmod", 5)) { + curl_off_t perms; + const char *p = sshc->quote_path1; + if(curlx_str_octal(&p, &perms, 07777)) { Curl_safefree(sshc->quote_path1); Curl_safefree(sshc->quote_path2); failf(data, "Syntax error: chmod permissions not a number"); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; return; } - sshc->quote_attrs->permissions = perms; + sshc->quote_attrs->permissions = (mode_t)perms; sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_PERMISSIONS; } - else if(strncasecompare(cmd, "chown", 5)) { - sshc->quote_attrs->uid = (uint32_t)strtoul(sshc->quote_path1, NULL, 10); + else if(!strncmp(cmd, "chown", 5)) { + const char *p = sshc->quote_path1; + curl_off_t uid; + (void)curlx_str_number(&p, &uid, UINT_MAX); if(sshc->quote_attrs->uid == 0 && !ISDIGIT(sshc->quote_path1[0]) && - !sshc->acceptfail) { + !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); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; return; } sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_UIDGID; } - else if(strncasecompare(cmd, "atime", 5) || - strncasecompare(cmd, "mtime", 5)) { + else if(!strncmp(cmd, "atime", 5) || + !strncmp(cmd, "mtime", 5)) { time_t date = Curl_getdate_capped(sshc->quote_path1); bool fail = FALSE; if(date == -1) { @@ -2931,12 +3055,12 @@ static void sftp_quote_stat(struct Curl_easy *data) if(fail) { Curl_safefree(sshc->quote_path1); Curl_safefree(sshc->quote_path2); - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; sshc->actualcode = CURLE_QUOTE_ERROR; return; } - if(strncasecompare(cmd, "atime", 5)) + if(!strncmp(cmd, "atime", 5)) sshc->quote_attrs->atime = (uint32_t)date; else /* mtime */ sshc->quote_attrs->mtime = (uint32_t)date; @@ -2945,7 +3069,7 @@ static void sftp_quote_stat(struct Curl_easy *data) } /* Now send the completed structure... */ - state(data, SSH_SFTP_QUOTE_SETSTAT); + myssh_state(data, sshc, SSH_SFTP_QUOTE_SETSTAT); return; } diff --git a/Utilities/cmcurl/lib/vssh/libssh2.c b/Utilities/cmcurl/lib/vssh/libssh2.c index 429abac314..c16b3ac388 100644 --- a/Utilities/cmcurl/lib/vssh/libssh2.c +++ b/Utilities/cmcurl/lib/vssh/libssh2.c @@ -24,7 +24,7 @@ /* #define CURL_LIBSSH2_DEBUG */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_LIBSSH2 @@ -49,46 +49,45 @@ #endif #include -#include "urldata.h" -#include "sendf.h" -#include "hostip.h" -#include "progress.h" -#include "transfer.h" -#include "escape.h" -#include "http.h" /* for HTTP proxy tunnel stuff */ +#include "../urldata.h" +#include "../sendf.h" +#include "../hostip.h" +#include "../progress.h" +#include "../transfer.h" +#include "../escape.h" +#include "../http.h" /* for HTTP proxy tunnel stuff */ #include "ssh.h" -#include "url.h" -#include "speedcheck.h" -#include "getinfo.h" -#include "strdup.h" -#include "strcase.h" -#include "vtls/vtls.h" -#include "cfilters.h" -#include "connect.h" -#include "inet_ntop.h" -#include "parsedate.h" /* for the week day and month names */ -#include "sockaddr.h" /* required for Curl_sockaddr_storage */ -#include "strtoofft.h" -#include "multiif.h" -#include "select.h" -#include "warnless.h" +#include "../url.h" +#include "../speedcheck.h" +#include "../getinfo.h" +#include "../strdup.h" +#include "../strcase.h" +#include "../vtls/vtls.h" +#include "../cfilters.h" +#include "../connect.h" +#include "../inet_ntop.h" +#include "../parsedate.h" /* for the week day and month names */ +#include "../sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "../multiif.h" +#include "../select.h" +#include "../curlx/warnless.h" #include "curl_path.h" - -#include /* for base64 encoding/decoding */ -#include - +#include "../curlx/strparse.h" +#include "../curlx/base64.h" /* for base64 encoding/decoding */ +#include "../curl_sha256.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* Local functions: */ static const char *sftp_libssh2_strerror(unsigned long err); static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc); static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc); static LIBSSH2_FREE_FUNC(my_libssh2_free); -static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data); +static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, + struct ssh_conn *sshc); static CURLcode ssh_connect(struct Curl_easy *data, bool *done); static CURLcode ssh_multi_statemach(struct Curl_easy *data, bool *done); static CURLcode ssh_do(struct Curl_easy *data, bool *done); @@ -107,7 +106,8 @@ static int ssh_getsock(struct Curl_easy *data, struct connectdata *conn, static CURLcode ssh_setup_connection(struct Curl_easy *data, struct connectdata *conn); static void ssh_attach(struct Curl_easy *data, struct connectdata *conn); - +static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data, + bool block); /* * SCP protocol handler. */ @@ -299,10 +299,10 @@ static LIBSSH2_FREE_FUNC(my_libssh2_free) * SSH State machine related code */ /* This is the ONLY way to change SSH state! */ -static void state(struct Curl_easy *data, sshstate nowstate) +static void myssh_state(struct Curl_easy *data, + struct ssh_conn *sshc, + sshstate nowstate) { - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char * const names[] = { @@ -369,14 +369,14 @@ static void state(struct Curl_easy *data, sshstate nowstate) }; /* a precaution to make sure the lists are in sync */ - DEBUGASSERT(sizeof(names)/sizeof(names[0]) == SSH_LAST); + DEBUGASSERT(CURL_ARRAYSIZE(names) == SSH_LAST); if(sshc->state != nowstate) { infof(data, "SFTP %p state change from %s to %s", (void *)sshc, names[sshc->state], names[nowstate]); } #endif - + (void)data; sshc->state = nowstate; } @@ -429,7 +429,8 @@ static enum curl_khtype convert_ssh2_keytype(int sshkeytype) return keytype; } -static CURLcode ssh_knownhost(struct Curl_easy *data) +static CURLcode ssh_knownhost(struct Curl_easy *data, + struct ssh_conn *sshc) { int sshkeytype = 0; size_t keylen = 0; @@ -439,7 +440,6 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) if(data->set.str[STRING_SSH_KNOWNHOSTS]) { /* 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; const char *remotekey = libssh2_session_hostkey(sshc->ssh_session, &keylen, &sshkeytype); @@ -544,11 +544,11 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) switch(rc) { default: /* unknown return codes will equal reject */ case CURLKHSTAT_REJECT: - state(data, SSH_SESSION_FREE); + myssh_state(data, sshc, SSH_SESSION_FREE); FALLTHROUGH(); case CURLKHSTAT_DEFER: /* DEFER means bail out but keep the SSH_HOSTKEY state */ - result = sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + result = CURLE_PEER_FAILED_VERIFICATION; break; case CURLKHSTAT_FINE_REPLACE: /* remove old host+key that does not match */ @@ -590,17 +590,16 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) return result; } -static CURLcode ssh_check_fingerprint(struct Curl_easy *data) +static CURLcode ssh_check_fingerprint(struct Curl_easy *data, + struct ssh_conn *sshc) { - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]; const char *pubkey_sha256 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256]; infof(data, "SSH MD5 public key: %s", - pubkey_md5 != NULL ? pubkey_md5 : "NULL"); + pubkey_md5 != NULL ? pubkey_md5 : "NULL"); infof(data, "SSH SHA256 public key: %s", - pubkey_sha256 != NULL ? pubkey_sha256 : "NULL"); + pubkey_sha256 != NULL ? pubkey_sha256 : "NULL"); if(pubkey_sha256) { const char *fingerprint = NULL; @@ -629,25 +628,22 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) failf(data, "Denied establishing ssh session: sha256 fingerprint " "not available"); - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_PEER_FAILED_VERIFICATION; } /* The length of fingerprint is 32 bytes for SHA256. * See libssh2_hostkey_hash documentation. */ - if(Curl_base64_encode(fingerprint, 32, &fingerprint_b64, - &fingerprint_b64_len) != CURLE_OK) { - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; + if(curlx_base64_encode(fingerprint, 32, &fingerprint_b64, + &fingerprint_b64_len) != CURLE_OK) { + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_PEER_FAILED_VERIFICATION; } if(!fingerprint_b64) { failf(data, "sha256 fingerprint could not be encoded"); - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_PEER_FAILED_VERIFICATION; } infof(data, "SSH SHA256 fingerprint: %s", fingerprint_b64); @@ -672,9 +668,8 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) "Denied establishing ssh session: mismatch sha256 fingerprint. " "Remote %s is not equal to %s", fingerprint_b64, pubkey_sha256); free(fingerprint_b64); - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_PEER_FAILED_VERIFICATION; } free(fingerprint_b64); @@ -684,7 +679,7 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) if(pubkey_md5) { char md5buffer[33]; - const char *fingerprint = NULL; + const char *fingerprint; fingerprint = libssh2_hostkey_hash(sshc->ssh_session, LIBSSH2_HOSTKEY_HASH_MD5); @@ -712,9 +707,8 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) "Denied establishing ssh session: md5 fingerprint " "not available"); } - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_PEER_FAILED_VERIFICATION; } infof(data, "MD5 checksum match"); } @@ -734,20 +728,18 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) (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; - return sshc->actualcode; + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_PEER_FAILED_VERIFICATION; } } else { - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_PEER_FAILED_VERIFICATION; } return CURLE_OK; } else { - return ssh_knownhost(data); + return ssh_knownhost(data, sshc); } } else { @@ -760,7 +752,8 @@ static CURLcode ssh_check_fingerprint(struct Curl_easy *data) * ssh_force_knownhost_key_type() will check the known hosts file and try to * force a specific public key type from the server if an entry is found. */ -static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data) +static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, + struct ssh_conn *sshc) { CURLcode result = CURLE_OK; @@ -789,14 +782,15 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data) const char *hostkey_method = NULL; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; struct libssh2_knownhost* store = NULL; const char *kh_name_end = NULL; size_t kh_name_size = 0; int port = 0; bool found = FALSE; - if(sshc->kh && !data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) { + if(sshc->kh && + !data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] && + !data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256]) { /* lets try to find our host in the known hosts file */ while(!libssh2_knownhost_get(sshc->kh, &store, store)) { /* For non-standard ports, the name will be enclosed in */ @@ -931,7 +925,7 @@ static CURLcode sftp_quote(struct Curl_easy *data, 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_OUT, "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 @@ -940,7 +934,7 @@ static CURLcode sftp_quote(struct Curl_easy *data, result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); free(tmp); if(!result) - state(data, SSH_SFTP_NEXT_QUOTE); + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); return result; } @@ -970,11 +964,11 @@ static CURLcode sftp_quote(struct Curl_easy *data, * 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)) { + if(!strncmp(cmd, "chgrp ", 6) || + !strncmp(cmd, "chmod ", 6) || + !strncmp(cmd, "chown ", 6) || + !strncmp(cmd, "atime ", 6) || + !strncmp(cmd, "mtime ", 6)) { /* attribute change */ /* sshc->quote_path1 contains the mode to set */ @@ -987,11 +981,11 @@ static CURLcode sftp_quote(struct Curl_easy *data, return result; } memset(&sshp->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); - state(data, SSH_SFTP_QUOTE_STAT); + myssh_state(data, sshc, SSH_SFTP_QUOTE_STAT); return result; } - if(strncasecompare(cmd, "ln ", 3) || - strncasecompare(cmd, "symlink ", 8)) { + if(!strncmp(cmd, "ln ", 3) || + !strncmp(cmd, "symlink ", 8)) { /* symbolic linking */ /* sshc->quote_path1 is the source */ /* get the destination */ @@ -1002,15 +996,15 @@ static CURLcode sftp_quote(struct Curl_easy *data, Curl_safefree(sshc->quote_path1); return result; } - state(data, SSH_SFTP_QUOTE_SYMLINK); + myssh_state(data, sshc, SSH_SFTP_QUOTE_SYMLINK); return result; } - else if(strncasecompare(cmd, "mkdir ", 6)) { + else if(!strncmp(cmd, "mkdir ", 6)) { /* create dir */ - state(data, SSH_SFTP_QUOTE_MKDIR); + myssh_state(data, sshc, SSH_SFTP_QUOTE_MKDIR); return result; } - else if(strncasecompare(cmd, "rename ", 7)) { + else if(!strncmp(cmd, "rename ", 7)) { /* rename file */ /* first param is the source path */ /* second param is the dest. path */ @@ -1021,20 +1015,20 @@ static CURLcode sftp_quote(struct Curl_easy *data, Curl_safefree(sshc->quote_path1); return result; } - state(data, SSH_SFTP_QUOTE_RENAME); + myssh_state(data, sshc, SSH_SFTP_QUOTE_RENAME); return result; } - else if(strncasecompare(cmd, "rmdir ", 6)) { + else if(!strncmp(cmd, "rmdir ", 6)) { /* delete dir */ - state(data, SSH_SFTP_QUOTE_RMDIR); + myssh_state(data, sshc, SSH_SFTP_QUOTE_RMDIR); return result; } - else if(strncasecompare(cmd, "rm ", 3)) { - state(data, SSH_SFTP_QUOTE_UNLINK); + else if(!strncmp(cmd, "rm ", 3)) { + myssh_state(data, sshc, SSH_SFTP_QUOTE_UNLINK); return result; } - else if(strncasecompare(cmd, "statvfs ", 8)) { - state(data, SSH_SFTP_QUOTE_STATVFS); + else if(!strncmp(cmd, "statvfs ", 8)) { + myssh_state(data, sshc, SSH_SFTP_QUOTE_STATVFS); return result; } @@ -1100,6 +1094,7 @@ sftp_upload_init(struct Curl_easy *data, LIBSSH2_SFTP_OPENFILE); if(!sshc->sftp_handle) { + CURLcode result; unsigned long sftperr; int rc = libssh2_session_last_errno(sshc->ssh_session); @@ -1116,12 +1111,10 @@ sftp_upload_init(struct Curl_easy *data, 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; + myssh_state(data, sshc, SSH_SFTP_CLOSE); failf(data, "Creating the dir/file failed: %s", sftp_libssh2_strerror(sftperr)); - return CURLE_OK; + return sftp_libssh2_error_to_CURLE(sftperr); } if(((sftperr == LIBSSH2_FX_NO_SUCH_FILE) || (sftperr == LIBSSH2_FX_FAILURE) || @@ -1130,24 +1123,23 @@ sftp_upload_init(struct Curl_easy *data, (strlen(sshp->path) > 1))) { /* try to create the path remotely */ sshc->secondCreateDirs = 1; - state(data, SSH_SFTP_CREATE_DIRS_INIT); + myssh_state(data, sshc, 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) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + result = sftp_libssh2_error_to_CURLE(sftperr); + if(!result) { /* 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; + result = 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; + return result; } /* If we have a restart point then we need to seek to the correct @@ -1228,13 +1220,15 @@ sftp_upload_init(struct Curl_easy *data, timeout here */ Curl_expire(data, 0, EXPIRE_RUN_NOW); - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); return CURLE_OK; } -static CURLcode -sftp_pkey_init(struct Curl_easy *data, - struct ssh_conn *sshc) +/* make sure that this does not collide with an actual libssh2 error code */ +#define ERROR_LIBBSH2 1 + +static CURLcode ssh_state_pkey_init(struct Curl_easy *data, + struct ssh_conn *sshc) { /* * Check the supported auth types in the order I feel is most secure @@ -1263,7 +1257,7 @@ sftp_pkey_init(struct Curl_easy *data, if(!sshc->rsa) out_of_memory = TRUE; else if(stat(sshc->rsa, &sbuf)) { - Curl_safefree(sshc->rsa); + free(sshc->rsa); sshc->rsa = aprintf("%s/.ssh/id_dsa", home); if(!sshc->rsa) out_of_memory = TRUE; @@ -1277,10 +1271,10 @@ sftp_pkey_init(struct Curl_easy *data, /* Nothing found; try the current dir. */ sshc->rsa = strdup("id_rsa"); if(sshc->rsa && stat(sshc->rsa, &sbuf)) { - Curl_safefree(sshc->rsa); + free(sshc->rsa); sshc->rsa = strdup("id_dsa"); if(sshc->rsa && stat(sshc->rsa, &sbuf)) { - Curl_safefree(sshc->rsa); + free(sshc->rsa); /* Out of guesses. Set to the empty string to avoid * surprising info messages. */ sshc->rsa = strdup(""); @@ -1305,8 +1299,7 @@ sftp_pkey_init(struct Curl_easy *data, 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; + myssh_state(data, sshc, SSH_SESSION_FREE); return CURLE_OUT_OF_MEMORY; } @@ -1318,12 +1311,12 @@ sftp_pkey_init(struct Curl_easy *data, 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); + myssh_state(data, sshc, SSH_AUTH_PKEY); } else { - state(data, SSH_AUTH_PASS_INIT); + myssh_state(data, sshc, SSH_AUTH_PASS_INIT); } - return CURLE_OK; + return 0; } static CURLcode @@ -1345,7 +1338,7 @@ sftp_quote_stat(struct Curl_easy *data, sshc->acceptfail = TRUE; } - if(!strncasecompare(cmd, "chmod", 5)) { + if(!!strncmp(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. @@ -1367,8 +1360,11 @@ sftp_quote_stat(struct Curl_easy *data, } /* Now set the new attributes... */ - if(strncasecompare(cmd, "chgrp", 5)) { - sshp->quote_attrs.gid = strtoul(sshc->quote_path1, NULL, 10); + if(!strncmp(cmd, "chgrp", 5)) { + const char *p = sshc->quote_path1; + curl_off_t gid; + (void)curlx_str_number(&p, &gid, ULONG_MAX); + sshp->quote_attrs.gid = (unsigned long)gid; sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; if(sshp->quote_attrs.gid == 0 && !ISDIGIT(sshc->quote_path1[0]) && !sshc->acceptfail) { @@ -1376,18 +1372,23 @@ sftp_quote_stat(struct Curl_easy *data, 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; + else if(!strncmp(cmd, "chmod", 5)) { + curl_off_t perms; + const char *p = sshc->quote_path1; /* permissions are octal */ - if(sshp->quote_attrs.permissions == 0 && - !ISDIGIT(sshc->quote_path1[0])) { + if(curlx_str_octal(&p, &perms, 07777)) { failf(data, "Syntax error: chmod permissions not a number"); goto fail; } + + sshp->quote_attrs.permissions = (unsigned long)perms; + sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS; } - else if(strncasecompare(cmd, "chown", 5)) { - sshp->quote_attrs.uid = strtoul(sshc->quote_path1, NULL, 10); + else if(!strncmp(cmd, "chown", 5)) { + const char *p = sshc->quote_path1; + curl_off_t uid; + (void)curlx_str_number(&p, &uid, ULONG_MAX); + sshp->quote_attrs.uid = (unsigned long)uid; sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; if(sshp->quote_attrs.uid == 0 && !ISDIGIT(sshc->quote_path1[0]) && !sshc->acceptfail) { @@ -1395,8 +1396,8 @@ sftp_quote_stat(struct Curl_easy *data, goto fail; } } - else if(strncasecompare(cmd, "atime", 5) || - strncasecompare(cmd, "mtime", 5)) { + else if(!strncmp(cmd, "atime", 5) || + !strncmp(cmd, "mtime", 5)) { time_t date = Curl_getdate_capped(sshc->quote_path1); bool fail = FALSE; @@ -1413,7 +1414,7 @@ sftp_quote_stat(struct Curl_easy *data, #endif if(fail) goto fail; - if(strncasecompare(cmd, "atime", 5)) + if(!strncmp(cmd, "atime", 5)) sshp->quote_attrs.atime = (unsigned long)date; else /* mtime */ sshp->quote_attrs.mtime = (unsigned long)date; @@ -1422,7 +1423,7 @@ sftp_quote_stat(struct Curl_easy *data, } /* Now send the completed structure... */ - state(data, SSH_SFTP_QUOTE_SETSTAT); + myssh_state(data, sshc, SSH_SFTP_QUOTE_SETSTAT); return CURLE_OK; fail: Curl_safefree(sshc->quote_path1); @@ -1466,20 +1467,19 @@ sftp_download_stat(struct Curl_easy *data, } if(data->state.use_range) { curl_off_t from, to; - char *ptr; - char *ptr2; - CURLofft to_t; - CURLofft from_t; + const char *p = data->state.range; + int to_t, from_t; - from_t = curlx_strtoofft(data->state.range, &ptr, 10, &from); - if(from_t == CURL_OFFT_FLOW) + from_t = curlx_str_number(&p, &from, CURL_OFF_T_MAX); + if(from_t == STRE_OVERFLOW) return CURLE_RANGE_ERROR; - while(*ptr && (ISBLANK(*ptr) || (*ptr == '-'))) - ptr++; - to_t = curlx_strtoofft(ptr, &ptr2, 10, &to); - if(to_t == CURL_OFFT_FLOW) + curlx_str_passblanks(&p); + (void)curlx_str_single(&p, '-'); + + to_t = curlx_str_numblanks(&p, &to); + if(to_t == STRE_OVERFLOW) return CURLE_RANGE_ERROR; - if((to_t == CURL_OFFT_INVAL) /* no "to" value given */ + if((to_t == STRE_NO_NUM) /* no "to" value given */ || (to >= size)) { to = size - 1; } @@ -1545,7 +1545,7 @@ sftp_download_stat(struct Curl_easy *data, /* no data to transfer */ Curl_xfer_setup_nop(data); infof(data, "File already completely downloaded"); - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); return CURLE_OK; } Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE); @@ -1557,7 +1557,7 @@ sftp_download_stat(struct Curl_easy *data, out writableable as the underlying libssh2 recv function will deal with both accordingly */ data->state.select_bits = CURL_CSELECT_IN; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); return CURLE_OK; } @@ -1585,44 +1585,1107 @@ static CURLcode sftp_readdir(struct Curl_easy *data, sshp->readdir_filename, readdir_len); if(!result) - result = Curl_client_write(data, CLIENTWRITE_BODY, - (char *)"\n", 1); + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); if(result) return result; } else { - result = Curl_dyn_add(&sshp->readdir, sshp->readdir_longentry); + result = curlx_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); + result = curlx_dyn_addf(&sshp->readdir_link, "%s%s", sshp->path, + sshp->readdir_filename); + myssh_state(data, sshc, SSH_SFTP_READDIR_LINK); } else { - state(data, SSH_SFTP_READDIR_BOTTOM); + myssh_state(data, sshc, SSH_SFTP_READDIR_BOTTOM); } } return result; } } else if(!rc) { - state(data, SSH_SFTP_READDIR_DONE); + myssh_state(data, sshc, SSH_SFTP_READDIR_DONE); } else { unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); - result = sftp_libssh2_error_to_CURLE(sftperr); - sshc->actualcode = result ? result : CURLE_SSH; + result = sftperr ? sftp_libssh2_error_to_CURLE(sftperr) : 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); + myssh_state(data, sshc, SSH_SFTP_CLOSE); } return result; } + +static CURLcode ssh_state_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + CURLcode result; + sshc->secondCreateDirs = 0; + sshc->nextstate = SSH_NO_STATE; + + /* Set libssh2 to non-blocking, since everything internally is + non-blocking */ + libssh2_session_set_blocking(sshc->ssh_session, 0); + + result = ssh_force_knownhost_key_type(data, sshc); + if(result) + myssh_state(data, sshc, SSH_SESSION_FREE); + else + myssh_state(data, sshc, SSH_S_STARTUP); + return result; +} + +static CURLcode ssh_state_startup(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + struct connectdata *conn = data->conn; + int rc = libssh2_session_handshake(sshc->ssh_session, + conn->sock[FIRSTSOCKET]); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, NULL, 0); + failf(data, "Failure establishing ssh session: %d, %s", rc, err_msg); + + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_FAILED_INIT; + } + + myssh_state(data, sshc, SSH_HOSTKEY); + return CURLE_OK; +} + +static CURLcode ssh_state_hostkey(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* + * Before we authenticate we should check the hostkey's fingerprint + * against our known hosts. How that is handled (reading from file, + * whatever) is up to us. + */ + CURLcode result = ssh_check_fingerprint(data, sshc); + if(!result) + myssh_state(data, sshc, SSH_AUTHLIST); + return result; +} + +static CURLcode ssh_state_authlist(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* + * Figure out authentication methods + * NB: As soon as we have provided a username to an openssh server we + * 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 will not work in my + * experience. + * So always specify it here. + */ + struct connectdata *conn = data->conn; + sshc->authlist = libssh2_userauth_list(sshc->ssh_session, + conn->user, + curlx_uztoui(strlen(conn->user))); + + if(!sshc->authlist) { + int rc; + if(libssh2_userauth_authenticated(sshc->ssh_session)) { + sshc->authed = TRUE; + infof(data, "SSH user accepted with no authentication"); + myssh_state(data, sshc, SSH_AUTH_DONE); + return CURLE_OK; + } + rc = libssh2_session_last_errno(sshc->ssh_session); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + myssh_state(data, sshc, SSH_SESSION_FREE); + return libssh2_session_error_to_CURLE(rc); + } + infof(data, "SSH authentication methods available: %s", + sshc->authlist); + + myssh_state(data, sshc, SSH_AUTH_PKEY_INIT); + return CURLE_OK; +} + +static CURLcode ssh_state_auth_pkey(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* The function below checks if the files exists, no need to stat() here. + */ + struct connectdata *conn = data->conn; + int rc = + libssh2_userauth_publickey_fromfile_ex(sshc->ssh_session, + conn->user, + curlx_uztoui( + strlen(conn->user)), + sshc->rsa_pub, + sshc->rsa, sshc->passphrase); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + Curl_safefree(sshc->rsa_pub); + Curl_safefree(sshc->rsa); + + if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized SSH public key authentication"); + myssh_state(data, sshc, SSH_AUTH_DONE); + } + else { + char *err_msg = NULL; + 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); + myssh_state(data, sshc, SSH_AUTH_PASS_INIT); + } + return CURLE_OK; +} + +static CURLcode ssh_state_auth_pass_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + if((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) && + (strstr(sshc->authlist, "password") != NULL)) { + myssh_state(data, sshc, SSH_AUTH_PASS); + } + else { + myssh_state(data, sshc, SSH_AUTH_HOST_INIT); + } + return CURLE_OK; +} + +static CURLcode ssh_state_auth_pass(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + struct connectdata *conn = data->conn; + int rc = + libssh2_userauth_password_ex(sshc->ssh_session, conn->user, + curlx_uztoui(strlen(conn->user)), + conn->passwd, + curlx_uztoui(strlen(conn->passwd)), + NULL); + if(rc == LIBSSH2_ERROR_EAGAIN) { + return CURLE_AGAIN; + } + if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized password authentication"); + myssh_state(data, sshc, SSH_AUTH_DONE); + } + else { + myssh_state(data, sshc, SSH_AUTH_HOST_INIT); + } + return CURLE_OK; +} + +static CURLcode ssh_state_auth_host_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + if((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) && + (strstr(sshc->authlist, "hostbased") != NULL)) { + myssh_state(data, sshc, SSH_AUTH_HOST); + } + else { + myssh_state(data, sshc, SSH_AUTH_AGENT_INIT); + } + return CURLE_OK; +} + +static CURLcode ssh_state_auth_agent_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc = 0; + if((data->set.ssh_auth_types & CURLSSH_AUTH_AGENT) + && (strstr(sshc->authlist, "publickey") != NULL)) { + + /* Connect to the ssh-agent */ + /* The agent could be shared by a curl thread i believe + but nothing obvious as keys can be added/removed at any time */ + if(!sshc->ssh_agent) { + sshc->ssh_agent = libssh2_agent_init(sshc->ssh_session); + if(!sshc->ssh_agent) { + infof(data, "Could not create agent object"); + + myssh_state(data, sshc, SSH_AUTH_KEY_INIT); + return CURLE_OK; + } + } + + rc = libssh2_agent_connect(sshc->ssh_agent); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + if(rc < 0) { + infof(data, "Failure connecting to agent"); + myssh_state(data, sshc, SSH_AUTH_KEY_INIT); + } + else { + myssh_state(data, sshc, SSH_AUTH_AGENT_LIST); + } + } + else + myssh_state(data, sshc, SSH_AUTH_KEY_INIT); + return CURLE_OK; +} + +static CURLcode ssh_state_auth_agent_list(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc = libssh2_agent_list_identities(sshc->ssh_agent); + + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + if(rc < 0) { + infof(data, "Failure requesting identities to agent"); + myssh_state(data, sshc, SSH_AUTH_KEY_INIT); + } + else { + myssh_state(data, sshc, SSH_AUTH_AGENT); + sshc->sshagent_prev_identity = NULL; + } + return CURLE_OK; +} + +static CURLcode ssh_state_auth_agent(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* as prev_identity evolves only after an identity user auth finished we + can safely request it again as long as EAGAIN is returned here or by + libssh2_agent_userauth */ + int rc = libssh2_agent_get_identity(sshc->ssh_agent, + &sshc->sshagent_identity, + sshc->sshagent_prev_identity); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc == 0) { + struct connectdata *conn = data->conn; + rc = libssh2_agent_userauth(sshc->ssh_agent, conn->user, + sshc->sshagent_identity); + + if(rc < 0) { + if(rc != LIBSSH2_ERROR_EAGAIN) { + /* tried and failed? go to next identity */ + sshc->sshagent_prev_identity = sshc->sshagent_identity; + } + return CURLE_OK; + } + } + + if(rc < 0) + infof(data, "Failure requesting identities to agent"); + else if(rc == 1) + infof(data, "No identity would match"); + + if(rc == LIBSSH2_ERROR_NONE) { + sshc->authed = TRUE; + infof(data, "Agent based authentication successful"); + myssh_state(data, sshc, SSH_AUTH_DONE); + } + else { + myssh_state(data, sshc, SSH_AUTH_KEY_INIT); + } + return CURLE_OK; +} + +static CURLcode ssh_state_auth_key_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + if((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) + && (strstr(sshc->authlist, "keyboard-interactive") != NULL)) { + myssh_state(data, sshc, SSH_AUTH_KEY); + } + else { + myssh_state(data, sshc, SSH_AUTH_DONE); + } + return CURLE_OK; +} + +static CURLcode ssh_state_auth_key(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* Authentication failed. Continue with keyboard-interactive now. */ + struct connectdata *conn = data->conn; + int rc = + libssh2_userauth_keyboard_interactive_ex(sshc->ssh_session, + conn->user, + curlx_uztoui( + strlen(conn->user)), + &kbd_callback); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized keyboard interactive authentication"); + myssh_state(data, sshc, SSH_AUTH_DONE); + return CURLE_OK; + } + return CURLE_LOGIN_DENIED; +} + +static CURLcode ssh_state_auth_done(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + struct connectdata *conn = data->conn; + if(!sshc->authed) { + failf(data, "Authentication failure"); + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_LOGIN_DENIED; + } + + /* + * At this point we have an authenticated ssh session. + */ + infof(data, "Authentication complete"); + + Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSH is connected */ + + conn->sockfd = conn->sock[FIRSTSOCKET]; + conn->writesockfd = CURL_SOCKET_BAD; + + if(conn->handler->protocol == CURLPROTO_SFTP) { + myssh_state(data, sshc, SSH_SFTP_INIT); + return CURLE_OK; + } + infof(data, "SSH CONNECT phase done"); + myssh_state(data, sshc, SSH_STOP); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* + * Start the libssh2 sftp session + */ + sshc->sftp_session = libssh2_sftp_init(sshc->ssh_session); + if(!sshc->sftp_session) { + char *err_msg = NULL; + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + failf(data, "Failure initializing sftp session: %s", err_msg); + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_FAILED_INIT; + } + myssh_state(data, sshc, SSH_SFTP_REALPATH); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_realpath(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + /* + * Get the "home" directory + */ + int rc = libssh2_sftp_symlink_ex(sshc->sftp_session, + ".", curlx_uztoui(strlen(".")), + sshp->readdir_filename, CURL_PATH_MAX, + LIBSSH2_SFTP_REALPATH); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc > 0) { + /* It seems that this string is not always null-terminated */ + sshp->readdir_filename[rc] = '\0'; + free(sshc->homedir); + sshc->homedir = strdup(sshp->readdir_filename); + if(!sshc->homedir) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + return CURLE_OUT_OF_MEMORY; + } + free(data->state.most_recent_ftp_entrypath); + data->state.most_recent_ftp_entrypath = strdup(sshc->homedir); + if(!data->state.most_recent_ftp_entrypath) + return CURLE_OUT_OF_MEMORY; + } + else { + /* Return the error type */ + unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session); + CURLcode result; + if(sftperr) + result = sftp_libssh2_error_to_CURLE(sftperr); + else + /* in this case, the error was not in the SFTP level but for example + a time-out or similar */ + result = CURLE_SSH; + DEBUGF(infof(data, "error = %lu makes libcurl = %d", + sftperr, (int)result)); + myssh_state(data, sshc, SSH_STOP); + return result; + } + + /* 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 + working path will not. */ + DEBUGF(infof(data, "SSH CONNECT phase done")); + myssh_state(data, sshc, SSH_STOP); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_init(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + CURLcode result = Curl_getworkingpath(data, sshc->homedir, &sshp->path); + if(result) { + myssh_state(data, sshc, SSH_STOP); + return result; + } + + if(data->set.quote) { + infof(data, "Sending quote commands"); + sshc->quote_item = data->set.quote; + myssh_state(data, sshc, SSH_SFTP_QUOTE); + } + else { + myssh_state(data, sshc, SSH_SFTP_GETINFO); + } + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_postquote_init(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + if(data->set.postquote) { + infof(data, "Sending quote commands"); + sshc->quote_item = data->set.postquote; + myssh_state(data, sshc, SSH_SFTP_QUOTE); + } + else { + myssh_state(data, sshc, SSH_STOP); + } + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + /* Send quote commands */ + CURLcode result = sftp_quote(data, sshc, sshp); + if(result) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + } + return result; +} + +static CURLcode ssh_state_sftp_next_quote(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + + sshc->quote_item = sshc->quote_item->next; + + if(sshc->quote_item) { + myssh_state(data, sshc, SSH_SFTP_QUOTE); + } + else { + if(sshc->nextstate != SSH_NO_STATE) { + myssh_state(data, sshc, sshc->nextstate); + sshc->nextstate = SSH_NO_STATE; + } + else { + myssh_state(data, sshc, SSH_SFTP_GETINFO); + } + } + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_stat(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, + bool *blockp) +{ + CURLcode result = sftp_quote_stat(data, sshc, sshp, blockp); + if(result) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + } + return result; +} + +static CURLcode ssh_state_sftp_quote_setstat(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + int rc = + libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_SETSTAT, + &sshp->quote_attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc && !sshc->acceptfail) { + 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", + sftp_libssh2_strerror(sftperr)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_QUOTE_ERROR; + } + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_symlink(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc = + libssh2_sftp_symlink_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_SYMLINK); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc && !sshc->acceptfail) { + 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", + sftp_libssh2_strerror(sftperr)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_QUOTE_ERROR; + } + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_mkdir(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + (long)data->set.new_directory_perms); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc && !sshc->acceptfail) { + 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)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_QUOTE_ERROR; + } + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_rename(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc = + libssh2_sftp_rename_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_RENAME_OVERWRITE | + LIBSSH2_SFTP_RENAME_ATOMIC | + LIBSSH2_SFTP_RENAME_NATIVE); + + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc && !sshc->acceptfail) { + 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", + sftp_libssh2_strerror(sftperr)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_QUOTE_ERROR; + } + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_rmdir(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc = libssh2_sftp_rmdir_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1))); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc && !sshc->acceptfail) { + 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)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_QUOTE_ERROR; + } + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_unlink(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + int rc = libssh2_sftp_unlink_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1))); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc && !sshc->acceptfail) { + 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)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_QUOTE_ERROR; + } + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_quote_statvfs(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + LIBSSH2_SFTP_STATVFS statvfs; + int rc = libssh2_sftp_statvfs(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + &statvfs); + + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc && !sshc->acceptfail) { + 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)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_QUOTE_ERROR; + } + else if(rc == 0) { +#ifdef _MSC_VER +#define CURL_LIBSSH2_VFS_SIZE_MASK "I64u" +#else +#define CURL_LIBSSH2_VFS_SIZE_MASK "llu" +#endif + CURLcode result; + char *tmp = aprintf("statvfs:\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, + statvfs.f_ffree, statvfs.f_favail, + statvfs.f_fsid, statvfs.f_flag, + statvfs.f_namemax); + if(!tmp) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return CURLE_OUT_OF_MEMORY; + } + + result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp)); + free(tmp); + if(result) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + return result; + } + } + myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_create_dirs_mkdir(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + /* 'mode' - parameter is preliminary - default to 0644 */ + int rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + (long)data->set.new_directory_perms); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + *sshc->slash_pos = '/'; + ++sshc->slash_pos; + if(rc < 0) { + /* + * 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 + */ + 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)) { + myssh_state(data, sshc, SSH_SFTP_CLOSE); + return sftp_libssh2_error_to_CURLE(sftperr); + } + } + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_readdir_init(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + Curl_pgrsSetDownloadSize(data, -1); + if(data->req.no_body) { + myssh_state(data, sshc, SSH_STOP); + return CURLE_OK; + } + + /* + * 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); + if(!sshc->sftp_handle) { + unsigned long sftperr; + if(libssh2_session_last_errno(sshc->ssh_session) == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + failf(data, "Could not open directory for reading: %s", + sftp_libssh2_strerror(sftperr)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + return sftp_libssh2_error_to_CURLE(sftperr); + } + myssh_state(data, sshc, SSH_SFTP_READDIR); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_readdir_link(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + CURLcode result; + int rc = + libssh2_sftp_symlink_ex(sshc->sftp_session, + curlx_dyn_ptr(&sshp->readdir_link), + (unsigned int) + curlx_dyn_len(&sshp->readdir_link), + sshp->readdir_filename, + CURL_PATH_MAX, LIBSSH2_SFTP_READLINK); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + curlx_dyn_free(&sshp->readdir_link); + + /* append filename and extra output */ + result = curlx_dyn_addf(&sshp->readdir, " -> %s", sshp->readdir_filename); + if(result) + myssh_state(data, sshc, SSH_SFTP_CLOSE); + else + myssh_state(data, sshc, SSH_SFTP_READDIR_BOTTOM); + return result; +} + +static CURLcode ssh_state_scp_download_init(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + curl_off_t bytecount; + + /* + * We must check the remote file; if it is a directory no values will + * be set in sb + */ + + /* + * If support for >2GB files exists, use it. + */ + + /* get a fresh new channel from the ssh layer */ +#if LIBSSH2_VERSION_NUM < 0x010700 + struct stat sb; + memset(&sb, 0, sizeof(struct stat)); + sshc->ssh_channel = libssh2_scp_recv(sshc->ssh_session, + sshp->path, &sb); +#else + libssh2_struct_stat sb; + memset(&sb, 0, sizeof(libssh2_struct_stat)); + sshc->ssh_channel = libssh2_scp_recv2(sshc->ssh_session, + sshp->path, &sb); +#endif + + if(!sshc->ssh_channel) { + int ssh_err; + char *err_msg = NULL; + + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0)); + failf(data, "%s", err_msg); + myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE); + return libssh2_session_error_to_CURLE(ssh_err); + } + + /* download data */ + bytecount = (curl_off_t)sb.st_size; + data->req.maxdownload = (curl_off_t)sb.st_size; + Curl_xfer_setup1(data, CURL_XFER_RECV, bytecount, 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; + + myssh_state(data, sshc, SSH_STOP); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_close(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + int rc = 0; + if(sshc->sftp_handle) { + rc = libssh2_sftp_close(sshc->sftp_handle); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg); + } + sshc->sftp_handle = NULL; + } + + Curl_safefree(sshp->path); + + DEBUGF(infof(data, "SFTP DONE done")); + + /* Check if nextstate is set and move .nextstate could be POSTQUOTE_INIT + After nextstate is executed, the control should come back to + SSH_SFTP_CLOSE to pass the correct result back */ + if(sshc->nextstate != SSH_NO_STATE && + sshc->nextstate != SSH_SFTP_CLOSE) { + myssh_state(data, sshc, sshc->nextstate); + sshc->nextstate = SSH_SFTP_CLOSE; + } + else + myssh_state(data, sshc, SSH_STOP); + + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_shutdown(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* during times we get here due to a broken transfer and then the + sftp_handle might not have been taken down so make sure that is done + before we proceed */ + int rc = 0; + if(sshc->sftp_handle) { + rc = libssh2_sftp_close(sshc->sftp_handle); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, + NULL, 0); + infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg); + } + sshc->sftp_handle = NULL; + } + if(sshc->sftp_session) { + rc = libssh2_sftp_shutdown(sshc->sftp_session); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc < 0) { + infof(data, "Failed to stop libssh2 sftp subsystem"); + } + sshc->sftp_session = NULL; + } + + Curl_safefree(sshc->homedir); + + myssh_state(data, sshc, SSH_SESSION_DISCONNECT); + return CURLE_OK; +} + +static CURLcode ssh_state_sftp_download_init(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + /* + * Work on getting the specified file + */ + sshc->sftp_handle = + libssh2_sftp_open_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + 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) { + return CURLE_AGAIN; + } + sftperr = libssh2_sftp_last_error(sshc->sftp_session); + failf(data, "Could not open remote file for reading: %s", + sftp_libssh2_strerror(sftperr)); + myssh_state(data, sshc, SSH_SFTP_CLOSE); + return sftp_libssh2_error_to_CURLE(sftperr); + } + myssh_state(data, sshc, SSH_SFTP_DOWNLOAD_STAT); + return CURLE_OK; +} + +static CURLcode ssh_state_scp_upload_init(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp) +{ + /* + * 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. + */ + sshc->ssh_channel = + libssh2_scp_send64(sshc->ssh_session, sshp->path, + (int)data->set.new_file_perms, + (libssh2_int64_t)data->state.infilesize, 0, 0); + if(!sshc->ssh_channel) { + int ssh_err; + char *err_msg = NULL; + CURLcode result; + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0)); + failf(data, "%s", err_msg); + myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE); + result = libssh2_session_error_to_CURLE(ssh_err); + + /* Map generic errors to upload failed */ + if(result == CURLE_SSH || + result == CURLE_REMOTE_FILE_NOT_FOUND) + result = CURLE_UPLOAD_FAILED; + return result; + } + + /* upload data */ + data->req.size = data->state.infilesize; + Curl_pgrsSetUploadSize(data, data->state.infilesize); + 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 scp send function will deal + with both accordingly */ + data->state.select_bits = CURL_CSELECT_OUT; + + myssh_state(data, sshc, SSH_STOP); + + return CURLE_OK; +} + +static CURLcode ssh_state_session_disconnect(struct Curl_easy *data, + struct ssh_conn *sshc) +{ + /* 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 */ + int rc = 0; + if(sshc->ssh_channel) { + rc = libssh2_channel_free(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_OK; + + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to free libssh2 scp subsystem: %d %s", + rc, err_msg); + } + sshc->ssh_channel = NULL; + } + + if(sshc->ssh_session) { + rc = libssh2_session_disconnect(sshc->ssh_session, "Shutdown"); + if(rc == LIBSSH2_ERROR_EAGAIN) + return CURLE_AGAIN; + + if(rc < 0) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to disconnect libssh2 session: %d %s", + rc, err_msg); + } + } + + Curl_safefree(sshc->homedir); + + myssh_state(data, sshc, SSH_SESSION_FREE); + return CURLE_OK; +} /* * 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 @@ -1630,719 +2693,195 @@ static CURLcode sftp_readdir(struct Curl_easy *data, * meaning it wants to be called again when the socket is ready */ -static CURLcode ssh_statemachine(struct Curl_easy *data, bool *block) +static CURLcode ssh_statemachine(struct Curl_easy *data, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, + 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; - - int rc = LIBSSH2_ERROR_NONE; *block = 0; /* we are not blocking by default */ do { switch(sshc->state) { case SSH_INIT: - sshc->secondCreateDirs = 0; - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_OK; - - /* Set libssh2 to non-blocking, since everything internally is - non-blocking */ - libssh2_session_set_blocking(sshc->ssh_session, 0); - - result = ssh_force_knownhost_key_type(data); - if(result) { - state(data, SSH_SESSION_FREE); - sshc->actualcode = result; + result = ssh_state_init(data, sshc); + if(result) break; - } - - state(data, SSH_S_STARTUP); FALLTHROUGH(); case SSH_S_STARTUP: - rc = libssh2_session_handshake(sshc->ssh_session, - conn->sock[FIRSTSOCKET]); - if(rc == LIBSSH2_ERROR_EAGAIN) { + result = ssh_state_startup(data, sshc); + if(result) break; - } - if(rc) { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, NULL, 0); - failf(data, "Failure establishing ssh session: %d, %s", rc, err_msg); - - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_FAILED_INIT; - break; - } - - state(data, SSH_HOSTKEY); - FALLTHROUGH(); + case SSH_HOSTKEY: - /* - * Before we authenticate we should check the hostkey's fingerprint - * against our known hosts. How that is handled (reading from file, - * whatever) is up to us. - */ - result = ssh_check_fingerprint(data); - if(!result) - state(data, SSH_AUTHLIST); - /* ssh_check_fingerprint sets state appropriately on error */ + result = ssh_state_hostkey(data, sshc); break; case SSH_AUTHLIST: - /* - * Figure out authentication methods - * NB: As soon as we have provided a username to an openssh server we - * 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 will not work in my - * experience. - * So always specify it here. - */ - sshc->authlist = libssh2_userauth_list(sshc->ssh_session, - conn->user, - curlx_uztoui(strlen(conn->user))); - - if(!sshc->authlist) { - if(libssh2_userauth_authenticated(sshc->ssh_session)) { - sshc->authed = TRUE; - infof(data, "SSH user accepted with no authentication"); - state(data, SSH_AUTH_DONE); - break; - } - 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(rc); - } - break; - } - infof(data, "SSH authentication methods available: %s", - sshc->authlist); - - state(data, SSH_AUTH_PKEY_INIT); + result = ssh_state_authlist(data, sshc); break; case SSH_AUTH_PKEY_INIT: - result = sftp_pkey_init(data, sshc); + result = ssh_state_pkey_init(data, sshc); break; case SSH_AUTH_PKEY: - /* The function below checks if the files exists, no need to stat() here. - */ - rc = libssh2_userauth_publickey_fromfile_ex(sshc->ssh_session, - conn->user, - curlx_uztoui( - strlen(conn->user)), - sshc->rsa_pub, - sshc->rsa, sshc->passphrase); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - - Curl_safefree(sshc->rsa_pub); - Curl_safefree(sshc->rsa); - - if(rc == 0) { - sshc->authed = TRUE; - infof(data, "Initialized SSH public key authentication"); - state(data, SSH_AUTH_DONE); - } - else { - char *err_msg = NULL; - 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 */ - } + result = ssh_state_auth_pkey(data, sshc); break; case SSH_AUTH_PASS_INIT: - if((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) && - (strstr(sshc->authlist, "password") != NULL)) { - state(data, SSH_AUTH_PASS); - } - else { - state(data, SSH_AUTH_HOST_INIT); - rc = 0; /* clear rc and continue */ - } + result = ssh_state_auth_pass_init(data, sshc); break; case SSH_AUTH_PASS: - rc = libssh2_userauth_password_ex(sshc->ssh_session, conn->user, - curlx_uztoui(strlen(conn->user)), - conn->passwd, - curlx_uztoui(strlen(conn->passwd)), - NULL); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc == 0) { - sshc->authed = TRUE; - infof(data, "Initialized password authentication"); - state(data, SSH_AUTH_DONE); - } - else { - state(data, SSH_AUTH_HOST_INIT); - rc = 0; /* clear rc and continue */ - } + result = ssh_state_auth_pass(data, sshc); break; case SSH_AUTH_HOST_INIT: - if((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) && - (strstr(sshc->authlist, "hostbased") != NULL)) { - state(data, SSH_AUTH_HOST); - } - else { - state(data, SSH_AUTH_AGENT_INIT); - } + result = ssh_state_auth_host_init(data, sshc); break; case SSH_AUTH_HOST: - state(data, SSH_AUTH_AGENT_INIT); + myssh_state(data, sshc, SSH_AUTH_AGENT_INIT); break; case SSH_AUTH_AGENT_INIT: - if((data->set.ssh_auth_types & CURLSSH_AUTH_AGENT) - && (strstr(sshc->authlist, "publickey") != NULL)) { - - /* Connect to the ssh-agent */ - /* The agent could be shared by a curl thread i believe - but nothing obvious as keys can be added/removed at any time */ - if(!sshc->ssh_agent) { - sshc->ssh_agent = libssh2_agent_init(sshc->ssh_session); - if(!sshc->ssh_agent) { - infof(data, "Could not create agent object"); - - state(data, SSH_AUTH_KEY_INIT); - break; - } - } - - rc = libssh2_agent_connect(sshc->ssh_agent); - if(rc == LIBSSH2_ERROR_EAGAIN) - break; - if(rc < 0) { - infof(data, "Failure connecting to agent"); - state(data, SSH_AUTH_KEY_INIT); - rc = 0; /* clear rc and continue */ - } - else { - state(data, SSH_AUTH_AGENT_LIST); - } - } - else - state(data, SSH_AUTH_KEY_INIT); + result = ssh_state_auth_agent_init(data, sshc); break; case SSH_AUTH_AGENT_LIST: - rc = libssh2_agent_list_identities(sshc->ssh_agent); - - if(rc == LIBSSH2_ERROR_EAGAIN) - break; - if(rc < 0) { - infof(data, "Failure requesting identities to agent"); - state(data, SSH_AUTH_KEY_INIT); - rc = 0; /* clear rc and continue */ - } - else { - state(data, SSH_AUTH_AGENT); - sshc->sshagent_prev_identity = NULL; - } + result = ssh_state_auth_agent_list(data, sshc); break; case SSH_AUTH_AGENT: - /* as prev_identity evolves only after an identity user auth finished we - can safely request it again as long as EAGAIN is returned here or by - libssh2_agent_userauth */ - rc = libssh2_agent_get_identity(sshc->ssh_agent, - &sshc->sshagent_identity, - sshc->sshagent_prev_identity); - if(rc == LIBSSH2_ERROR_EAGAIN) - break; - - if(rc == 0) { - rc = libssh2_agent_userauth(sshc->ssh_agent, conn->user, - sshc->sshagent_identity); - - if(rc < 0) { - if(rc != LIBSSH2_ERROR_EAGAIN) { - /* tried and failed? go to next identity */ - sshc->sshagent_prev_identity = sshc->sshagent_identity; - } - break; - } - } - - if(rc < 0) - infof(data, "Failure requesting identities to agent"); - else if(rc == 1) - infof(data, "No identity would match"); - - if(rc == LIBSSH2_ERROR_NONE) { - sshc->authed = TRUE; - infof(data, "Agent based authentication successful"); - state(data, SSH_AUTH_DONE); - } - else { - state(data, SSH_AUTH_KEY_INIT); - rc = 0; /* clear rc and continue */ - } + result = ssh_state_auth_agent(data, sshc); break; case SSH_AUTH_KEY_INIT: - if((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) - && (strstr(sshc->authlist, "keyboard-interactive") != NULL)) { - state(data, SSH_AUTH_KEY); - } - else { - state(data, SSH_AUTH_DONE); - } + result = ssh_state_auth_key_init(data, sshc); break; case SSH_AUTH_KEY: - /* Authentication failed. Continue with keyboard-interactive now. */ - rc = libssh2_userauth_keyboard_interactive_ex(sshc->ssh_session, - conn->user, - curlx_uztoui( - strlen(conn->user)), - &kbd_callback); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc == 0) { - sshc->authed = TRUE; - infof(data, "Initialized keyboard interactive authentication"); - } - state(data, SSH_AUTH_DONE); + result = ssh_state_auth_key(data, sshc); break; case SSH_AUTH_DONE: - if(!sshc->authed) { - failf(data, "Authentication failure"); - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_LOGIN_DENIED; - break; - } - - /* - * At this point we have an authenticated ssh session. - */ - infof(data, "Authentication complete"); - - Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSH is connected */ - - conn->sockfd = conn->sock[FIRSTSOCKET]; - conn->writesockfd = CURL_SOCKET_BAD; - - if(conn->handler->protocol == CURLPROTO_SFTP) { - state(data, SSH_SFTP_INIT); - break; - } - infof(data, "SSH CONNECT phase done"); - state(data, SSH_STOP); + result = ssh_state_auth_done(data, sshc); break; case SSH_SFTP_INIT: - /* - * Start the libssh2 sftp session - */ - sshc->sftp_session = libssh2_sftp_init(sshc->ssh_session); - if(!sshc->sftp_session) { - char *err_msg = NULL; - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - failf(data, "Failure initializing sftp session: %s", err_msg); - state(data, SSH_SESSION_FREE); - sshc->actualcode = CURLE_FAILED_INIT; - break; - } - state(data, SSH_SFTP_REALPATH); + result = ssh_state_sftp_init(data, sshc); break; case SSH_SFTP_REALPATH: - /* - * Get the "home" directory - */ - rc = libssh2_sftp_symlink_ex(sshc->sftp_session, - ".", curlx_uztoui(strlen(".")), - sshp->readdir_filename, CURL_PATH_MAX, - LIBSSH2_SFTP_REALPATH); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc > 0) { - /* It seems that this string is not always NULL terminated */ - 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; - break; - } - data->state.most_recent_ftp_entrypath = sshc->homedir; - } - else { - /* Return the error type */ - 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 was not in the SFTP level but for example - a time-out or similar */ - result = CURLE_SSH; - sshc->actualcode = result; - DEBUGF(infof(data, "error = %lu makes libcurl = %d", - sftperr, (int)result)); - 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 - working path will not. */ - DEBUGF(infof(data, "SSH CONNECT phase done")); - state(data, SSH_STOP); - break; + result = ssh_state_sftp_realpath(data, sshc, sshp); + break; case SSH_SFTP_QUOTE_INIT: - - result = Curl_getworkingpath(data, sshc->homedir, &sshp->path); - if(result) { - sshc->actualcode = result; - state(data, SSH_STOP); - break; - } - - if(data->set.quote) { - infof(data, "Sending quote commands"); - sshc->quote_item = data->set.quote; - state(data, SSH_SFTP_QUOTE); - } - else { - state(data, SSH_SFTP_GETINFO); - } + result = ssh_state_sftp_quote_init(data, sshc, sshp); break; case SSH_SFTP_POSTQUOTE_INIT: - if(data->set.postquote) { - infof(data, "Sending quote commands"); - sshc->quote_item = data->set.postquote; - state(data, SSH_SFTP_QUOTE); - } - else { - state(data, SSH_STOP); - } + result = ssh_state_sftp_postquote_init(data, sshc); break; case SSH_SFTP_QUOTE: - /* Send quote commands */ - result = sftp_quote(data, sshc, sshp); - if(result) { - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - } + result = ssh_state_sftp_quote(data, sshc, sshp); break; case SSH_SFTP_NEXT_QUOTE: - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - - sshc->quote_item = sshc->quote_item->next; - - if(sshc->quote_item) { - state(data, SSH_SFTP_QUOTE); - } - else { - if(sshc->nextstate != SSH_NO_STATE) { - state(data, sshc->nextstate); - sshc->nextstate = SSH_NO_STATE; - } - else { - state(data, SSH_SFTP_GETINFO); - } - } + result = ssh_state_sftp_next_quote(data, sshc); break; case SSH_SFTP_QUOTE_STAT: - result = sftp_quote_stat(data, sshc, sshp, block); - if(result) { - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - } + result = ssh_state_sftp_quote_stat(data, sshc, sshp, block); break; case SSH_SFTP_QUOTE_SETSTAT: - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, - curlx_uztoui(strlen(sshc->quote_path2)), - LIBSSH2_SFTP_SETSTAT, - &sshp->quote_attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { - 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", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(data, SSH_SFTP_NEXT_QUOTE); + result = ssh_state_sftp_quote_setstat(data, sshc, sshp); break; case SSH_SFTP_QUOTE_SYMLINK: - rc = libssh2_sftp_symlink_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1)), - sshc->quote_path2, - curlx_uztoui(strlen(sshc->quote_path2)), - LIBSSH2_SFTP_SYMLINK); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { - 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", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(data, SSH_SFTP_NEXT_QUOTE); + result = ssh_state_sftp_quote_symlink(data, sshc); break; case SSH_SFTP_QUOTE_MKDIR: - rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1)), - (long)data->set.new_directory_perms); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { - 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)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(data, SSH_SFTP_NEXT_QUOTE); + result = ssh_state_sftp_quote_mkdir(data, sshc); break; case SSH_SFTP_QUOTE_RENAME: - rc = libssh2_sftp_rename_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1)), - sshc->quote_path2, - curlx_uztoui(strlen(sshc->quote_path2)), - LIBSSH2_SFTP_RENAME_OVERWRITE | - LIBSSH2_SFTP_RENAME_ATOMIC | - LIBSSH2_SFTP_RENAME_NATIVE); - - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { - 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", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(data, SSH_SFTP_NEXT_QUOTE); + result = ssh_state_sftp_quote_rename(data, sshc); break; case SSH_SFTP_QUOTE_RMDIR: - rc = libssh2_sftp_rmdir_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1))); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { - 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)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(data, SSH_SFTP_NEXT_QUOTE); + result = ssh_state_sftp_quote_rmdir(data, sshc); break; case SSH_SFTP_QUOTE_UNLINK: - rc = libssh2_sftp_unlink_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1))); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { - 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); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(data, SSH_SFTP_NEXT_QUOTE); + result = ssh_state_sftp_quote_unlink(data, sshc); break; case SSH_SFTP_QUOTE_STATVFS: - { - LIBSSH2_SFTP_STATVFS statvfs; - rc = libssh2_sftp_statvfs(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1)), - &statvfs); - - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc && !sshc->acceptfail) { - 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)); - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - 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: %" 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, - statvfs.f_ffree, statvfs.f_favail, - statvfs.f_fsid, statvfs.f_flag, - statvfs.f_namemax); - if(!tmp) { - result = CURLE_OUT_OF_MEMORY; - state(data, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - break; - } - - 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; - } - } - state(data, SSH_SFTP_NEXT_QUOTE); + result = ssh_state_sftp_quote_statvfs(data, sshc); break; - } case SSH_SFTP_GETINFO: - { if(data->set.get_filetime) { - state(data, SSH_SFTP_FILETIME); + myssh_state(data, sshc, SSH_SFTP_FILETIME); } else { - state(data, SSH_SFTP_TRANS_INIT); + myssh_state(data, sshc, SSH_SFTP_TRANS_INIT); } break; - } case SSH_SFTP_FILETIME: { LIBSSH2_SFTP_ATTRIBUTES attrs; - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, - curlx_uztoui(strlen(sshp->path)), - LIBSSH2_SFTP_STAT, &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) { + result = CURLE_AGAIN; break; } if(rc == 0) { data->info.filetime = (time_t)attrs.mtime; } - state(data, SSH_SFTP_TRANS_INIT); + myssh_state(data, sshc, SSH_SFTP_TRANS_INIT); break; } case SSH_SFTP_TRANS_INIT: if(data->state.upload) - state(data, SSH_SFTP_UPLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); else { if(sshp->path[strlen(sshp->path)-1] == '/') - state(data, SSH_SFTP_READDIR_INIT); + myssh_state(data, sshc, SSH_SFTP_READDIR_INIT); else - state(data, SSH_SFTP_DOWNLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_DOWNLOAD_INIT); } break; case SSH_SFTP_UPLOAD_INIT: result = sftp_upload_init(data, sshc, sshp, block); if(result) { - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; } break; case SSH_SFTP_CREATE_DIRS_INIT: if(strlen(sshp->path) > 1) { sshc->slash_pos = sshp->path + 1; /* ignore the leading '/' */ - state(data, SSH_SFTP_CREATE_DIRS); + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS); } else { - state(data, SSH_SFTP_UPLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); } break; @@ -2352,402 +2891,121 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, bool *block) *sshc->slash_pos = 0; infof(data, "Creating directory '%s'", sshp->path); - state(data, SSH_SFTP_CREATE_DIRS_MKDIR); + myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS_MKDIR); break; } - state(data, SSH_SFTP_UPLOAD_INIT); + myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); break; case SSH_SFTP_CREATE_DIRS_MKDIR: - /* 'mode' - parameter is preliminary - default to 0644 */ - rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshp->path, - curlx_uztoui(strlen(sshp->path)), - (long)data->set.new_directory_perms); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - *sshc->slash_pos = '/'; - ++sshc->slash_pos; - if(rc < 0) { - /* - * 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 - */ - 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; - break; - } - rc = 0; /* clear rc and continue */ - } - state(data, SSH_SFTP_CREATE_DIRS); + result = ssh_state_sftp_create_dirs_mkdir(data, sshc, sshp); break; case SSH_SFTP_READDIR_INIT: - Curl_pgrsSetDownloadSize(data, -1); - if(data->req.no_body) { - state(data, SSH_STOP); - break; - } - - /* - * 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); - if(!sshc->sftp_handle) { - unsigned long sftperr; - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - failf(data, "Could not open directory for reading: %s", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - result = sftp_libssh2_error_to_CURLE(sftperr); - sshc->actualcode = result ? result : CURLE_SSH; - break; - } - Curl_dyn_init(&sshp->readdir, CURL_PATH_MAX * 2); - state(data, SSH_SFTP_READDIR); + result = ssh_state_sftp_readdir_init(data, sshc, sshp); break; case SSH_SFTP_READDIR: result = sftp_readdir(data, sshc, sshp, block); if(result) { - sshc->actualcode = result; - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); } break; case SSH_SFTP_READDIR_LINK: - rc = - libssh2_sftp_symlink_ex(sshc->sftp_session, - Curl_dyn_ptr(&sshp->readdir_link), - (unsigned int) - Curl_dyn_len(&sshp->readdir_link), - sshp->readdir_filename, - CURL_PATH_MAX, LIBSSH2_SFTP_READLINK); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - Curl_dyn_free(&sshp->readdir_link); - - /* append filename and extra output */ - result = Curl_dyn_addf(&sshp->readdir, " -> %s", sshp->readdir_filename); - - if(result) { - state(data, SSH_SFTP_CLOSE); - sshc->actualcode = result; - break; - } - - state(data, SSH_SFTP_READDIR_BOTTOM); + result = ssh_state_sftp_readdir_link(data, sshc, sshp); break; case SSH_SFTP_READDIR_BOTTOM: - result = Curl_dyn_addn(&sshp->readdir, "\n", 1); + result = curlx_dyn_addn(&sshp->readdir, "\n", 1); if(!result) result = Curl_client_write(data, CLIENTWRITE_BODY, - Curl_dyn_ptr(&sshp->readdir), - Curl_dyn_len(&sshp->readdir)); + curlx_dyn_ptr(&sshp->readdir), + curlx_dyn_len(&sshp->readdir)); if(result) { - Curl_dyn_free(&sshp->readdir); - state(data, SSH_STOP); + curlx_dyn_free(&sshp->readdir); + myssh_state(data, sshc, SSH_STOP); } else { - Curl_dyn_reset(&sshp->readdir); - state(data, SSH_SFTP_READDIR); + curlx_dyn_reset(&sshp->readdir); + myssh_state(data, sshc, SSH_SFTP_READDIR); } break; case SSH_SFTP_READDIR_DONE: - if(libssh2_sftp_closedir(sshc->sftp_handle) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - sshc->sftp_handle = NULL; + if(libssh2_sftp_closedir(sshc->sftp_handle) == LIBSSH2_ERROR_EAGAIN) + result = CURLE_AGAIN; + else { + sshc->sftp_handle = NULL; - /* no data to transfer */ - Curl_xfer_setup_nop(data); - state(data, SSH_STOP); + /* no data to transfer */ + Curl_xfer_setup_nop(data); + myssh_state(data, sshc, SSH_STOP); + } break; case SSH_SFTP_DOWNLOAD_INIT: - /* - * Work on getting the specified file - */ - sshc->sftp_handle = - libssh2_sftp_open_ex(sshc->sftp_session, sshp->path, - curlx_uztoui(strlen(sshp->path)), - 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; - break; - } - sftperr = libssh2_sftp_last_error(sshc->sftp_session); - failf(data, "Could not open remote file for reading: %s", - sftp_libssh2_strerror(sftperr)); - state(data, SSH_SFTP_CLOSE); - result = sftp_libssh2_error_to_CURLE(sftperr); - sshc->actualcode = result ? result : CURLE_SSH; - break; - } - state(data, SSH_SFTP_DOWNLOAD_STAT); + result = ssh_state_sftp_download_init(data, sshc, sshp); break; case SSH_SFTP_DOWNLOAD_STAT: result = sftp_download_stat(data, sshc, sshp, block); if(result) { - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; } break; case SSH_SFTP_CLOSE: - if(sshc->sftp_handle) { - rc = libssh2_sftp_close(sshc->sftp_handle); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc < 0) { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg); - } - sshc->sftp_handle = NULL; - } - - Curl_safefree(sshp->path); - - DEBUGF(infof(data, "SFTP DONE done")); - - /* Check if nextstate is set and move .nextstate could be POSTQUOTE_INIT - After nextstate is executed, the control should come back to - SSH_SFTP_CLOSE to pass the correct result back */ - if(sshc->nextstate != SSH_NO_STATE && - sshc->nextstate != SSH_SFTP_CLOSE) { - state(data, sshc->nextstate); - sshc->nextstate = SSH_SFTP_CLOSE; - } - else { - state(data, SSH_STOP); - result = sshc->actualcode; - } + result = ssh_state_sftp_close(data, sshc, sshp); break; case SSH_SFTP_SHUTDOWN: - /* during times we get here due to a broken transfer and then the - sftp_handle might not have been taken down so make sure that is done - before we proceed */ - - if(sshc->sftp_handle) { - rc = libssh2_sftp_close(sshc->sftp_handle); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc < 0) { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, - NULL, 0); - infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg); - } - sshc->sftp_handle = NULL; - } - if(sshc->sftp_session) { - rc = libssh2_sftp_shutdown(sshc->sftp_session); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc < 0) { - infof(data, "Failed to stop libssh2 sftp subsystem"); - } - sshc->sftp_session = NULL; - } - - Curl_safefree(sshc->homedir); - data->state.most_recent_ftp_entrypath = NULL; - - state(data, SSH_SESSION_DISCONNECT); + result = ssh_state_sftp_shutdown(data, sshc); break; case SSH_SCP_TRANS_INIT: result = Curl_getworkingpath(data, sshc->homedir, &sshp->path); if(result) { - sshc->actualcode = result; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } if(data->state.upload) { if(data->state.infilesize < 0) { failf(data, "SCP requires a known file size for upload"); - sshc->actualcode = CURLE_UPLOAD_FAILED; - state(data, SSH_SCP_CHANNEL_FREE); + result = CURLE_UPLOAD_FAILED; + myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE); break; } - state(data, SSH_SCP_UPLOAD_INIT); + myssh_state(data, sshc, SSH_SCP_UPLOAD_INIT); } else { - state(data, SSH_SCP_DOWNLOAD_INIT); + myssh_state(data, sshc, SSH_SCP_DOWNLOAD_INIT); } break; 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 - * not done the destination file will be named the same name as the last - * directory in the path. - */ - sshc->ssh_channel = - libssh2_scp_send64(sshc->ssh_session, sshp->path, - (int)data->set.new_file_perms, - (libssh2_int64_t)data->state.infilesize, 0, 0); - if(!sshc->ssh_channel) { - int ssh_err; - char *err_msg = NULL; - - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - - ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0)); - failf(data, "%s", err_msg); - state(data, SSH_SCP_CHANNEL_FREE); - sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err); - /* Map generic errors to upload failed */ - if(sshc->actualcode == CURLE_SSH || - sshc->actualcode == CURLE_REMOTE_FILE_NOT_FOUND) - sshc->actualcode = CURLE_UPLOAD_FAILED; - break; - } - - /* upload data */ - data->req.size = data->state.infilesize; - Curl_pgrsSetUploadSize(data, data->state.infilesize); - Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); - - /* not set by Curl_xfer_setup to preserve keepon bits */ - conn->sockfd = conn->writesockfd; - - if(result) { - state(data, SSH_SCP_CHANNEL_FREE); - sshc->actualcode = result; - } - else { - /* 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 */ - data->state.select_bits = CURL_CSELECT_OUT; - - state(data, SSH_STOP); - } + result = ssh_state_scp_upload_init(data, sshc, sshp); break; case SSH_SCP_DOWNLOAD_INIT: - { - curl_off_t bytecount; - - /* - * We must check the remote file; if it is a directory no values will - * be set in sb - */ - - /* - * If support for >2GB files exists, use it. - */ - - /* get a fresh new channel from the ssh layer */ -#if LIBSSH2_VERSION_NUM < 0x010700 - struct stat sb; - memset(&sb, 0, sizeof(struct stat)); - sshc->ssh_channel = libssh2_scp_recv(sshc->ssh_session, - sshp->path, &sb); -#else - libssh2_struct_stat sb; - memset(&sb, 0, sizeof(libssh2_struct_stat)); - sshc->ssh_channel = libssh2_scp_recv2(sshc->ssh_session, - sshp->path, &sb); -#endif - - if(!sshc->ssh_channel) { - int ssh_err; - char *err_msg = NULL; - - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - - - ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0)); - failf(data, "%s", err_msg); - state(data, SSH_SCP_CHANNEL_FREE); - sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err); - break; - } - - /* download data */ - bytecount = (curl_off_t)sb.st_size; - data->req.maxdownload = (curl_off_t)sb.st_size; - Curl_xfer_setup1(data, CURL_XFER_RECV, bytecount, FALSE); - - /* 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 */ - data->state.select_bits = CURL_CSELECT_IN; - - if(result) { - state(data, SSH_SCP_CHANNEL_FREE); - sshc->actualcode = result; - } - else - state(data, SSH_STOP); - } - break; + result = ssh_state_scp_download_init(data, sshc, sshp); + break; case SSH_SCP_DONE: if(data->state.upload) - state(data, SSH_SCP_SEND_EOF); + myssh_state(data, sshc, SSH_SCP_SEND_EOF); else - state(data, SSH_SCP_CHANNEL_FREE); + myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE); break; case SSH_SCP_SEND_EOF: if(sshc->ssh_channel) { - rc = libssh2_channel_send_eof(sshc->ssh_channel); + int rc = libssh2_channel_send_eof(sshc->ssh_channel); if(rc == LIBSSH2_ERROR_EAGAIN) { + result = CURLE_AGAIN; break; } if(rc) { @@ -2758,13 +3016,14 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, bool *block) rc, err_msg); } } - state(data, SSH_SCP_WAIT_EOF); + myssh_state(data, sshc, SSH_SCP_WAIT_EOF); break; case SSH_SCP_WAIT_EOF: if(sshc->ssh_channel) { - rc = libssh2_channel_wait_eof(sshc->ssh_channel); + int rc = libssh2_channel_wait_eof(sshc->ssh_channel); if(rc == LIBSSH2_ERROR_EAGAIN) { + result = CURLE_AGAIN; break; } if(rc) { @@ -2774,13 +3033,14 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, bool *block) infof(data, "Failed to get channel EOF: %d %s", rc, err_msg); } } - state(data, SSH_SCP_WAIT_CLOSE); + myssh_state(data, sshc, SSH_SCP_WAIT_CLOSE); break; case SSH_SCP_WAIT_CLOSE: if(sshc->ssh_channel) { - rc = libssh2_channel_wait_closed(sshc->ssh_channel); + int rc = libssh2_channel_wait_closed(sshc->ssh_channel); if(rc == LIBSSH2_ERROR_EAGAIN) { + result = CURLE_AGAIN; break; } if(rc) { @@ -2790,13 +3050,14 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, bool *block) infof(data, "Channel failed to close: %d %s", rc, err_msg); } } - state(data, SSH_SCP_CHANNEL_FREE); + myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE); break; case SSH_SCP_CHANNEL_FREE: if(sshc->ssh_channel) { - rc = libssh2_channel_free(sshc->ssh_channel); + int rc = libssh2_channel_free(sshc->ssh_channel); if(rc == LIBSSH2_ERROR_EAGAIN) { + result = CURLE_AGAIN; break; } if(rc < 0) { @@ -2809,133 +3070,38 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, bool *block) sshc->ssh_channel = NULL; } DEBUGF(infof(data, "SCP DONE phase complete")); -#if 0 /* PREV */ - state(data, SSH_SESSION_DISCONNECT); -#endif - state(data, SSH_STOP); - result = sshc->actualcode; + myssh_state(data, sshc, SSH_STOP); break; case SSH_SESSION_DISCONNECT: - /* 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) { - rc = libssh2_channel_free(sshc->ssh_channel); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc < 0) { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - infof(data, "Failed to free libssh2 scp subsystem: %d %s", - rc, err_msg); - } - sshc->ssh_channel = NULL; - } - - if(sshc->ssh_session) { - rc = libssh2_session_disconnect(sshc->ssh_session, "Shutdown"); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc < 0) { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - infof(data, "Failed to disconnect libssh2 session: %d %s", - rc, err_msg); - } - } - - Curl_safefree(sshc->homedir); - data->state.most_recent_ftp_entrypath = NULL; - - state(data, SSH_SESSION_FREE); + result = ssh_state_session_disconnect(data, sshc); break; case SSH_SESSION_FREE: - if(sshc->kh) { - libssh2_knownhost_free(sshc->kh); - sshc->kh = NULL; - } - - if(sshc->ssh_agent) { - rc = libssh2_agent_disconnect(sshc->ssh_agent); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc < 0) { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - infof(data, "Failed to disconnect from libssh2 agent: %d %s", - rc, err_msg); - } - libssh2_agent_free(sshc->ssh_agent); - sshc->ssh_agent = NULL; - - /* NB: there is no need to free identities, they are part of internal - agent stuff */ - sshc->sshagent_identity = NULL; - sshc->sshagent_prev_identity = NULL; - } - - if(sshc->ssh_session) { - rc = libssh2_session_free(sshc->ssh_session); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - if(rc < 0) { - char *err_msg = NULL; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - infof(data, "Failed to free libssh2 session: %d %s", rc, err_msg); - } - sshc->ssh_session = NULL; - } - - /* worst-case scenario cleanup */ - - DEBUGASSERT(sshc->ssh_session == NULL); - DEBUGASSERT(sshc->ssh_channel == NULL); - DEBUGASSERT(sshc->sftp_session == NULL); - DEBUGASSERT(sshc->sftp_handle == NULL); - DEBUGASSERT(sshc->kh == NULL); - DEBUGASSERT(sshc->ssh_agent == NULL); - - Curl_safefree(sshc->rsa_pub); - Curl_safefree(sshc->rsa); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - Curl_safefree(sshc->homedir); - + result = sshc_cleanup(sshc, data, FALSE); + if(result) + break; /* the code we are about to return */ - result = sshc->actualcode; - memset(sshc, 0, sizeof(struct ssh_conn)); - connclose(conn, "SSH session free"); sshc->state = SSH_SESSION_FREE; /* current */ - sshc->nextstate = SSH_NO_STATE; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; case SSH_QUIT: default: /* internal error */ - sshc->nextstate = SSH_NO_STATE; - state(data, SSH_STOP); + myssh_state(data, sshc, SSH_STOP); break; } - } while(!rc && (sshc->state != SSH_STOP)); + } while(!result && (sshc->state != SSH_STOP)); - if(rc == LIBSSH2_ERROR_EAGAIN) { + if(result == CURLE_AGAIN) { /* we would block, we need to wait for the socket to be ready (in the right direction too)! */ *block = TRUE; + result = CURLE_OK; } return result; @@ -2968,10 +3134,11 @@ static int ssh_getsock(struct Curl_easy *data, * 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) +static void ssh_block2waitfor(struct Curl_easy *data, + struct ssh_conn *sshc, + bool block) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; int dir = 0; if(block) { dir = libssh2_session_block_directions(sshc->ssh_session); @@ -2991,35 +3158,39 @@ static void ssh_block2waitfor(struct Curl_easy *data, bool block) static CURLcode ssh_multi_statemach(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); CURLcode result = CURLE_OK; bool block; /* we store the status and use that to provide a ssh_getsock() implementation */ + if(!sshc || !sshp) + return CURLE_FAILED_INIT; + do { - result = ssh_statemachine(data, &block); + result = ssh_statemachine(data, sshc, sshp, &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); + ssh_block2waitfor(data, sshc, block); return result; } static CURLcode ssh_block_statemach(struct Curl_easy *data, - struct connectdata *conn, + struct ssh_conn *sshc, + struct SSHPROTO *sshp, bool disconnect) { - struct ssh_conn *sshc = &conn->proto.sshc; CURLcode result = CURLE_OK; - struct curltime dis = Curl_now(); + struct curltime dis = curlx_now(); while((sshc->state != SSH_STOP) && !result) { bool block; timediff_t left = 1000; - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); - result = ssh_statemachine(data, &block); + result = ssh_statemachine(data, sshc, sshp, &block); if(result) break; @@ -3037,7 +3208,7 @@ static CURLcode ssh_block_statemach(struct Curl_easy *data, return CURLE_OPERATION_TIMEDOUT; } } - else if(Curl_timediff(now, dis) > 1000) { + else if(curlx_timediff(now, dis) > 1000) { /* disconnect timeout */ failf(data, "Disconnect timed out"); result = CURLE_OK; @@ -3046,7 +3217,7 @@ static CURLcode ssh_block_statemach(struct Curl_easy *data, if(block) { int dir = libssh2_session_block_directions(sshc->ssh_session); - curl_socket_t sock = conn->sock[FIRSTSOCKET]; + curl_socket_t sock = data->conn->sock[FIRSTSOCKET]; curl_socket_t fd_read = CURL_SOCKET_BAD; curl_socket_t fd_write = CURL_SOCKET_BAD; if(LIBSSH2_SESSION_BLOCK_INBOUND & dir) @@ -3062,17 +3233,51 @@ static CURLcode ssh_block_statemach(struct Curl_easy *data, return result; } +static void myssh_easy_dtor(void *key, size_t klen, void *entry) +{ + struct SSHPROTO *sshp = entry; + (void)key; + (void)klen; + Curl_safefree(sshp->path); + curlx_dyn_free(&sshp->readdir); + curlx_dyn_free(&sshp->readdir_link); + free(sshp); +} + +static void myssh_conn_dtor(void *key, size_t klen, void *entry) +{ + struct ssh_conn *sshc = entry; + (void)key; + (void)klen; + sshc_cleanup(sshc, NULL, TRUE); + free(sshc); +} + /* * SSH setup and connection */ static CURLcode ssh_setup_connection(struct Curl_easy *data, struct connectdata *conn) { - struct SSHPROTO *ssh; + struct ssh_conn *sshc; + struct SSHPROTO *sshp; (void)conn; - data->req.p.ssh = ssh = calloc(1, sizeof(struct SSHPROTO)); - if(!ssh) + sshc = calloc(1, sizeof(*sshc)); + if(!sshc) + return CURLE_OUT_OF_MEMORY; + + sshc->initialised = TRUE; + if(Curl_conn_meta_set(conn, CURL_META_SSH_CONN, sshc, myssh_conn_dtor)) + return CURLE_OUT_OF_MEMORY; + + sshp = calloc(1, sizeof(*sshp)); + if(!sshp) + return CURLE_OUT_OF_MEMORY; + + curlx_dyn_init(&sshp->readdir, CURL_PATH_MAX * 2); + curlx_dyn_init(&sshp->readdir_link, CURL_PATH_MAX); + if(Curl_meta_set(data, CURL_META_SSH_EASY, sshp, myssh_easy_dtor)) return CURLE_OUT_OF_MEMORY; return CURLE_OK; @@ -3090,20 +3295,23 @@ static ssize_t ssh_tls_recv(libssh2_socket_t sock, void *buffer, CURLcode result; struct connectdata *conn = data->conn; Curl_recv *backup = conn->recv[0]; - struct ssh_conn *ssh = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); int socknum = Curl_conn_sockindex(data, sock); (void)flags; + if(!sshc) + return -1; + /* swap in the TLS reader function for this call only, and then swap back the SSH one again */ - conn->recv[0] = ssh->tls_recv; + conn->recv[0] = sshc->tls_recv; result = Curl_conn_recv(data, socknum, buffer, length, &nread); conn->recv[0] = backup; if(result == CURLE_AGAIN) return -EAGAIN; /* magic return code for libssh2 */ else if(result) return -1; /* generic error */ - Curl_debug(data, CURLINFO_DATA_IN, (char *)buffer, (size_t)nread); + Curl_debug(data, CURLINFO_DATA_IN, (const char *)buffer, (size_t)nread); return nread; } @@ -3115,20 +3323,23 @@ static ssize_t ssh_tls_send(libssh2_socket_t sock, const void *buffer, CURLcode result; struct connectdata *conn = data->conn; Curl_send *backup = conn->send[0]; - struct ssh_conn *ssh = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); int socknum = Curl_conn_sockindex(data, sock); (void)flags; + if(!sshc) + return -1; + /* swap in the TLS writer function for this call only, and then swap back the SSH one again */ - conn->send[0] = ssh->tls_send; + conn->send[0] = sshc->tls_send; 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, nwrite); + Curl_debug(data, CURLINFO_DATA_OUT, (const char *)buffer, nwrite); return (ssize_t)nwrite; } #endif @@ -3142,27 +3353,50 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) #ifdef CURL_LIBSSH2_DEBUG curl_socket_t sock; #endif - struct ssh_conn *sshc; - CURLcode result; struct connectdata *conn = data->conn; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + CURLcode result; - /* initialize per-handle data if not already */ - if(!data->req.p.ssh) { - result = ssh_setup_connection(data, conn); - if(result) - return result; +#if LIBSSH2_VERSION_NUM >= 0x010b00 + { + const char *crypto_str; + switch(libssh2_crypto_engine()) { + case libssh2_gcrypt: + crypto_str = "libgcrypt"; + break; + case libssh2_mbedtls: + crypto_str = "mbedTLS"; + break; + case libssh2_openssl: + crypto_str = "openssl compatible"; + break; + case libssh2_os400qc3: + crypto_str = "OS400QC3"; + break; + case libssh2_wincng: + crypto_str = "WinCNG"; + break; + default: + crypto_str = NULL; + break; + } + if(crypto_str) + infof(data, "libssh2 cryptography backend: %s", crypto_str); } +#endif + + if(!sshc) + return CURLE_FAILED_INIT; /* We default to persistent connections. We set this already in this connect function to make the reuse checks properly be able to check this bit. */ connkeep(conn, "SSH default"); - sshc = &conn->proto.sshc; - + if(conn->user) + infof(data, "User: '%s'", conn->user); + else + infof(data, "User: NULL"); #ifdef CURL_LIBSSH2_DEBUG - if(conn->user) { - infof(data, "User: %s", conn->user); - } if(conn->passwd) { infof(data, "Password: %s", conn->passwd); } @@ -3281,7 +3515,7 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) infof(data, "SSH socket: %d", (int)sock); #endif /* CURL_LIBSSH2_DEBUG */ - state(data, SSH_INIT); + myssh_state(data, sshc, SSH_INIT); result = ssh_multi_statemach(data, done); @@ -3302,14 +3536,17 @@ CURLcode scp_perform(struct Curl_easy *data, bool *connected, bool *dophase_done) { + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); CURLcode result = CURLE_OK; DEBUGF(infof(data, "DO phase starts")); *dophase_done = FALSE; /* not done yet */ + if(!sshc) + return CURLE_FAILED_INIT; /* start the first command in the DO phase */ - state(data, SSH_SCP_TRANS_INIT); + myssh_state(data, sshc, SSH_SCP_TRANS_INIT); /* run the state-machine */ result = ssh_multi_statemach(data, dophase_done); @@ -3346,13 +3583,13 @@ static CURLcode ssh_do(struct Curl_easy *data, bool *done) CURLcode result; bool connected = FALSE; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); *done = FALSE; /* default to false */ + if(!sshc) + return CURLE_FAILED_INIT; data->req.size = -1; /* make sure this is unknown at this point */ - - sshc->actualcode = CURLE_OK; /* reset error code */ sshc->secondCreateDirs = 0; /* reset the create dir attempt state variable */ @@ -3369,6 +3606,110 @@ static CURLcode ssh_do(struct Curl_easy *data, bool *done) return result; } +static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data, + bool block) +{ + int rc; + + if(sshc->initialised) { + if(sshc->kh) { + libssh2_knownhost_free(sshc->kh); + sshc->kh = NULL; + } + + if(sshc->ssh_agent) { + rc = libssh2_agent_disconnect(sshc->ssh_agent); + if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) + return CURLE_AGAIN; + + if((rc < 0) && data) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to disconnect from libssh2 agent: %d %s", + rc, err_msg); + } + libssh2_agent_free(sshc->ssh_agent); + sshc->ssh_agent = NULL; + + /* NB: there is no need to free identities, they are part of internal + agent stuff */ + sshc->sshagent_identity = NULL; + sshc->sshagent_prev_identity = NULL; + } + + if(sshc->sftp_handle) { + rc = libssh2_sftp_close(sshc->sftp_handle); + if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) + return CURLE_AGAIN; + + if((rc < 0) && data) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, + NULL, 0); + infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg); + } + sshc->sftp_handle = NULL; + } + + if(sshc->ssh_channel) { + rc = libssh2_channel_free(sshc->ssh_channel); + if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) + return CURLE_AGAIN; + + if((rc < 0) && data) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to free libssh2 scp subsystem: %d %s", + rc, err_msg); + } + sshc->ssh_channel = NULL; + } + + if(sshc->sftp_session) { + rc = libssh2_sftp_shutdown(sshc->sftp_session); + if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) + return CURLE_AGAIN; + + if((rc < 0) && data) + infof(data, "Failed to stop libssh2 sftp subsystem"); + sshc->sftp_session = NULL; + } + + if(sshc->ssh_session) { + rc = libssh2_session_free(sshc->ssh_session); + if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) + return CURLE_AGAIN; + + if((rc < 0) && data) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to free libssh2 session: %d %s", rc, err_msg); + } + sshc->ssh_session = NULL; + } + + /* worst-case scenario cleanup */ + DEBUGASSERT(sshc->ssh_session == NULL); + DEBUGASSERT(sshc->ssh_channel == NULL); + DEBUGASSERT(sshc->sftp_session == NULL); + DEBUGASSERT(sshc->sftp_handle == NULL); + DEBUGASSERT(sshc->kh == NULL); + DEBUGASSERT(sshc->ssh_agent == NULL); + + Curl_safefree(sshc->rsa_pub); + Curl_safefree(sshc->rsa); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + Curl_safefree(sshc->homedir); + sshc->initialised = FALSE; + } + return CURLE_OK; +} + + /* BLOCKING, but the function is using the state machine so the only reason this is still blocking is that the multi interface code has no support for disconnecting operations that takes a while */ @@ -3377,15 +3718,18 @@ static CURLcode scp_disconnect(struct Curl_easy *data, bool dead_connection) { CURLcode result = CURLE_OK; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); (void) dead_connection; - if(sshc->ssh_session) { + if(sshc && sshc->ssh_session && sshp) { /* only if there is a session still around to use! */ - state(data, SSH_SESSION_DISCONNECT); - result = ssh_block_statemach(data, conn, TRUE); + myssh_state(data, sshc, SSH_SESSION_DISCONNECT); + result = ssh_block_statemach(data, sshc, sshp, TRUE); } + if(sshc) + return sshc_cleanup(sshc, data, TRUE); return result; } @@ -3393,19 +3737,19 @@ static CURLcode scp_disconnect(struct Curl_easy *data, done functions */ static CURLcode ssh_done(struct Curl_easy *data, CURLcode status) { + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); CURLcode result = CURLE_OK; - struct SSHPROTO *sshp = data->req.p.ssh; - struct connectdata *conn = data->conn; + + if(!sshc || !sshp) + return CURLE_FAILED_INIT; if(!status) /* run the state-machine */ - result = ssh_block_statemach(data, conn, FALSE); + result = ssh_block_statemach(data, sshc, sshp, FALSE); else result = status; - Curl_safefree(sshp->path); - Curl_dyn_free(&sshp->readdir); - if(Curl_pgrsDone(data)) return CURLE_ABORTED_BY_CALLBACK; @@ -3417,13 +3761,13 @@ static CURLcode ssh_done(struct Curl_easy *data, CURLcode status) static CURLcode scp_done(struct Curl_easy *data, CURLcode status, bool premature) { + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); (void)premature; /* not used */ - if(!status) - state(data, SSH_SCP_DONE); + if(sshc && !status) + myssh_state(data, sshc, SSH_SCP_DONE); return ssh_done(data, status); - } static ssize_t scp_send(struct Curl_easy *data, int sockindex, @@ -3431,14 +3775,18 @@ static ssize_t scp_send(struct Curl_easy *data, int sockindex, { ssize_t nwrite; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void)sockindex; /* we only support SCP on the fixed known primary socket */ (void)eos; + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } /* libssh2_channel_write() returns int! */ nwrite = (ssize_t) libssh2_channel_write(sshc->ssh_channel, mem, len); - ssh_block2waitfor(data, (nwrite == LIBSSH2_ERROR_EAGAIN)); + ssh_block2waitfor(data, sshc, (nwrite == LIBSSH2_ERROR_EAGAIN)); if(nwrite == LIBSSH2_ERROR_EAGAIN) { *err = CURLE_AGAIN; @@ -3457,13 +3805,17 @@ static ssize_t scp_recv(struct Curl_easy *data, int sockindex, { ssize_t nread; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void)sockindex; /* we only support SCP on the fixed known primary socket */ + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } /* libssh2_channel_read() returns int */ nread = (ssize_t) libssh2_channel_read(sshc->ssh_channel, mem, len); - ssh_block2waitfor(data, (nread == LIBSSH2_ERROR_EAGAIN)); + ssh_block2waitfor(data, sshc, (nread == LIBSSH2_ERROR_EAGAIN)); if(nread == LIBSSH2_ERROR_EAGAIN) { *err = CURLE_AGAIN; nread = -1; @@ -3490,14 +3842,17 @@ CURLcode sftp_perform(struct Curl_easy *data, bool *connected, bool *dophase_done) { + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); CURLcode result = CURLE_OK; DEBUGF(infof(data, "DO phase starts")); *dophase_done = FALSE; /* not done yet */ + if(!sshc) + return CURLE_FAILED_INIT; /* start the first command in the DO phase */ - state(data, SSH_SFTP_QUOTE_INIT); + myssh_state(data, sshc, SSH_SFTP_QUOTE_INIT); /* run the state-machine */ result = ssh_multi_statemach(data, dophase_done); @@ -3530,18 +3885,21 @@ static CURLcode sftp_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { CURLcode result = CURLE_OK; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); (void) dead_connection; DEBUGF(infof(data, "SSH DISCONNECT starts now")); - if(sshc->ssh_session) { + if(sshc && sshc->ssh_session && sshp) { /* only if there is a session still around to use! */ - state(data, SSH_SFTP_SHUTDOWN); - result = ssh_block_statemach(data, conn, TRUE); + myssh_state(data, sshc, SSH_SFTP_SHUTDOWN); + result = ssh_block_statemach(data, sshc, sshp, TRUE); } DEBUGF(infof(data, "SSH DISCONNECT is done")); + if(sshc) + sshc_cleanup(sshc, data, TRUE); return result; @@ -3551,7 +3909,10 @@ static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, bool premature) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + + if(!sshc) + return CURLE_FAILED_INIT; if(!status) { /* Post quote commands are executed after the SFTP_CLOSE state to avoid @@ -3559,7 +3920,7 @@ static CURLcode sftp_done(struct Curl_easy *data, CURLcode status, operation */ if(!premature && data->set.postquote && !conn->bits.retry) sshc->nextstate = SSH_SFTP_POSTQUOTE_INIT; - state(data, SSH_SFTP_CLOSE); + myssh_state(data, sshc, SSH_SFTP_CLOSE); } return ssh_done(data, status); } @@ -3570,13 +3931,17 @@ static ssize_t sftp_send(struct Curl_easy *data, int sockindex, { ssize_t nwrite; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void)sockindex; (void)eos; + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } nwrite = libssh2_sftp_write(sshc->sftp_handle, mem, len); - ssh_block2waitfor(data, (nwrite == LIBSSH2_ERROR_EAGAIN)); + ssh_block2waitfor(data, sshc, (nwrite == LIBSSH2_ERROR_EAGAIN)); if(nwrite == LIBSSH2_ERROR_EAGAIN) { *err = CURLE_AGAIN; @@ -3599,12 +3964,16 @@ static ssize_t sftp_recv(struct Curl_easy *data, int sockindex, { ssize_t nread; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); (void)sockindex; + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } nread = libssh2_sftp_read(sshc->sftp_handle, mem, len); - ssh_block2waitfor(data, (nread == LIBSSH2_ERROR_EAGAIN)); + ssh_block2waitfor(data, sshc, (nread == LIBSSH2_ERROR_EAGAIN)); if(nread == LIBSSH2_ERROR_EAGAIN) { *err = CURLE_AGAIN; @@ -3711,8 +4080,8 @@ static void ssh_attach(struct Curl_easy *data, struct connectdata *conn) DEBUGASSERT(data); DEBUGASSERT(conn); if(conn->handler->protocol & PROTO_FAMILY_SSH) { - struct ssh_conn *sshc = &conn->proto.sshc; - if(sshc->ssh_session) { + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + if(sshc && sshc->ssh_session) { /* only re-attach if the session already exists */ void **abstract = libssh2_session_abstract(sshc->ssh_session); *abstract = data; diff --git a/Utilities/cmcurl/lib/vssh/ssh.h b/Utilities/cmcurl/lib/vssh/ssh.h index bbbe95d7de..feee886562 100644 --- a/Utilities/cmcurl/lib/vssh/ssh.h +++ b/Utilities/cmcurl/lib/vssh/ssh.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_LIBSSH2) #include @@ -41,6 +41,11 @@ #include "curl_path.h" +/* meta key for storing protocol meta at easy handle */ +#define CURL_META_SSH_EASY "meta:proto:ssh:easy" +/* meta key for storing protocol meta at connection */ +#define CURL_META_SSH_CONN "meta:proto:ssh:conn" + /**************************************************************************** * SSH unique setup ***************************************************************************/ @@ -141,12 +146,8 @@ struct ssh_conn { const char *passphrase; /* pass-phrase to use */ char *rsa_pub; /* strdup'ed public key file */ char *rsa; /* strdup'ed private key file */ - bool authed; /* the connection has been authenticated fine */ - bool acceptfail; /* used by the SFTP_QUOTE (continue if - quote command fails) */ sshstate state; /* always use ssh.c:state() to change state! */ sshstate nextstate; /* the state to goto after stopping */ - CURLcode actualcode; /* the actual error code */ struct curl_slist *quote_item; /* for the quote option */ char *quote_path1; /* two generic pointers for the QUOTE stuff */ char *quote_path2; @@ -162,6 +163,7 @@ struct ssh_conn { char *slash_pos; /* used by the SFTP_CREATE_DIRS state */ #if defined(USE_LIBSSH) + CURLcode actualcode; /* the actual error code */ char *readdir_linkPath; size_t readdir_len; struct dynbuf readdir_buf; @@ -206,12 +208,17 @@ struct ssh_conn { struct libssh2_agent_publickey *sshagent_prev_identity; LIBSSH2_KNOWNHOSTS *kh; #elif defined(USE_WOLFSSH) + CURLcode actualcode; /* the actual error code */ WOLFSSH *ssh_session; WOLFSSH_CTX *ctx; word32 handleSz; byte handle[WOLFSSH_MAX_HANDLE]; curl_off_t offset; #endif /* USE_LIBSSH */ + BIT(initialised); + BIT(authed); /* the connection has been authenticated fine */ + BIT(acceptfail); /* used by the SFTP_QUOTE (continue if + quote command fails) */ }; #ifdef USE_LIBSSH diff --git a/Utilities/cmcurl/lib/vssh/wolfssh.c b/Utilities/cmcurl/lib/vssh/wolfssh.c index e78a18e71a..5097ca02c5 100644 --- a/Utilities/cmcurl/lib/vssh/wolfssh.c +++ b/Utilities/cmcurl/lib/vssh/wolfssh.c @@ -22,30 +22,30 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_WOLFSSH #include -#include "urldata.h" -#include "cfilters.h" -#include "connect.h" -#include "sendf.h" -#include "progress.h" +#include "../urldata.h" +#include "../url.h" +#include "../cfilters.h" +#include "../connect.h" +#include "../sendf.h" +#include "../progress.h" #include "curl_path.h" -#include "strtoofft.h" -#include "transfer.h" -#include "speedcheck.h" -#include "select.h" -#include "multiif.h" -#include "warnless.h" -#include "strdup.h" +#include "../transfer.h" +#include "../speedcheck.h" +#include "../select.h" +#include "../multiif.h" +#include "../curlx/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" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" static CURLcode wssh_connect(struct Curl_easy *data, bool *done); static CURLcode wssh_multi_statemach(struct Curl_easy *data, bool *done); @@ -71,6 +71,7 @@ static int wssh_getsock(struct Curl_easy *data, curl_socket_t *sock); static CURLcode wssh_setup_connection(struct Curl_easy *data, struct connectdata *conn); +static void wssh_sshc_cleanup(struct ssh_conn *sshc); #if 0 /* @@ -138,10 +139,10 @@ const struct Curl_handler Curl_handler_sftp = { * SSH State machine related code */ /* This is the ONLY way to change SSH state! */ -static void state(struct Curl_easy *data, sshstate nowstate) +static void wssh_state(struct Curl_easy *data, + struct ssh_conn *sshc, + sshstate nowstate) { - struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char * const names[] = { @@ -208,14 +209,14 @@ static void state(struct Curl_easy *data, sshstate nowstate) }; /* a precaution to make sure the lists are in sync */ - DEBUGASSERT(sizeof(names)/sizeof(names[0]) == SSH_LAST); + DEBUGASSERT(CURL_ARRAYSIZE(names) == SSH_LAST); if(sshc->state != nowstate) { infof(data, "wolfssh %p state change from %s to %s", (void *)sshc, names[sshc->state], names[nowstate]); } #endif - + (void)data; sshc->state = nowstate; } @@ -252,19 +253,23 @@ static ssize_t wsftp_send(struct Curl_easy *data, int sockindex, const void *mem, size_t len, bool eos, CURLcode *err) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); word32 offset[2]; int rc; (void)sockindex; (void)eos; + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } offset[0] = (word32)sshc->offset & 0xFFFFFFFF; offset[1] = (word32)(sshc->offset >> 32) & 0xFFFFFFFF; rc = wolfSSH_SFTP_SendWritePacket(sshc->ssh_session, sshc->handle, sshc->handleSz, &offset[0], - (byte *)mem, (word32)len); + (byte *)CURL_UNCONST(mem), (word32)len); if(rc == WS_FATAL_ERROR) rc = wolfSSH_get_error(sshc->ssh_session); @@ -298,10 +303,14 @@ static ssize_t wsftp_recv(struct Curl_easy *data, int sockindex, { int rc; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); word32 offset[2]; (void)sockindex; + if(!sshc) { + *err = CURLE_FAILED_INIT; + return -1; + } offset[0] = (word32)sshc->offset & 0xFFFFFFFF; offset[1] = (word32)(sshc->offset >> 32) & 0xFFFFFFFF; @@ -333,17 +342,45 @@ static ssize_t wsftp_recv(struct Curl_easy *data, int sockindex, return (ssize_t)rc; } +static void wssh_easy_dtor(void *key, size_t klen, void *entry) +{ + struct SSHPROTO *sshp = entry; + (void)key; + (void)klen; + Curl_safefree(sshp->path); + free(sshp); +} + +static void wssh_conn_dtor(void *key, size_t klen, void *entry) +{ + struct ssh_conn *sshc = entry; + (void)key; + (void)klen; + wssh_sshc_cleanup(sshc); + free(sshc); +} + /* * SSH setup and connection */ static CURLcode wssh_setup_connection(struct Curl_easy *data, struct connectdata *conn) { - struct SSHPROTO *ssh; + struct ssh_conn *sshc; + struct SSHPROTO *sshp; (void)conn; - data->req.p.ssh = ssh = calloc(1, sizeof(struct SSHPROTO)); - if(!ssh) + sshc = calloc(1, sizeof(*sshc)); + if(!sshc) + return CURLE_OUT_OF_MEMORY; + + sshc->initialised = TRUE; + if(Curl_conn_meta_set(conn, CURL_META_SSH_CONN, sshc, wssh_conn_dtor)) + return CURLE_OUT_OF_MEMORY; + + sshp = calloc(1, sizeof(*sshp)); + if(!sshp || + Curl_meta_set(data, CURL_META_SSH_EASY, sshp, wssh_easy_dtor)) return CURLE_OUT_OF_MEMORY; return CURLE_OK; @@ -368,13 +405,13 @@ static int userauth(byte authtype, static CURLcode wssh_connect(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); + struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); curl_socket_t sock = conn->sock[FIRSTSOCKET]; int rc; - /* initialize per-handle data if not already */ - if(!data->req.p.ssh) - wssh_setup_connection(data, conn); + if(!sshc || !sshp) + return CURLE_FAILED_INIT; /* We default to persistent connections. We set this already in this connect function to make the reuse checks properly be able to check this bit. */ @@ -388,7 +425,6 @@ static CURLcode wssh_connect(struct Curl_easy *data, bool *done) conn->recv[FIRSTSOCKET] = wsftp_recv; conn->send[FIRSTSOCKET] = wsftp_send; } - sshc = &conn->proto.sshc; sshc->ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); if(!sshc->ctx) { failf(data, "No wolfSSH context"); @@ -423,14 +459,13 @@ static CURLcode wssh_connect(struct Curl_easy *data, bool *done) *done = TRUE; if(conn->handler->protocol & CURLPROTO_SCP) - state(data, SSH_INIT); + wssh_state(data, sshc, SSH_INIT); else - state(data, SSH_SFTP_INIT); + wssh_state(data, sshc, SSH_SFTP_INIT); return wssh_multi_statemach(data, done); error: - wolfSSH_free(sshc->ssh_session); - wolfSSH_CTX_free(sshc->ctx); + wssh_sshc_cleanup(sshc); return CURLE_FAILED_INIT; } @@ -441,20 +476,24 @@ error: * wants to be called again when the socket is ready */ -static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) +static CURLcode wssh_statemach_act(struct Curl_easy *data, + struct ssh_conn *sshc, + bool *block) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; - struct SSHPROTO *sftp_scp = data->req.p.ssh; + struct SSHPROTO *sftp_scp = Curl_meta_get(data, CURL_META_SSH_EASY); WS_SFTPNAME *name; int rc = 0; *block = FALSE; /* we are not blocking by default */ + if(!sftp_scp) + return CURLE_FAILED_INIT; + do { switch(sshc->state) { case SSH_INIT: - state(data, SSH_S_STARTUP); + wssh_state(data, sshc, SSH_S_STARTUP); break; case SSH_S_STARTUP: @@ -472,11 +511,11 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) return CURLE_OK; } else if(rc != WS_SUCCESS) { - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); return CURLE_SSH; } infof(data, "wolfssh connected"); - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); break; case SSH_STOP: break; @@ -497,7 +536,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) } else if(rc == WS_SUCCESS) { infof(data, "wolfssh SFTP connected"); - state(data, SSH_SFTP_REALPATH); + wssh_state(data, sshc, SSH_SFTP_REALPATH); } else { failf(data, "wolfssh SFTP connect error %d", rc); @@ -505,7 +544,8 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) } break; case SSH_SFTP_REALPATH: - name = wolfSSH_SFTP_RealPath(sshc->ssh_session, (char *)"."); + name = wolfSSH_SFTP_RealPath(sshc->ssh_session, + (char *)CURL_UNCONST(".")); rc = wolfSSH_get_error(sshc->ssh_session); if(rc == WS_WANT_READ) { *block = TRUE; @@ -522,7 +562,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) if(!sshc->homedir) sshc->actualcode = CURLE_OUT_OF_MEMORY; wolfSSH_SFTPNAME_list_free(name); - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); return CURLE_OK; } failf(data, "wolfssh SFTP realpath %d", rc); @@ -532,35 +572,35 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) result = Curl_getworkingpath(data, sshc->homedir, &sftp_scp->path); if(result) { sshc->actualcode = result; - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); break; } if(data->set.quote) { infof(data, "Sending quote commands"); sshc->quote_item = data->set.quote; - state(data, SSH_SFTP_QUOTE); + wssh_state(data, sshc, SSH_SFTP_QUOTE); } else { - state(data, SSH_SFTP_GETINFO); + wssh_state(data, sshc, SSH_SFTP_GETINFO); } break; case SSH_SFTP_GETINFO: if(data->set.get_filetime) { - state(data, SSH_SFTP_FILETIME); + wssh_state(data, sshc, SSH_SFTP_FILETIME); } else { - state(data, SSH_SFTP_TRANS_INIT); + wssh_state(data, sshc, SSH_SFTP_TRANS_INIT); } break; case SSH_SFTP_TRANS_INIT: if(data->state.upload) - state(data, SSH_SFTP_UPLOAD_INIT); + wssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); else { if(sftp_scp->path[strlen(sftp_scp->path)-1] == '/') - state(data, SSH_SFTP_READDIR_INIT); + wssh_state(data, sshc, SSH_SFTP_READDIR_INIT); else - state(data, SSH_SFTP_DOWNLOAD_INIT); + wssh_state(data, sshc, SSH_SFTP_DOWNLOAD_INIT); } break; case SSH_SFTP_UPLOAD_INIT: { @@ -623,7 +663,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) failf(data, "wolfssh SFTP upload open failed: %d", rc); return CURLE_SSH; } - state(data, SSH_SFTP_DOWNLOAD_STAT); + wssh_state(data, sshc, SSH_SFTP_DOWNLOAD_STAT); /* If we have a restart point then we need to seek to the correct position. */ @@ -689,7 +729,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) conn->sockfd = conn->writesockfd; if(result) { - state(data, SSH_SFTP_CLOSE); + wssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->actualcode = result; } else { @@ -707,7 +747,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) timeout here */ Curl_expire(data, 0, EXPIRE_RUN_NOW); - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); } break; } @@ -730,7 +770,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) } else if(rc == WS_SUCCESS) { infof(data, "wolfssh SFTP open succeeded"); - state(data, SSH_SFTP_DOWNLOAD_STAT); + wssh_state(data, sshc, SSH_SFTP_DOWNLOAD_STAT); return CURLE_OK; } @@ -785,7 +825,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) /* no data to transfer */ Curl_xfer_setup_nop(data); infof(data, "File already completely downloaded"); - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); break; } Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE); @@ -801,20 +841,24 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) if(result) { /* this should never occur; the close state should be entered at the time the error occurs */ - state(data, SSH_SFTP_CLOSE); + wssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->actualcode = result; } else { - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); } break; } case SSH_SFTP_CLOSE: - if(sshc->handleSz) + if(sshc->handleSz) { rc = wolfSSH_SFTP_Close(sshc->ssh_session, sshc->handle, sshc->handleSz); - else + if(rc != WS_SUCCESS) + rc = wolfSSH_get_error(sshc->ssh_session); + } + else { rc = WS_SUCCESS; /* directory listing */ + } if(rc == WS_WANT_READ) { *block = TRUE; conn->waitfor = KEEP_RECV; @@ -826,7 +870,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) return CURLE_OK; } else if(rc == WS_SUCCESS) { - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); return CURLE_OK; } @@ -836,10 +880,10 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) case SSH_SFTP_READDIR_INIT: Curl_pgrsSetDownloadSize(data, -1); if(data->req.no_body) { - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); break; } - state(data, SSH_SFTP_READDIR); + wssh_state(data, sshc, SSH_SFTP_READDIR); break; case SSH_SFTP_READDIR: @@ -867,7 +911,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) data->set.list_only ? name->fName : name->lName); if(!line) { - state(data, SSH_SFTP_CLOSE); + wssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->actualcode = CURLE_OUT_OF_MEMORY; break; } @@ -881,17 +925,15 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) name = name->next; } wolfSSH_SFTPNAME_list_free(origname); - state(data, SSH_STOP); + wssh_state(data, sshc, SSH_STOP); return result; } failf(data, "wolfssh SFTP ls failed: %d", rc); return CURLE_SSH; case SSH_SFTP_SHUTDOWN: - Curl_safefree(sshc->homedir); - wolfSSH_free(sshc->ssh_session); - wolfSSH_CTX_free(sshc->ctx); - state(data, SSH_STOP); + wssh_sshc_cleanup(sshc); + wssh_state(data, sshc, SSH_STOP); return CURLE_OK; default: break; @@ -904,12 +946,15 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) static CURLcode wssh_multi_statemach(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); CURLcode result = CURLE_OK; bool block; /* we store the status and use that to provide a ssh_getsock() implementation */ + if(!sshc) + return CURLE_FAILED_INIT; + do { - result = wssh_statemach_act(data, &block); + result = wssh_statemach_act(data, sshc, &block); *done = (sshc->state == SSH_STOP); /* if there is no error, it is not done and it did not EWOULDBLOCK, then try again */ @@ -934,6 +979,7 @@ CURLcode wscp_perform(struct Curl_easy *data, static CURLcode wsftp_perform(struct Curl_easy *data, + struct ssh_conn *sshc, bool *connected, bool *dophase_done) { @@ -944,7 +990,7 @@ CURLcode wsftp_perform(struct Curl_easy *data, *dophase_done = FALSE; /* not done yet */ /* start the first command in the DO phase */ - state(data, SSH_SFTP_QUOTE_INIT); + wssh_state(data, sshc, SSH_SFTP_QUOTE_INIT); /* run the state-machine */ result = wssh_multi_statemach(data, dophase_done); @@ -966,9 +1012,12 @@ static CURLcode wssh_do(struct Curl_easy *data, bool *done) CURLcode result; bool connected = FALSE; struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); *done = FALSE; /* default to false */ + if(!sshc) + return CURLE_FAILED_INIT; + data->req.size = -1; /* make sure this is unknown at this point */ sshc->actualcode = CURLE_OK; /* reset error code */ sshc->secondCreateDirs = 0; /* reset the create dir attempt state @@ -982,24 +1031,24 @@ static CURLcode wssh_do(struct Curl_easy *data, bool *done) if(conn->handler->protocol & CURLPROTO_SCP) result = wscp_perform(data, &connected, done); else - result = wsftp_perform(data, &connected, done); + result = wsftp_perform(data, sshc, &connected, done); return result; } static CURLcode wssh_block_statemach(struct Curl_easy *data, - bool disconnect) + struct ssh_conn *sshc, + bool disconnect) { struct connectdata *conn = data->conn; - struct ssh_conn *sshc = &conn->proto.sshc; CURLcode result = CURLE_OK; while((sshc->state != SSH_STOP) && !result) { bool block; timediff_t left = 1000; - struct curltime now = Curl_now(); + struct curltime now = curlx_now(); - result = wssh_statemach_act(data, &block); + result = wssh_statemach_act(data, sshc, &block); if(result) break; @@ -1039,20 +1088,19 @@ static CURLcode wssh_block_statemach(struct Curl_easy *data, /* generic done function for both SCP and SFTP called from their specific done functions */ -static CURLcode wssh_done(struct Curl_easy *data, CURLcode status) +static CURLcode wssh_done(struct Curl_easy *data, + struct ssh_conn *sshc, + CURLcode status) { CURLcode result = CURLE_OK; - struct SSHPROTO *sftp_scp = data->req.p.ssh; if(!status) { /* run the state-machine */ - result = wssh_block_statemach(data, FALSE); + result = wssh_block_statemach(data, sshc, FALSE); } else result = status; - if(sftp_scp) - Curl_safefree(sftp_scp->path); if(Curl_pgrsDone(data)) return CURLE_ABORTED_BY_CALLBACK; @@ -1060,6 +1108,19 @@ static CURLcode wssh_done(struct Curl_easy *data, CURLcode status) return result; } +static void wssh_sshc_cleanup(struct ssh_conn *sshc) +{ + if(sshc->ssh_session) { + wolfSSH_free(sshc->ssh_session); + sshc->ssh_session = NULL; + } + if(sshc->ctx) { + wolfSSH_CTX_free(sshc->ctx); + sshc->ctx = NULL; + } + Curl_safefree(sshc->homedir); +} + #if 0 static CURLcode wscp_done(struct Curl_easy *data, CURLcode code, bool premature) @@ -1085,11 +1146,11 @@ static CURLcode wscp_doing(struct Curl_easy *data, static CURLcode wscp_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); CURLcode result = CURLE_OK; - (void)data; - (void)conn; (void)dead_connection; - + if(sshc) + wssh_sshc_cleanup(sshc); return result; } #endif @@ -1097,10 +1158,14 @@ static CURLcode wscp_disconnect(struct Curl_easy *data, static CURLcode wsftp_done(struct Curl_easy *data, CURLcode code, bool premature) { + struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN); (void)premature; - state(data, SSH_SFTP_CLOSE); + if(!sshc) + return CURLE_FAILED_INIT; - return wssh_done(data, code); + wssh_state(data, sshc, SSH_SFTP_CLOSE); + + return wssh_done(data, sshc, code); } static CURLcode wsftp_doing(struct Curl_easy *data, @@ -1118,17 +1183,20 @@ static CURLcode wsftp_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead) { + struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN); CURLcode result = CURLE_OK; (void)dead; DEBUGF(infof(data, "SSH DISCONNECT starts now")); - if(conn->proto.sshc.ssh_session) { + if(sshc && sshc->ssh_session) { /* only if there is a session still around to use! */ - state(data, SSH_SFTP_SHUTDOWN); - result = wssh_block_statemach(data, TRUE); + wssh_state(data, sshc, SSH_SFTP_SHUTDOWN); + result = wssh_block_statemach(data, sshc, TRUE); } + if(sshc) + wssh_sshc_cleanup(sshc); DEBUGF(infof(data, "SSH DISCONNECT is done")); return result; } diff --git a/Utilities/cmcurl/lib/vtls/bearssl.c b/Utilities/cmcurl/lib/vtls/bearssl.c index c52f28d748..4b65244451 100644 --- a/Utilities/cmcurl/lib/vtls/bearssl.c +++ b/Utilities/cmcurl/lib/vtls/bearssl.c @@ -21,7 +21,7 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_BEARSSL @@ -29,20 +29,20 @@ #include "bearssl.h" #include "cipher_suite.h" -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" +#include "../urldata.h" +#include "../sendf.h" +#include "../curlx/inet_pton.h" #include "vtls.h" #include "vtls_int.h" #include "vtls_scache.h" -#include "connect.h" -#include "select.h" -#include "multiif.h" -#include "curl_printf.h" +#include "../connect.h" +#include "../select.h" +#include "../multiif.h" +#include "../curl_printf.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" struct x509_context { const br_x509_class *vtable; @@ -153,7 +153,7 @@ static CURLcode load_cafile(struct cafile_source *source, } else if(source->type == CAFILE_SOURCE_BLOB) { n = source->len; - p = (unsigned char *) source->data; + p = (const unsigned char *) source->data; } while(n) { pushed = br_pem_decoder_push(&pc, p, n); @@ -338,7 +338,7 @@ static unsigned x509_end_chain(const br_x509_class **ctx) static const br_x509_pkey *x509_get_pkey(const br_x509_class *const *ctx, unsigned *usages) { - struct x509_context *x509 = (struct x509_context *)ctx; + struct x509_context *x509 = (struct x509_context *)CURL_UNCONST(ctx); if(!x509->verifypeer) { /* Nothing in the chain is verified, just return the public key of the @@ -484,7 +484,7 @@ static const uint16_t ciphertable[] = { BR_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ }; -#define NUM_OF_CIPHERS (sizeof(ciphertable) / sizeof(ciphertable[0])) +#define NUM_OF_CIPHERS CURL_ARRAYSIZE(ciphertable) static CURLcode bearssl_set_selected_ciphers(struct Curl_easy *data, br_ssl_engine_context *ssl_eng, @@ -611,12 +611,12 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, if(ssl_config->primary.cache_session) { struct Curl_ssl_session *sc_session = NULL; - const br_ssl_session_parameters *session; ret = Curl_ssl_scache_take(cf, data, connssl->peer.scache_key, &sc_session); if(!ret && sc_session && sc_session->sdata && sc_session->sdata_len) { - session = (br_ssl_session_parameters *)(void *)sc_session->sdata; + const br_ssl_session_parameters *session; + session = (const br_ssl_session_parameters *)sc_session->sdata; br_ssl_engine_set_session_parameters(&backend->ctx.eng, session); session_set = 1; infof(data, "BearSSL: reusing session ID"); @@ -729,7 +729,7 @@ 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, FALSE, + ret = Curl_conn_cf_send(cf->next, data, (const char *)buf, len, FALSE, &result); CURL_TRC_CF(data, cf, "ssl_send(len=%zu) -> %zd, %d", len, ret, result); if(ret <= 0) { @@ -911,18 +911,14 @@ static ssize_t bearssl_recv(struct Curl_cfilter *cf, struct Curl_easy *data, return applen; } -static CURLcode bearssl_connect_common(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool nonblocking, - bool *done) +static CURLcode bearssl_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { CURLcode ret; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - timediff_t timeout_ms; - int what; - CURL_TRC_CF(data, cf, "connect_common(blocking=%d)", !nonblocking); + CURL_TRC_CF(data, cf, "connect()"); /* check if the connection has already been established */ if(ssl_connection_complete == connssl->state) { CURL_TRC_CF(data, cf, "connect_common, connected"); @@ -930,61 +926,18 @@ static CURLcode bearssl_connect_common(struct Curl_cfilter *cf, return CURLE_OK; } + *done = FALSE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + if(ssl_connect_1 == connssl->connecting_state) { ret = bearssl_connect_step1(cf, data); if(ret) return ret; } - while(ssl_connect_2 == connssl->connecting_state) { - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* 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); - CURL_TRC_CF(data, cf, "connect_common, check socket -> %d", what); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if this - * connection is done nonblocking and this loop would execute again. This - * permits the owner of a multi handle to abort a connection attempt - * 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; + if(ssl_connect_2 == connssl->connecting_state) { ret = bearssl_connect_step2(cf, data); - if(ret || (nonblocking && (ssl_connect_2 == connssl->connecting_state))) + if(ret) return ret; } @@ -998,11 +951,6 @@ static CURLcode bearssl_connect_common(struct Curl_cfilter *cf, connssl->state = ssl_connection_complete; *done = TRUE; } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; return CURLE_OK; } @@ -1044,28 +992,6 @@ static CURLcode bearssl_random(struct Curl_easy *data UNUSED_PARAM, return CURLE_OK; } -static CURLcode bearssl_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - CURLcode ret; - bool done = FALSE; - - ret = bearssl_connect_common(cf, data, FALSE, &done); - if(ret) - return ret; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -static CURLcode bearssl_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - return bearssl_connect_common(cf, data, TRUE, done); -} - static void *bearssl_get_internals(struct ssl_connect_data *connssl, CURLINFO info UNUSED_PARAM) { @@ -1161,7 +1087,6 @@ const struct Curl_ssl Curl_ssl_bearssl = { bearssl_random, /* random */ NULL, /* cert_status_request */ bearssl_connect, /* connect */ - bearssl_connect_nonblocking, /* connect_nonblocking */ Curl_ssl_adjust_pollset, /* adjust_pollset */ bearssl_get_internals, /* get_internals */ bearssl_close, /* close_one */ diff --git a/Utilities/cmcurl/lib/vtls/bearssl.h b/Utilities/cmcurl/lib/vtls/bearssl.h index b3651b092c..8bb254f7da 100644 --- a/Utilities/cmcurl/lib/vtls/bearssl.h +++ b/Utilities/cmcurl/lib/vtls/bearssl.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_BEARSSL diff --git a/Utilities/cmcurl/lib/vtls/cipher_suite.c b/Utilities/cmcurl/lib/vtls/cipher_suite.c index a694b14627..d058bd3f0e 100644 --- a/Utilities/cmcurl/lib/vtls/cipher_suite.c +++ b/Utilities/cmcurl/lib/vtls/cipher_suite.c @@ -21,13 +21,13 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#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 "../curl_printf.h" +#include "../strcase.h" #include /* @@ -725,7 +725,7 @@ static const struct cs_entry cs_list [] = { CS_ENTRY(0xCCAE, RSA,PSK,CHACHA20,POLY1305,,,,), #endif }; -#define CS_LIST_LEN (sizeof(cs_list) / sizeof(cs_list[0])) +#define CS_LIST_LEN CURL_ARRAYSIZE(cs_list) static int cs_str_to_zip(const char *cs_str, size_t cs_len, uint8_t zip[6]) @@ -786,12 +786,12 @@ static int cs_zip_to_str(const uint8_t zip[6], /* 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[1] = (uint8_t)(((zip[0] << 4) & 0x3F) | zip[1] >> 4); + indexes[2] = (uint8_t)(((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[5] = (uint8_t)(((zip[3] << 4) & 0x3F) | zip[4] >> 4); + indexes[6] = (uint8_t)(((zip[4] << 2) & 0x3F) | zip[5] >> 6); indexes[7] = ((zip[5] << 0) & 0x3F); if(indexes[0] == CS_TXT_IDX_TLS) diff --git a/Utilities/cmcurl/lib/vtls/cipher_suite.h b/Utilities/cmcurl/lib/vtls/cipher_suite.h index 6d980103a5..cd556db10f 100644 --- a/Utilities/cmcurl/lib/vtls/cipher_suite.h +++ b/Utilities/cmcurl/lib/vtls/cipher_suite.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \ defined(USE_BEARSSL) || defined(USE_RUSTLS) diff --git a/Utilities/cmcurl/lib/vtls/gtls.c b/Utilities/cmcurl/lib/vtls/gtls.c index a47d803c72..74c36fed94 100644 --- a/Utilities/cmcurl/lib/vtls/gtls.c +++ b/Utilities/cmcurl/lib/vtls/gtls.c @@ -30,7 +30,7 @@ * since they were not present in 1.0.X. */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_GNUTLS @@ -40,34 +40,28 @@ #include #include -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" +#include "../urldata.h" +#include "../sendf.h" +#include "../curlx/inet_pton.h" #include "keylog.h" #include "gtls.h" #include "vtls.h" #include "vtls_int.h" #include "vtls_scache.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 "strdup.h" -#include "warnless.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 "../strdup.h" +#include "../curlx/warnless.h" #include "x509asn1.h" -#include "multiif.h" -#include "curl_printf.h" -#include "curl_memory.h" +#include "../multiif.h" +#include "../curl_printf.h" +#include "../curl_memory.h" /* The last #include file should be: */ -#include "memdebug.h" - -#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" +#include "../memdebug.h" /* Enable GnuTLS debugging by defining GTLSDEBUG */ /*#define GTLSDEBUG */ @@ -106,6 +100,7 @@ static ssize_t gtls_push(void *s, const void *buf, size_t blen) blen, nwritten, result); backend->gtls.io_result = result; if(nwritten < 0) { + /* !checksrc! disable ERRNOVAR 1 */ gnutls_transport_set_errno(backend->gtls.session, (CURLE_AGAIN == result) ? EAGAIN : EINVAL); nwritten = -1; @@ -127,6 +122,7 @@ static ssize_t gtls_pull(void *s, void *buf, size_t blen) if(!backend->gtls.shared_creds->trust_setup) { result = Curl_gtls_client_trust_setup(cf, data, &backend->gtls); if(result) { + /* !checksrc! disable ERRNOVAR 1 */ gnutls_transport_set_errno(backend->gtls.session, EINVAL); backend->gtls.io_result = result; return -1; @@ -138,6 +134,7 @@ static ssize_t gtls_pull(void *s, void *buf, size_t blen) blen, nread, result); backend->gtls.io_result = result; if(nread < 0) { + /* !checksrc! disable ERRNOVAR 1 */ gnutls_transport_set_errno(backend->gtls.session, (CURLE_AGAIN == result) ? EAGAIN : EINVAL); nread = -1; @@ -236,117 +233,70 @@ static void unload_file(gnutls_datum_t data) } -/* this function does a SSL/TLS (re-)handshake */ +/* this function does an SSL/TLS (re-)handshake */ static CURLcode handshake(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool duringconnect, - bool nonblocking) + struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; struct gtls_ssl_backend_data *backend = (struct gtls_ssl_backend_data *)connssl->backend; gnutls_session_t session; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + int rc; DEBUGASSERT(backend); session = backend->gtls.session; - connssl->connecting_state = ssl_connect_2; - for(;;) { - timediff_t timeout_ms; - int rc; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + backend->gtls.io_result = CURLE_OK; + rc = gnutls_handshake(session); - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, duringconnect); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it is available. */ - if(connssl->io_need) { - int what; - 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); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) - return CURLE_AGAIN; - else if(timeout_ms) { - /* timeout */ - failf(data, "SSL connection timeout at %ld", (long)timeout_ms); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* 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->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) { - gnutls_alert_description_t alert = gnutls_alert_get(session); - strerr = gnutls_alert_get_name(alert); - } - - if(!strerr) - strerr = gnutls_strerror(rc); - - 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) { - 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); - return CURLE_SSL_CONNECT_ERROR; - } - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - return CURLE_OK; + 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->io_need = + gnutls_record_get_direction(session) ? + CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV; + return CURLE_AGAIN; + } + else if((rc < 0) && !gnutls_error_is_fatal(rc)) { + const char *strerr = NULL; + + if(rc == GNUTLS_E_WARNING_ALERT_RECEIVED) { + gnutls_alert_description_t alert = gnutls_alert_get(session); + strerr = gnutls_alert_get_name(alert); + } + + if(!strerr) + strerr = gnutls_strerror(rc); + + infof(data, "gnutls_handshake() warning: %s", strerr); + return CURLE_AGAIN; + } + 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) { + 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); + return CURLE_SSL_CONNECT_ERROR; + } + + return CURLE_OK; } static gnutls_x509_crt_fmt_t gnutls_do_file_type(const char *type) @@ -366,12 +316,18 @@ static gnutls_x509_crt_fmt_t gnutls_do_file_type(const char *type) */ #define GNUTLS_SRP "+SRP" +#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" + static CURLcode 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) + bool tls13support) { long ssl_version = conn_config->version; long ssl_version_max = conn_config->version_max; @@ -470,7 +426,7 @@ CURLcode Curl_gtls_shared_creds_create(struct Curl_easy *data, } shared->refcount = 1; - shared->time = Curl_now(); + shared->time = curlx_now(); *pcreds = shared; return CURLE_OK; } @@ -584,8 +540,8 @@ 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); + struct curltime now = curlx_now(); + timediff_t elapsed_ms = curlx_timediff(now, sc->time); timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; if(timeout_ms < 0) @@ -611,7 +567,7 @@ gtls_get_cached_creds(struct Curl_cfilter *cf, struct Curl_easy *data) if(data->multi) { shared_creds = Curl_hash_pick(&data->multi->proto_hash, - (void *)MPROTO_GTLS_X509_KEY, + CURL_UNCONST(MPROTO_GTLS_X509_KEY), sizeof(MPROTO_GTLS_X509_KEY)-1); if(shared_creds && shared_creds->creds && !gtls_shared_creds_expired(data, shared_creds) && @@ -655,7 +611,7 @@ static void gtls_set_cached_creds(struct Curl_cfilter *cf, return; if(!Curl_hash_add2(&data->multi->proto_hash, - (void *)MPROTO_GTLS_X509_KEY, + CURL_UNCONST(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 */ @@ -827,6 +783,63 @@ static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype, return 0; } +static CURLcode gtls_set_priority(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct gtls_ctx *gtls, + const char *priority) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct dynbuf buf; + const char *err = NULL; + CURLcode result = CURLE_OK; + int rc; + + curlx_dyn_init(&buf, 4096); + +#ifdef USE_GNUTLS_SRP + if(conn_config->username) { + /* Only add SRP to the cipher list if SRP is requested. Otherwise + * GnuTLS will disable TLS 1.3 support. */ + result = curlx_dyn_add(&buf, priority); + if(!result) + result = curlx_dyn_add(&buf, ":" GNUTLS_SRP); + if(result) + goto out; + priority = curlx_dyn_ptr(&buf); + } +#endif + + if(conn_config->cipher_list) { + if((conn_config->cipher_list[0] == '+') || + (conn_config->cipher_list[0] == '-') || + (conn_config->cipher_list[0] == '!')) { + /* add it to out own */ + if(!curlx_dyn_len(&buf)) { /* not added yet */ + result = curlx_dyn_add(&buf, priority); + if(result) + goto out; + } + result = curlx_dyn_addf(&buf, ":%s", conn_config->cipher_list); + if(result) + goto out; + priority = curlx_dyn_ptr(&buf); + } + else /* replace our own completely */ + priority = conn_config->cipher_list; + } + + infof(data, "GnuTLS priority: %s", priority); + rc = gnutls_priority_set_direct(gtls->session, priority, &err); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "Error %d setting GnuTLS priority: %s", rc, err); + result = CURLE_SSL_CONNECT_ERROR; + } + +out: + curlx_dyn_free(&buf); + return result; +} + static CURLcode gtls_client_init(struct Curl_cfilter *cf, struct Curl_easy *data, struct ssl_peer *peer, @@ -839,8 +852,7 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, int rc; bool sni = TRUE; /* default is SNI enabled */ const char *prioritylist; - const char *err = NULL; - const char *tls13support; + bool tls13support; CURLcode result; if(!gtls_inited) @@ -935,7 +947,7 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; /* "In GnuTLS 3.6.5, TLS 1.3 is enabled by default" */ - tls13support = gnutls_check_version("3.6.5"); + 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 runtime error indicates that SRP is not supported by this @@ -960,33 +972,9 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, if(result) return result; -#ifdef USE_GNUTLS_SRP - /* Only add SRP to the cipher list if SRP is requested. Otherwise - * GnuTLS will disable TLS 1.3 support. */ - if(config->username) { - char *prioritysrp = aprintf("%s:" GNUTLS_SRP, prioritylist); - if(!prioritysrp) - return CURLE_OUT_OF_MEMORY; - rc = gnutls_priority_set_direct(gtls->session, prioritysrp, &err); - free(prioritysrp); - - if((rc == GNUTLS_E_INVALID_REQUEST) && err) { - infof(data, "This GnuTLS does not support SRP"); - } - } - else { -#endif - infof(data, "GnuTLS ciphers: %s", prioritylist); - rc = gnutls_priority_set_direct(gtls->session, prioritylist, &err); -#ifdef USE_GNUTLS_SRP - } -#endif - - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "Error %d setting GnuTLS cipher list starting with %s", - rc, err); - return CURLE_SSL_CONNECT_ERROR; - } + result = gtls_set_priority(cf, data, gtls, prioritylist); + if(result) + return result; if(config->clientcert) { if(!gtls->shared_creds->trust_setup) { @@ -1006,7 +994,7 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } } - else if(ssl_config->key_passwd) { + else { 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 | @@ -1021,22 +1009,12 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, supported_key_encryption_algorithms); if(rc != GNUTLS_E_SUCCESS) { failf(data, - "error reading X.509 potentially-encrypted key file: %s", + "error reading X.509 %skey file: %s", + ssl_config->key_passwd ? "potentially-encrypted " : "", gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; } } - else { - if(gnutls_certificate_set_x509_key_file( - gtls->shared_creds->creds, - config->clientcert, - ssl_config->key ? ssl_config->key : config->clientcert, - 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; - } - } } #ifdef USE_GNUTLS_SRP @@ -1089,6 +1067,7 @@ static int keylog_callback(gnutls_session_t session, const char *label, static CURLcode gtls_on_session_reuse(struct Curl_cfilter *cf, struct Curl_easy *data, + struct alpn_spec *alpns, struct Curl_ssl_session *scs, bool *do_early_data) { @@ -1104,13 +1083,13 @@ static CURLcode gtls_on_session_reuse(struct Curl_cfilter *cf, /* Seems to be GnuTLS way to signal no EarlyData in session */ CURL_TRC_CF(data, cf, "SSL session does not allow earlydata"); } - else if(!Curl_alpn_contains_proto(connssl->alpn, scs->alpn)) { + else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) { CURL_TRC_CF(data, cf, "SSL session has different ALPN, no early data"); } else { infof(data, "SSL session allows %zu bytes of early data, " "reusing ALPN '%s'", connssl->earlydata_max, scs->alpn); - connssl->earlydata_state = ssl_earlydata_use; + connssl->earlydata_state = ssl_earlydata_await; connssl->state = ssl_connection_deferred; result = Curl_alpn_set_negotiated(cf, data, connssl, (const unsigned char *)scs->alpn, @@ -1124,7 +1103,7 @@ 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, + const struct alpn_spec *alpns_requested, Curl_gtls_ctx_setup_cb *cb_setup, void *cb_user_data, void *ssl_user_data, @@ -1133,13 +1112,16 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, 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 *scs = NULL; - gnutls_datum_t gtls_alpns[5]; + gnutls_datum_t gtls_alpns[ALPN_ENTRIES_MAX]; size_t gtls_alpns_count = 0; bool gtls_session_setup = FALSE; - CURLcode result; + struct alpn_spec alpns; + CURLcode result = CURLE_OK; int rc; DEBUGASSERT(gctx); + Curl_alpn_copy(&alpns, alpns_requested); + /* This might be a reconnect, so we check for a session ID in the cache to speed up things. We need to do this before constructing the gnutls session since we need to set flags depending on the kind of reuse. */ @@ -1148,7 +1130,8 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, if(result) goto out; - if(scs && scs->sdata && scs->sdata_len) { + if(scs && scs->sdata && scs->sdata_len && + (!scs->alpn || Curl_alpn_contains_proto(&alpns, scs->alpn))) { /* we got a cached session, use it! */ result = gtls_client_init(cf, data, peer, scs->earlydata_max, gctx); @@ -1162,30 +1145,19 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, else { infof(data, "SSL reusing session with ALPN '%s'", scs->alpn ? scs->alpn : "-"); - if(ssl_config->earlydata && + if(ssl_config->earlydata && scs->alpn && !cf->conn->connect_only && (gnutls_protocol_get_version(gctx->session) == GNUTLS_TLS1_3)) { bool do_early_data = FALSE; if(sess_reuse_cb) { - result = sess_reuse_cb(cf, data, scs, &do_early_data); + result = sess_reuse_cb(cf, data, &alpns, scs, &do_early_data); if(result) goto out; } if(do_early_data) { /* 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 *)scs->alpn; - gtls_alpns[0].size = (unsigned)strlen(scs->alpn); - if(gnutls_alpn_set_protocols(gctx->session, - gtls_alpns, 1, - GNUTLS_ALPN_MANDATORY)) { - failf(data, "failed setting ALPN"); - result = CURLE_SSL_CONNECT_ERROR; - goto out; - } - /* don't set again below */ - gtls_alpns_count = 0; - alpn = NULL; + Curl_alpn_restrict_to(&alpns, scs->alpn); } } } @@ -1215,24 +1187,14 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, /* 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 *salpn = (unsigned char *)alpn; - unsigned char slen; - for(i = 0; (i < CURL_ARRAYSIZE(gtls_alpns)) && alen; ++i) { - slen = salpn[0]; - if(slen >= alen) - return CURLE_FAILED_INIT; - gtls_alpns[i].data = salpn + 1; - gtls_alpns[i].size = slen; - salpn += slen + 1; - alen -= (size_t)slen + 1; + if(!gtls_alpns_count && alpns.count) { + size_t i; + DEBUGASSERT(CURL_ARRAYSIZE(gtls_alpns) >= alpns.count); + for(i = 0; i < alpns.count; ++i) { + gtls_alpns[i].data = (unsigned char *)alpns.entries[i]; + gtls_alpns[i].size = (unsigned int)strlen(alpns.entries[i]); } - if(alen) { /* not all alpn chars used, wrong format or too many */ - result = CURLE_FAILED_INIT; - goto out; - } - gtls_alpns_count = i; + gtls_alpns_count = alpns.count; } if(gtls_alpns_count && @@ -1254,33 +1216,24 @@ 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, - NULL, NULL, cf, gtls_on_session_reuse); + connssl->alpn, NULL, NULL, cf, + gtls_on_session_reuse); if(result) return result; if(connssl->alpn && (connssl->state != ssl_connection_deferred)) { + struct alpn_proto_buf proto; + memset(&proto, 0, sizeof(proto)); Curl_alpn_to_proto_str(&proto, connssl->alpn); infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } @@ -1639,10 +1592,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, peer->hostname, addrbuf) > 0) + if(curlx_inet_pton(AF_INET, peer->hostname, addrbuf) > 0) addrlen = 4; #ifdef USE_IPV6 - else if(Curl_inet_pton(AF_INET6, peer->hostname, addrbuf) > 0) + else if(curlx_inet_pton(AF_INET6, peer->hostname, addrbuf) > 0) addrlen = 16; #endif @@ -1828,30 +1781,6 @@ out: return result; } -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) { @@ -1886,8 +1815,7 @@ static CURLcode gtls_send_earlydata(struct Curl_cfilter *cf, 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); + infof(data, "SSL sending %zu bytes of early data", connssl->earlydata_skip); out: return result; } @@ -1901,17 +1829,22 @@ out: '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) { +static CURLcode gtls_connect_common(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { struct ssl_connect_data *connssl = cf->ctx; struct gtls_ssl_backend_data *backend = (struct gtls_ssl_backend_data *)connssl->backend; CURLcode result = CURLE_OK; DEBUGASSERT(backend); + /* check if the connection has already been established */ + if(ssl_connection_complete == connssl->state) { + *done = TRUE; + return CURLE_OK; + } + + *done = FALSE; /* Initiate the connection, if not already done */ if(connssl->connecting_state == ssl_connect_1) { @@ -1922,7 +1855,7 @@ gtls_connect_common(struct Curl_cfilter *cf, } if(connssl->connecting_state == ssl_connect_2) { - if(connssl->earlydata_state == ssl_earlydata_use) { + if(connssl->earlydata_state == ssl_earlydata_await) { goto out; } else if(connssl->earlydata_state == ssl_earlydata_sending) { @@ -1930,13 +1863,11 @@ gtls_connect_common(struct Curl_cfilter *cf, 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); + result = handshake(cf, data); if(result) goto out; connssl->connecting_state = ssl_connect_3; @@ -1951,7 +1882,6 @@ gtls_connect_common(struct Curl_cfilter *cf, 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 */ @@ -1964,82 +1894,43 @@ gtls_connect_common(struct Curl_cfilter *cf, if(result) goto out; - if(connssl->earlydata_state == ssl_earlydata_sent) { - /* report the true time the handshake was done */ - connssl->handshake_done = Curl_now(); - Curl_pgrsTimeWas(data, TIMER_APPCONNECT, connssl->handshake_done); - 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; - } + if(connssl->earlydata_state > ssl_earlydata_none) { + /* We should be in this state by now */ + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sent); + connssl->earlydata_state = + (gnutls_session_get_flags(backend->gtls.session) & + GNUTLS_SFLAGS_EARLY_DATA) ? + ssl_earlydata_accepted : ssl_earlydata_rejected; } + connssl->connecting_state = ssl_connect_done; } + if(connssl->connecting_state == ssl_connect_done) + DEBUGASSERT(connssl->state == ssl_connection_complete); + out: if(result == CURLE_AGAIN) { *done = FALSE; return CURLE_OK; } - *done = ((connssl->connecting_state == ssl_connect_1) || + *done = ((connssl->state == ssl_connection_complete) || (connssl->state == ssl_connection_deferred)); + CURL_TRC_CF(data, cf, "gtls_connect_common() -> %d, done=%d", result, *done); return result; } -static CURLcode gtls_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) +static CURLcode gtls_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { struct ssl_connect_data *connssl = cf->ctx; - if(connssl->state == ssl_connection_deferred) { + if((connssl->state == ssl_connection_deferred) && + (connssl->earlydata_state == ssl_earlydata_await)) { /* 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); -} - -static CURLcode gtls_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - CURLcode result; - bool done = FALSE; - - result = gtls_connect_common(cf, data, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - 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); + return gtls_connect_common(cf, data, done); } static bool gtls_data_pending(struct Curl_cfilter *cf, @@ -2069,38 +1960,9 @@ static ssize_t gtls_send(struct Curl_cfilter *cf, ssize_t rc; size_t nwritten, total_written = 0; + (void)data; DEBUGASSERT(backend); - 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); - } - - 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); @@ -2121,7 +1983,7 @@ static ssize_t gtls_send(struct Curl_cfilter *cf, nwritten = (size_t)rc; total_written += nwritten; DEBUGASSERT(nwritten <= blen); - buf = (char *)buf + nwritten; + buf = (char *)CURL_UNCONST(buf) + nwritten; blen -= nwritten; } rc = total_written; @@ -2247,21 +2109,6 @@ 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; @@ -2272,9 +2119,8 @@ static ssize_t gtls_recv(struct Curl_cfilter *cf, if(ret == GNUTLS_E_REHANDSHAKE) { /* BLOCKING call, this is bad but a work-around for now. Fixing this "the proper way" takes a whole lot of work. */ - CURLcode result = handshake(cf, data, FALSE, FALSE); + CURLcode result = handshake(cf, data); if(result) - /* handshake() writes error message on its own */ *curlcode = result; else *curlcode = CURLE_AGAIN; /* then return as if this was a wouldblock */ @@ -2344,6 +2190,7 @@ const struct Curl_ssl Curl_ssl_gnutls = { SSLSUPP_CERTINFO | SSLSUPP_PINNEDPUBKEY | SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST | SSLSUPP_CA_CACHE, sizeof(struct gtls_ssl_backend_data), @@ -2356,7 +2203,6 @@ const struct Curl_ssl Curl_ssl_gnutls = { gtls_random, /* random */ gtls_cert_status_request, /* cert_status_request */ gtls_connect, /* connect */ - gtls_connect_nonblocking, /* connect_nonblocking */ Curl_ssl_adjust_pollset, /* adjust_pollset */ gtls_get_internals, /* get_internals */ gtls_close, /* close_one */ diff --git a/Utilities/cmcurl/lib/vtls/gtls.h b/Utilities/cmcurl/lib/vtls/gtls.h index a17dcd7adb..35af9db139 100644 --- a/Utilities/cmcurl/lib/vtls/gtls.h +++ b/Utilities/cmcurl/lib/vtls/gtls.h @@ -24,13 +24,13 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #include #ifdef USE_GNUTLS #include -#include "timeval.h" +#include "../curlx/timeval.h" #ifdef HAVE_GNUTLS_SRP /* the function exists */ @@ -42,6 +42,7 @@ struct Curl_easy; struct Curl_cfilter; +struct alpn_spec; struct ssl_primary_config; struct ssl_config_data; struct ssl_peer; @@ -81,6 +82,7 @@ typedef CURLcode Curl_gtls_ctx_setup_cb(struct Curl_cfilter *cf, typedef CURLcode Curl_gtls_init_session_reuse_cb(struct Curl_cfilter *cf, struct Curl_easy *data, + struct alpn_spec *alpns, struct Curl_ssl_session *scs, bool *do_early_data); @@ -88,7 +90,7 @@ 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, + const struct alpn_spec *alpns, Curl_gtls_ctx_setup_cb *cb_setup, void *cb_user_data, void *ssl_user_data, diff --git a/Utilities/cmcurl/lib/vtls/hostcheck.c b/Utilities/cmcurl/lib/vtls/hostcheck.c index e46439a5ec..8ca69941e8 100644 --- a/Utilities/cmcurl/lib/vtls/hostcheck.c +++ b/Utilities/cmcurl/lib/vtls/hostcheck.c @@ -22,7 +22,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_OPENSSL) \ || defined(USE_SCHANNEL) @@ -34,15 +34,14 @@ #ifdef HAVE_NETINET_IN6_H #include #endif -#include "curl_memrchr.h" - +#include "../curl_memrchr.h" #include "hostcheck.h" -#include "strcase.h" -#include "hostip.h" +#include "../strcase.h" +#include "../hostip.h" -#include "curl_memory.h" +#include "../curl_memory.h" /* The last #include file should be: */ -#include "memdebug.h" +#include "../memdebug.h" /* check the two input strings with given length, but do not assume they end in nul-bytes */ diff --git a/Utilities/cmcurl/lib/vtls/keylog.c b/Utilities/cmcurl/lib/vtls/keylog.c index ca86c15608..8487d43e8c 100644 --- a/Utilities/cmcurl/lib/vtls/keylog.c +++ b/Utilities/cmcurl/lib/vtls/keylog.c @@ -21,32 +21,22 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_OPENSSL) || \ defined(USE_GNUTLS) || \ defined(USE_WOLFSSL) || \ (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || \ - defined(USE_QUICHE) + defined(USE_QUICHE) || \ + defined(USE_RUSTLS) #include "keylog.h" #include +#include "../escape.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" - -#define KEYLOG_LABEL_MAXLEN (sizeof("CLIENT_HANDSHAKE_TRAFFIC_SECRET") - 1) - -#define CLIENT_RANDOM_SIZE 32 - -/* - * The master secret in TLS 1.2 and before is always 48 bytes. In TLS 1.3, the - * secret size depends on the cipher suite's hash function which is 32 bytes - * for SHA-256 and 48 bytes for SHA-384. - */ -#define SECRET_MAXLEN 48 - +#include "../curl_memory.h" +#include "../memdebug.h" /* The fp for the open SSLKEYLOGFILE, or NULL if not open */ static FILE *keylog_file_fp; @@ -104,7 +94,7 @@ Curl_tls_keylog_write_line(const char *line) linelen = strlen(line); if(linelen == 0 || linelen > sizeof(buf) - 2) { - /* Empty line or too big to fit in a LF and NUL. */ + /* Empty line or too big to fit in an LF and NUL. */ return FALSE; } @@ -125,10 +115,9 @@ Curl_tls_keylog_write(const char *label, const unsigned char client_random[CLIENT_RANDOM_SIZE], const unsigned char *secret, size_t secretlen) { - const char *hex = "0123456789ABCDEF"; size_t pos, i; - char line[KEYLOG_LABEL_MAXLEN + 1 + 2 * CLIENT_RANDOM_SIZE + 1 + - 2 * SECRET_MAXLEN + 1 + 1]; + unsigned char line[KEYLOG_LABEL_MAXLEN + 1 + 2 * CLIENT_RANDOM_SIZE + 1 + + 2 * SECRET_MAXLEN + 1 + 1]; if(!keylog_file_fp) { return FALSE; @@ -145,22 +134,22 @@ Curl_tls_keylog_write(const char *label, /* Client Random */ for(i = 0; i < CLIENT_RANDOM_SIZE; i++) { - line[pos++] = hex[client_random[i] >> 4]; - line[pos++] = hex[client_random[i] & 0xF]; + Curl_hexbyte(&line[pos], client_random[i], FALSE); + pos += 2; } line[pos++] = ' '; /* Secret */ for(i = 0; i < secretlen; i++) { - line[pos++] = hex[secret[i] >> 4]; - line[pos++] = hex[secret[i] & 0xF]; + Curl_hexbyte(&line[pos], secret[i], FALSE); + pos += 2; } line[pos++] = '\n'; line[pos] = '\0'; /* Using fputs here instead of fprintf since libcurl's fprintf replacement may not be thread-safe. */ - fputs(line, keylog_file_fp); + fputs((char *)line, keylog_file_fp); return TRUE; } diff --git a/Utilities/cmcurl/lib/vtls/keylog.h b/Utilities/cmcurl/lib/vtls/keylog.h index eff5bf38f3..ec82abf547 100644 --- a/Utilities/cmcurl/lib/vtls/keylog.h +++ b/Utilities/cmcurl/lib/vtls/keylog.h @@ -23,7 +23,18 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" + +#define KEYLOG_LABEL_MAXLEN (sizeof("CLIENT_HANDSHAKE_TRAFFIC_SECRET") - 1) + +#define CLIENT_RANDOM_SIZE 32 + +/* + * The master secret in TLS 1.2 and before is always 48 bytes. In TLS 1.3, the + * secret size depends on the cipher suite's hash function which is 32 bytes + * for SHA-256 and 48 bytes for SHA-384. + */ +#define SECRET_MAXLEN 48 /* * Opens the TLS key log file if requested by the user. The SSLKEYLOGFILE @@ -50,7 +61,7 @@ bool Curl_tls_keylog_write(const char *label, const unsigned char *secret, size_t secretlen); /* - * Appends a line to the key log file, ensure it is terminated by a LF. + * Appends a line to the key log file, ensure it is terminated by an LF. * Returns true iff the key log file is open and a valid line was provided. */ bool Curl_tls_keylog_write_line(const char *line); diff --git a/Utilities/cmcurl/lib/vtls/mbedtls.c b/Utilities/cmcurl/lib/vtls/mbedtls.c index 13e44c7c01..7af207caa7 100644 --- a/Utilities/cmcurl/lib/vtls/mbedtls.c +++ b/Utilities/cmcurl/lib/vtls/mbedtls.c @@ -29,7 +29,7 @@ * */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_MBEDTLS @@ -57,26 +57,26 @@ #endif /* MBEDTLS_VERSION_MAJOR >= 2 */ #include "cipher_suite.h" -#include "strcase.h" -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" +#include "../strcase.h" +#include "../urldata.h" +#include "../sendf.h" +#include "../curlx/inet_pton.h" #include "mbedtls.h" #include "vtls.h" #include "vtls_int.h" #include "vtls_scache.h" #include "x509asn1.h" -#include "parsedate.h" -#include "connect.h" /* for the connect timeout */ -#include "select.h" -#include "multiif.h" +#include "../parsedate.h" +#include "../connect.h" /* for the connect timeout */ +#include "../select.h" +#include "../multiif.h" #include "mbedtls_threadlock.h" -#include "strdup.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" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* ALPN for http2 */ #if defined(USE_HTTP2) && defined(MBEDTLS_SSL_ALPN) @@ -198,7 +198,7 @@ static int mbedtls_bio_cf_write(void *bio, if(!data) return 0; - nwritten = Curl_conn_cf_send(cf->next, data, (char *)buf, blen, FALSE, + nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf, blen, FALSE, &result); CURL_TRC_CF(data, cf, "mbedtls_bio_cf_out_write(len=%zu) -> %zd, err=%d", blen, nwritten, result); @@ -273,7 +273,13 @@ mbed_set_ssl_version_min_max(struct Curl_easy *data, #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; + mbedtls_ssl_protocol_version ver_max = +#ifdef HAS_TLS13_SUPPORT + MBEDTLS_SSL_VERSION_TLS1_3 +#else + MBEDTLS_SSL_VERSION_TLS1_2 +#endif + ; #endif switch(conn_config->version) { @@ -292,7 +298,11 @@ mbed_set_ssl_version_min_max(struct Curl_easy *data, case CURL_SSLVERSION_TLSv1_1: #endif case CURL_SSLVERSION_TLSv1_2: - /* ver_min = MBEDTLS_SSL_VERSION_TLS1_2; */ +#if MBEDTLS_VERSION_NUMBER < 0x03020000 + ver_min = MBEDTLS_SSL_MINOR_VERSION_3; /* TLS 1.2 */ +#else + ver_min = MBEDTLS_SSL_VERSION_TLS1_2; +#endif break; case CURL_SSLVERSION_TLSv1_3: #ifdef HAS_TLS13_SUPPORT @@ -314,7 +324,11 @@ mbed_set_ssl_version_min_max(struct Curl_easy *data, break; #endif case CURL_SSLVERSION_MAX_TLSv1_2: - /* ver_max = MBEDTLS_SSL_VERSION_TLS1_2; */ +#if MBEDTLS_VERSION_NUMBER < 0x03020000 + ver_max = MBEDTLS_SSL_MINOR_VERSION_3; /* TLS 1.2 */ +#else + ver_max = MBEDTLS_SSL_VERSION_TLS1_2; +#endif break; #if MBEDTLS_VERSION_NUMBER < 0x03000000 case CURL_SSLVERSION_MAX_TLSv1_1: @@ -726,6 +740,9 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->key, ssl_config->key_passwd); #endif + if(ret == 0 && !(mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_RSA) || + mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_ECKEY))) + ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); @@ -754,6 +771,9 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) (const unsigned char *)passwd, passwd ? strlen(passwd) : 0); #endif + if(ret == 0 && !(mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_RSA) || + mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_ECKEY))) + ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); @@ -762,10 +782,6 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_SSL_CERTPROBLEM; } } - - if(ret == 0 && !(mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_RSA) || - mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_ECKEY))) - ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; } /* Load the CRL */ @@ -1211,7 +1227,7 @@ static ssize_t mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, len = backend->send_blocked_len; } - ret = mbedtls_ssl_write(&backend->ssl, (unsigned char *)mem, len); + ret = mbedtls_ssl_write(&backend->ssl, (const unsigned char *)mem, len); if(ret < 0) { CURL_TRC_CF(data, cf, "mbedtls_ssl_write(len=%zu) -> -0x%04X", @@ -1440,16 +1456,12 @@ static CURLcode mbedtls_random(struct Curl_easy *data, #endif } -static CURLcode -mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, - bool nonblocking, - bool *done) +static CURLcode mbedtls_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { CURLcode retcode; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - timediff_t timeout_ms; - int what; /* check if the connection has already been established */ if(ssl_connection_complete == connssl->state) { @@ -1457,73 +1469,20 @@ mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, return CURLE_OK; } - if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we are allowed */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); + *done = FALSE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } + if(ssl_connect_1 == connssl->connecting_state) { retcode = mbed_connect_step1(cf, data); if(retcode) return retcode; } - while(ssl_connect_2 == connssl->connecting_state) { - - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* 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); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if - * this connection is part of a multi handle and this loop would - * execute again. This permits the owner of a multi handle to - * abort a connection attempt 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; + if(ssl_connect_2 == connssl->connecting_state) { retcode = mbed_connect_step2(cf, data); - if(retcode || - (nonblocking && (ssl_connect_2 == connssl->connecting_state))) + if(retcode) return retcode; - - } /* repeat step2 until all transactions are done. */ + } if(ssl_connect_3 == connssl->connecting_state) { /* For tls1.3 we get notified about new sessions */ @@ -1548,34 +1507,6 @@ mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, connssl->state = ssl_connection_complete; *done = TRUE; } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -static CURLcode mbedtls_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - return mbed_connect_common(cf, data, TRUE, done); -} - - -static CURLcode mbedtls_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - CURLcode retcode; - bool done = FALSE; - - retcode = mbed_connect_common(cf, data, FALSE, &done); - if(retcode) - return retcode; - - DEBUGASSERT(done); return CURLE_OK; } @@ -1682,7 +1613,6 @@ const struct Curl_ssl Curl_ssl_mbedtls = { mbedtls_random, /* random */ NULL, /* cert_status_request */ mbedtls_connect, /* connect */ - mbedtls_connect_nonblocking, /* connect_nonblocking */ Curl_ssl_adjust_pollset, /* adjust_pollset */ mbedtls_get_internals, /* get_internals */ mbedtls_close, /* close_one */ diff --git a/Utilities/cmcurl/lib/vtls/mbedtls.h b/Utilities/cmcurl/lib/vtls/mbedtls.h index d8a0a06eb6..3876fe36fc 100644 --- a/Utilities/cmcurl/lib/vtls/mbedtls.h +++ b/Utilities/cmcurl/lib/vtls/mbedtls.h @@ -24,7 +24,7 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_MBEDTLS diff --git a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c index b96a904fcb..682c221852 100644 --- a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c +++ b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c @@ -22,7 +22,7 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_MBEDTLS) && \ ((defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)) || \ @@ -36,10 +36,10 @@ #endif #include "mbedtls_threadlock.h" -#include "curl_printf.h" -#include "curl_memory.h" +#include "../curl_printf.h" +#include "../curl_memory.h" /* The last #include file should be: */ -#include "memdebug.h" +#include "../memdebug.h" /* number of thread locks */ #define NUMT 2 diff --git a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h index 484626852f..9402af6e41 100644 --- a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h +++ b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h @@ -24,7 +24,7 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_MBEDTLS diff --git a/Utilities/cmcurl/lib/vtls/openssl.c b/Utilities/cmcurl/lib/vtls/openssl.c index 71f4b0d1d1..76919b611c 100644 --- a/Utilities/cmcurl/lib/vtls/openssl.c +++ b/Utilities/cmcurl/lib/vtls/openssl.c @@ -27,14 +27,14 @@ * but vtls.c should ever call or use these functions. */ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_QUICHE) || defined(USE_OPENSSL) #include /* Wincrypt must be included before anything that could include OpenSSL. */ -#if defined(USE_WIN32_CRYPTO) +#ifdef USE_WIN32_CRYPTO #include /* Undefine wincrypt conflicting symbols for BoringSSL. */ #undef X509_NAME @@ -45,25 +45,27 @@ #undef OCSP_RESPONSE #endif -#include "urldata.h" -#include "sendf.h" -#include "formdata.h" /* for the boundary function */ -#include "url.h" /* for the ssl config check function */ -#include "inet_pton.h" +#include "../urldata.h" +#include "../sendf.h" +#include "../formdata.h" /* for the boundary function */ +#include "../url.h" /* for the ssl config check function */ +#include "../curlx/inet_pton.h" #include "openssl.h" -#include "connect.h" -#include "slist.h" -#include "select.h" +#include "../connect.h" +#include "../slist.h" +#include "../select.h" #include "vtls.h" #include "vtls_int.h" #include "vtls_scache.h" -#include "vauth/vauth.h" +#include "../vauth/vauth.h" #include "keylog.h" -#include "strcase.h" +#include "../strcase.h" #include "hostcheck.h" -#include "multiif.h" -#include "strerror.h" -#include "curl_printf.h" +#include "../multiif.h" +#include "../curlx/strparse.h" +#include "../strdup.h" +#include "../strerror.h" +#include "../curl_printf.h" #include #include @@ -83,7 +85,7 @@ #include #include -#if defined(HAVE_SSL_SET1_ECH_CONFIG_LIST) +#ifdef HAVE_SSL_SET1_ECH_CONFIG_LIST #define USE_ECH_OPENSSL #endif @@ -93,28 +95,38 @@ # endif #endif /* USE_ECH_OPENSSL */ -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_OCSP) +#ifndef OPENSSL_NO_OCSP #include #endif -#if (OPENSSL_VERSION_NUMBER >= 0x0090700fL) && /* 0.9.7 or later */ \ - !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_UI_CONSOLE) +#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_UI_CONSOLE) #define USE_OPENSSL_ENGINE #include #endif -#if OPENSSL_VERSION_NUMBER >= 0x03000000fL && !defined(OPENSSL_NO_UI_CONSOLE) +#ifdef LIBRESSL_VERSION_NUMBER +# /* As of LibreSSL 2.0.0-4.0.0: OPENSSL_VERSION_NUMBER == 0x20000000L */ +# if LIBRESSL_VERSION_NUMBER < 0x2090100fL /* 2019-04-13 */ +# error "LibreSSL 2.9.1 or later required" +# endif +#elif OPENSSL_VERSION_NUMBER < 0x1000201fL /* 2015-03-19 */ +# error "OpenSSL 1.0.2a or later required" +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x3000000fL && !defined(OPENSSL_NO_UI_CONSOLE) #include #include /* this is used in the following conditions to make them easier to read */ #define OPENSSL_HAS_PROVIDERS + +static void ossl_provider_cleanup(struct Curl_easy *data); #endif -#include "warnless.h" +#include "../curlx/warnless.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* Uncomment the ALLOW_RENEG line to a real #define if you want to allow TLS renegotiations when built with BoringSSL. Renegotiating is non-compliant @@ -131,49 +143,24 @@ #include #endif -#if OPENSSL_VERSION_NUMBER >= 0x00909000L -#define SSL_METHOD_QUAL const +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +#define OSSL_UI_METHOD_CAST(x) (x) #else -#define SSL_METHOD_QUAL +#define OSSL_UI_METHOD_CAST(x) CURL_UNCONST(x) #endif -#if (OPENSSL_VERSION_NUMBER >= 0x10000000L) -#define HAVE_ERR_REMOVE_THREAD_STATE 1 -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && /* OpenSSL 1.1.0+ */ \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x20700000L) -#define SSLEAY_VERSION_NUMBER OPENSSL_VERSION_NUMBER +#if OPENSSL_VERSION_NUMBER >= 0x10100000L /* OpenSSL 1.1.0+ and LibreSSL */ #define HAVE_X509_GET0_EXTENSIONS 1 /* added in 1.1.0 -pre1 */ #define HAVE_OPAQUE_EVP_PKEY 1 /* since 1.1.0 -pre3 */ #define HAVE_OPAQUE_RSA_DSA_DH 1 /* since 1.1.0 -pre5 */ -#define CONST_EXTS const #define HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED 1 - -/* funny typecast define due to difference in API */ -#ifdef LIBRESSL_VERSION_NUMBER -#define ARG2_X509_signature_print (X509_ALGOR *) -#else -#define ARG2_X509_signature_print -#endif - #else /* For OpenSSL before 1.1.0 */ #define ASN1_STRING_get0_data(x) ASN1_STRING_data(x) #define X509_get0_notBefore(x) X509_get_notBefore(x) #define X509_get0_notAfter(x) X509_get_notAfter(x) -#define CONST_EXTS /* nope */ -#ifndef LIBRESSL_VERSION_NUMBER #define OpenSSL_version_num() SSLeay() #endif -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) && /* 1.0.2 or later */ \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x20700000L) -#define HAVE_X509_GET0_SIGNATURE 1 -#endif #if OPENSSL_VERSION_NUMBER >= 0x10002003L && \ OPENSSL_VERSION_NUMBER <= 0x10002FFFL && \ @@ -181,11 +168,6 @@ #define HAVE_SSL_COMP_FREE_COMPRESSION_METHODS 1 #endif -#if (OPENSSL_VERSION_NUMBER < 0x0090808fL) -/* not present in older OpenSSL */ -#define OPENSSL_load_builtin_modules(x) -#endif - #if (OPENSSL_VERSION_NUMBER >= 0x30000000L) #define HAVE_EVP_PKEY_GET_PARAMS 1 #endif @@ -210,37 +192,32 @@ LIBRESSL_VERSION_NUMBER >= 0x3040100fL)) && \ !defined(OPENSSL_IS_BORINGSSL) #define HAVE_SSL_CTX_SET_CIPHERSUITES - #if !defined(OPENSSL_IS_AWSLC) + #ifndef OPENSSL_IS_AWSLC #define HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH #endif #endif -/* - * Whether SSL_CTX_set1_curves_list is available. - * OpenSSL: supported since 1.0.2, see - * 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) +/* Whether SSL_CTX_set1_sigalgs_list is available + * OpenSSL: supported since 1.0.2 (commit 0b362de5f575) + * BoringSSL: supported since 0.20240913.0 (commit 826ce15) + * LibreSSL: no */ -#if ((OPENSSL_VERSION_NUMBER >= 0x10002000L) && \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x20503000L)) || \ - defined(OPENSSL_IS_BORINGSSL) -#define HAVE_SSL_CTX_SET_EC_CURVES +#if (OPENSSL_VERSION_NUMBER >= 0x10002000L && \ + !defined(LIBRESSL_VERSION_NUMBER)) + #define HAVE_SSL_CTX_SET1_SIGALGS #endif -#if defined(LIBRESSL_VERSION_NUMBER) +#ifdef LIBRESSL_VERSION_NUMBER #define OSSL_PACKAGE "LibreSSL" #elif defined(OPENSSL_IS_BORINGSSL) #define OSSL_PACKAGE "BoringSSL" #elif defined(OPENSSL_IS_AWSLC) #define OSSL_PACKAGE "AWS-LC" +#elif (defined(USE_NGTCP2) && defined(USE_NGHTTP3) && \ + !defined(OPENSSL_QUIC_API2)) || defined(USE_MSH3) +#define OSSL_PACKAGE "quictls" #else -# if (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || defined(USE_MSH3) -# define OSSL_PACKAGE "quictls" -# else -# define OSSL_PACKAGE "OpenSSL" -#endif +#define OSSL_PACKAGE "OpenSSL" #endif #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) @@ -274,8 +251,6 @@ typedef int numcert_t; #endif #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x2070100fL) && \ !defined(OPENSSL_IS_BORINGSSL) && \ !defined(OPENSSL_IS_AWSLC) #define HAVE_OPENSSL_VERSION @@ -307,13 +282,6 @@ typedef unsigned long sslerr_t; #undef HAVE_SSL_X509_GET_SIGNATURE_NID #endif -/* What API version do we use? */ -#if defined(LIBRESSL_VERSION_NUMBER) -#define USE_PRE_1_1_API (LIBRESSL_VERSION_NUMBER < 0x2070000f) -#else /* !LIBRESSL_VERSION_NUMBER */ -#define USE_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L) -#endif /* !LIBRESSL_VERSION_NUMBER */ - static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl); static CURLcode push_certinfo(struct Curl_easy *data, @@ -377,10 +345,16 @@ static int asn1_object_dump(ASN1_OBJECT *a, char *buf, size_t len) static CURLcode X509V3_ext(struct Curl_easy *data, int certnum, - CONST_EXTS STACK_OF(X509_EXTENSION) *exts) + const STACK_OF(X509_EXTENSION) *extsarg) { int i; CURLcode result = CURLE_OK; +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ + !defined(LIBRESSL_VERSION_NUMBER) + const STACK_OF(X509_EXTENSION) *exts = extsarg; +#else + STACK_OF(X509_EXTENSION) *exts = CURL_UNCONST(extsarg); +#endif if((int)sk_X509_EXTENSION_num(exts) <= 0) /* no extensions, bail out */ @@ -469,7 +443,7 @@ static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl) if(result) break; -#if defined(HAVE_X509_GET0_SIGNATURE) && defined(HAVE_X509_GET0_EXTENSIONS) +#ifdef HAVE_X509_GET0_EXTENSIONS { const X509_ALGOR *sigalg = NULL; X509_PUBKEY *xpubkey = NULL; @@ -686,21 +660,19 @@ static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl) #ifdef USE_OPENSSL -#if USE_PRE_1_1_API -#if !defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER < 0x2070000fL +#if OPENSSL_VERSION_NUMBER < 0x10100000L #define BIO_set_init(x,v) ((x)->init=(v)) #define BIO_get_data(x) ((x)->ptr) #define BIO_set_data(x,v) ((x)->ptr=(v)) -#endif #define BIO_get_shutdown(x) ((x)->shutdown) #define BIO_set_shutdown(x,v) ((x)->shutdown=(v)) -#endif /* USE_PRE_1_1_API */ +#endif /* HAVE_PRE_1_1_API */ static int ossl_bio_cf_create(BIO *bio) { BIO_set_shutdown(bio, 1); BIO_set_init(bio, 1); -#if USE_PRE_1_1_API +#if OPENSSL_VERSION_NUMBER < 0x10100000L bio->num = -1; #endif BIO_set_data(bio, NULL); @@ -737,9 +709,11 @@ static long ossl_bio_cf_ctrl(BIO *bio, int cmd, long num, void *ptr) ret = 1; break; #ifdef BIO_CTRL_EOF - case BIO_CTRL_EOF: + case BIO_CTRL_EOF: { /* EOF has been reached on input? */ - return !cf->next || !cf->next->connected; + struct ssl_connect_data *connssl = cf->ctx; + return connssl->peer_closed; + } #endif default: ret = 0; @@ -817,7 +791,7 @@ static int ossl_bio_cf_in_read(BIO *bio, char *buf, int blen) return (int)nread; } -#if USE_PRE_1_1_API +#if OPENSSL_VERSION_NUMBER < 0x10100000L static BIO_METHOD ossl_bio_cf_meth_1_0 = { BIO_TYPE_MEM, @@ -863,14 +837,6 @@ static void ossl_bio_cf_method_free(BIO_METHOD *m) #endif -/* - * Number of bytes to read from the random number seed file. This must be - * a finite value (because some entropy "files" like /dev/urandom have - * an infinite length), but must be large enough to provide enough - * entropy to properly seed OpenSSL's PRNG. - */ -#define RAND_LOAD_LENGTH 1024 - #ifdef HAVE_KEYLOG_CALLBACK static void ossl_keylog_callback(const SSL *ssl, const char *line) { @@ -894,9 +860,7 @@ ossl_log_tls12_secret(const SSL *ssl, bool *keylog_done) if(!session || *keylog_done) return; -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x20700000L) +#if OPENSSL_VERSION_NUMBER >= 0x10100000L /* 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); @@ -943,15 +907,15 @@ static const char *SSL_ERROR_to_str(int err) return "SSL_ERROR_WANT_CONNECT"; case SSL_ERROR_WANT_ACCEPT: return "SSL_ERROR_WANT_ACCEPT"; -#if defined(SSL_ERROR_WANT_ASYNC) +#ifdef SSL_ERROR_WANT_ASYNC case SSL_ERROR_WANT_ASYNC: return "SSL_ERROR_WANT_ASYNC"; #endif -#if defined(SSL_ERROR_WANT_ASYNC_JOB) +#ifdef SSL_ERROR_WANT_ASYNC_JOB case SSL_ERROR_WANT_ASYNC_JOB: return "SSL_ERROR_WANT_ASYNC_JOB"; #endif -#if defined(SSL_ERROR_WANT_EARLY) +#ifdef SSL_ERROR_WANT_EARLY case SSL_ERROR_WANT_EARLY: return "SSL_ERROR_WANT_EARLY"; #endif @@ -994,14 +958,14 @@ static char *ossl_strerror(unsigned long error, char *buf, size_t size) } static int passwd_callback(char *buf, int num, int encrypting, - void *global_passwd) + void *password) { DEBUGASSERT(0 == encrypting); - if(!encrypting && num >= 0) { - int klen = curlx_uztosi(strlen((char *)global_passwd)); + if(!encrypting && num >= 0 && password) { + int klen = curlx_uztosi(strlen((char *)password)); if(num > klen) { - memcpy(buf, global_passwd, klen + 1); + memcpy(buf, password, klen + 1); return klen; } } @@ -1041,13 +1005,13 @@ static CURLcode ossl_seed(struct Curl_easy *data) size_t len = sizeof(randb); size_t i, i_max; for(i = 0, i_max = len / sizeof(struct curltime); i < i_max; ++i) { - struct curltime tv = Curl_now(); + struct curltime tv = curlx_now(); Curl_wait_ms(1); 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) * + tv.tv_sec ^= ((curlx_now().tv_sec + (time_t)curlx_now().tv_usec) * (time_t)(i + 3)) << 8; - tv.tv_usec ^= (int) ((Curl_now().tv_sec + (time_t)Curl_now().tv_usec) * + tv.tv_usec ^= (int) ((curlx_now().tv_sec + (time_t)curlx_now().tv_usec) * (time_t)(i + 4)) << 16; memcpy(&randb[i * sizeof(struct curltime)], &tv, sizeof(struct curltime)); @@ -1055,6 +1019,14 @@ static CURLcode ossl_seed(struct Curl_easy *data) RAND_add(randb, (int)len, (double)len/2); } while(!rand_enough()); + /* + * Number of bytes to read from the random number seed file. This must be + * a finite value (because some entropy "files" like /dev/urandom have + * an infinite length), but must be large enough to provide enough + * entropy to properly seed OpenSSL's PRNG. + */ +# define RAND_LOAD_LENGTH 1024 + { /* generates a default path for the random seed file */ char fname[256]; @@ -1154,7 +1126,7 @@ static bool is_pkcs11_uri(const char *string) #endif static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine); -#if !defined(USE_OPENSSL_ENGINE) && defined(OPENSSL_HAS_PROVIDERS) +#if defined(OPENSSL_HAS_PROVIDERS) static CURLcode ossl_set_provider(struct Curl_easy *data, const char *provider); #endif @@ -1177,7 +1149,7 @@ static int use_certificate_blob(SSL_CTX *ctx, const struct curl_blob *blob, else if(type == SSL_FILETYPE_PEM) { /* ERR_R_PEM_LIB; */ x = PEM_read_bio_X509(in, NULL, - passwd_callback, (void *)key_passwd); + passwd_callback, CURL_UNCONST(key_passwd)); } else { ret = 0; @@ -1207,7 +1179,7 @@ static int use_privatekey_blob(SSL_CTX *ctx, const struct curl_blob *blob, if(type == SSL_FILETYPE_PEM) pkey = PEM_read_bio_PrivateKey(in, NULL, passwd_callback, - (void *)key_passwd); + CURL_UNCONST(key_passwd)); else if(type == SSL_FILETYPE_ASN1) pkey = d2i_PrivateKey_bio(in, NULL); else @@ -1227,13 +1199,8 @@ static int use_certificate_chain_blob(SSL_CTX *ctx, const struct curl_blob *blob, const char *key_passwd) { -/* SSL_CTX_add1_chain_cert introduced in OpenSSL 1.0.2 */ -#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) && /* OpenSSL 1.0.2 or later */ \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - (LIBRESSL_VERSION_NUMBER < 0x2090100fL)) /* LibreSSL 2.9.1 or later */ int ret = 0; X509 *x = NULL; - void *passwd_callback_userdata = (void *)key_passwd; BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len)); if(!in) return CURLE_OUT_OF_MEMORY; @@ -1241,7 +1208,7 @@ use_certificate_chain_blob(SSL_CTX *ctx, const struct curl_blob *blob, ERR_clear_error(); x = PEM_read_bio_X509_AUX(in, NULL, - passwd_callback, (void *)key_passwd); + passwd_callback, CURL_UNCONST(key_passwd)); if(!x) goto end; @@ -1260,7 +1227,7 @@ use_certificate_chain_blob(SSL_CTX *ctx, const struct curl_blob *blob, } while((ca = PEM_read_bio_X509(in, NULL, passwd_callback, - passwd_callback_userdata)) + CURL_UNCONST(key_passwd))) != NULL) { if(!SSL_CTX_add0_chain_cert(ctx, ca)) { @@ -1282,12 +1249,6 @@ end: X509_free(x); BIO_free(in); return ret; -#else - (void)ctx; /* unused */ - (void)blob; /* unused */ - (void)key_passwd; /* unused */ - return 0; -#endif } static @@ -1309,9 +1270,7 @@ int cert_stuff(struct Curl_easy *data, if(cert_file || cert_blob || (file_type == SSL_FILETYPE_ENGINE) || (file_type == SSL_FILETYPE_PROVIDER)) { SSL *ssl; - X509 *x509 = NULL; - EVP_PKEY *pri = NULL; - STACK_OF(X509) *ca = NULL; + X509 *x509; int cert_done = 0; int cert_use_result; @@ -1385,7 +1344,7 @@ int cert_stuff(struct Curl_easy *data, /* Does the engine supports LOAD_CERT_CTRL ? */ if(!ENGINE_ctrl(data->state.engine, ENGINE_CTRL_GET_CMD_FROM_NAME, - 0, (void *)cmd_name, NULL)) { + 0, CURL_UNCONST(cmd_name), NULL)) { failf(data, "ssl engine does not support loading certificates"); return 0; } @@ -1420,7 +1379,8 @@ int cert_stuff(struct Curl_easy *data, } } break; -#elif defined(OPENSSL_HAS_PROVIDERS) +#endif +#if defined(OPENSSL_HAS_PROVIDERS) /* fall through to compatible provider */ case SSL_FILETYPE_PROVIDER: { @@ -1436,10 +1396,11 @@ int cert_stuff(struct Curl_easy *data, if(data->state.provider) { /* Load the certificate from the provider */ - OSSL_STORE_CTX *store = NULL; OSSL_STORE_INFO *info = NULL; X509 *cert = NULL; - store = OSSL_STORE_open(cert_file, NULL, NULL, NULL, NULL); + OSSL_STORE_CTX *store = + OSSL_STORE_open_ex(cert_file, data->state.libctx, + NULL, NULL, NULL, NULL, NULL, NULL); if(!store) { failf(data, "Failed to open OpenSSL store: %s", ossl_strerror(ERR_get_error(), error_buffer, @@ -1452,29 +1413,20 @@ int cert_stuff(struct Curl_easy *data, sizeof(error_buffer))); } - for(info = OSSL_STORE_load(store); - info != NULL; - info = OSSL_STORE_load(store)) { + info = OSSL_STORE_load(store); + if(info) { int ossl_type = OSSL_STORE_INFO_get_type(info); - if(ossl_type == OSSL_STORE_INFO_CERT) { + if(ossl_type == OSSL_STORE_INFO_CERT) cert = OSSL_STORE_INFO_get1_CERT(info); - } - else { - failf(data, "Ignoring object not matching our type: %d", - ossl_type); - OSSL_STORE_INFO_free(info); - continue; - } OSSL_STORE_INFO_free(info); - break; } OSSL_STORE_close(store); if(!cert) { failf(data, "No cert found in the openssl store: %s", ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer))); - goto fail; + return 0; } if(SSL_CTX_use_certificate(ctx, cert) != 1) { @@ -1491,15 +1443,14 @@ int cert_stuff(struct Curl_easy *data, } } break; -#else - failf(data, "file type ENG nor PROV for certificate not implemented"); - return 0; #endif case SSL_FILETYPE_PKCS12: { BIO *cert_bio = NULL; PKCS12 *p12 = NULL; + EVP_PKEY *pri; + STACK_OF(X509) *ca = NULL; if(cert_blob) { cert_bio = BIO_new_mem_buf(cert_blob->data, (int)(cert_blob->len)); if(!cert_bio) { @@ -1650,7 +1601,7 @@ fail: if(data->state.engine) { UI_METHOD *ui_method = - UI_create_method((char *)"curl user interface"); + UI_create_method(OSSL_UI_METHOD_CAST("curl user interface")); if(!ui_method) { failf(data, "unable do create " OSSL_PACKAGE " user-interface method"); @@ -1681,14 +1632,15 @@ fail: } } break; -#elif defined(OPENSSL_HAS_PROVIDERS) +#endif +#if defined(OPENSSL_HAS_PROVIDERS) /* fall through to compatible provider */ case SSL_FILETYPE_PROVIDER: { /* Implicitly use pkcs11 provider if none was provided and the - * cert_file is a PKCS#11 URI */ + * key_file is a PKCS#11 URI */ if(!data->state.provider) { - if(is_pkcs11_uri(cert_file)) { + if(is_pkcs11_uri(key_file)) { if(ossl_set_provider(data, "pkcs11") != CURLE_OK) { return 0; } @@ -1701,7 +1653,7 @@ fail: OSSL_STORE_CTX *store = NULL; OSSL_STORE_INFO *info = NULL; UI_METHOD *ui_method = - UI_create_method((char *)"curl user interface"); + UI_create_method(OSSL_UI_METHOD_CAST("curl user interface")); if(!ui_method) { failf(data, "unable do create " OSSL_PACKAGE " user-interface method"); @@ -1712,7 +1664,9 @@ fail: UI_method_set_reader(ui_method, ssl_ui_reader); UI_method_set_writer(ui_method, ssl_ui_writer); - store = OSSL_STORE_open(key_file, ui_method, NULL, NULL, NULL); + store = OSSL_STORE_open_ex(key_file, data->state.libctx, + data->state.propq, ui_method, NULL, NULL, + NULL, NULL); if(!store) { failf(data, "Failed to open OpenSSL store: %s", ossl_strerror(ERR_get_error(), error_buffer, @@ -1725,22 +1679,13 @@ fail: sizeof(error_buffer))); } - for(info = OSSL_STORE_load(store); - info != NULL; - info = OSSL_STORE_load(store)) { + info = OSSL_STORE_load(store); + if(info) { int ossl_type = OSSL_STORE_INFO_get_type(info); - if(ossl_type == OSSL_STORE_INFO_PKEY) { + if(ossl_type == OSSL_STORE_INFO_PKEY) priv_key = OSSL_STORE_INFO_get1_PKEY(info); - } - else { - failf(data, "Ignoring object not matching our type: %d", - ossl_type); - OSSL_STORE_INFO_free(info); - continue; - } OSSL_STORE_INFO_free(info); - break; } OSSL_STORE_close(store); UI_destroy_method(ui_method); @@ -1748,7 +1693,7 @@ fail: failf(data, "No private key found in the openssl store: %s", ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer))); - goto fail; + return 0; } if(SSL_CTX_use_PrivateKey(ctx, priv_key) != 1) { @@ -1766,9 +1711,6 @@ fail: } } break; -#else - failf(data, "file type ENG nor PROV for private key not implemented"); - return 0; #endif case SSL_FILETYPE_PKCS12: @@ -1845,11 +1787,11 @@ static CURLcode x509_name_oneline(X509_NAME *a, struct dynbuf *d) CURLcode result = CURLE_OUT_OF_MEMORY; if(bio_out) { - Curl_dyn_reset(d); + curlx_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); + result = curlx_dyn_addn(d, biomem->data, biomem->length); BIO_free(bio_out); } } @@ -1864,8 +1806,7 @@ static CURLcode x509_name_oneline(X509_NAME *a, struct dynbuf *d) */ static int ossl_init(void) { -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ - (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER >= 0x2070000fL) +#if OPENSSL_VERSION_NUMBER >= 0x10100000L const uint64_t flags = #ifdef OPENSSL_INIT_ENGINE_ALL_BUILTIN /* not present in BoringSSL */ @@ -1915,8 +1856,7 @@ static int ossl_init(void) /* Global cleanup */ static void ossl_cleanup(void) { -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ - (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER >= 0x2070000fL) +#if OPENSSL_VERSION_NUMBER >= 0x10100000L /* OpenSSL 1.1 deprecates all these cleanup functions and turns them into no-ops in OpenSSL 1.0 compatibility mode */ #else @@ -1932,11 +1872,7 @@ static void ossl_cleanup(void) ERR_free_strings(); /* Free thread local error state, destroying hash upon zero refcount */ -#ifdef HAVE_ERR_REMOVE_THREAD_STATE ERR_remove_thread_state(NULL); -#else - ERR_remove_state(0); -#endif /* Free all memory allocated by all configuration modules */ CONF_modules_free(); @@ -1949,47 +1885,39 @@ static void ossl_cleanup(void) Curl_tls_keylog_close(); } -/* Selects an OpenSSL crypto engine +/* Selects an OpenSSL crypto engine or provider. */ -static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine) +static CURLcode ossl_set_engine(struct Curl_easy *data, const char *name) { #ifdef USE_OPENSSL_ENGINE - ENGINE *e; + CURLcode result = CURLE_SSL_ENGINE_NOTFOUND; + ENGINE *e = ENGINE_by_id(name); -#if OPENSSL_VERSION_NUMBER >= 0x00909000L - e = ENGINE_by_id(engine); -#else - /* avoid memory leak */ - for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)) { - const char *e_id = ENGINE_get_id(e); - if(!strcmp(engine, e_id)) - break; + if(e) { + + if(data->state.engine) { + ENGINE_finish(data->state.engine); + ENGINE_free(data->state.engine); + data->state.engine = NULL; + } + if(!ENGINE_init(e)) { + char buf[256]; + + ENGINE_free(e); + failf(data, "Failed to initialise SSL Engine '%s': %s", + name, ossl_strerror(ERR_get_error(), buf, sizeof(buf))); + result = CURLE_SSL_ENGINE_INITFAILED; + e = NULL; + } + data->state.engine = e; + return result; } #endif - - if(!e) { - failf(data, "SSL Engine '%s' not found", engine); - return CURLE_SSL_ENGINE_NOTFOUND; - } - - if(data->state.engine) { - ENGINE_finish(data->state.engine); - ENGINE_free(data->state.engine); - data->state.engine = NULL; - } - if(!ENGINE_init(e)) { - char buf[256]; - - ENGINE_free(e); - failf(data, "Failed to initialise SSL Engine '%s': %s", - engine, ossl_strerror(ERR_get_error(), buf, sizeof(buf))); - return CURLE_SSL_ENGINE_INITFAILED; - } - data->state.engine = e; - return CURLE_OK; +#ifdef OPENSSL_HAS_PROVIDERS + return ossl_set_provider(data, name); #else - (void)engine; - failf(data, "SSL Engine not supported"); + (void)name; + failf(data, "OpenSSL engine not found"); return CURLE_SSL_ENGINE_NOTFOUND; #endif } @@ -2038,33 +1966,97 @@ static struct curl_slist *ossl_engines_list(struct Curl_easy *data) return list; } -#if !defined(USE_OPENSSL_ENGINE) && defined(OPENSSL_HAS_PROVIDERS) -/* Selects an OpenSSL crypto provider - */ -static CURLcode ossl_set_provider(struct Curl_easy *data, const char *provider) -{ - OSSL_PROVIDER *pkcs11_provider = NULL; - char error_buffer[256]; +#if defined(OPENSSL_HAS_PROVIDERS) - if(OSSL_PROVIDER_available(NULL, provider)) { - /* already loaded through the configuration - no action needed */ - data->state.provider = TRUE; +static void ossl_provider_cleanup(struct Curl_easy *data) +{ + if(data->state.baseprov) { + OSSL_PROVIDER_unload(data->state.baseprov); + data->state.baseprov = NULL; + } + if(data->state.provider) { + OSSL_PROVIDER_unload(data->state.provider); + data->state.provider = NULL; + } + OSSL_LIB_CTX_free(data->state.libctx); + data->state.libctx = NULL; + Curl_safefree(data->state.propq); + data->state.provider_loaded = FALSE; +} + +#define MAX_PROVIDER_LEN 128 /* reasonable */ + +/* Selects an OpenSSL crypto provider. + * + * A provider might need an associated property, a string passed on to + * OpenSSL. Specify this as [PROVIDER][:PROPERTY]: separate the name and the + * property with a colon. No colon means no property is set. + * + * An example provider + property looks like "tpm2:?provider=tpm2". + */ +static CURLcode ossl_set_provider(struct Curl_easy *data, const char *iname) +{ + char name[MAX_PROVIDER_LEN + 1]; + struct Curl_str prov; + const char *propq = NULL; + + if(!iname) { + /* clear and cleanup provider use */ + ossl_provider_cleanup(data); return CURLE_OK; } - if(data->state.provider_failed) { - return CURLE_SSL_ENGINE_NOTFOUND; + if(curlx_str_until(&iname, &prov, MAX_PROVIDER_LEN, ':')) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(!curlx_str_single(&iname, ':')) + /* there was a colon, get the propq until the end of string */ + propq = iname; + + /* we need the name in a buffer, null-terminated */ + memcpy(name, curlx_str(&prov), curlx_strlen(&prov)); + name[curlx_strlen(&prov)] = 0; + + if(!data->state.libctx) { + OSSL_LIB_CTX *libctx = OSSL_LIB_CTX_new(); + if(!libctx) + return CURLE_OUT_OF_MEMORY; + if(propq) { + data->state.propq = strdup(propq); + if(!data->state.propq) { + OSSL_LIB_CTX_free(libctx); + return CURLE_OUT_OF_MEMORY; + } + } + data->state.libctx = libctx; } - pkcs11_provider = OSSL_PROVIDER_try_load(NULL, provider, 1); - if(!pkcs11_provider) { + if(OSSL_PROVIDER_available(data->state.libctx, name)) { + /* already loaded through the configuration - no action needed */ + data->state.provider_loaded = TRUE; + return CURLE_OK; + } + + data->state.provider = + OSSL_PROVIDER_try_load(data->state.libctx, name, 1); + if(!data->state.provider) { + char error_buffer[256]; failf(data, "Failed to initialize provider: %s", ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer))); - /* Do not attempt to load it again */ - data->state.provider_failed = TRUE; + ossl_provider_cleanup(data); return CURLE_SSL_ENGINE_NOTFOUND; } - data->state.provider = TRUE; + + /* load the base provider as well */ + data->state.baseprov = + OSSL_PROVIDER_try_load(data->state.libctx, "base", 1); + if(!data->state.baseprov) { + ossl_provider_cleanup(data); + failf(data, "Failed to load base"); + return CURLE_SSL_ENGINE_NOTFOUND; + } + else + data->state.provider_loaded = TRUE; return CURLE_OK; } #endif @@ -2224,8 +2216,10 @@ static void ossl_close_all(struct Curl_easy *data) #else (void)data; #endif -#if !defined(HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED) && \ - defined(HAVE_ERR_REMOVE_THREAD_STATE) +#ifdef OPENSSL_HAS_PROVIDERS + ossl_provider_cleanup(data); +#endif +#ifndef HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED /* OpenSSL 1.0.1 and 1.0.2 build an error queue that is stored per-thread so we need to clean it here in case the thread will be killed. All OpenSSL code should extract the error in association with the error so clearing @@ -2302,14 +2296,14 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, hostlen = strlen(peer->hostname); switch(peer->type) { case CURL_SSL_PEER_IPV4: - if(!Curl_inet_pton(AF_INET, peer->hostname, &addr)) + if(!curlx_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)) + if(!curlx_inet_pton(AF_INET6, peer->hostname, &addr)) return CURLE_PEER_FAILED_VERIFICATION; target = GEN_IPADD; addrlen = sizeof(struct in6_addr); @@ -2355,7 +2349,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, /* only check alternatives of the same type the target is */ if(check->type == target) { /* get data and length */ - const char *altptr = (char *)ASN1_STRING_get0_data(check->d.ia5); + const char *altptr = (const char *)ASN1_STRING_get0_data(check->d.ia5); size_t altlen = (size_t) ASN1_STRING_length(check->d.ia5); switch(target) { @@ -2441,7 +2435,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, if(tmp) { if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) { cnlen = ASN1_STRING_length(tmp); - cn = (unsigned char *) ASN1_STRING_get0_data(tmp); + cn = (unsigned char *)CURL_UNCONST(ASN1_STRING_get0_data(tmp)); } else { /* not a UTF8 name */ cnlen = ASN1_STRING_to_UTF8(&cn, tmp); @@ -2483,14 +2477,13 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, return result; } -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) +#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP) static CURLcode verifystatus(struct Curl_cfilter *cf, struct Curl_easy *data, struct ossl_ctx *octx) { int i, ocsp_status; -#if defined(OPENSSL_IS_AWSLC) +#ifdef OPENSSL_IS_AWSLC const uint8_t *status; #else unsigned char *status; @@ -2549,34 +2542,6 @@ static CURLcode verifystatus(struct Curl_cfilter *cf, } 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 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. */ - - /* First make sure the peer cert chain includes both a peer and an issuer, - and the OCSP response contains a responder cert. */ - if(sk_X509_num(ch) >= 2 && sk_X509_num(br->certs) >= 1) { - X509 *responder = sk_X509_value(br->certs, sk_X509_num(br->certs) - 1); - - /* Find issuer of responder cert and add it to the OCSP response chain */ - for(i = 0; i < sk_X509_num(ch); i++) { - X509 *issuer = sk_X509_value(ch, i); - if(X509_check_issued(issuer, responder) == X509_V_OK) { - if(!OCSP_basic_add1_cert(br, issuer)) { - failf(data, "Could not add issuer cert to OCSP response"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } - } - } - } -#endif - if(OCSP_basic_verify(br, ch, st, 0) <= 0) { failf(data, "OCSP response verification failed"); result = CURLE_SSL_INVALIDCERTSTATUS; @@ -2848,15 +2813,15 @@ static void ossl_trace(int direction, int ssl_ver, int content_type, tls_rt_name = ""; if(content_type == SSL3_RT_CHANGE_CIPHER_SPEC) { - msg_type = *(char *)buf; + msg_type = *(const char *)buf; msg_name = "Change cipher spec"; } else if(content_type == SSL3_RT_ALERT) { - msg_type = (((char *)buf)[0] << 8) + ((char *)buf)[1]; + msg_type = (((const char *)buf)[0] << 8) + ((const char *)buf)[1]; msg_name = SSL_alert_desc_string_long(msg_type); } else { - msg_type = *(char *)buf; + msg_type = *(const char *)buf; msg_name = ssl_msg_type(ssl_ver, msg_type); } @@ -2868,7 +2833,7 @@ static void ossl_trace(int direction, int ssl_ver, int content_type, } Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT : - CURLINFO_SSL_DATA_IN, (char *)buf, len); + CURLINFO_SSL_DATA_IN, (const char *)buf, len); (void) ssl; } #endif @@ -2876,9 +2841,8 @@ static void ossl_trace(int direction, int ssl_ver, int content_type, #ifdef USE_OPENSSL /* ====================================================== */ -/* Check for OpenSSL 1.0.2 which has ALPN support. */ -#if OPENSSL_VERSION_NUMBER >= 0x10002000L \ - && !defined(OPENSSL_NO_TLSEXT) +/* Check for ALPN support. */ +#ifndef OPENSSL_NO_TLSEXT # define HAS_ALPN_OPENSSL #endif @@ -3019,20 +2983,10 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, #endif FALLTHROUGH(); case CURL_SSLVERSION_TLSv1_2: -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1_1; -#else - failf(data, OSSL_PACKAGE " was built without TLS 1.2 support"); - return CURLE_NOT_BUILT_IN; -#endif FALLTHROUGH(); case CURL_SSLVERSION_TLSv1_1: -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1; -#else - failf(data, OSSL_PACKAGE " was built without TLS 1.1 support"); - return CURLE_NOT_BUILT_IN; -#endif FALLTHROUGH(); case CURL_SSLVERSION_TLSv1_0: case CURL_SSLVERSION_TLSv1: @@ -3041,14 +2995,10 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, switch(ssl_version_max) { case CURL_SSLVERSION_MAX_TLSv1_0: -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1_1; -#endif FALLTHROUGH(); case CURL_SSLVERSION_MAX_TLSv1_1: -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1_2; -#endif FALLTHROUGH(); case CURL_SSLVERSION_MAX_TLSv1_2: #ifdef TLS1_3_VERSION @@ -3072,10 +3022,13 @@ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, const char *ssl_peer_key, SSL_SESSION *session, int ietf_tls_id, - const char *alpn) + const char *alpn, + unsigned char *quic_tp, + size_t quic_tp_len) { const struct ssl_config_data *config; unsigned char *der_session_buf = NULL; + unsigned char *qtp_clone = NULL; CURLcode result = CURLE_OK; if(!cf || !data) @@ -3086,6 +3039,7 @@ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, struct Curl_ssl_session *sc_session = NULL; size_t der_session_size; unsigned char *der_session_ptr; + size_t earlydata_max = 0; der_session_size = i2d_SSL_SESSION(session, NULL); if(der_session_size == 0) { @@ -3105,11 +3059,23 @@ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, goto out; } - result = Curl_ssl_session_create(der_session_buf, der_session_size, - ietf_tls_id, alpn, - (curl_off_t)time(NULL) + - SSL_SESSION_get_timeout(session), 0, - &sc_session); +#ifdef HAVE_OPENSSL_EARLYDATA + earlydata_max = SSL_SESSION_get_max_early_data(session); +#endif + if(quic_tp && quic_tp_len) { + qtp_clone = Curl_memdup0((char *)quic_tp, quic_tp_len); + if(!qtp_clone) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + + result = Curl_ssl_session_create2(der_session_buf, der_session_size, + ietf_tls_id, alpn, + (curl_off_t)time(NULL) + + SSL_SESSION_get_timeout(session), + earlydata_max, qtp_clone, quic_tp_len, + &sc_session); der_session_buf = NULL; /* took ownership of sdata */ if(!result) { result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session); @@ -3132,7 +3098,8 @@ static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) struct Curl_easy *data = CF_DATA_CURRENT(cf); struct ssl_connect_data *connssl = cf->ctx; Curl_ossl_add_session(cf, data, connssl->peer.scache_key, ssl_sessionid, - SSL_version(ssl), connssl->negotiated.alpn); + SSL_version(ssl), connssl->negotiated.alpn, + NULL, 0); } return 0; } @@ -3193,7 +3160,7 @@ static CURLcode load_cacert_from_memory(X509_STORE *store, return (count > 0) ? CURLE_OK : CURLE_SSL_CACERT_BADFILE; } -#if defined(USE_WIN32_CRYPTO) +#ifdef USE_WIN32_CRYPTO static CURLcode import_windows_cert_store(struct Curl_easy *data, const char *name, X509_STORE *store, @@ -3361,7 +3328,7 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, return CURLE_OUT_OF_MEMORY; if(verifypeer) { -#if defined(USE_WIN32_CRYPTO) +#ifdef USE_WIN32_CRYPTO /* Import certificates from the Windows root certificate store if requested. https://stackoverflow.com/questions/9507184/ @@ -3479,7 +3446,7 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, https://web.archive.org/web/20190422050538/ rt.openssl.org/Ticket/Display.html?id=3621 */ -#if defined(X509_V_FLAG_TRUSTED_FIRST) +#ifdef X509_V_FLAG_TRUSTED_FIRST X509_STORE_set_flags(store, X509_V_FLAG_TRUSTED_FIRST); #endif #ifdef X509_V_FLAG_PARTIAL_CHAIN @@ -3500,7 +3467,7 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, return result; } -#if defined(HAVE_SSL_X509_STORE_SHARE) +#ifdef HAVE_SSL_X509_STORE_SHARE /* key to use at `multi->proto_hash` */ #define MPROTO_OSSL_X509_KEY "tls:ossl:x509:share" @@ -3533,8 +3500,8 @@ ossl_cached_x509_store_expired(const struct Curl_easy *data, if(cfg->ca_cache_timeout < 0) return FALSE; else { - struct curltime now = Curl_now(); - timediff_t elapsed_ms = Curl_timediff(now, mb->time); + struct curltime now = curlx_now(); + timediff_t elapsed_ms = curlx_timediff(now, mb->time); timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; return elapsed_ms >= timeout_ms; @@ -3561,7 +3528,7 @@ static X509_STORE *ossl_get_cached_x509_store(struct Curl_cfilter *cf, DEBUGASSERT(multi); share = multi ? Curl_hash_pick(&multi->proto_hash, - (void *)MPROTO_OSSL_X509_KEY, + CURL_UNCONST(MPROTO_OSSL_X509_KEY), sizeof(MPROTO_OSSL_X509_KEY)-1) : NULL; if(share && share->store && !ossl_cached_x509_store_expired(data, share) && @@ -3584,7 +3551,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, if(!multi) return; share = Curl_hash_pick(&multi->proto_hash, - (void *)MPROTO_OSSL_X509_KEY, + CURL_UNCONST(MPROTO_OSSL_X509_KEY), sizeof(MPROTO_OSSL_X509_KEY)-1); if(!share) { @@ -3592,7 +3559,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, if(!share) return; if(!Curl_hash_add2(&multi->proto_hash, - (void *)MPROTO_OSSL_X509_KEY, + CURL_UNCONST(MPROTO_OSSL_X509_KEY), sizeof(MPROTO_OSSL_X509_KEY)-1, share, oss_x509_share_free)) { free(share); @@ -3616,7 +3583,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, free(share->CAfile); } - share->time = Curl_now(); + share->time = curlx_now(); share->store = store; share->CAfile = CAfile; } @@ -3668,40 +3635,299 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, } #endif /* HAVE_SSL_X509_STORE_SHARE */ -CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, + +static CURLcode +ossl_init_session_and_alpns(struct ossl_ctx *octx, struct Curl_cfilter *cf, struct Curl_easy *data, struct ssl_peer *peer, - 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) + const struct alpn_spec *alpns_requested, + Curl_ossl_init_session_reuse_cb *sess_reuse_cb) { - CURLcode result = CURLE_OK; - const char *ciphers; - SSL_METHOD_QUAL SSL_METHOD *req_method = NULL; - ctx_option_t ctx_options = 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); - unsigned 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; + struct alpn_spec alpns; char error_buffer[256]; + CURLcode result; - /* Make funny stuff to get random input */ - result = ossl_seed(data); - if(result) - return result; + Curl_alpn_copy(&alpns, alpns_requested); - ssl_config->certverifyresult = !X509_V_OK; + octx->reused_session = FALSE; + if(ssl_config->primary.cache_session) { + struct Curl_ssl_session *scs = NULL; + result = Curl_ssl_scache_take(cf, data, peer->scache_key, &scs); + if(!result && scs && scs->sdata && scs->sdata_len) { + const unsigned char *der_sessionid = scs->sdata; + size_t der_sessionid_size = scs->sdata_len; + SSL_SESSION *ssl_session = NULL; + + /* If OpenSSL does not accept the session from the cache, this + * is not an error. We just continue without it. */ + ssl_session = d2i_SSL_SESSION(NULL, &der_sessionid, + (long)der_sessionid_size); + if(ssl_session) { + if(!SSL_set_session(octx->ssl, ssl_session)) { + infof(data, "SSL: SSL_set_session not accepted, " + "continuing without: %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + } + else { + infof(data, "SSL reusing session with ALPN '%s'", + scs->alpn ? scs->alpn : "-"); + octx->reused_session = TRUE; +#ifdef HAVE_OPENSSL_EARLYDATA + if(ssl_config->earlydata && scs->alpn && + SSL_SESSION_get_max_early_data(ssl_session) && + !cf->conn->connect_only && + (SSL_version(octx->ssl) == TLS1_3_VERSION)) { + bool do_early_data = FALSE; + if(sess_reuse_cb) { + result = sess_reuse_cb(cf, data, &alpns, scs, &do_early_data); + if(result) + return result; + } + if(do_early_data) { + /* We only try the ALPN protocol the session used before, + * otherwise we might send early data for the wrong protocol */ + Curl_alpn_restrict_to(&alpns, scs->alpn); + } + } +#else + (void)sess_reuse_cb; +#endif + } + SSL_SESSION_free(ssl_session); + } + else { + infof(data, "SSL session not accepted by OpenSSL, continuing without"); + } + } + Curl_ssl_scache_return(cf, data, peer->scache_key, scs); + } + +#ifdef HAS_ALPN_OPENSSL + if(alpns.count) { + struct alpn_proto_buf proto; + memset(&proto, 0, sizeof(proto)); + result = Curl_alpn_to_proto_buf(&proto, &alpns); + if(result) { + failf(data, "Error determining ALPN"); + return CURLE_SSL_CONNECT_ERROR; + } + if(SSL_set_alpn_protos(octx->ssl, proto.data, (int)proto.len)) { + failf(data, "Error setting ALPN"); + return CURLE_SSL_CONNECT_ERROR; + } + } +#endif + + return CURLE_OK; +} + +#ifdef USE_ECH_OPENSSL +static CURLcode ossl_init_ech(struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer) +{ + unsigned char *ech_config = NULL; + size_t ech_config_len = 0; + char *outername = data->set.str[STRING_ECH_PUBLIC]; + int trying_ech_now = 0; + CURLcode result; + + if(!ECH_ENABLED(data)) + return CURLE_OK; + + if(data->set.tls_ech & CURLECH_GREASE) { + infof(data, "ECH: will GREASE ClientHello"); +# if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + 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) { +# if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + /* have to do base64 decode here for BoringSSL */ + 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 = curlx_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_ech_config_list 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_set1_ech_config_list(octx->ssl, ech_config, + ech_config_len) != 1) { + infof(data, "ECH: SSL_ECH_set1_ech_config_list 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_dnscache_get(data, peer->hostname, peer->port, + cf->conn->ip_version); + 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(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) { + infof(data, "ECH: SSL_set1_ech_config_list 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 %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); + } + } +# if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + 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_set1_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 /* OPENSSL_IS_BORINGSSL || OPENSSL_IS_AWSLC */ + 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; + } + + return CURLE_OK; +} +#endif /* USE_ECH_OPENSSL */ + + +static CURLcode ossl_init_ssl(struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const struct alpn_spec *alpns_requested, + void *ssl_user_data, + Curl_ossl_init_session_reuse_cb *sess_reuse_cb) +{ + /* Let's make an SSL structure */ + 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(octx->ssl, ssl_user_data); + +#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP) + if(Curl_ssl_cf_get_primary_config(cf)->verifystatus) + 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(octx->ssl, ssl_renegotiate_freely); +#endif + + SSL_set_connect_state(octx->ssl); + + octx->server_cert = NULL; +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + if(peer->sni) { + if(!SSL_set_tlsext_host_name(octx->ssl, peer->sni)) { + failf(data, "Failed set SNI"); + return CURLE_SSL_CONNECT_ERROR; + } + } + +#ifdef USE_ECH_OPENSSL + { + CURLcode result = ossl_init_ech(octx, cf, data, peer); + if(result) + return result; + } +#endif /* USE_ECH_OPENSSL */ + +#endif + + return ossl_init_session_and_alpns(octx, cf, data, peer, + alpns_requested, sess_reuse_cb); +} + + +static CURLcode ossl_init_method(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const SSL_METHOD **pmethod, + unsigned int *pssl_version_min) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + + *pmethod = NULL; + *pssl_version_min = conn_config->version; switch(peer->transport) { case TRNSPRT_TCP: /* check to see if we have been told to use an explicit SSL/TLS version */ - switch(ssl_version_min) { + switch(*pssl_version_min) { case CURL_SSLVERSION_DEFAULT: case CURL_SSLVERSION_TLSv1: case CURL_SSLVERSION_TLSv1_0: @@ -3710,9 +3936,9 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, case CURL_SSLVERSION_TLSv1_3: /* it will be handled later with the context options */ #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) - req_method = TLS_client_method(); + *pmethod = TLS_client_method(); #else - req_method = SSLv23_client_method(); + *pmethod = SSLv23_client_method(); #endif break; case CURL_SSLVERSION_SSLv2: @@ -3727,7 +3953,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, } break; case TRNSPRT_QUIC: - ssl_version_min = CURL_SSLVERSION_TLSv1_3; + *pssl_version_min = CURL_SSLVERSION_TLSv1_3; if(conn_config->version_max && (conn_config->version_max != CURL_SSLVERSION_MAX_TLSv1_3)) { failf(data, "QUIC needs at least TLS version 1.3"); @@ -3735,11 +3961,11 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, } #ifdef USE_OPENSSL_QUIC - req_method = OSSL_QUIC_client_method(); + *pmethod = OSSL_QUIC_client_method(); #elif (OPENSSL_VERSION_NUMBER >= 0x10100000L) - req_method = TLS_method(); + *pmethod = TLS_method(); #else - req_method = SSLv23_client_method(); + *pmethod = SSLv23_client_method(); #endif break; default: @@ -3747,9 +3973,53 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, return CURLE_SSL_CONNECT_ERROR; } + return *pmethod ? CURLE_OK : CURLE_SSL_CONNECT_ERROR; +} + + +CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const struct alpn_spec *alpns_requested, + Curl_ossl_ctx_setup_cb *cb_setup, + void *cb_user_data, + Curl_ossl_new_session_cb *cb_new_session, + void *ssl_user_data, + Curl_ossl_init_session_reuse_cb *sess_reuse_cb) +{ + CURLcode result = CURLE_OK; + const char *ciphers; + const SSL_METHOD *req_method = NULL; + ctx_option_t ctx_options = 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); + 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; + unsigned int ssl_version_min; + char error_buffer[256]; + + /* Make funny stuff to get random input */ + result = ossl_seed(data); + if(result) + return result; + + ssl_config->certverifyresult = !X509_V_OK; + + result = ossl_init_method(cf, data, peer, &req_method, &ssl_version_min); + if(result) + return result; + DEBUGASSERT(req_method); DEBUGASSERT(!octx->ssl_ctx); - octx->ssl_ctx = SSL_CTX_new(req_method); + octx->ssl_ctx = +#ifdef OPENSSL_HAS_PROVIDERS + data->state.libctx ? + SSL_CTX_new_ex(data->state.libctx, data->state.propq, req_method): +#endif + SSL_CTX_new(req_method); if(!octx->ssl_ctx) { failf(data, "SSL: could not create a context: %s", @@ -3799,7 +4069,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, 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: + OpenSSL added a work-around for an 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" @@ -3867,27 +4137,6 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, SSL_CTX_set_mode(octx->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); #endif - if(alpn && alpn_len) { -#ifdef HAS_ALPN_OPENSSL - if(SSL_CTX_set_alpn_protos(octx->ssl_ctx, alpn, (int)alpn_len)) { - failf(data, "Error setting ALPN"); - return CURLE_SSL_CONNECT_ERROR; - } -#endif - } - - if(ssl_cert || ssl_cert_blob || ssl_cert_type) { - if(!result && - !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)) - result = CURLE_SSL_CERTPROBLEM; - if(result) - /* failf() is already done in cert_stuff() */ - return result; - } - ciphers = conn_config->cipher_list; if(!ciphers && (peer->transport != TRNSPRT_QUIC)) ciphers = DEFAULT_CIPHER_SELECTION; @@ -3914,21 +4163,51 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, } #endif + if(ssl_cert || ssl_cert_blob || ssl_cert_type) { + if(!result && + !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)) + result = CURLE_SSL_CERTPROBLEM; + if(result) + /* failf() is already done in cert_stuff() */ + return result; + } + #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(octx->ssl_ctx, 1); #endif -#ifdef HAVE_SSL_CTX_SET_EC_CURVES { const char *curves = conn_config->curves; if(curves) { - if(!SSL_CTX_set1_curves_list(octx->ssl_ctx, curves)) { +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +#define OSSL_CURVE_CAST(x) (x) +#else +#define OSSL_CURVE_CAST(x) (char *)CURL_UNCONST(x) +#endif + if(!SSL_CTX_set1_curves_list(octx->ssl_ctx, OSSL_CURVE_CAST(curves))) { failf(data, "failed setting curves list: '%s'", curves); return CURLE_SSL_CIPHER; } } } + +#ifdef HAVE_SSL_CTX_SET1_SIGALGS +#define OSSL_SIGALG_CAST(x) OSSL_CURVE_CAST(x) + { + const char *signature_algorithms = conn_config->signature_algorithms; + if(signature_algorithms) { + if(!SSL_CTX_set1_sigalgs_list(octx->ssl_ctx, + OSSL_SIGALG_CAST(signature_algorithms))) { + failf(data, "failed setting signature algorithms: '%s'", + signature_algorithms); + return CURLE_SSL_CIPHER; + } + } + } #endif #ifdef USE_OPENSSL_SRP @@ -4002,198 +4281,38 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, } } - /* Let's make an SSL structure */ - 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; + return ossl_init_ssl(octx, cf, data, peer, alpns_requested, + ssl_user_data, sess_reuse_cb); +} + +static CURLcode ossl_on_session_reuse(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + struct Curl_ssl_session *scs, + bool *do_early_data) +{ + struct ssl_connect_data *connssl = cf->ctx; + CURLcode result = CURLE_OK; + + *do_early_data = FALSE; + connssl->earlydata_max = scs->earlydata_max; + if(!connssl->earlydata_max) { + CURL_TRC_CF(data, cf, "SSL session does not allow earlydata"); } - - 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(octx->ssl, TLSEXT_STATUSTYPE_ocsp); -#endif - -#if (defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)) && \ - defined(ALLOW_RENEG) - SSL_set_renegotiate_mode(octx->ssl, ssl_renegotiate_freely); -#endif - - SSL_set_connect_state(octx->ssl); - - octx->server_cert = 0x0; -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - if(peer->sni) { - if(!SSL_set_tlsext_host_name(octx->ssl, peer->sni)) { - failf(data, "Failed set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } + else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) { + CURL_TRC_CF(data, cf, "SSL session has different ALPN, no early data"); } - -#ifdef USE_ECH_OPENSSL - 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"); -# if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) - 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) { -# if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) - /* have to do base64 decode here for BoringSSL */ - 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_ech_config_list 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_set1_ech_config_list(octx->ssl, ech_config, - ech_config_len) != 1) { - infof(data, "ECH: SSL_ECH_set1_ech_config_list 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"); - if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) { - infof(data, "ECH: SSL_set1_ech_config_list 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 %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); - } - } -# if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) - 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_set1_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 /* OPENSSL_IS_BORINGSSL || OPENSSL_IS_AWSLC */ - 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; - } + else { + infof(data, "SSL session allows %zu bytes of early data, " + "reusing ALPN '%s'", connssl->earlydata_max, scs->alpn); + connssl->earlydata_state = ssl_earlydata_await; + connssl->state = ssl_connection_deferred; + result = Curl_alpn_set_negotiated(cf, data, connssl, + (const unsigned char *)scs->alpn, + scs->alpn ? strlen(scs->alpn) : 0); + *do_early_data = !result; } -#endif /* USE_ECH_OPENSSL */ - -#endif - - octx->reused_session = FALSE; - if(ssl_config->primary.cache_session) { - struct Curl_ssl_session *sc_session = NULL; - - result = Curl_ssl_scache_take(cf, data, peer->scache_key, &sc_session); - if(!result && sc_session && sc_session->sdata && sc_session->sdata_len) { - const unsigned char *der_sessionid = sc_session->sdata; - size_t der_sessionid_size = sc_session->sdata_len; - SSL_SESSION *ssl_session = NULL; - - /* If OpenSSL does not accept the session from the cache, this - * is not an error. We just continue without it. */ - ssl_session = d2i_SSL_SESSION(NULL, &der_sessionid, - (long)der_sessionid_size); - if(ssl_session) { - if(!SSL_set_session(octx->ssl, ssl_session)) { - infof(data, "SSL: SSL_set_session not accepted, " - "continuing without: %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer))); - } - else { - infof(data, "SSL reusing session"); - octx->reused_session = TRUE; - } - SSL_SESSION_free(ssl_session); - } - else { - infof(data, "SSL session not accepted by OpenSSL, continuing without"); - } - } - Curl_ssl_scache_return(cf, data, peer->scache_key, sc_session); - } - - return CURLE_OK; + return result; } static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, @@ -4201,26 +4320,16 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, { 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_OPENSSL - 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, - proto.data, proto.len, NULL, NULL, - ossl_new_session_cb, cf); + connssl->alpn, NULL, NULL, + ossl_new_session_cb, cf, + ossl_on_session_reuse); if(result) return result; @@ -4246,7 +4355,9 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, #endif #ifdef HAS_ALPN_OPENSSL - if(connssl->alpn) { + if(connssl->alpn && (connssl->state != ssl_connection_deferred)) { + struct alpn_proto_buf proto; + memset(&proto, 0, sizeof(proto)); Curl_alpn_to_proto_str(&proto, connssl->alpn); infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } @@ -4289,7 +4400,7 @@ static void ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL* ssl, char *b64str = NULL; size_t blen = 0; - result = Curl_base64_encode((const char *)rcs, rcl, &b64str, &blen); + result = curlx_base64_encode((const char *)rcs, rcl, &b64str, &blen); if(!result && b64str) { infof(data, "ECH: retry_configs %s", b64str); free(b64str); @@ -4359,27 +4470,25 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, if(SSL_ERROR_WANT_READ == detail) { CURL_TRC_CF(data, cf, "SSL_connect() -> want recv"); connssl->io_need = CURL_SSL_IO_NEED_RECV; - return CURLE_OK; + return CURLE_AGAIN; } if(SSL_ERROR_WANT_WRITE == detail) { CURL_TRC_CF(data, cf, "SSL_connect() -> want send"); connssl->io_need = CURL_SSL_IO_NEED_SEND; - return CURLE_OK; + return CURLE_AGAIN; } #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; + return CURLE_AGAIN; } #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; + return CURLE_AGAIN; } #endif else { @@ -4416,7 +4525,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, else failf(data, "%s", "SSL certificate verification failed"); } -#if defined(SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED) +#ifdef 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) && @@ -4682,7 +4791,7 @@ static void infof_certstack(struct Curl_easy *data, const SSL *ssl) sizeof(group_name), NULL); msnprintf(group_name_final, sizeof(group_name_final), "/%s", group_name); } - type_name = EVP_PKEY_get0_type_name(current_pkey); + type_name = current_pkey ? EVP_PKEY_get0_type_name(current_pkey) : NULL; #else get_group_name = 0; type_name = NULL; @@ -4722,7 +4831,7 @@ CURLcode Curl_oss_check_peer_cert(struct Curl_cfilter *cf, DEBUGASSERT(octx); - Curl_dyn_init(&dname, MAX_CERT_NAME_LENGTH); + curlx_dyn_init(&dname, MAX_CERT_NAME_LENGTH); if(!mem) { failf(data, @@ -4752,19 +4861,20 @@ CURLcode Curl_oss_check_peer_cert(struct Curl_cfilter *cf, result = x509_name_oneline(X509_get_subject_name(octx->server_cert), &dname); - infof(data, " subject: %s", result ? "[NONE]" : Curl_dyn_ptr(&dname)); + infof(data, " subject: %s", result ? "[NONE]" : curlx_dyn_ptr(&dname)); #ifndef CURL_DISABLE_VERBOSE_STRINGS { + char *buf; long len; 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); + len = BIO_get_mem_data(mem, (char **) &buf); + infof(data, " start date: %.*s", (int)len, buf); (void)BIO_reset(mem); 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); + len = BIO_get_mem_data(mem, (char **) &buf); + infof(data, " expire date: %.*s", (int)len, buf); (void)BIO_reset(mem); } #endif @@ -4776,7 +4886,7 @@ CURLcode Curl_oss_check_peer_cert(struct Curl_cfilter *cf, if(result) { X509_free(octx->server_cert); octx->server_cert = NULL; - Curl_dyn_free(&dname); + curlx_dyn_free(&dname); return result; } } @@ -4789,8 +4899,8 @@ CURLcode Curl_oss_check_peer_cert(struct Curl_cfilter *cf, result = CURLE_PEER_FAILED_VERIFICATION; } else { - infof(data, " issuer: %s", Curl_dyn_ptr(&dname)); - Curl_dyn_free(&dname); + infof(data, " issuer: %s", curlx_dyn_ptr(&dname)); + curlx_dyn_free(&dname); /* We could do all sorts of certificate verification stuff here before deallocating the certificate. */ @@ -4885,8 +4995,7 @@ CURLcode Curl_oss_check_peer_cert(struct Curl_cfilter *cf, } infof_certstack(data, octx->ssl); -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) +#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP) if(conn_config->verifystatus && !octx->reused_session) { /* do not do this after Session ID reuse */ result = verifystatus(cf, data, octx); @@ -4938,137 +5047,169 @@ static CURLcode ossl_connect_step3(struct Curl_cfilter *cf, */ result = Curl_oss_check_peer_cert(cf, data, octx, &connssl->peer); - if(!result) - connssl->connecting_state = ssl_connect_done; - else + if(result) /* on error, remove sessions we might have in the pool */ Curl_ssl_scache_remove_all(cf, data, connssl->peer.scache_key); return result; } -static CURLcode ossl_connect_common(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool nonblocking, - bool *done) +#ifdef HAVE_OPENSSL_EARLYDATA +static CURLcode ossl_send_earlydata(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + CURLcode result = CURLE_OK; + const unsigned char *buf; + size_t blen, nwritten; + int rc; + + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sending); + octx->io_result = CURLE_OK; + while(Curl_bufq_peek(&connssl->earlydata, &buf, &blen)) { + nwritten = 0; + rc = SSL_write_early_data(octx->ssl, buf, blen, &nwritten); + CURL_TRC_CF(data, cf, "SSL_write_early_data(len=%zu) -> %d, %zu", + blen, rc, nwritten); + if(rc <= 0) { + long sslerror; + char error_buffer[256]; + int err = SSL_get_error(octx->ssl, rc); + + switch(err) { + case SSL_ERROR_WANT_READ: + connssl->io_need = CURL_SSL_IO_NEED_RECV; + result = CURLE_AGAIN; + goto out; + case SSL_ERROR_WANT_WRITE: + connssl->io_need = CURL_SSL_IO_NEED_SEND; + result = CURLE_AGAIN; + goto out; + case SSL_ERROR_SYSCALL: { + int sockerr = SOCKERRNO; + + if(octx->io_result == CURLE_AGAIN) { + result = CURLE_AGAIN; + goto out; + } + sslerror = ERR_get_error(); + if(sslerror) + ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)); + else if(sockerr) + Curl_strerror(sockerr, error_buffer, sizeof(error_buffer)); + else + msnprintf(error_buffer, sizeof(error_buffer), "%s", + SSL_ERROR_to_str(err)); + + failf(data, OSSL_PACKAGE " SSL_write:early_data: %s, errno %d", + error_buffer, sockerr); + result = CURLE_SEND_ERROR; + goto out; + } + 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. */ + sslerror = ERR_get_error(); + failf(data, "SSL_write_early_data() error: %s", + ossl_strerror(sslerror, error_buffer, sizeof(error_buffer))); + result = CURLE_SEND_ERROR; + goto out; + } + default: + /* a true error */ + failf(data, OSSL_PACKAGE " SSL_write_early_data: %s, errno %d", + SSL_ERROR_to_str(err), SOCKERRNO); + result = CURLE_SEND_ERROR; + goto out; + } + } + Curl_bufq_skip(&connssl->earlydata, nwritten); + } + /* sent everything there was */ + infof(data, "SSL sending %zu bytes of early data", connssl->earlydata_skip); +out: + return result; +} +#endif /* HAVE_OPENSSL_EARLYDATA */ + +static CURLcode ossl_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { CURLcode result = CURLE_OK; struct ssl_connect_data *connssl = cf->ctx; - 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; return CURLE_OK; } + *done = FALSE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we are allowed */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time is already up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - + CURL_TRC_CF(data, cf, "ossl_connect, step1"); result = ossl_connect_step1(cf, data); if(result) goto out; } - while(ssl_connect_2 == connssl->connecting_state) { - - /* check allowed time left */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - result = CURLE_OPERATION_TIMEDOUT; + if(ssl_connect_2 == connssl->connecting_state) { + CURL_TRC_CF(data, cf, "ossl_connect, step2"); +#ifdef HAVE_OPENSSL_EARLYDATA + if(connssl->earlydata_state == ssl_earlydata_await) { goto out; } - - /* 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); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - result = CURLE_SSL_CONNECT_ERROR; + else if(connssl->earlydata_state == ssl_earlydata_sending) { + result = ossl_send_earlydata(cf, data); + if(result) goto out; - } - if(0 == what) { - /* timeout */ - failf(data, "SSL connection timeout"); - result = CURLE_OPERATION_TIMEDOUT; - goto out; - } - /* socket is readable or writable */ + connssl->earlydata_state = ssl_earlydata_sent; } +#endif + DEBUGASSERT((connssl->earlydata_state == ssl_earlydata_none) || + (connssl->earlydata_state == ssl_earlydata_sent)); - /* Run transaction, and return to the caller if it failed or if this - * connection is done nonblocking and this loop would execute again. This - * permits the owner of a multi handle to abort a connection attempt - * before step2 has completed while ensuring that a client using select() - * 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))) - goto out; - - } /* repeat step2 until all transactions are done. */ - - if(ssl_connect_3 == connssl->connecting_state) { - result = ossl_connect_step3(cf, data); if(result) goto out; } - if(ssl_connect_done == connssl->connecting_state) { - connssl->state = ssl_connection_complete; - *done = TRUE; + if(ssl_connect_3 == connssl->connecting_state) { + CURL_TRC_CF(data, cf, "ossl_connect, step3"); + result = ossl_connect_step3(cf, data); + if(result) + goto out; + connssl->connecting_state = ssl_connect_done; +#ifdef HAVE_OPENSSL_EARLYDATA + if(connssl->earlydata_state > ssl_earlydata_none) { + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + /* We should be in this state by now */ + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sent); + connssl->earlydata_state = + (SSL_get_early_data_status(octx->ssl) == SSL_EARLY_DATA_ACCEPTED) ? + ssl_earlydata_accepted : ssl_earlydata_rejected; + } +#endif } - else - *done = FALSE; - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; + if(ssl_connect_done == connssl->connecting_state) { + CURL_TRC_CF(data, cf, "ossl_connect, done"); + connssl->state = ssl_connection_complete; + } out: + if(result == CURLE_AGAIN) { + *done = FALSE; + return CURLE_OK; + } + *done = ((connssl->state == ssl_connection_complete) || + (connssl->state == ssl_connection_deferred)); return result; } -static CURLcode ossl_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - return ossl_connect_common(cf, data, TRUE, done); -} - -static CURLcode ossl_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - CURLcode result; - bool done = FALSE; - - result = ossl_connect_common(cf, data, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - static bool ossl_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { @@ -5342,10 +5483,10 @@ static CURLcode ossl_get_channel_binding(struct Curl_easy *data, int sockindex, } /* Append "tls-server-end-point:" */ - if(Curl_dyn_addn(binding, prefix, sizeof(prefix) - 1) != CURLE_OK) + if(curlx_dyn_addn(binding, prefix, sizeof(prefix) - 1) != CURLE_OK) return CURLE_OUT_OF_MEMORY; /* Append digest */ - if(Curl_dyn_addn(binding, buf, length)) + if(curlx_dyn_addn(binding, buf, length)) return CURLE_OUT_OF_MEMORY; return CURLE_OK; @@ -5361,7 +5502,6 @@ static CURLcode ossl_get_channel_binding(struct Curl_easy *data, int sockindex, size_t Curl_ossl_version(char *buffer, size_t size) { #ifdef LIBRESSL_VERSION_NUMBER -#ifdef HAVE_OPENSSL_VERSION char *p; size_t count; const char *ver = OpenSSL_version(OPENSSL_VERSION); @@ -5375,13 +5515,6 @@ size_t Curl_ossl_version(char *buffer, size_t size) *p = '_'; } return count; -#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); -#endif #elif defined(OPENSSL_IS_BORINGSSL) #ifdef CURL_BORINGSSL_VERSION return msnprintf(buffer, size, "%s/%s", @@ -5405,25 +5538,19 @@ size_t Curl_ossl_version(char *buffer, size_t size) sub[2]='\0'; sub[1]='\0'; ssleay_value = OpenSSL_version_num(); - if(ssleay_value < 0x906000) { - ssleay_value = SSLEAY_VERSION_NUMBER; - sub[0]='\0'; - } - else { - if(ssleay_value&0xff0) { - int minor_ver = (ssleay_value >> 4) & 0xff; - if(minor_ver > 26) { - /* handle extended version introduced for 0.9.8za */ - sub[1] = (char) ((minor_ver - 1) % 26 + 'a' + 1); - sub[0] = 'z'; - } - else { - sub[0] = (char) (minor_ver + 'a' - 1); - } + if(ssleay_value&0xff0) { + int minor_ver = (ssleay_value >> 4) & 0xff; + if(minor_ver > 26) { + /* handle extended version introduced for 0.9.8za */ + sub[1] = (char) ((minor_ver - 1) % 26 + 'a' + 1); + sub[0] = 'z'; + } + else { + sub[0] = (char) (minor_ver + 'a' - 1); } - else - sub[0]='\0'; } + else + sub[0]='\0'; return msnprintf(buffer, size, "%s/%lx.%lx.%lx%s" #ifdef OPENSSL_FIPS @@ -5456,7 +5583,7 @@ static CURLcode ossl_random(struct Curl_easy *data, return rc == 1 ? CURLE_OK : CURLE_FAILED_INIT; } -#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256) +#ifndef OPENSSL_NO_SHA256 static CURLcode ossl_sha256sum(const unsigned char *tmp, /* input */ size_t tmplen, unsigned char *sha256sum /* output */, @@ -5482,8 +5609,7 @@ static CURLcode ossl_sha256sum(const unsigned char *tmp, /* input */ static bool ossl_cert_status_request(void) { -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) +#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP) return TRUE; #else return FALSE; @@ -5511,6 +5637,9 @@ const struct Curl_ssl Curl_ssl_openssl = { #ifdef HAVE_SSL_CTX_SET_CIPHERSUITES SSLSUPP_TLS13_CIPHERSUITES | #endif +#ifdef HAVE_SSL_CTX_SET1_SIGALGS + SSLSUPP_SIGNATURE_ALGORITHMS | +#endif #ifdef USE_ECH_OPENSSL SSLSUPP_ECH | #endif @@ -5528,16 +5657,15 @@ const struct Curl_ssl Curl_ssl_openssl = { ossl_random, /* random */ ossl_cert_status_request, /* cert_status_request */ ossl_connect, /* connect */ - ossl_connect_nonblocking, /* connect_nonblocking */ Curl_ssl_adjust_pollset, /* adjust_pollset */ ossl_get_internals, /* get_internals */ ossl_close, /* close_one */ ossl_close_all, /* close_all */ - ossl_set_engine, /* set_engine */ + ossl_set_engine, /* set_engine or provider */ ossl_set_engine_default, /* set_engine_default */ ossl_engines_list, /* engines_list */ NULL, /* false_start */ -#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256) +#ifndef OPENSSL_NO_SHA256 ossl_sha256sum, /* sha256sum */ #else NULL, /* sha256sum */ diff --git a/Utilities/cmcurl/lib/vtls/openssl.h b/Utilities/cmcurl/lib/vtls/openssl.h index b2940f4309..8d063e25ac 100644 --- a/Utilities/cmcurl/lib/vtls/openssl.h +++ b/Utilities/cmcurl/lib/vtls/openssl.h @@ -24,7 +24,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_OPENSSL /* @@ -35,7 +35,7 @@ #include #include -#include "urldata.h" +#include "../urldata.h" /* * Whether SSL_CTX_set_keylog_callback is available. @@ -49,7 +49,16 @@ #define HAVE_KEYLOG_CALLBACK #endif +/* Check for OpenSSL 1.1.1 which has early data support. */ +#undef HAVE_OPENSSL_EARLYDATA +#if OPENSSL_VERSION_NUMBER >= 0x10100010L && defined(TLS1_3_VERSION) && \ + !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) +#define HAVE_OPENSSL_EARLYDATA +#endif + +struct alpn_spec; struct ssl_peer; +struct Curl_ssl_session; /* Struct to hold a curl OpenSSL instance */ struct ossl_ctx { @@ -75,16 +84,22 @@ typedef CURLcode Curl_ossl_ctx_setup_cb(struct Curl_cfilter *cf, void *user_data); typedef int Curl_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid); +typedef CURLcode Curl_ossl_init_session_reuse_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + struct Curl_ssl_session *scs, + bool *do_early_data); CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, struct Curl_cfilter *cf, struct Curl_easy *data, struct ssl_peer *peer, - const unsigned char *alpn, size_t alpn_len, + const struct alpn_spec *alpns, Curl_ossl_ctx_setup_cb *cb_setup, void *cb_user_data, Curl_ossl_new_session_cb *cb_new_session, - void *ssl_user_data); + void *ssl_user_data, + Curl_ossl_init_session_reuse_cb *sess_reuse_cb); #if (OPENSSL_VERSION_NUMBER < 0x30000000L) #define SSL_get1_peer_certificate SSL_get_peer_certificate @@ -113,7 +128,9 @@ CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, const char *ssl_peer_key, SSL_SESSION *ssl_sessionid, int ietf_tls_id, - const char *alpn); + const char *alpn, + unsigned char *quic_tp, + size_t quic_tp_len); /* * Get the server cert, verify it and show it, etc., only call failf() if diff --git a/Utilities/cmcurl/lib/vtls/rustls.c b/Utilities/cmcurl/lib/vtls/rustls.c index 948d0e9cce..cb9fd6230a 100644 --- a/Utilities/cmcurl/lib/vtls/rustls.c +++ b/Utilities/cmcurl/lib/vtls/rustls.c @@ -8,6 +8,7 @@ * Copyright (C) Jacob Hoffman-Andrews, * * Copyright (C) kpcyrd, + * Copyright (C) Daniel McCarney, * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -23,27 +24,24 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_RUSTLS -#include "curl_printf.h" +#include "../curl_printf.h" -#include #include -#include "inet_pton.h" -#include "urldata.h" -#include "sendf.h" +#include "../curlx/inet_pton.h" +#include "../urldata.h" +#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 "keylog.h" +#include "../strerror.h" #include "cipher_suite.h" -#include "rand.h" +#include "x509asn1.h" struct rustls_ssl_backend_data { @@ -55,7 +53,7 @@ struct rustls_ssl_backend_data }; /* For a given rustls_result error code, return the best-matching CURLcode. */ -static CURLcode map_error(rustls_result r) +static CURLcode map_error(const rustls_result r) { if(rustls_result_is_cert_error(r)) { return CURLE_PEER_FAILED_VERIFICATION; @@ -70,10 +68,19 @@ static CURLcode map_error(rustls_result r) } } +static void +rustls_failf(struct Curl_easy *data, const rustls_result rr, const char *msg) +{ + char errorbuf[STRERROR_LEN]; + size_t errorlen; + rustls_error(rr, errorbuf, sizeof(errorbuf), &errorlen); + failf(data, "%s: %.*s", msg, (int)errorlen, errorbuf); +} + static bool cr_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - struct ssl_connect_data *ctx = cf->ctx; + const struct ssl_connect_data *ctx = cf->ctx; struct rustls_ssl_backend_data *backend; (void)data; @@ -90,7 +97,7 @@ struct io_ctx { static int read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n) { - struct io_ctx *io_ctx = userdata; + const struct io_ctx *io_ctx = userdata; struct ssl_connect_data *const connssl = io_ctx->cf->ctx; CURLcode result; int ret = 0; @@ -98,6 +105,7 @@ read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n) (char *)buf, len, &result); if(nread < 0) { nread = 0; + /* !checksrc! disable ERRNOVAR 4 */ if(CURLE_AGAIN == result) ret = EAGAIN; else @@ -114,7 +122,7 @@ read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n) static int write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n) { - struct io_ctx *io_ctx = userdata; + const struct io_ctx *io_ctx = userdata; CURLcode result; int ret = 0; ssize_t nwritten = Curl_conn_cf_send(io_ctx->cf->next, io_ctx->data, @@ -136,7 +144,7 @@ write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n) static ssize_t tls_recv_more(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode *err) { - struct ssl_connect_data *const connssl = cf->ctx; + const struct ssl_connect_data *const connssl = cf->ctx; struct rustls_ssl_backend_data *const backend = (struct rustls_ssl_backend_data *)connssl->backend; struct io_ctx io_ctx; @@ -162,11 +170,7 @@ static ssize_t tls_recv_more(struct Curl_cfilter *cf, rresult = rustls_connection_process_new_packets(backend->conn); if(rresult != RUSTLS_RESULT_OK) { - char errorbuf[255]; - size_t errorlen; - rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); - failf(data, "rustls_connection_process_new_packets: %.*s", - (int)errorlen, errorbuf); + rustls_failf(data, rresult, "rustls_connection_process_new_packets"); *err = map_error(rresult); return -1; } @@ -192,7 +196,7 @@ static ssize_t cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *plainbuf, size_t plainlen, CURLcode *err) { - struct ssl_connect_data *const connssl = cf->ctx; + const struct ssl_connect_data *const connssl = cf->ctx; struct rustls_ssl_backend_data *const backend = (struct rustls_ssl_backend_data *)connssl->backend; struct rustls_connection *rconn = NULL; @@ -232,10 +236,7 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } else if(rresult != RUSTLS_RESULT_OK) { /* 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", (int)errorlen, errorbuf); + rustls_failf(data, rresult, "rustls_connection_read"); *err = CURLE_RECV_ERROR; nread = -1; goto out; @@ -278,7 +279,6 @@ static CURLcode cr_flush_out(struct Curl_cfilter *cf, struct Curl_easy *data, 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; @@ -304,7 +304,7 @@ static CURLcode cr_flush_out(struct Curl_cfilter *cf, struct Curl_easy *data, CURL_TRC_CF(data, cf, "cf_send: wrote %zu TLS bytes", tlswritten); tlswritten_total += tlswritten; } - return result; + return CURLE_OK; } /* @@ -321,14 +321,11 @@ static ssize_t cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *plainbuf, size_t plainlen, CURLcode *err) { - struct ssl_connect_data *const connssl = cf->ctx; + const struct ssl_connect_data *const connssl = cf->ctx; struct rustls_ssl_backend_data *const backend = (struct rustls_ssl_backend_data *)connssl->backend; struct rustls_connection *rconn = NULL; size_t plainwritten = 0; - rustls_result rresult; - char errorbuf[256]; - size_t errorlen; const unsigned char *buf = plainbuf; size_t blen = plainlen; ssize_t nwritten = 0; @@ -360,11 +357,11 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, } if(blen > 0) { + rustls_result rresult; 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", (int)errorlen, errorbuf); + rustls_failf(data, rresult, "rustls_connection_write"); *err = CURLE_WRITE_ERROR; return -1; } @@ -418,9 +415,9 @@ read_file_into(const char *filename, while(!feof(f)) { uint8_t buf[256]; - size_t rr = fread(buf, 1, sizeof(buf), f); + const size_t rr = fread(buf, 1, sizeof(buf), f); if(rr == 0 || - CURLE_OK != Curl_dyn_addn(out, buf, rr)) { + CURLE_OK != curlx_dyn_addn(out, buf, rr)) { fclose(f); return 0; } @@ -436,8 +433,8 @@ cr_get_selected_ciphers(struct Curl_easy *data, 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 size_t supported_len = *selected_size; + const 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; @@ -522,247 +519,608 @@ add_ciphers: *selected_size = count; } -static CURLcode -cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data, - struct rustls_ssl_backend_data *const backend) +static void +cr_keylog_log_cb(struct rustls_str label, + const uint8_t *client_random, size_t client_random_len, + const uint8_t *secret, size_t secret_len) { - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + char clabel[KEYLOG_LABEL_MAXLEN]; + (void)client_random_len; + DEBUGASSERT(client_random_len == CLIENT_RANDOM_SIZE); + /* Turning a "rustls_str" into a null delimited "c" string */ + msnprintf(clabel, label.len + 1, "%.*s", (int)label.len, label.data); + Curl_tls_keylog_write(clabel, client_random, secret, secret_len); +} + +static CURLcode +init_config_builder(struct Curl_easy *data, + const struct ssl_primary_config *conn_config, + struct rustls_client_config_builder **config_builder) +{ + const struct rustls_supported_ciphersuite **cipher_suites = NULL; 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; + + uint16_t tls_versions[2] = { + RUSTLS_TLS_VERSION_TLSV1_2, + RUSTLS_TLS_VERSION_TLSV1_3, + }; + size_t tls_versions_len = 2; + size_t cipher_suites_len = + rustls_default_crypto_provider_ciphersuites_len(); + + CURLcode result = CURLE_OK; + rustls_result rr; + + 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"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto cleanup; + } + + 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"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto cleanup; + } + +#if defined(USE_ECH) + if(ECH_ENABLED(data)) { + tls_versions[0] = RUSTLS_TLS_VERSION_TLSV1_3; + tls_versions_len = 1; + infof(data, "rustls: ECH enabled, forcing TLSv1.3"); + } +#endif /* USE_ECH */ + + cipher_suites = malloc(sizeof(cipher_suites) * (cipher_suites_len)); + if(!cipher_suites) { + result = CURLE_OUT_OF_MEMORY; + goto cleanup; + } + + 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"); + result = CURLE_SSL_CIPHER; + goto cleanup; + } + + rr = rustls_crypto_provider_builder_new_from_default( + &custom_provider_builder); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, + "failed to create crypto provider builder from default"); + result = CURLE_SSL_CIPHER; + goto cleanup; + } + + rr = + rustls_crypto_provider_builder_set_cipher_suites( + custom_provider_builder, + cipher_suites, + cipher_suites_len); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, + "failed to set ciphersuites for crypto provider builder"); + result = CURLE_SSL_CIPHER; + goto cleanup; + } + + rr = rustls_crypto_provider_builder_build( + custom_provider_builder, &custom_provider); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to build custom crypto provider"); + result = CURLE_SSL_CIPHER; + goto cleanup; + } + + rr = rustls_client_config_builder_new_custom(custom_provider, + tls_versions, + tls_versions_len, + config_builder); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to create client config builder"); + result = CURLE_SSL_CIPHER; + goto cleanup; + } + +cleanup: + if(cipher_suites) { + free(cipher_suites); + } + if(custom_provider_builder) { + rustls_crypto_provider_builder_free(custom_provider_builder); + } + if(custom_provider) { + rustls_crypto_provider_free(custom_provider); + } + return result; +} + +static void +init_config_builder_alpn(struct Curl_easy *data, + const struct ssl_connect_data *connssl, + struct rustls_client_config_builder *config_builder) { + struct alpn_proto_buf proto; + rustls_slice_bytes alpn[ALPN_ENTRIES_MAX]; + size_t i; + + for(i = 0; i < connssl->alpn->count; ++i) { + alpn[i].data = (const uint8_t *)connssl->alpn->entries[i]; + alpn[i].len = strlen(connssl->alpn->entries[i]); + } + rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, + connssl->alpn->count); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); +} + +static CURLcode +init_config_builder_verifier_crl( + struct Curl_easy *data, + const struct ssl_primary_config *conn_config, + struct rustls_web_pki_server_cert_verifier_builder *builder) +{ + CURLcode result = CURLE_OK; + struct dynbuf crl_contents; + rustls_result rr; + + curlx_dyn_init(&crl_contents, DYN_CRLFILE_SIZE); + if(!read_file_into(conn_config->CRLfile, &crl_contents)) { + failf(data, "rustls: failed to read revocation list file"); + result = CURLE_SSL_CRL_BADFILE; + goto cleanup; + } + + rr = rustls_web_pki_server_cert_verifier_builder_add_crl( + builder, + curlx_dyn_uptr(&crl_contents), + curlx_dyn_len(&crl_contents)); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to parse revocation list"); + result = CURLE_SSL_CRL_BADFILE; + goto cleanup; + } + +cleanup: + curlx_dyn_free(&crl_contents); + return result; +} + +static CURLcode +init_config_builder_verifier(struct Curl_easy *data, + struct rustls_client_config_builder *builder, + const struct ssl_primary_config *conn_config, + const struct curl_blob *ca_info_blob, + const char * const ssl_cafile) { 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; + rustls_result rr = RUSTLS_RESULT_OK; + CURLcode result = CURLE_OK; + + roots_builder = rustls_root_cert_store_builder_new(); + if(ca_info_blob) { + rr = rustls_root_cert_store_builder_add_pem(roots_builder, + ca_info_blob->data, + ca_info_blob->len, + 1); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to parse trusted certificates from blob"); + + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + } + else if(ssl_cafile) { + rr = rustls_root_cert_store_builder_load_roots_from_file(roots_builder, + ssl_cafile, + 1); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to load trusted certificates"); + + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + } + + rr = rustls_root_cert_store_builder_build(roots_builder, &roots); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to build trusted root certificate store"); + result = CURLE_SSL_CACERT_BADFILE; + } + + verifier_builder = rustls_web_pki_server_cert_verifier_builder_new(roots); + + if(conn_config->CRLfile) { + result = init_config_builder_verifier_crl(data, + conn_config, + verifier_builder); + if(result != CURLE_OK) { + goto cleanup; + } + } + + rr = rustls_web_pki_server_cert_verifier_builder_build( + verifier_builder, &server_cert_verifier); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to build certificate verifier"); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + + rustls_client_config_builder_set_server_verifier(builder, + server_cert_verifier); +cleanup: + if(roots_builder) { + rustls_root_cert_store_builder_free(roots_builder); + } + if(roots) { + rustls_root_cert_store_free(roots); + } + if(verifier_builder) { + rustls_web_pki_server_cert_verifier_builder_free(verifier_builder); + } + if(server_cert_verifier) { + rustls_server_cert_verifier_free(server_cert_verifier); + } + + return result; +} + +static CURLcode +init_config_builder_platform_verifier( + struct Curl_easy *data, + struct rustls_client_config_builder *builder) +{ + struct rustls_server_cert_verifier *server_cert_verifier = NULL; + CURLcode result = CURLE_OK; + rustls_result rr; + + rr = rustls_platform_server_cert_verifier(&server_cert_verifier); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to create platform certificate verifier"); + result = CURLE_SSL_CACERT_BADFILE; + goto cleanup; + } + + rustls_client_config_builder_set_server_verifier(builder, + server_cert_verifier); + +cleanup: + if(server_cert_verifier) { + rustls_server_cert_verifier_free(server_cert_verifier); + } + return result; +} + +static CURLcode +init_config_builder_keylog(struct Curl_easy *data, + struct rustls_client_config_builder *builder) +{ + rustls_result rr; + + Curl_tls_keylog_open(); + if(!Curl_tls_keylog_enabled()) { + return CURLE_OK; + } + + rr = rustls_client_config_builder_set_key_log(builder, + cr_keylog_log_cb, + NULL); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "rustls_client_config_builder_set_key_log"); + Curl_tls_keylog_close(); + return map_error(rr); + } + + return CURLE_OK; +} + +static CURLcode +init_config_builder_client_auth(struct Curl_easy *data, + const struct ssl_primary_config *conn_config, + const struct ssl_config_data *ssl_config, + struct rustls_client_config_builder *builder) +{ + struct dynbuf cert_contents; + struct dynbuf key_contents; + rustls_result rr; + const struct rustls_certified_key *certified_key = NULL; + CURLcode result = CURLE_OK; + + if(conn_config->clientcert && !ssl_config->key) { + failf(data, "rustls: must provide key with certificate '%s'", + conn_config->clientcert); + return CURLE_SSL_CERTPROBLEM; + } + else if(!conn_config->clientcert && ssl_config->key) { + failf(data, "rustls: must provide certificate with key '%s'", + conn_config->clientcert); + return CURLE_SSL_CERTPROBLEM; + } + + curlx_dyn_init(&cert_contents, DYN_CERTFILE_SIZE); + curlx_dyn_init(&key_contents, DYN_KEYFILE_SIZE); + + if(!read_file_into(conn_config->clientcert, &cert_contents)) { + failf(data, "rustls: failed to read client certificate file: '%s'", + conn_config->clientcert); + result = CURLE_SSL_CERTPROBLEM; + goto cleanup; + } + + if(!read_file_into(ssl_config->key, &key_contents)) { + failf(data, "rustls: failed to read key file: '%s'", ssl_config->key); + result = CURLE_SSL_CERTPROBLEM; + goto cleanup; + } + + rr = rustls_certified_key_build(curlx_dyn_uptr(&cert_contents), + curlx_dyn_len(&cert_contents), + curlx_dyn_uptr(&key_contents), + curlx_dyn_len(&key_contents), + &certified_key); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "rustls: failed to build certified key"); + result = CURLE_SSL_CERTPROBLEM; + goto cleanup; + } + + rr = rustls_certified_key_keys_match(certified_key); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, + rr, + "rustls: client certificate and keypair files do not match:"); + + result = CURLE_SSL_CERTPROBLEM; + goto cleanup; + } + + rr = rustls_client_config_builder_set_certified_key(builder, + &certified_key, + 1); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "rustls: failed to set certified key"); + result = CURLE_SSL_CERTPROBLEM; + goto cleanup; + } + +cleanup: + curlx_dyn_free(&cert_contents); + curlx_dyn_free(&key_contents); + if(certified_key) { + rustls_certified_key_free(certified_key); + } + return result; +} + +#if defined(USE_ECH) +static CURLcode +init_config_builder_ech(struct Curl_easy *data, + const struct ssl_connect_data *connssl, + struct rustls_client_config_builder *builder) +{ + const rustls_hpke *hpke = rustls_supported_hpke(); + unsigned char *ech_config = NULL; + size_t ech_config_len = 0; + struct Curl_dns_entry *dns = NULL; + struct Curl_https_rrinfo *rinfo = NULL; + CURLcode result = CURLE_OK; + rustls_result rr; + + if(!hpke) { + failf(data, + "rustls: ECH unavailable, rustls-ffi built without " + "HPKE compatible crypto provider"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } + + if(data->set.str[STRING_ECH_PUBLIC]) { + failf(data, "rustls: ECH outername not supported"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } + + if(data->set.tls_ech == CURLECH_GREASE) { + rr = rustls_client_config_builder_enable_ech_grease(builder, hpke); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "rustls: failed to configure ECH GREASE"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } + return CURLE_OK; + } + + if(data->set.tls_ech & CURLECH_CLA_CFG && data->set.str[STRING_ECH_CONFIG]) { + const char *b64 = data->set.str[STRING_ECH_CONFIG]; + size_t decode_result; + if(!b64) { + infof(data, "rustls: ECHConfig from command line empty"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } + /* rustls-ffi expects the raw TLS encoded ECHConfigList bytes */ + decode_result = curlx_base64_decode(b64, &ech_config, &ech_config_len); + if(decode_result || !ech_config) { + infof(data, "rustls: cannot base64 decode ECHConfig from command line"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } + } + else { + if(connssl->peer.hostname) { + dns = Curl_dnscache_get(data, connssl->peer.hostname, + connssl->peer.port, data->conn->ip_version); + } + if(!dns) { + failf(data, "rustls: ECH requested but no DNS info available"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } + rinfo = dns->hinfo; + if(!rinfo || !rinfo->echconfiglist) { + failf(data, "rustls: ECH requested but no ECHConfig available"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } + ech_config = rinfo->echconfiglist; + ech_config_len = rinfo->echconfiglist_len; + } + + rr = rustls_client_config_builder_enable_ech(builder, + ech_config, + ech_config_len, + hpke); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "rustls: failed to configure ECH"); + result = CURLE_SSL_CONNECT_ERROR; + goto cleanup; + } +cleanup: + /* if we base64 decoded, we can free now */ + if(data->set.tls_ech & CURLECH_CLA_CFG && data->set.str[STRING_ECH_CONFIG]) { + free(ech_config); + } + if(dns) { + Curl_resolv_unlink(data, &dns); + } + return result; +} +#endif /* USE_ECH */ + +static CURLcode +cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data, + struct rustls_ssl_backend_data *const backend) +{ + const struct ssl_connect_data *connssl = cf->ctx; + const 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 rustls_connection *rconn = NULL; + struct rustls_client_config_builder *config_builder = 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; - char errorbuf[256]; - size_t errorlen; - rustls_result result; + CURLcode result = CURLE_OK; + rustls_result rr; DEBUGASSERT(backend); rconn = backend->conn; - { - 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; - } + result = init_config_builder(data, conn_config, &config_builder); + if(result != CURLE_OK) { + return result; } - 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]; - size_t i; - - for(i = 0; i < connssl->alpn->count; ++i) { - alpn[i].data = (const uint8_t *)connssl->alpn->entries[i]; - alpn[i].len = strlen(connssl->alpn->entries[i]); - } - rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, - connssl->alpn->count); - Curl_alpn_to_proto_str(&proto, connssl->alpn); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + init_config_builder_alpn(data, connssl, config_builder); } - if(!verifypeer) { + + if(!conn_config->verifypeer) { rustls_client_config_builder_dangerous_set_certificate_verifier( config_builder, cr_verify_none); } + else if(ssl_config->native_ca_store) { + result = init_config_builder_platform_verifier(data, config_builder); + if(result != CURLE_OK) { + rustls_client_config_builder_free(config_builder); + return result; + } + } 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; - } - } - - 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 build trusted root certificate store"); + result = init_config_builder_verifier(data, + config_builder, + conn_config, + ca_info_blob, + ssl_cafile); + if(result != CURLE_OK) { rustls_client_config_builder_free(config_builder); - return CURLE_SSL_CACERT_BADFILE; + return result; } - - verifier_builder = rustls_web_pki_server_cert_verifier_builder_new(roots); - rustls_root_cert_store_free(roots); - - 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; - } - } - - 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 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); } - result = rustls_client_config_builder_build( + if(conn_config->clientcert || ssl_config->key) { + result = init_config_builder_client_auth(data, + conn_config, + ssl_config, + config_builder); + if(result != CURLE_OK) { + rustls_client_config_builder_free(config_builder); + return result; + } + } + +#if defined(USE_ECH) + if(ECH_ENABLED(data)) { + result = init_config_builder_ech(data, connssl, config_builder); + if(result != CURLE_OK && data->set.tls_ech & CURLECH_HARD) { + rustls_client_config_builder_free(config_builder); + return result; + } + } +#endif /* USE_ECH */ + + result = init_config_builder_keylog(data, config_builder); + if(result != CURLE_OK) { + rustls_client_config_builder_free(config_builder); + return result; + } + + rr = rustls_client_config_builder_build( config_builder, &backend->config); - if(result != RUSTLS_RESULT_OK) { - failf(data, "rustls: failed to build client config"); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, rr, "failed to build client config"); + rustls_client_config_builder_free(config_builder); 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", (int)errorlen, errorbuf); + rr = rustls_client_connection_new(backend->config, + connssl->peer.hostname, + &rconn); + if(rr != RUSTLS_RESULT_OK) { + rustls_failf(data, result, "rustls_client_connection_new"); return CURLE_COULDNT_CONNECT; } DEBUGASSERT(rconn); rustls_connection_set_userdata(rconn, backend); backend->conn = rconn; - return CURLE_OK; + + return result; } static void @@ -779,48 +1137,36 @@ cr_set_negotiated_alpn(struct Curl_cfilter *cf, struct Curl_easy *data, /* 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*`. + * This function will set `*done` to true once the handshake is complete. + * This function never reads the value of `*done*`. */ static CURLcode -cr_connect_common(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool blocking, - bool *done) +cr_connect(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 = + const struct rustls_ssl_backend_data *const backend = (struct rustls_ssl_backend_data *)connssl->backend; - struct rustls_connection *rconn = NULL; + const struct rustls_connection *rconn = NULL; CURLcode tmperr = CURLE_OK; int result; - int what; bool wants_read; bool wants_write; - curl_socket_t writefd; - curl_socket_t readfd; - timediff_t timeout_ms; - timediff_t socket_check_timeout; DEBUGASSERT(backend); - CURL_TRC_CF(data, cf, "cr_connect_common, state=%d", connssl->state); + CURL_TRC_CF(data, cf, "cr_connect, 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); + CURL_TRC_CF(data, cf, "cr_connect, init backend -> %d", result); if(result != CURLE_OK) { return result; } connssl->state = ssl_connection_negotiating; } - rconn = backend->conn; /* Read/write data until the handshake is done or the socket would block. */ @@ -846,8 +1192,10 @@ cr_connect_common(struct Curl_cfilter *cf, } /* REALLY Done with the handshake. */ { - uint16_t proto = rustls_connection_get_protocol_version(rconn); - uint16_t cipher = rustls_connection_get_negotiated_ciphersuite(rconn); + const uint16_t proto = + rustls_connection_get_protocol_version(rconn); + const 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) @@ -858,6 +1206,43 @@ cr_connect_common(struct Curl_cfilter *cf, infof(data, "rustls: handshake complete, %s, cipher: %s", ver, buf); } + if(data->set.ssl.certinfo) { + size_t num_certs = 0; + while(rustls_connection_get_peer_certificate(rconn, (int)num_certs)) { + num_certs++; + } + result = Curl_ssl_init_certinfo(data, (int)num_certs); + if(result) + return result; + for(size_t i = 0; i < num_certs; i++) { + const rustls_certificate *cert; + const unsigned char *der_data; + size_t der_len; + rustls_result rresult = RUSTLS_RESULT_OK; + cert = rustls_connection_get_peer_certificate(rconn, i); + DEBUGASSERT(cert); /* Should exist since we counted already */ + rresult = rustls_certificate_get_der(cert, &der_data, &der_len); + if(rresult != RUSTLS_RESULT_OK) { + char errorbuf[255]; + size_t errorlen; + rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); + failf(data, + "Failed getting DER of server certificate #%ld: %.*s", i, + (int)errorlen, errorbuf); + return map_error(rresult); + } + { + const char *beg; + const char *end; + beg = (const char *)der_data; + end = (const char *)(der_data + der_len); + result = Curl_extract_certinfo(data, (int)i, beg, end); + if(result) + return result; + } + } + } + connssl->state = ssl_connection_complete; *done = TRUE; return CURLE_OK; @@ -868,50 +1253,14 @@ cr_connect_common(struct Curl_cfilter *cf, 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; - - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - 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) { - CURL_TRC_CF(data, cf, "Curl_socket_check: %s would block", - wants_read && wants_write ? "writing and reading" : - wants_write ? "writing" : "reading"); - 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) { CURL_TRC_CF(data, cf, "rustls_connection wants us to write_tls."); cr_send(cf, data, NULL, 0, &tmperr); if(tmperr == CURLE_AGAIN) { CURL_TRC_CF(data, cf, "writing would block"); - /* fall through */ + connssl->io_need = CURL_SSL_IO_NEED_SEND; + return CURLE_OK; } else if(tmperr != CURLE_OK) { return tmperr; @@ -923,7 +1272,8 @@ cr_connect_common(struct Curl_cfilter *cf, if(tls_recv_more(cf, data, &tmperr) < 0) { if(tmperr == CURLE_AGAIN) { CURL_TRC_CF(data, cf, "reading would block"); - /* fall through */ + connssl->io_need = CURL_SSL_IO_NEED_RECV; + return CURLE_OK; } else if(tmperr == CURLE_RECV_ERROR) { return CURLE_SSL_CONNECT_ERROR; @@ -940,20 +1290,6 @@ cr_connect_common(struct Curl_cfilter *cf, DEBUGASSERT(FALSE); } -static CURLcode -cr_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, bool *done) -{ - return cr_connect_common(cf, data, false, done); -} - -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 * cr_get_internals(struct ssl_connect_data *connssl, CURLINFO info UNUSED_PARAM) @@ -967,14 +1303,13 @@ cr_get_internals(struct ssl_connect_data *connssl, static CURLcode cr_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data, - bool send_shutdown, bool *done) + const 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 result = CURLE_OK; ssize_t nwritten, nread; - char buf[1024]; size_t i; DEBUGASSERT(backend); @@ -1007,6 +1342,7 @@ cr_shutdown(struct Curl_cfilter *cf, } for(i = 0; i < 10; ++i) { + char buf[1024]; nread = cr_recv(cf, data, buf, (int)sizeof(buf), &result); if(nread <= 0) break; @@ -1036,7 +1372,7 @@ out: static void cr_close(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct ssl_connect_data *connssl = cf->ctx; + const struct ssl_connect_data *connssl = cf->ctx; struct rustls_ssl_backend_data *backend = (struct rustls_ssl_backend_data *)connssl->backend; @@ -1054,7 +1390,7 @@ cr_close(struct Curl_cfilter *cf, struct Curl_easy *data) static size_t cr_version(char *buffer, size_t size) { - struct rustls_str ver = rustls_version(); + const struct rustls_str ver = rustls_version(); return msnprintf(buffer, size, "%.*s", (int)ver.len, ver.data); } @@ -1068,23 +1404,29 @@ cr_random(struct Curl_easy *data, unsigned char *entropy, size_t length) return map_error(rresult); } +static void cr_cleanup(void) +{ + Curl_tls_keylog_close(); +} + const struct Curl_ssl Curl_ssl_rustls = { { CURLSSLBACKEND_RUSTLS, "rustls" }, SSLSUPP_CAINFO_BLOB | /* supports */ SSLSUPP_HTTPS_PROXY | SSLSUPP_CIPHER_LIST | - SSLSUPP_TLS13_CIPHERSUITES, + SSLSUPP_TLS13_CIPHERSUITES | + SSLSUPP_CERTINFO | + SSLSUPP_ECH, sizeof(struct rustls_ssl_backend_data), NULL, /* init */ - NULL, /* cleanup */ + cr_cleanup, /* cleanup */ cr_version, /* version */ cr_shutdown, /* shutdown */ cr_data_pending, /* data_pending */ cr_random, /* random */ NULL, /* cert_status_request */ - cr_connect_blocking, /* connect */ - cr_connect_nonblocking, /* connect_nonblocking */ + cr_connect, /* connect */ Curl_ssl_adjust_pollset, /* adjust_pollset */ cr_get_internals, /* get_internals */ cr_close, /* close_one */ diff --git a/Utilities/cmcurl/lib/vtls/rustls.h b/Utilities/cmcurl/lib/vtls/rustls.h index bfbe23de3e..74d39d4d11 100644 --- a/Utilities/cmcurl/lib/vtls/rustls.h +++ b/Utilities/cmcurl/lib/vtls/rustls.h @@ -25,7 +25,7 @@ #ifndef HEADER_CURL_RUSTLS_H #define HEADER_CURL_RUSTLS_H -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_RUSTLS diff --git a/Utilities/cmcurl/lib/vtls/schannel.c b/Utilities/cmcurl/lib/vtls/schannel.c index 2af29a42ec..bea8eef8c0 100644 --- a/Utilities/cmcurl/lib/vtls/schannel.c +++ b/Utilities/cmcurl/lib/vtls/schannel.c @@ -29,7 +29,7 @@ * but vtls.c should ever call or use these functions. */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_SCHANNEL @@ -42,23 +42,25 @@ #include "vtls.h" #include "vtls_int.h" #include "vtls_scache.h" -#include "strcase.h" -#include "sendf.h" -#include "connect.h" /* for the connect timeout */ -#include "strerror.h" -#include "select.h" /* for the socket readiness */ -#include "inet_pton.h" /* for IP addr SNI check */ -#include "curl_multibyte.h" -#include "warnless.h" +#include "../strcase.h" +#include "../sendf.h" +#include "../connect.h" /* for the connect timeout */ +#include "../strerror.h" +#include "../select.h" /* for the socket readiness */ +#include "../curlx/inet_pton.h" /* for IP addr SNI check */ +#include "../curlx/multibyte.h" +#include "../curlx/warnless.h" #include "x509asn1.h" -#include "curl_printf.h" -#include "multiif.h" -#include "version_win32.h" -#include "rand.h" +#include "../curl_printf.h" +#include "../multiif.h" +#include "../system_win32.h" +#include "../curlx/version_win32.h" +#include "../rand.h" +#include "../curlx/strparse.h" /* The last #include file should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#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 @@ -71,19 +73,6 @@ #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: - - https://technet.microsoft.com/en-us/library/hh831771%28v=ws.11%29.aspx -*/ -#if defined(_MSC_VER) && (_MSC_VER >= 1800) && !defined(_USING_V110_SDK71_) -# define HAS_ALPN_SCHANNEL -#endif - -#ifndef BCRYPT_CHACHA20_POLY1305_ALGORITHM -#define BCRYPT_CHACHA20_POLY1305_ALGORITHM L"CHACHA20_POLY1305" -#endif - #ifndef BCRYPT_CHAIN_MODE_CCM #define BCRYPT_CHAIN_MODE_CCM L"ChainingModeCCM" #endif @@ -152,10 +141,31 @@ #define CALG_SHA_256 0x0000800c #endif +/* Work around typo in CeGCC (as of 0.59.1) w32api headers */ +#if defined(__MINGW32CE__) && \ + !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 +#ifndef CERT_FIND_HAS_PRIVATE_KEY +#define CERT_FIND_HAS_PRIVATE_KEY (21 << CERT_COMPARE_SHIFT) +#endif + +/* ALPN requires version 8.1 of the Windows SDK, which was + shipped with Visual Studio 2013, aka _MSC_VER 1800: + https://technet.microsoft.com/en-us/library/hh831771%28v=ws.11%29.aspx + Or mingw-w64 9.0 or upper. +*/ +#if (defined(__MINGW64_VERSION_MAJOR) && __MINGW64_VERSION_MAJOR >= 9) || \ + (defined(_MSC_VER) && (_MSC_VER >= 1800) && !defined(_USING_V110_SDK71_)) +#define HAS_ALPN_SCHANNEL +static bool s_win_has_alpn; +#endif + static CURLcode schannel_pkp_pin_peer_pubkey(struct Curl_cfilter *cf, struct Curl_easy *data, const char *pinnedpubkey); @@ -232,8 +242,6 @@ schannel_set_ssl_version_min_max(DWORD *enabled_protocols, return CURLE_OK; } -/* longest is 26, buffer is slightly bigger */ -#define LONGEST_ALG_ID 32 #define CIPHEROPTION(x) {#x, x} struct algo { @@ -350,9 +358,9 @@ static const struct algo algs[]= { }; static int -get_alg_id_by_name(char *name) +get_alg_id_by_name(const char *name) { - char *nameEnd = strchr(name, ':'); + const char *nameEnd = strchr(name, ':'); size_t n = nameEnd ? (size_t)(nameEnd - name) : strlen(name); int i; @@ -369,12 +377,13 @@ static CURLcode set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers, ALG_ID *algIds) { - char *startCur = ciphers; + const char *startCur = ciphers; int algCount = 0; while(startCur && (0 != *startCur) && (algCount < NUM_CIPHERS)) { - long alg = strtol(startCur, 0, 0); - if(!alg) + curl_off_t alg; + if(curlx_str_number(&startCur, &alg, INT_MAX) || !alg) alg = get_alg_id_by_name(startCur); + if(alg) algIds[algCount++] = (ALG_ID)alg; else if(!strncmp(startCur, "USE_STRONG_CRYPTO", @@ -606,6 +615,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, WCHAR* pszPassword; size_t pwd_len = 0; int str_w_len = 0; + int cert_find_flags; const char *cert_showfilename_error = blob ? "(memory blob)" : data->set.ssl.primary.clientcert; curlx_unicodefree(cert_path); @@ -654,8 +664,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, else pszPassword[0] = 0; - if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL)) + if(Curl_isVistaOrGreater) cert_store = PFXImportCertStore(&datablob, pszPassword, PKCS12_NO_PERSIST_KEY); else @@ -678,9 +687,17 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, return CURLE_SSL_CERTPROBLEM; } + /* CERT_FIND_HAS_PRIVATE_KEY is only available in Windows 8 / Server + 2012, (NT v6.2). For earlier versions we use CURL_FIND_ANY. */ + if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) + cert_find_flags = CERT_FIND_HAS_PRIVATE_KEY; + else + cert_find_flags = CERT_FIND_ANY; + client_certs[0] = CertFindCertificateInStore( cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, - CERT_FIND_ANY, NULL, NULL); + cert_find_flags, NULL, NULL); if(!client_certs[0]) { failf(data, "schannel: Failed to get certificate from file %s" @@ -802,11 +819,12 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, #endif sspi_status = - Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, - SECPKG_CRED_OUTBOUND, NULL, - &credentials, NULL, NULL, - &backend->cred->cred_handle, - &backend->cred->time_stamp); + Curl_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *)CURL_UNCONST(UNISP_NAME), + SECPKG_CRED_OUTBOUND, NULL, + &credentials, NULL, NULL, + &backend->cred->cred_handle, + &backend->cred->time_stamp); } else { /* Pre-Windows 10 1809 or the user set a legacy algorithm list. @@ -842,11 +860,12 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, #endif sspi_status = - Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, - SECPKG_CRED_OUTBOUND, NULL, - &schannel_cred, NULL, NULL, - &backend->cred->cred_handle, - &backend->cred->time_stamp); + Curl_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *)CURL_UNCONST(UNISP_NAME), + SECPKG_CRED_OUTBOUND, NULL, + &schannel_cred, NULL, NULL, + &backend->cred->cred_handle, + &backend->cred->time_stamp); } #ifdef HAS_CLIENT_CERT_PATH @@ -882,7 +901,9 @@ schannel_connect_step1(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; +#ifndef UNDER_CE struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); +#endif struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); SecBuffer outbuf; SecBufferDesc outbuf_desc; @@ -892,7 +913,6 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) unsigned char alpn_buffer[128]; #endif SECURITY_STATUS sspi_status = SEC_E_OK; - struct Curl_schannel_cred *old_cred = NULL; CURLcode result; DEBUGASSERT(backend); @@ -909,20 +929,14 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } #ifdef HAS_ALPN_SCHANNEL - /* ALPN is only supported on Windows 8.1 / Server 2012 R2 and above. - 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); + backend->use_alpn = connssl->alpn && s_win_has_alpn; #else backend->use_alpn = FALSE; #endif -#ifdef _WIN32_WCE +#ifdef UNDER_CE #ifdef HAS_MANUAL_VERIFY_API - /* certificate validation on CE does not seem to work right; we will + /* certificate validation on Windows CE does not seem to work right; we will * do it following a more manual process. */ backend->use_manual_cred_validation = TRUE; #else @@ -955,9 +969,10 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* check for an existing reusable credential handle */ if(ssl_config->primary.cache_session) { + struct Curl_schannel_cred *old_cred; Curl_ssl_scache_lock(data); - if(Curl_ssl_scache_get_obj(cf, data, connssl->peer.scache_key, - (void **)&old_cred)) { + old_cred = Curl_ssl_scache_get_obj(cf, data, connssl->peer.scache_key); + if(old_cred) { backend->cred = old_cred; DEBUGF(infof(data, "schannel: reusing existing credential handle")); @@ -973,7 +988,7 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) if(!backend->cred) { char *snihost; result = schannel_acquire_credential_handle(cf, data); - if(result) + if(result || !backend->cred) return result; /* schannel_acquire_credential_handle() sets backend->cred accordingly or it returns error otherwise. */ @@ -1629,6 +1644,7 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) args.data = data; args.idx = 0; args.certs_count = certs_count; + args.result = CURLE_OK; traverse_cert_store(ccert_context, add_cert_to_certinfo, &args); result = args.result; } @@ -1642,16 +1658,12 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_OK; } -static CURLcode -schannel_connect_common(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool nonblocking, bool *done) +static CURLcode schannel_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { - CURLcode result; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); - timediff_t timeout_ms; - int what; + CURLcode result; /* check if the connection has already been established */ if(ssl_connection_complete == connssl->state) { @@ -1659,73 +1671,19 @@ schannel_connect_common(struct Curl_cfilter *cf, return CURLE_OK; } + *done = FALSE; + if(ssl_connect_1 == connssl->connecting_state) { - /* check out how much more time we are allowed */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL/TLS connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - result = schannel_connect_step1(cf, data); if(result) return result; } - while(ssl_connect_2 == connssl->connecting_state) { - - /* check out how much more time we are allowed */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL/TLS connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* 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); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL/TLS socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL/TLS connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if - * this connection is part of a multi handle and this loop would - * execute again. This permits the owner of a multi handle to - * abort a connection attempt before step2 has completed while - * ensuring that a client using select() or epoll() will always - * have a valid fdset to wait on. - */ + if(ssl_connect_2 == connssl->connecting_state) { result = schannel_connect_step2(cf, data); - if(result || (nonblocking && (ssl_connect_2 == connssl->connecting_state))) + if(result) return result; - - } /* repeat step2 until all transactions are done. */ + } if(ssl_connect_3 == connssl->connecting_state) { result = schannel_connect_step3(cf, data); @@ -1752,11 +1710,6 @@ schannel_connect_common(struct Curl_cfilter *cf, *done = TRUE; } - else - *done = FALSE; - - /* reset our connection state machine */ - connssl->connecting_state = ssl_connect_1; return CURLE_OK; } @@ -2002,7 +1955,6 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, backend->encdata_offset), size, err); if(*err) { - nread = -1; if(*err == CURLE_AGAIN) SCH_DEV(infof(data, "schannel: recv returned CURLE_AGAIN")); else if(*err == CURLE_RECV_ERROR) @@ -2129,7 +2081,7 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, 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); + *err = schannel_connect(cf, data, &done); backend->recv_renegotiating = FALSE; if(*err) { infof(data, "schannel: renegotiation failed"); @@ -2240,28 +2192,6 @@ cleanup: return *err ? -1 : 0; } -static CURLcode schannel_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - return schannel_connect_common(cf, data, TRUE, done); -} - -static CURLcode schannel_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - CURLcode result; - bool done = FALSE; - - result = schannel_connect_common(cf, data, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - static bool schannel_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { @@ -2466,6 +2396,34 @@ static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data) static int schannel_init(void) { +#if defined(HAS_ALPN_SCHANNEL) && !defined(UNDER_CE) + bool wine = FALSE; + bool wine_has_alpn = FALSE; + +#ifndef CURL_WINDOWS_UWP + typedef const char *(APIENTRY *WINE_GET_VERSION_FN)(void); + /* GetModuleHandle() not available for UWP. + Assume no WINE because WINE has no UWP support. */ + WINE_GET_VERSION_FN p_wine_get_version = + CURLX_FUNCTION_CAST(WINE_GET_VERSION_FN, + (GetProcAddress(GetModuleHandleA("ntdll"), + "wine_get_version"))); + wine = !!p_wine_get_version; + if(wine) { + const char *wine_version = p_wine_get_version(); /* e.g. "6.0.2" */ + /* Assume ALPN support with WINE 6.0 or upper */ + wine_has_alpn = wine_version && atoi(wine_version) >= 6; + } +#endif + if(wine) + s_win_has_alpn = wine_has_alpn; + else { + /* ALPN is supported on Windows 8.1 / Server 2012 R2 and above. */ + s_win_has_alpn = curlx_verify_windows_version(6, 3, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL); + } +#endif /* HAS_ALPN_SCHANNEL && !UNDER_CE */ + return Curl_sspi_global_init() == CURLE_OK ? 1 : 0; } @@ -2589,7 +2547,12 @@ static void schannel_checksum(const unsigned char *input, if(!CryptCreateHash(hProv, algId, 0, 0, &hHash)) break; /* failed */ +#ifdef __MINGW32CE__ + /* workaround for CeGCC, should be (const BYTE*) */ + if(!CryptHashData(hHash, (BYTE*)CURL_UNCONST(input), (DWORD)inputlen, 0)) +#else if(!CryptHashData(hHash, input, (DWORD)inputlen, 0)) +#endif break; /* failed */ /* get hash size */ @@ -2653,7 +2616,7 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, } share = Curl_hash_pick(&multi->proto_hash, - (void *)MPROTO_SCHANNEL_CERT_SHARE_KEY, + CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1); if(!share || !share->cert_store) { return NULL; @@ -2669,8 +2632,8 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, 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); + now = curlx_now(); + elapsed_ms = curlx_timediff(now, share->time); if(elapsed_ms >= timeout_ms) { return NULL; } @@ -2731,7 +2694,7 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, } share = Curl_hash_pick(&multi->proto_hash, - (void *)MPROTO_SCHANNEL_CERT_SHARE_KEY, + CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1); if(!share) { share = calloc(1, sizeof(*share)); @@ -2739,7 +2702,7 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, return FALSE; } if(!Curl_hash_add2(&multi->proto_hash, - (void *)MPROTO_SCHANNEL_CERT_SHARE_KEY, + CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1, share, schannel_cert_share_free)) { free(share); @@ -2769,7 +2732,7 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, } free(share->CAfile); - share->time = Curl_now(); + share->time = curlx_now(); share->cert_store = cert_store; share->CAinfo_blob_size = CAinfo_blob_size; share->CAfile = CAfile; @@ -2800,7 +2763,6 @@ const struct Curl_ssl Curl_ssl_schannel = { schannel_random, /* random */ NULL, /* cert_status_request */ schannel_connect, /* connect */ - schannel_connect_nonblocking, /* connect_nonblocking */ Curl_ssl_adjust_pollset, /* adjust_pollset */ schannel_get_internals, /* get_internals */ schannel_close, /* close_one */ diff --git a/Utilities/cmcurl/lib/vtls/schannel.h b/Utilities/cmcurl/lib/vtls/schannel.h index 69f1baddb8..c3512dd132 100644 --- a/Utilities/cmcurl/lib/vtls/schannel.h +++ b/Utilities/cmcurl/lib/vtls/schannel.h @@ -24,7 +24,7 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_SCHANNEL @@ -50,10 +50,10 @@ #include #include -#include "curl_sspi.h" +#include "../curl_sspi.h" -#include "cfilters.h" -#include "urldata.h" +#include "../cfilters.h" +#include "../urldata.h" /* has been included via the above . * Or in case of ldap.c, it was included via . diff --git a/Utilities/cmcurl/lib/vtls/schannel_int.h b/Utilities/cmcurl/lib/vtls/schannel_int.h index 81476bc6d8..fe10125456 100644 --- a/Utilities/cmcurl/lib/vtls/schannel_int.h +++ b/Utilities/cmcurl/lib/vtls/schannel_int.h @@ -24,7 +24,7 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_SCHANNEL @@ -150,17 +150,17 @@ struct schannel_ssl_backend_data { 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; unsigned long req_flags, ret_flags; CURLcode recv_unrecoverable_err; /* schannel_recv had an unrecoverable err */ - bool recv_sspi_close_notify; /* true if connection closed by close_notify */ - bool recv_connection_closed; /* true if connection closed, regardless how */ - bool recv_renegotiating; /* true if recv is doing renegotiation */ - bool use_alpn; /* true if ALPN is used for this connection */ + BIT(recv_sspi_close_notify); /* true if connection closed by close_notify */ + BIT(recv_connection_closed); /* true if connection closed, regardless how */ + BIT(recv_renegotiating); /* true if recv is doing renegotiation */ + BIT(use_alpn); /* true if ALPN is used for this connection */ #ifdef HAS_MANUAL_VERIFY_API - bool use_manual_cred_validation; /* true if manual cred validation is used */ + BIT(use_manual_cred_validation); /* true if manual cred validation is used */ #endif BIT(sent_shutdown); + BIT(encdata_is_incomplete); }; /* key to use at `multi->proto_hash` */ diff --git a/Utilities/cmcurl/lib/vtls/schannel_verify.c b/Utilities/cmcurl/lib/vtls/schannel_verify.c index fede3908fe..f843342b9c 100644 --- a/Utilities/cmcurl/lib/vtls/schannel_verify.c +++ b/Utilities/cmcurl/lib/vtls/schannel_verify.c @@ -29,7 +29,7 @@ * only be invoked by code in schannel.c. */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_SCHANNEL #ifndef USE_WINDOWS_SSPI @@ -39,24 +39,41 @@ #include "schannel.h" #include "schannel_int.h" -#include "inet_pton.h" +#include "../curlx/inet_pton.h" #include "vtls.h" #include "vtls_int.h" -#include "sendf.h" -#include "strerror.h" -#include "curl_multibyte.h" -#include "curl_printf.h" +#include "../sendf.h" +#include "../strerror.h" +#include "../curlx/winapi.h" +#include "../curlx/multibyte.h" +#include "../curl_printf.h" #include "hostcheck.h" -#include "version_win32.h" +#include "../curlx/version_win32.h" /* The last #include file should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" #define BACKEND ((struct schannel_ssl_backend_data *)connssl->backend) #ifdef HAS_MANUAL_VERIFY_API +#ifdef __MINGW32CE__ +#define CERT_QUERY_OBJECT_BLOB 0x00000002 +#define CERT_QUERY_CONTENT_CERT 1 +#define CERT_QUERY_CONTENT_FLAG_CERT (1 << CERT_QUERY_CONTENT_CERT) +#define CERT_QUERY_FORMAT_BINARY 1 +#define CERT_QUERY_FORMAT_BASE64_ENCODED 2 +#define CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED 3 +#define CERT_QUERY_FORMAT_FLAG_ALL \ + (1 << CERT_QUERY_FORMAT_BINARY) | \ + (1 << CERT_QUERY_FORMAT_BASE64_ENCODED) | \ + (1 << CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED) +#define CERT_CHAIN_REVOCATION_CHECK_CHAIN 0x20000000 +#define CERT_NAME_DISABLE_IE4_UTF8_FLAG 0x00010000 +#define CERT_TRUST_IS_OFFLINE_REVOCATION 0x01000000 +#endif /* __MINGW32CE__ */ + #define MAX_CAFILE_SIZE 1048576 /* 1 MiB */ #define BEGIN_CERT "-----BEGIN CERTIFICATE-----" #define END_CERT "\n-----END CERTIFICATE-----" @@ -76,6 +93,7 @@ struct cert_chain_engine_config_win7 { HCERTSTORE hExclusiveTrustedPeople; }; +#ifndef UNDER_CE static int is_cr_or_lf(char c) { return c == '\r' || c == '\n'; @@ -138,13 +156,13 @@ static CURLcode add_certs_data_to_store(HCERTSTORE trust_store, } else { CERT_BLOB cert_blob; - CERT_CONTEXT *cert_context = NULL; + const CERT_CONTEXT *cert_context = NULL; BOOL add_cert_result = FALSE; DWORD actual_content_type = 0; DWORD cert_size = (DWORD) ((end_cert_ptr + end_cert_len) - begin_cert_ptr); - cert_blob.pbData = (BYTE *)begin_cert_ptr; + cert_blob.pbData = (BYTE *)CURL_UNCONST(begin_cert_ptr); cert_blob.cbData = cert_size; if(!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &cert_blob, @@ -157,12 +175,12 @@ static CURLcode add_certs_data_to_store(HCERTSTORE trust_store, NULL, NULL, (const void **)&cert_context)) { - char buffer[STRERROR_LEN]; + char buffer[WINAPI_ERROR_LEN]; failf(data, "schannel: failed to extract certificate from CA file " "'%s': %s", ca_file_text, - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); result = CURLE_SSL_CACERT_BADFILE; more_certs = 0; } @@ -186,13 +204,13 @@ static CURLcode add_certs_data_to_store(HCERTSTORE trust_store, NULL); CertFreeCertificateContext(cert_context); if(!add_cert_result) { - char buffer[STRERROR_LEN]; + char buffer[WINAPI_ERROR_LEN]; failf(data, "schannel: failed to add certificate from CA file '%s' " "to certificate store: %s", ca_file_text, - Curl_winapi_strerror(GetLastError(), buffer, - sizeof(buffer))); + curlx_winapi_strerror(GetLastError(), buffer, + sizeof(buffer))); result = CURLE_SSL_CACERT_BADFILE; more_certs = 0; } @@ -232,13 +250,13 @@ static CURLcode add_certs_file_to_store(HCERTSTORE trust_store, size_t ca_file_bufsize = 0; DWORD total_bytes_read = 0; - ca_file_tstr = curlx_convert_UTF8_to_tchar((char *)ca_file); + ca_file_tstr = curlx_convert_UTF8_to_tchar(ca_file); if(!ca_file_tstr) { - char buffer[STRERROR_LEN]; + char buffer[WINAPI_ERROR_LEN]; failf(data, "schannel: invalid path name for CA file '%s': %s", ca_file, - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); result = CURLE_SSL_CACERT_BADFILE; goto cleanup; } @@ -256,21 +274,21 @@ static CURLcode add_certs_file_to_store(HCERTSTORE trust_store, FILE_ATTRIBUTE_NORMAL, NULL); if(ca_file_handle == INVALID_HANDLE_VALUE) { - char buffer[STRERROR_LEN]; + char buffer[WINAPI_ERROR_LEN]; failf(data, "schannel: failed to open CA file '%s': %s", ca_file, - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); result = CURLE_SSL_CACERT_BADFILE; goto cleanup; } if(!GetFileSizeEx(ca_file_handle, &file_size)) { - char buffer[STRERROR_LEN]; + char buffer[WINAPI_ERROR_LEN]; failf(data, "schannel: failed to determine size of CA file '%s': %s", ca_file, - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); result = CURLE_SSL_CACERT_BADFILE; goto cleanup; } @@ -296,11 +314,11 @@ static CURLcode add_certs_file_to_store(HCERTSTORE trust_store, if(!ReadFile(ca_file_handle, ca_file_buffer + total_bytes_read, bytes_to_read, &bytes_read, NULL)) { - char buffer[STRERROR_LEN]; + char buffer[WINAPI_ERROR_LEN]; failf(data, "schannel: failed to read from CA file '%s': %s", ca_file, - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); result = CURLE_SSL_CACERT_BADFILE; goto cleanup; } @@ -313,7 +331,7 @@ static CURLcode add_certs_file_to_store(HCERTSTORE trust_store, } } - /* Null terminate the buffer */ + /* null-terminate the buffer */ ca_file_buffer[ca_file_bufsize] = '\0'; result = add_certs_data_to_store(trust_store, @@ -330,9 +348,11 @@ cleanup: return result; } +#endif #endif /* HAS_MANUAL_VERIFY_API */ +#ifndef UNDER_CE /* * Returns the number of characters necessary to populate all the host_names. * If host_names is not NULL, populate it with all the hostnames. Each string @@ -380,6 +400,9 @@ static DWORD cert_get_name_string(struct Curl_easy *data, (void)Win8_compat; #endif + if(!alt_name_info) + return 0; + compute_content = host_names != NULL && length != 0; /* Initialize default return values. */ @@ -442,14 +465,14 @@ static bool get_num_host_info(struct num_ip_data *ip_blob, struct in6_addr ia6; bool result = FALSE; - int res = Curl_inet_pton(AF_INET, hostname, &ia); + int res = curlx_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); + res = curlx_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)); @@ -511,15 +534,68 @@ static bool get_alt_name_info(struct Curl_easy *data, #endif return result; } +#endif /* !UNDER_CE */ /* Verify the server's hostname */ CURLcode Curl_verify_host(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct ssl_connect_data *connssl = cf->ctx; - SECURITY_STATUS sspi_status; CURLcode result = CURLE_PEER_FAILED_VERIFICATION; + struct ssl_connect_data *connssl = cf->ctx; CERT_CONTEXT *pCertContextServer = NULL; +#ifdef UNDER_CE + TCHAR cert_hostname_buff[256]; + DWORD len; + + /* This code does not support certificates with multiple alternative names. + * Right now we are only asking for the first preferred alternative name. + * Instead we would need to do all via CERT_NAME_SEARCH_ALL_NAMES_FLAG + * (If Windows CE supports that?) and run this section in a loop for each. + * https://msdn.microsoft.com/en-us/library/windows/desktop/aa376086.aspx + * curl: (51) schannel: CertGetNameString() certificate hostname + * (.google.com) did not match connection (google.com) + */ + len = CertGetNameString(pCertContextServer, + CERT_NAME_DNS_TYPE, + CERT_NAME_DISABLE_IE4_UTF8_FLAG, + NULL, + cert_hostname_buff, + 256); + if(len > 0) { + /* 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 + */ + char *cert_hostname = curlx_convert_tchar_to_UTF8(cert_hostname_buff); + if(!cert_hostname) { + result = CURLE_OUT_OF_MEMORY; + } + else{ + const char *conn_hostname = connssl->peer.hostname; + if(Curl_cert_hostcheck(cert_hostname, strlen(cert_hostname), + conn_hostname, strlen(conn_hostname))) { + infof(data, + "schannel: connection hostname (%s) validated " + "against certificate name (%s)\n", + conn_hostname, cert_hostname); + result = CURLE_OK; + } + else{ + failf(data, + "schannel: connection hostname (%s) " + "does not match certificate name (%s)", + conn_hostname, cert_hostname); + } + Curl_safefree(cert_hostname); + } + } + else { + failf(data, + "schannel: CertGetNameString did not provide any " + "certificate name information"); + } +#else + SECURITY_STATUS sspi_status; TCHAR *cert_hostname_buff = NULL; size_t cert_hostname_buff_index = 0; const char *conn_hostname = connssl->peer.hostname; @@ -539,7 +615,7 @@ CURLcode Curl_verify_host(struct Curl_cfilter *cf, &pCertContextServer); if((sspi_status != SEC_E_OK) || !pCertContextServer) { - char buffer[STRERROR_LEN]; + char buffer[WINAPI_ERROR_LEN]; failf(data, "schannel: Failed to read remote certificate context: %s", Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); goto cleanup; @@ -664,6 +740,7 @@ cleanup: if(pCertContextServer) CertFreeCertificateContext(pCertContextServer); +#endif /* !UNDER_CE */ return result; } @@ -681,23 +758,26 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, CERT_CONTEXT *pCertContextServer = NULL; const CERT_CHAIN_CONTEXT *pChainContext = NULL; HCERTCHAINENGINE cert_chain_engine = NULL; +#ifndef UNDER_CE HCERTSTORE trust_store = NULL; HCERTSTORE own_trust_store = NULL; +#endif /* !UNDER_CE */ DEBUGASSERT(BACKEND); sspi_status = Curl_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, - SECPKG_ATTR_REMOTE_CERT_CONTEXT, - &pCertContextServer); + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + &pCertContextServer); if((sspi_status != SEC_E_OK) || !pCertContextServer) { - char buffer[STRERROR_LEN]; + char buffer[WINAPI_ERROR_LEN]; failf(data, "schannel: Failed to read remote certificate context: %s", Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); result = CURLE_PEER_FAILED_VERIFICATION; } +#ifndef UNDER_CE if(result == CURLE_OK && (conn_config->CAfile || conn_config->ca_info_blob) && BACKEND->use_manual_cred_validation) { @@ -727,9 +807,9 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, CERT_STORE_CREATE_NEW_FLAG, NULL); if(!trust_store) { - char buffer[STRERROR_LEN]; + char buffer[WINAPI_ERROR_LEN]; failf(data, "schannel: failed to create certificate store: %s", - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); result = CURLE_SSL_CACERT_BADFILE; } else { @@ -774,14 +854,15 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, CertCreateCertificateChainEngine( (CERT_CHAIN_ENGINE_CONFIG *)&engine_config, &cert_chain_engine); if(!create_engine_result) { - char buffer[STRERROR_LEN]; + char buffer[WINAPI_ERROR_LEN]; failf(data, "schannel: failed to create certificate chain engine: %s", - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); result = CURLE_SSL_CACERT_BADFILE; } } } +#endif /* !UNDER_CE */ if(result == CURLE_OK) { CERT_CHAIN_PARA ChainPara; @@ -798,9 +879,9 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, CERT_CHAIN_REVOCATION_CHECK_CHAIN), NULL, &pChainContext)) { - char buffer[STRERROR_LEN]; + char buffer[WINAPI_ERROR_LEN]; failf(data, "schannel: CertGetCertificateChain failed: %s", - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); + curlx_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); pChainContext = NULL; result = CURLE_PEER_FAILED_VERIFICATION; } @@ -848,6 +929,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, } } +#ifndef UNDER_CE if(cert_chain_engine) { CertFreeCertificateChainEngine(cert_chain_engine); } @@ -855,6 +937,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, if(own_trust_store) { CertCloseStore(own_trust_store, 0); } +#endif /* !UNDER_CE */ if(pChainContext) CertFreeCertificateChain(pChainContext); diff --git a/Utilities/cmcurl/lib/vtls/sectransp.c b/Utilities/cmcurl/lib/vtls/sectransp.c index a0f6dccc28..2ae2ef35a2 100644 --- a/Utilities/cmcurl/lib/vtls/sectransp.c +++ b/Utilities/cmcurl/lib/vtls/sectransp.c @@ -28,18 +28,18 @@ * TLS/SSL layer. No code but vtls.c should ever call or use these functions. */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_SECTRANSP -#include "urldata.h" /* for the Curl_easy definition */ -#include "curl_base64.h" -#include "strtok.h" -#include "multiif.h" -#include "strcase.h" +#include "../urldata.h" /* for the Curl_easy definition */ +#include "../curlx/base64.h" +#include "../curlx/strparse.h" +#include "../multiif.h" +#include "../strcase.h" #include "x509asn1.h" #include "vtls_scache.h" -#include "strerror.h" +#include "../strerror.h" #include "cipher_suite.h" #ifdef __clang__ @@ -134,19 +134,19 @@ #include #endif /* CURL_BUILD_MAC */ -#include "sendf.h" -#include "inet_pton.h" -#include "connect.h" -#include "select.h" +#include "../sendf.h" +#include "../curlx/inet_pton.h" +#include "../connect.h" +#include "../select.h" #include "vtls.h" #include "vtls_int.h" #include "sectransp.h" -#include "curl_printf.h" -#include "strdup.h" +#include "../curl_printf.h" +#include "../strdup.h" -#include "curl_memory.h" +#include "../curl_memory.h" /* The last #include file should be: */ -#include "memdebug.h" +#include "../memdebug.h" /* From MacTypes.h (which we cannot include because it is not present in @@ -211,9 +211,6 @@ static const uint16_t default_ciphers[] = { #endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ }; -#define DEFAULT_CIPHERS_LEN sizeof(default_ciphers)/sizeof(default_ciphers[0]) - - /* pinned public key support tests */ /* version 1 supports macOS 10.12+ and iOS 10+ */ @@ -266,7 +263,7 @@ static OSStatus sectransp_bio_cf_in_read(SSLConnectionRef connection, void *buf, size_t *dataLength) /* IN/OUT */ { - struct Curl_cfilter *cf = (struct Curl_cfilter *)connection; + const struct Curl_cfilter *cf = (const struct Curl_cfilter *)connection; struct ssl_connect_data *connssl = cf->ctx; struct st_ssl_backend_data *backend = (struct st_ssl_backend_data *)connssl->backend; @@ -306,7 +303,7 @@ 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; + const struct Curl_cfilter *cf = (const struct Curl_cfilter *)connection; struct ssl_connect_data *connssl = cf->ctx; struct st_ssl_backend_data *backend = (struct st_ssl_backend_data *)connssl->backend; @@ -341,30 +338,28 @@ static OSStatus sectransp_bio_cf_out_write(SSLConnectionRef connection, CF_INLINE void GetDarwinVersionNumber(int *major, int *minor) { int mib[2]; - char *os_version; size_t os_version_len; - char *os_version_major, *os_version_minor; - char *tok_buf; + char buf[256]; /* Get the Darwin kernel version from the kernel using sysctl(): */ mib[0] = CTL_KERN; mib[1] = KERN_OSRELEASE; if(sysctl(mib, 2, NULL, &os_version_len, NULL, 0) == -1) return; - os_version = malloc(os_version_len*sizeof(char)); - if(!os_version) - return; - if(sysctl(mib, 2, os_version, &os_version_len, NULL, 0) == -1) { - free(os_version); - return; + if(os_version_len < sizeof(buf)) { + if(sysctl(mib, 2, buf, &os_version_len, NULL, 0) != -1) { + const char *os = buf; + curl_off_t fnum; + curl_off_t snum; + /* Parse the version: */ + if(!curlx_str_number(&os, &fnum, INT_MAX) && + !curlx_str_single(&os, '.') && + !curlx_str_number(&os, &snum, INT_MAX)) { + *major = (int)fnum; + *minor = (int)snum; + } + } } - - /* Parse the version: */ - os_version_major = Curl_strtok_r(os_version, ".", &tok_buf); - os_version_minor = Curl_strtok_r(NULL, ".", &tok_buf); - *major = atoi(os_version_major); - *minor = atoi(os_version_minor); - free(os_version); } #endif /* CURL_BUILD_MAC */ @@ -541,8 +536,8 @@ static OSStatus CopyIdentityWithLabel(char *label, for(i = 0; i < keys_list_count; i++) { OSStatus err = noErr; SecCertificateRef cert = NULL; - SecIdentityRef identity = - (SecIdentityRef) CFArrayGetValueAtIndex(keys_list, i); + const void *item = CFArrayGetValueAtIndex(keys_list, i); + SecIdentityRef identity = (SecIdentityRef)CURL_UNCONST(item); err = SecIdentityCopyCertificate(identity, &cert); if(err == noErr) { CFStringRef common_name = NULL; @@ -671,22 +666,22 @@ static OSStatus CopyIdentityFromPKCS12File(const char *cPath, count = CFArrayGetCount(items); for(i = 0; i < count; i++) { - CFTypeRef item = (CFTypeRef) CFArrayGetValueAtIndex(items, i); - CFTypeID itemID = CFGetTypeID(item); + const CFTypeRef item = CFArrayGetValueAtIndex(items, i); + CFTypeID itemID = CFGetTypeID(item); if(itemID == CFDictionaryGetTypeID()) { - CFTypeRef identity = (CFTypeRef) CFDictionaryGetValue( - (CFDictionaryRef) item, - kSecImportItemIdentity); + const CFTypeRef identity = CFDictionaryGetValue( + (CFDictionaryRef)item, + kSecImportItemIdentity); CFRetain(identity); - *out_cert_and_key = (SecIdentityRef) identity; + *out_cert_and_key = (SecIdentityRef)CURL_UNCONST(identity); break; } #if CURL_BUILD_MAC_10_7 else if(itemID == SecCertificateGetTypeID()) { status = SecIdentityCreateWithCertificate(NULL, - (SecCertificateRef) item, - out_cert_and_key); + (SecCertificateRef)CURL_UNCONST(item), + out_cert_and_key); break; } #endif @@ -927,7 +922,7 @@ static CURLcode sectransp_set_default_ciphers(struct Curl_easy *data, /* 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++) { + for(j = 0; j < CURL_ARRAYSIZE(default_ciphers); j++) { if(default_ciphers[j] == ciphers[i]) { ciphers[count++] = ciphers[i]; break; @@ -1096,10 +1091,13 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, if(result != CURLE_OK) return result; -#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && \ - defined(HAVE_BUILTIN_AVAILABLE) if(connssl->alpn) { +#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 +#ifdef HAVE_BUILTIN_AVAILABLE if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) { +#else + if(&SSLSetALPNProtocols && &SSLCopyALPNProtocols) { +#endif struct alpn_proto_buf proto; size_t i; CFStringRef cstr; @@ -1121,8 +1119,8 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, Curl_alpn_to_proto_str(&proto, connssl->alpn); infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } +#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ } -#endif if(ssl_config->key) { infof(data, "WARNING: SSL: CURLOPT_SSLKEY is ignored by Secure " @@ -1338,8 +1336,9 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, size_t ssl_sessionid_len; Curl_ssl_scache_lock(data); - if(Curl_ssl_scache_get_obj(cf, data, connssl->peer.scache_key, - (void **)&ssl_sessionid)) { + ssl_sessionid = Curl_ssl_scache_get_obj(cf, data, + connssl->peer.scache_key); + if(ssl_sessionid) { /* we got a session id, use it! */ err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, strlen(ssl_sessionid)); @@ -1436,7 +1435,7 @@ static long pem_to_der(const char *in, unsigned char **out, size_t *outlen) } b64[j] = '\0'; - err = Curl_base64_decode((const char *)b64, out, outlen); + err = curlx_base64_decode((const char *)b64, out, outlen); free(b64); if(err) { free(*out); @@ -1455,7 +1454,7 @@ static int read_cert(const char *file, unsigned char **out, size_t *outlen) unsigned char buf[512]; struct dynbuf certs; - Curl_dyn_init(&certs, MAX_CERTS_SIZE); + curlx_dyn_init(&certs, MAX_CERTS_SIZE); fd = open(file, 0); if(fd < 0) @@ -1467,18 +1466,18 @@ static int read_cert(const char *file, unsigned char **out, size_t *outlen) break; if(n < 0) { close(fd); - Curl_dyn_free(&certs); + curlx_dyn_free(&certs); return -1; } - if(Curl_dyn_addn(&certs, buf, n)) { + if(curlx_dyn_addn(&certs, buf, n)) { close(fd); return -1; } } close(fd); - *out = Curl_dyn_uptr(&certs); - *outlen = Curl_dyn_len(&certs); + *out = curlx_dyn_uptr(&certs); + *outlen = curlx_dyn_len(&certs); return 0; } @@ -1696,7 +1695,8 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, const char *pinnedpubkey) { /* Scratch */ size_t pubkeylen, realpubkeylen, spkiHeaderLength = 24; - unsigned char *pubkey = NULL, *realpubkey = NULL; + const unsigned char *pubkey = NULL; + unsigned char *realpubkey = NULL; const unsigned char *spkiHeader = NULL; CFDataRef publicKeyBits = NULL; @@ -1746,7 +1746,7 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, #endif /* SECTRANSP_PINNEDPUBKEY_V2 */ pubkeylen = (size_t)CFDataGetLength(publicKeyBits); - pubkey = (unsigned char *)CFDataGetBytePtr(publicKeyBits); + pubkey = (const unsigned char *)CFDataGetBytePtr(publicKeyBits); switch(pubkeylen) { case 526: @@ -2092,10 +2092,13 @@ check_handshake: break; } -#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && \ - defined(HAVE_BUILTIN_AVAILABLE) if(connssl->alpn) { +#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 +#ifdef HAVE_BUILTIN_AVAILABLE if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) { +#else + if(&SSLSetALPNProtocols && &SSLCopyALPNProtocols) { +#endif CFArrayRef alpnArr = NULL; CFStringRef chosenProtocol = NULL; err = SSLCopyALPNProtocols(backend->ssl_ctx, &alpnArr); @@ -2122,8 +2125,8 @@ check_handshake: if(alpnArr) CFRelease(alpnArr); } +#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ } -#endif return CURLE_OK; } @@ -2131,7 +2134,7 @@ check_handshake: static CURLcode add_cert_to_certinfo(struct Curl_easy *data, - SecCertificateRef server_cert, + const SecCertificateRef server_cert, int idx) { CURLcode result = CURLE_OK; @@ -2151,7 +2154,7 @@ add_cert_to_certinfo(struct Curl_easy *data, static CURLcode collect_server_cert_single(struct Curl_cfilter *cf, struct Curl_easy *data, - SecCertificateRef server_cert, + const SecCertificateRef server_cert, CFIndex idx) { CURLcode result = CURLE_OK; @@ -2248,8 +2251,8 @@ static CURLcode collect_server_cert(struct Curl_cfilter *cf, if(ssl_config->certinfo) result = Curl_ssl_init_certinfo(data, (int)count); for(i = 0L ; !result && (i < count) ; i++) { - server_cert = (SecCertificateRef)CFArrayGetValueAtIndex(server_certs, - i); + const void *item = CFArrayGetValueAtIndex(server_certs, i); + server_cert = (SecCertificateRef)CURL_UNCONST(item); result = collect_server_cert_single(cf, data, server_cert, i); } CFRelease(server_certs); @@ -2265,7 +2268,8 @@ static CURLcode collect_server_cert(struct Curl_cfilter *cf, if(ssl_config->certinfo) result = Curl_ssl_init_certinfo(data, (int)count); for(i = 0L ; !result && (i < count) ; i++) { - server_cert = (SecCertificateRef)CFArrayGetValueAtIndex(server_certs, i); + const void *item = CFArrayGetValueAtIndex(server_certs, i); + server_cert = (SecCertificateRef)CURL_UNCONST(item); result = collect_server_cert_single(cf, data, server_cert, i); } CFRelease(server_certs); @@ -2292,15 +2296,12 @@ static CURLcode sectransp_connect_step3(struct Curl_cfilter *cf, return CURLE_OK; } -static CURLcode -sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, - bool nonblocking, - bool *done) +static CURLcode sectransp_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { 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) { @@ -2308,73 +2309,20 @@ sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, return CURLE_OK; } + *done = FALSE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we are allowed */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - result = sectransp_connect_step1(cf, data); if(result) return result; } - while(ssl_connect_2 == connssl->connecting_state) { - - /* check allowed time left */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* 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); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if this - * connection is done nonblocking and this loop would execute again. This - * permits the owner of a multi handle to abort a connection attempt - * before step2 has completed while ensuring that a client using select() - * or epoll() will always have a valid fdset to wait on. - */ + if(ssl_connect_2 == connssl->connecting_state) { result = sectransp_connect_step2(cf, data); - if(result || (nonblocking && (ssl_connect_2 == connssl->connecting_state))) + if(result) return result; - - } /* repeat step2 until all transactions are done. */ - + } if(ssl_connect_3 == connssl->connecting_state) { result = sectransp_connect_step3(cf, data); @@ -2387,34 +2335,6 @@ sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, connssl->state = ssl_connection_complete; *done = TRUE; } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -static CURLcode sectransp_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - return sectransp_connect_common(cf, data, TRUE, done); -} - -static CURLcode sectransp_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - CURLcode result; - bool done = FALSE; - - result = sectransp_connect_common(cf, data, FALSE, &done); - - if(result) - return result; - - DEBUGASSERT(done); return CURLE_OK; } @@ -2549,7 +2469,7 @@ static bool sectransp_data_pending(struct Curl_cfilter *cf, DEBUGASSERT(backend); if(backend->ssl_ctx) { /* SSL is in use */ - CURL_TRC_CF((struct Curl_easy *)data, cf, "data_pending"); + CURL_TRC_CF((struct Curl_easy *)CURL_UNCONST(data), cf, "data_pending"); err = SSLGetBufferedReadSize(backend->ssl_ctx, &buffer); if(err == noErr) return buffer > 0UL; @@ -2759,7 +2679,6 @@ const struct Curl_ssl Curl_ssl_sectransp = { sectransp_random, /* random */ NULL, /* cert_status_request */ sectransp_connect, /* connect */ - sectransp_connect_nonblocking, /* connect_nonblocking */ Curl_ssl_adjust_pollset, /* adjust_pollset */ sectransp_get_internals, /* get_internals */ sectransp_close, /* close_one */ diff --git a/Utilities/cmcurl/lib/vtls/sectransp.h b/Utilities/cmcurl/lib/vtls/sectransp.h index 0f1085ad91..c82dc18445 100644 --- a/Utilities/cmcurl/lib/vtls/sectransp.h +++ b/Utilities/cmcurl/lib/vtls/sectransp.h @@ -24,7 +24,7 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_SECTRANSP diff --git a/Utilities/cmcurl/lib/vtls/vtls.c b/Utilities/cmcurl/lib/vtls/vtls.c index 9c59b4c97b..c6fb60d0a4 100644 --- a/Utilities/cmcurl/lib/vtls/vtls.c +++ b/Utilities/cmcurl/lib/vtls/vtls.c @@ -38,7 +38,7 @@ https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef HAVE_SYS_TYPES_H #include @@ -50,8 +50,8 @@ #include #endif -#include "urldata.h" -#include "cfilters.h" +#include "../urldata.h" +#include "../cfilters.h" #include "vtls.h" /* generic SSL protos etc */ #include "vtls_int.h" @@ -66,28 +66,28 @@ #include "bearssl.h" /* BearSSL versions */ #include "rustls.h" /* Rustls versions */ -#include "slist.h" -#include "sendf.h" -#include "strcase.h" -#include "url.h" -#include "progress.h" -#include "share.h" -#include "multiif.h" -#include "timeval.h" -#include "curl_md5.h" -#include "curl_sha256.h" -#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" +#include "../slist.h" +#include "../sendf.h" +#include "../strcase.h" +#include "../url.h" +#include "../progress.h" +#include "../share.h" +#include "../multiif.h" +#include "../curlx/timeval.h" +#include "../curl_md5.h" +#include "../curl_sha256.h" +#include "../curlx/warnless.h" +#include "../curlx/base64.h" +#include "../curl_printf.h" +#include "../curlx/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" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" #define CLONE_STRING(var) \ @@ -154,17 +154,19 @@ static const struct alpn_spec ALPN_SPEC_H2_H11 = { }; #endif -static const struct alpn_spec *alpn_get_spec(int httpwant, bool use_alpn) +static const struct alpn_spec * +alpn_get_spec(http_majors allowed, bool use_alpn) { if(!use_alpn) return NULL; #ifdef USE_HTTP2 - if(httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) + if(allowed & CURL_HTTP_V2x) { + if(allowed & CURL_HTTP_V1x) + return &ALPN_SPEC_H2_H11; return &ALPN_SPEC_H2; - if(httpwant >= CURL_HTTP_VERSION_2) - return &ALPN_SPEC_H2_H11; + } #else - (void)httpwant; + (void)allowed; #endif /* Use the ALPN protocol "http/1.1" for HTTP/1.x. Avoid "http/1.0" because some servers do not support it. */ @@ -213,6 +215,7 @@ match_ssl_primary_config(struct Curl_easy *data, strcasecompare(c1->cipher_list, c2->cipher_list) && strcasecompare(c1->cipher_list13, c2->cipher_list13) && strcasecompare(c1->curves, c2->curves) && + strcasecompare(c1->signature_algorithms, c2->signature_algorithms) && strcasecompare(c1->CRLfile, c2->CRLfile) && strcasecompare(c1->pinned_key, c2->pinned_key)) return TRUE; @@ -257,6 +260,7 @@ static bool clone_ssl_primary_config(struct ssl_primary_config *source, CLONE_STRING(cipher_list13); CLONE_STRING(pinned_key); CLONE_STRING(curves); + CLONE_STRING(signature_algorithms); CLONE_STRING(CRLfile); #ifdef USE_TLS_SRP CLONE_STRING(username); @@ -279,6 +283,7 @@ static void free_primary_ssl_config(struct ssl_primary_config *sslc) Curl_safefree(sslc->ca_info_blob); Curl_safefree(sslc->issuercert_blob); Curl_safefree(sslc->curves); + Curl_safefree(sslc->signature_algorithms); Curl_safefree(sslc->CRLfile); #ifdef USE_TLS_SRP Curl_safefree(sslc->username); @@ -297,6 +302,8 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data) data->set.str[STRING_SSL_CIPHER_LIST]; data->set.ssl.primary.cipher_list13 = data->set.str[STRING_SSL_CIPHER13_LIST]; + data->set.ssl.primary.signature_algorithms = + data->set.str[STRING_SSL_SIGNATURE_ALGORITHMS]; data->set.ssl.primary.pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; data->set.ssl.primary.cert_blob = data->set.blobs[BLOB_CERT]; @@ -483,39 +490,6 @@ static void cf_ctx_free(struct ssl_connect_data *ctx) } } -static CURLcode ssl_connect(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - CURLcode result; - - if(!ssl_prefs_check(data)) - return CURLE_SSL_CONNECT_ERROR; - - /* mark this is being ssl-enabled from here on. */ - connssl->state = ssl_connection_negotiating; - - result = connssl->ssl_impl->connect_blocking(cf, data); - - if(!result) { - DEBUGASSERT(connssl->state == ssl_connection_complete); - } - - return result; -} - -static CURLcode -ssl_connect_nonblocking(struct Curl_cfilter *cf, struct Curl_easy *data, - bool *done) -{ - struct ssl_connect_data *connssl = cf->ctx; - - if(!ssl_prefs_check(data)) - return CURLE_SSL_CONNECT_ERROR; - - /* mark this is being ssl requested from here on. */ - return connssl->ssl_impl->connect_nonblocking(cf, data, done); -} - CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex, struct dynbuf *binding) { @@ -642,17 +616,17 @@ CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data, DEBUGASSERT(certnum < ci->num_of_certs); - Curl_dyn_init(&build, CURL_X509_STR_MAX); + curlx_dyn_init(&build, CURL_X509_STR_MAX); - if(Curl_dyn_add(&build, label) || - Curl_dyn_addn(&build, ":", 1) || - Curl_dyn_addn(&build, value, valuelen)) + if(curlx_dyn_add(&build, label) || + curlx_dyn_addn(&build, ":", 1) || + curlx_dyn_addn(&build, value, valuelen)) return CURLE_OUT_OF_MEMORY; nl = Curl_slist_append_nodup(ci->certinfo[certnum], - Curl_dyn_ptr(&build)); + curlx_dyn_ptr(&build)); if(!nl) { - Curl_dyn_free(&build); + curlx_dyn_free(&build); curl_slist_free_all(ci->certinfo[certnum]); result = CURLE_OUT_OF_MEMORY; } @@ -689,7 +663,7 @@ static CURLcode pubkey_pem_to_der(const char *pem, if(!pem) return CURLE_BAD_CONTENT_ENCODING; - Curl_dyn_init(&pbuf, MAX_PINNED_PUBKEY_SIZE); + curlx_dyn_init(&pbuf, MAX_PINNED_PUBKEY_SIZE); begin_pos = strstr(pem, "-----BEGIN PUBLIC KEY-----"); if(!begin_pos) @@ -717,16 +691,19 @@ static CURLcode pubkey_pem_to_der(const char *pem, */ while(pem_count < pem_len) { if('\n' != pem[pem_count] && '\r' != pem[pem_count]) { - result = Curl_dyn_addn(&pbuf, &pem[pem_count], 1); + result = curlx_dyn_addn(&pbuf, &pem[pem_count], 1); if(result) return result; } ++pem_count; } - result = Curl_base64_decode(Curl_dyn_ptr(&pbuf), der, der_len); - - Curl_dyn_free(&pbuf); + if(curlx_dyn_len(&pbuf)) { + result = curlx_base64_decode(curlx_dyn_ptr(&pbuf), der, der_len); + curlx_dyn_free(&pbuf); + } + else + result = CURLE_BAD_CONTENT_ENCODING; return result; } @@ -770,9 +747,9 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, sha256sumdigest, CURL_SHA256_DIGEST_LENGTH); if(!encode) - encode = Curl_base64_encode((char *)sha256sumdigest, - CURL_SHA256_DIGEST_LENGTH, &encoded, - &encodedlen); + encode = curlx_base64_encode((char *)sha256sumdigest, + CURL_SHA256_DIGEST_LENGTH, &encoded, + &encodedlen); Curl_safefree(sha256sumdigest); if(encode) @@ -791,8 +768,8 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, do { end_pos = strstr(begin_pos, ";sha256//"); /* - * if there is an end_pos, null terminate, - * otherwise it will go to the end of the original string + * if there is an end_pos, null-terminate, otherwise it will go to the + * end of the original string */ if(end_pos) end_pos[0] = '\0'; @@ -827,7 +804,7 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, if(!fp) return result; - Curl_dyn_init(&buf, MAX_PINNED_PUBKEY_SIZE); + curlx_dyn_init(&buf, MAX_PINNED_PUBKEY_SIZE); /* Determine the file's size */ if(fseek(fp, 0, SEEK_END)) @@ -855,23 +832,23 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, 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)) + if(curlx_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, Curl_dyn_ptr(&buf), pubkeylen)) + if(!memcmp(pubkey, curlx_dyn_ptr(&buf), pubkeylen)) result = CURLE_OK; goto end; } /* - * Otherwise we will assume it is PEM and try to decode it - * after placing null terminator + * Otherwise we will assume it is PEM and try to decode it after placing + * null-terminator */ - pem_read = pubkey_pem_to_der(Curl_dyn_ptr(&buf), &pem_ptr, &pem_len); + pem_read = pubkey_pem_to_der(curlx_dyn_ptr(&buf), &pem_ptr, &pem_len); /* if it was not read successfully, exit */ if(pem_read) goto end; @@ -883,7 +860,7 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, if(pubkeylen == pem_len && !memcmp(pubkey, pem_ptr, pubkeylen)) result = CURLE_OK; end: - Curl_dyn_free(&buf); + curlx_dyn_free(&buf); Curl_safefree(pem_ptr); fclose(fp); } @@ -921,20 +898,11 @@ static int multissl_init(void) } static CURLcode multissl_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, bool *done) { if(multissl_setup(NULL)) return CURLE_FAILED_INIT; - return Curl_ssl->connect_blocking(cf, data); -} - -static CURLcode multissl_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - if(multissl_setup(NULL)) - return CURLE_FAILED_INIT; - return Curl_ssl->connect_nonblocking(cf, data, done); + return Curl_ssl->do_connect(cf, data, done); } static void multissl_adjust_pollset(struct Curl_cfilter *cf, @@ -993,7 +961,6 @@ static const struct Curl_ssl Curl_ssl_multi = { NULL, /* random */ NULL, /* cert_status_request */ multissl_connect, /* connect */ - multissl_connect_nonblocking, /* connect_nonblocking */ multissl_adjust_pollset, /* adjust_pollset */ multissl_get_internals, /* get_internals */ multissl_close, /* close_one */ @@ -1232,10 +1199,10 @@ static ssl_peer_type get_peer_type(const char *hostname) #else struct in_addr addr; #endif - if(Curl_inet_pton(AF_INET, hostname, &addr)) + if(curlx_inet_pton(AF_INET, hostname, &addr)) return CURL_SSL_PEER_IPV4; #ifdef USE_IPV6 - else if(Curl_inet_pton(AF_INET6, hostname, &addr)) { + else if(curlx_inet_pton(AF_INET6, hostname, &addr)) { return CURL_SSL_PEER_IPV6; } #endif @@ -1341,13 +1308,13 @@ static void ssl_cf_close(struct Curl_cfilter *cf, static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, struct Curl_easy *data, - bool blocking, bool *done) + bool *done) { struct ssl_connect_data *connssl = cf->ctx; struct cf_call_data save; CURLcode result; - if(cf->connected) { + if(cf->connected && (connssl->state != ssl_connection_deferred)) { *done = TRUE; return CURLE_OK; } @@ -1358,15 +1325,13 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, } if(!cf->next->connected) { - result = cf->next->cft->do_connect(cf->next, data, blocking, done); + result = cf->next->cft->do_connect(cf->next, data, done); if(result || !*done) return result; } CF_DATA_SAVE(save, cf, data); CURL_TRC_CF(data, cf, "cf_connect()"); - DEBUGASSERT(data->conn); - DEBUGASSERT(data->conn == cf->conn); DEBUGASSERT(connssl); *done = FALSE; @@ -1378,21 +1343,23 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, goto out; } - if(blocking) { - result = ssl_connect(cf, data); - *done = (result == CURLE_OK); - } - else { - result = ssl_connect_nonblocking(cf, data, done); + if(!connssl->prefs_checked) { + if(!ssl_prefs_check(data)) + return CURLE_SSL_CONNECT_ERROR; + connssl->prefs_checked = TRUE; } + result = connssl->ssl_impl->do_connect(cf, data, done); + if(!result && *done) { cf->connected = TRUE; if(connssl->state == ssl_connection_complete) - connssl->handshake_done = Curl_now(); + connssl->handshake_done = curlx_now(); /* Connection can be deferred when sending early data */ DEBUGASSERT(connssl->state == ssl_connection_complete || connssl->state == ssl_connection_deferred); + DEBUGASSERT(connssl->state != ssl_connection_deferred || + connssl->earlydata_state > ssl_earlydata_none); } out: CURL_TRC_CF(data, cf, "cf_connect() -> %d, done=%d", result, *done); @@ -1400,6 +1367,77 @@ out: return result; } +static CURLcode ssl_cf_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_await); + 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, "ssl_cf_set_earlydata(len=%zu) -> %zd", + blen, nwritten); + if(nwritten < 0) + return result; + } + return CURLE_OK; +} + +static CURLcode ssl_cf_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_await) { + result = ssl_cf_set_earlydata(cf, data, buf, blen); + if(result) + return result; + /* we buffered any early data we'd like to send. Actually + * do the connect now which sends it and performs the handshake. */ + connssl->earlydata_state = ssl_earlydata_sending; + connssl->earlydata_skip = Curl_bufq_len(&connssl->earlydata); + } + + result = ssl_cf_connect(cf, data, done); + + if(!result && *done) { + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, connssl->handshake_done); + switch(connssl->earlydata_state) { + case ssl_earlydata_none: + break; + case ssl_earlydata_accepted: + if(!Curl_ssl_cf_is_proxy(cf)) + Curl_pgrsEarlyData(data, (curl_off_t)connssl->earlydata_skip); + infof(data, "Server accepted %zu bytes of TLS early data.", + connssl->earlydata_skip); + break; + case 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; + break; + default: + /* This should not happen. Either we do not use early data or we + * should know if it was accepted or not. */ + DEBUGASSERT(NULL); + break; + } + } + return result; +} + static bool ssl_cf_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { @@ -1418,21 +1456,57 @@ 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, + struct Curl_easy *data, + const void *buf, size_t blen, bool eos, CURLcode *err) { struct ssl_connect_data *connssl = cf->ctx; struct cf_call_data save; - ssize_t nwritten = 0; + ssize_t nwritten = 0, early_written = 0; (void)eos; - /* OpenSSL and maybe other TLS libs do not like 0-length writes. Skip. */ *err = CURLE_OK; - if(len > 0) { - CF_DATA_SAVE(save, cf, data); - nwritten = connssl->ssl_impl->send_plain(cf, data, buf, len, err); - CF_DATA_RESTORE(cf, save); + CF_DATA_SAVE(save, cf, data); + + if(connssl->state == ssl_connection_deferred) { + bool done = FALSE; + *err = ssl_cf_connect_deferred(cf, data, buf, blen, &done); + if(*err) { + nwritten = -1; + goto out; + } + else if(!done) { + *err = CURLE_AGAIN; + nwritten = -1; + goto out; + } + DEBUGASSERT(connssl->state == ssl_connection_complete); } + + if(connssl->earlydata_skip) { + if(connssl->earlydata_skip >= blen) { + connssl->earlydata_skip -= blen; + *err = CURLE_OK; + nwritten = (ssize_t)blen; + goto out; + } + else { + early_written = connssl->earlydata_skip; + buf = ((const char *)buf) + connssl->earlydata_skip; + blen -= connssl->earlydata_skip; + connssl->earlydata_skip = 0; + } + } + + /* OpenSSL and maybe other TLS libs do not like 0-length writes. Skip. */ + if(blen > 0) + nwritten = connssl->ssl_impl->send_plain(cf, data, buf, blen, err); + + if(nwritten >= 0) + nwritten += early_written; + +out: + CF_DATA_RESTORE(cf, save); return nwritten; } @@ -1446,6 +1520,21 @@ static ssize_t ssl_cf_recv(struct Curl_cfilter *cf, CF_DATA_SAVE(save, cf, data); *err = CURLE_OK; + if(connssl->state == ssl_connection_deferred) { + bool done = FALSE; + *err = ssl_cf_connect_deferred(cf, data, NULL, 0, &done); + if(*err) { + nread = -1; + goto out; + } + else if(!done) { + *err = CURLE_AGAIN; + nread = -1; + goto out; + } + DEBUGASSERT(connssl->state == ssl_connection_complete); + } + nread = connssl->ssl_impl->recv_plain(cf, data, buf, len, err); if(nread > 0) { DEBUGASSERT((size_t)nread <= len); @@ -1454,6 +1543,8 @@ static ssize_t ssl_cf_recv(struct Curl_cfilter *cf, /* eof */ *err = CURLE_OK; } + +out: CURL_TRC_CF(data, cf, "cf_recv(len=%zu) -> %zd, %d", len, nread, *err); CF_DATA_RESTORE(cf, save); @@ -1468,7 +1559,9 @@ static CURLcode ssl_cf_shutdown(struct Curl_cfilter *cf, CURLcode result = CURLE_OK; *done = TRUE; - if(!cf->shutdown && Curl_ssl->shut_down) { + /* If we have done the SSL handshake, shut down the connection cleanly */ + if(cf->connected && (connssl->state == ssl_connection_complete) && + !cf->shutdown && Curl_ssl->shut_down) { struct cf_call_data save; CF_DATA_SAVE(save, cf, data); @@ -1576,8 +1669,14 @@ static CURLcode cf_ssl_create(struct Curl_cfilter **pcf, DEBUGASSERT(data->conn); - ctx = cf_ctx_new(data, alpn_get_spec(data->state.httpwant, +#ifdef CURL_DISABLE_HTTP + /* We only support ALPN for HTTP so far. */ + DEBUGASSERT(!conn->bits.tls_enable_alpn); + ctx = cf_ctx_new(data, NULL); +#else + ctx = cf_ctx_new(data, alpn_get_spec(data->state.http_neg.wanted, conn->bits.tls_enable_alpn)); +#endif if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -1627,16 +1726,16 @@ static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf, struct ssl_connect_data *ctx; CURLcode result; bool use_alpn = conn->bits.tls_enable_alpn; - int httpwant = CURL_HTTP_VERSION_1_1; + http_majors allowed = CURL_HTTP_V1x; #ifdef USE_HTTP2 if(conn->http_proxy.proxytype == CURLPROXY_HTTPS2) { use_alpn = TRUE; - httpwant = CURL_HTTP_VERSION_2; + allowed = (CURL_HTTP_V1x|CURL_HTTP_V2x); } #endif - ctx = cf_ctx_new(data, alpn_get_spec(httpwant, use_alpn)); + ctx = cf_ctx_new(data, alpn_get_spec(allowed, use_alpn)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -1768,7 +1867,7 @@ CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, if(cf->cft == &Curl_cft_ssl) { bool done; CURL_TRC_CF(data, cf, "shutdown and remove SSL, start"); - Curl_shutdown_start(data, sockindex, NULL); + Curl_shutdown_start(data, sockindex, 0, NULL); result = vtls_shutdown_blocking(cf, data, send_shutdown, &done); Curl_shutdown_clear(data, sockindex); if(!result && !done) /* blocking failed? */ @@ -1866,6 +1965,24 @@ bool Curl_alpn_contains_proto(const struct alpn_spec *spec, return FALSE; } +void Curl_alpn_restrict_to(struct alpn_spec *spec, const char *proto) +{ + size_t plen = strlen(proto); + DEBUGASSERT(plen < sizeof(spec->entries[0])); + if(plen < sizeof(spec->entries[0])) { + memcpy(spec->entries[0], proto, plen + 1); + spec->count = 1; + } +} + +void Curl_alpn_copy(struct alpn_spec *dest, const struct alpn_spec *src) +{ + if(src) + memcpy(dest, src, sizeof(*dest)); + else + memset(dest, 0, sizeof(*dest)); +} + CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf, struct Curl_easy *data, struct ssl_connect_data *connssl, diff --git a/Utilities/cmcurl/lib/vtls/vtls.h b/Utilities/cmcurl/lib/vtls/vtls.h index b751c3743c..0bb333b987 100644 --- a/Utilities/cmcurl/lib/vtls/vtls.h +++ b/Utilities/cmcurl/lib/vtls/vtls.h @@ -23,7 +23,7 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" struct connectdata; struct ssl_config_data; @@ -42,9 +42,10 @@ struct dynbuf; #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 SSLSUPP_SIGNATURE_ALGORITHMS (1<<10) /* supports TLS sigalgs */ #ifdef USE_ECH -# include "curl_base64.h" +# include "../curlx/base64.h" # define ECH_ENABLED(__data__) \ (__data__->set.tls_ech && \ !(__data__->set.tls_ech & CURLECH_DISABLE)\ diff --git a/Utilities/cmcurl/lib/vtls/vtls_int.h b/Utilities/cmcurl/lib/vtls/vtls_int.h index 3a5611dfe4..0632a07657 100644 --- a/Utilities/cmcurl/lib/vtls/vtls_int.h +++ b/Utilities/cmcurl/lib/vtls/vtls_int.h @@ -23,9 +23,9 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" -#include "cfilters.h" -#include "urldata.h" +#include "../curl_setup.h" +#include "../cfilters.h" +#include "../urldata.h" #include "vtls.h" #ifdef USE_SSL @@ -49,7 +49,7 @@ struct ssl_connect_data; #define ALPN_PROTO_BUF_MAX (ALPN_ENTRIES_MAX * (ALPN_NAME_MAX + 1)) struct alpn_spec { - const char entries[ALPN_ENTRIES_MAX][ALPN_NAME_MAX]; + char entries[ALPN_ENTRIES_MAX][ALPN_NAME_MAX]; size_t count; /* number of entries */ }; @@ -62,6 +62,8 @@ CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf, const struct alpn_spec *spec); CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf, const struct alpn_spec *spec); +void Curl_alpn_restrict_to(struct alpn_spec *spec, const char *proto); +void Curl_alpn_copy(struct alpn_spec *dest, const struct alpn_spec *src); CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -89,7 +91,7 @@ typedef enum { typedef enum { ssl_earlydata_none, - ssl_earlydata_use, + ssl_earlydata_await, ssl_earlydata_sending, ssl_earlydata_sent, ssl_earlydata_accepted, @@ -124,6 +126,7 @@ struct ssl_connect_data { 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 */ + BIT(prefs_checked); /* SSL preferences have been checked */ }; @@ -157,11 +160,8 @@ struct Curl_ssl { size_t length); bool (*cert_status_request)(void); - CURLcode (*connect_blocking)(struct Curl_cfilter *cf, - struct Curl_easy *data); - CURLcode (*connect_nonblocking)(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done); + CURLcode (*do_connect)(struct Curl_cfilter *cf, struct Curl_easy *data, + bool *done); /* During handshake/shutdown, adjust the pollset to include the socket * for POLLOUT or POLLIN as needed. Mandatory. */ diff --git a/Utilities/cmcurl/lib/vtls/vtls_scache.c b/Utilities/cmcurl/lib/vtls/vtls_scache.c index 365e945920..1fe4b5bbde 100644 --- a/Utilities/cmcurl/lib/vtls/vtls_scache.c +++ b/Utilities/cmcurl/lib/vtls/vtls_scache.c @@ -22,7 +22,7 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_SSL @@ -36,28 +36,31 @@ #include #endif -#include "urldata.h" -#include "cfilters.h" +#include "../urldata.h" +#include "../cfilters.h" #include "vtls.h" /* generic SSL protos etc */ #include "vtls_int.h" #include "vtls_scache.h" #include "vtls_spack.h" -#include "strcase.h" -#include "url.h" -#include "llist.h" -#include "share.h" -#include "curl_trc.h" -#include "curl_sha256.h" -#include "rand.h" -#include "warnless.h" -#include "curl_printf.h" -#include "strdup.h" +#include "../strcase.h" +#include "../url.h" +#include "../llist.h" +#include "../share.h" +#include "../curl_trc.h" +#include "../curl_sha256.h" +#include "../rand.h" +#include "../curlx/warnless.h" +#include "../curl_printf.h" +#include "../strdup.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" + + +static bool cf_ssl_peer_key_is_global(const char *peer_key); /* a peer+tls-config we cache sessions for */ struct Curl_ssl_scache_peer { @@ -73,6 +76,7 @@ struct Curl_ssl_scache_peer { size_t max_sessions; long age; /* just a number, the higher the more recent */ BIT(hmac_set); /* if key_salt and key_hmac are present */ + BIT(exportable); /* sessions for this peer can be exported */ }; #define CURL_SCACHE_MAGIC 0x000e1551 @@ -104,32 +108,18 @@ static struct Curl_ssl_scache *cf_ssl_scache_get(struct Curl_easy *data) return scache; } -static void cf_ssl_scache_clear_session(struct Curl_ssl_session *s) -{ - if(s->sdata) { - free((void *)s->sdata); - s->sdata = NULL; - } - s->sdata_len = 0; - if(s->quic_tp) { - free((void *)s->quic_tp); - s->quic_tp = NULL; - } - s->quic_tp_len = 0; - s->ietf_tls_id = 0; - s->valid_until = 0; - Curl_safefree(s->alpn); -} - -static void cf_ssl_scache_sesssion_ldestroy(void *udata, void *s) +static void cf_ssl_scache_session_ldestroy(void *udata, void *obj) { + struct Curl_ssl_session *s = obj; (void)udata; - cf_ssl_scache_clear_session(s); + free(CURL_UNCONST(s->sdata)); + free(CURL_UNCONST(s->quic_tp)); + free((void *)s->alpn); free(s); } CURLcode -Curl_ssl_session_create(unsigned char *sdata, size_t sdata_len, +Curl_ssl_session_create(void *sdata, size_t sdata_len, int ietf_tls_id, const char *alpn, curl_off_t valid_until, size_t earlydata_max, struct Curl_ssl_session **psession) @@ -140,7 +130,7 @@ Curl_ssl_session_create(unsigned char *sdata, size_t sdata_len, } CURLcode -Curl_ssl_session_create2(unsigned char *sdata, size_t sdata_len, +Curl_ssl_session_create2(void *sdata, size_t sdata_len, int ietf_tls_id, const char *alpn, curl_off_t valid_until, size_t earlydata_max, unsigned char *quic_tp, size_t quic_tp_len, @@ -171,7 +161,7 @@ Curl_ssl_session_create2(unsigned char *sdata, size_t sdata_len, if(alpn) { s->alpn = strdup(alpn); if(!s->alpn) { - cf_ssl_scache_sesssion_ldestroy(NULL, s); + cf_ssl_scache_session_ldestroy(NULL, s); return CURLE_OUT_OF_MEMORY; } } @@ -186,7 +176,7 @@ void Curl_ssl_session_destroy(struct Curl_ssl_session *s) if(Curl_node_llist(&s->list)) Curl_node_remove(&s->list); else { - cf_ssl_scache_sesssion_ldestroy(NULL, s); + cf_ssl_scache_session_ldestroy(NULL, s); } } } @@ -223,6 +213,19 @@ static void cf_ssl_scache_peer_set_obj(struct Curl_ssl_scache_peer *peer, peer->sobj_free = sobj_free; } +static void cf_ssl_cache_peer_update(struct Curl_ssl_scache_peer *peer) +{ + /* The sessions of this peer are exportable if + * - it has no confidential information + * - its peer key is not yet known, because sessions were + * imported using only the salt+hmac + * - the peer key is global, e.g. carrying no relative paths */ + peer->exportable = (!peer->clientcert && !peer->srp_username && + !peer->srp_password && + (!peer->ssl_peer_key || + cf_ssl_peer_key_is_global(peer->ssl_peer_key))); +} + static CURLcode cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer, const char *ssl_peer_key, @@ -265,6 +268,8 @@ cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer, if(!peer->srp_password) goto out; } + + cf_ssl_cache_peer_update(peer); result = CURLE_OK; out: if(result) @@ -336,7 +341,7 @@ CURLcode Curl_ssl_scache_create(size_t max_peers, for(i = 0; i < scache->peer_count; ++i) { scache->peers[i].max_sessions = max_sessions_per_peer; Curl_llist_init(&scache->peers[i].sessions, - cf_ssl_scache_sesssion_ldestroy); + cf_ssl_scache_session_ldestroy); } *pscache = scache; @@ -372,28 +377,34 @@ void Curl_ssl_scache_unlock(struct Curl_easy *data) static CURLcode cf_ssl_peer_key_add_path(struct dynbuf *buf, const char *name, - char *path) + char *path, + bool *is_local) { if(path && path[0]) { /* We try to add absolute paths, so that the session key can stay * valid when used in another process with different CWD. However, * when a path does not exist, this does not work. Then, we add * the path as is. */ -#ifdef _WIN32 +#ifdef UNDER_CE + (void)is_local; + return curlx_dyn_addf(buf, ":%s-%s", name, path); +#elif defined(_WIN32) char abspath[_MAX_PATH]; if(_fullpath(abspath, path, _MAX_PATH)) - return Curl_dyn_addf(buf, ":%s-%s", name, abspath); + return curlx_dyn_addf(buf, ":%s-%s", name, abspath); + *is_local = TRUE; #elif defined(HAVE_REALPATH) if(path[0] != '/') { char *abspath = realpath(path, NULL); if(abspath) { - CURLcode r = Curl_dyn_addf(buf, ":%s-%s", name, abspath); + CURLcode r = curlx_dyn_addf(buf, ":%s-%s", name, abspath); (free)(abspath); /* allocated by libc, free without memdebug */ return r; } + *is_local = TRUE; } #endif - return Curl_dyn_addf(buf, ":%s-%s", name, path); + return curlx_dyn_addf(buf, ":%s-%s", name, path); } return CURLE_OK; } @@ -407,14 +418,14 @@ static CURLcode cf_ssl_peer_key_add_hash(struct dynbuf *buf, unsigned char hash[CURL_SHA256_DIGEST_LENGTH]; size_t i; - r = Curl_dyn_addf(buf, ":%s-", name); + r = curlx_dyn_addf(buf, ":%s-", name); if(r) goto out; r = Curl_sha256it(hash, blob->data, blob->len); if(r) goto out; for(i = 0; i < CURL_SHA256_DIGEST_LENGTH; ++i) { - r = Curl_dyn_addf(buf, "%02x", hash[i]); + r = curlx_dyn_addf(buf, "%02x", hash[i]); if(r) goto out; } @@ -423,6 +434,17 @@ out: return r; } +#define CURL_SSLS_LOCAL_SUFFIX ":L" +#define CURL_SSLS_GLOBAL_SUFFIX ":G" + +static bool cf_ssl_peer_key_is_global(const char *peer_key) +{ + size_t len = peer_key ? strlen(peer_key) : 0; + return (len > 2) && + (peer_key[len - 1] == 'G') && + (peer_key[len - 2] == ':'); +} + CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, const struct ssl_peer *peer, const char *tls_id, @@ -431,12 +453,13 @@ CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf); struct dynbuf buf; size_t key_len; + bool is_local = FALSE; CURLcode r; *ppeer_key = NULL; - Curl_dyn_init(&buf, 10 * 1024); + curlx_dyn_init(&buf, 10 * 1024); - r = Curl_dyn_addf(&buf, "%s:%d", peer->hostname, peer->port); + r = curlx_dyn_addf(&buf, "%s:%d", peer->hostname, peer->port); if(r) goto out; @@ -444,86 +467,86 @@ CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, case TRNSPRT_TCP: break; case TRNSPRT_UDP: - r = Curl_dyn_add(&buf, ":UDP"); + r = curlx_dyn_add(&buf, ":UDP"); break; case TRNSPRT_QUIC: - r = Curl_dyn_add(&buf, ":QUIC"); + r = curlx_dyn_add(&buf, ":QUIC"); break; case TRNSPRT_UNIX: - r = Curl_dyn_add(&buf, ":UNIX"); + r = curlx_dyn_add(&buf, ":UNIX"); break; default: - r = Curl_dyn_addf(&buf, ":TRNSPRT-%d", peer->transport); + r = curlx_dyn_addf(&buf, ":TRNSPRT-%d", peer->transport); break; } if(r) goto out; if(!ssl->verifypeer) { - r = Curl_dyn_add(&buf, ":NO-VRFY-PEER"); + r = curlx_dyn_add(&buf, ":NO-VRFY-PEER"); if(r) goto out; } if(!ssl->verifyhost) { - r = Curl_dyn_add(&buf, ":NO-VRFY-HOST"); + r = curlx_dyn_add(&buf, ":NO-VRFY-HOST"); if(r) goto out; } if(ssl->verifystatus) { - r = Curl_dyn_add(&buf, ":VRFY-STATUS"); + r = curlx_dyn_add(&buf, ":VRFY-STATUS"); if(r) goto out; } if(!ssl->verifypeer || !ssl->verifyhost) { if(cf->conn->bits.conn_to_host) { - r = Curl_dyn_addf(&buf, ":CHOST-%s", cf->conn->conn_to_host.name); + r = curlx_dyn_addf(&buf, ":CHOST-%s", cf->conn->conn_to_host.name); if(r) goto out; } if(cf->conn->bits.conn_to_port) { - r = Curl_dyn_addf(&buf, ":CPORT-%d", cf->conn->conn_to_port); + r = curlx_dyn_addf(&buf, ":CPORT-%d", cf->conn->conn_to_port); if(r) goto out; } } if(ssl->version || ssl->version_max) { - r = Curl_dyn_addf(&buf, ":TLSVER-%d-%d", ssl->version, + r = curlx_dyn_addf(&buf, ":TLSVER-%d-%d", ssl->version, (ssl->version_max >> 16)); if(r) goto out; } if(ssl->ssl_options) { - r = Curl_dyn_addf(&buf, ":TLSOPT-%x", ssl->ssl_options); + r = curlx_dyn_addf(&buf, ":TLSOPT-%x", ssl->ssl_options); if(r) goto out; } if(ssl->cipher_list) { - r = Curl_dyn_addf(&buf, ":CIPHER-%s", ssl->cipher_list); + r = curlx_dyn_addf(&buf, ":CIPHER-%s", ssl->cipher_list); if(r) goto out; } if(ssl->cipher_list13) { - r = Curl_dyn_addf(&buf, ":CIPHER13-%s", ssl->cipher_list13); + r = curlx_dyn_addf(&buf, ":CIPHER13-%s", ssl->cipher_list13); if(r) goto out; } if(ssl->curves) { - r = Curl_dyn_addf(&buf, ":CURVES-%s", ssl->curves); + r = curlx_dyn_addf(&buf, ":CURVES-%s", ssl->curves); if(r) goto out; } if(ssl->verifypeer) { - r = cf_ssl_peer_key_add_path(&buf, "CA", ssl->CAfile); + r = cf_ssl_peer_key_add_path(&buf, "CA", ssl->CAfile, &is_local); if(r) goto out; - r = cf_ssl_peer_key_add_path(&buf, "CApath", ssl->CApath); + r = cf_ssl_peer_key_add_path(&buf, "CApath", ssl->CApath, &is_local); if(r) goto out; - r = cf_ssl_peer_key_add_path(&buf, "CRL", ssl->CRLfile); + r = cf_ssl_peer_key_add_path(&buf, "CRL", ssl->CRLfile, &is_local); if(r) goto out; - r = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert); + r = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert, &is_local); if(r) goto out; if(ssl->cert_blob) { @@ -543,19 +566,19 @@ CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, } } if(ssl->pinned_key && ssl->pinned_key[0]) { - r = Curl_dyn_addf(&buf, ":Pinned-%s", ssl->pinned_key); + r = curlx_dyn_addf(&buf, ":Pinned-%s", ssl->pinned_key); if(r) goto out; } if(ssl->clientcert && ssl->clientcert[0]) { - r = Curl_dyn_add(&buf, ":CCERT"); + r = curlx_dyn_add(&buf, ":CCERT"); if(r) goto out; } #ifdef USE_TLS_SRP if(ssl->username || ssl->password) { - r = Curl_dyn_add(&buf, ":SRP-AUTH"); + r = curlx_dyn_add(&buf, ":SRP-AUTH"); if(r) goto out; } @@ -565,17 +588,21 @@ CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, r = CURLE_FAILED_INIT; goto out; } - r = Curl_dyn_addf(&buf, ":IMPL-%s", tls_id); + r = curlx_dyn_addf(&buf, ":IMPL-%s", tls_id); if(r) goto out; - *ppeer_key = Curl_dyn_take(&buf, &key_len); - /* we just added printable char, and dynbuf always 0 terminates, - * no need to track length */ + r = curlx_dyn_addf(&buf, is_local ? + CURL_SSLS_LOCAL_SUFFIX : CURL_SSLS_GLOBAL_SUFFIX); + if(r) + goto out; + *ppeer_key = curlx_dyn_take(&buf, &key_len); + /* we just added printable char, and dynbuf always null-terminates, no need + * to track length */ out: - Curl_dyn_free(&buf); + curlx_dyn_free(&buf); return r; } @@ -655,6 +682,7 @@ cf_ssl_find_peer_by_key(struct Curl_easy *data, result = CURLE_OUT_OF_MEMORY; goto out; } + cf_ssl_cache_peer_update(&scache->peers[i]); *ppeer = &scache->peers[i]; goto out; } @@ -832,10 +860,6 @@ CURLcode Curl_ssl_scache_put(struct Curl_cfilter *cf, Curl_ssl_session_destroy(s); return CURLE_OK; } - if(!GOOD_SCACHE(scache)) { - Curl_ssl_session_destroy(s); - return CURLE_BAD_FUNCTION_ARGUMENT; - } Curl_ssl_scache_lock(data); result = cf_scache_add_session(cf, data, scache, ssl_peer_key, s); @@ -933,31 +957,29 @@ out: return result; } -bool Curl_ssl_scache_get_obj(struct Curl_cfilter *cf, - struct Curl_easy *data, - const char *ssl_peer_key, - void **sobj) +void *Curl_ssl_scache_get_obj(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key) { struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct Curl_ssl_scache_peer *peer = NULL; CURLcode result; + void *sobj; - *sobj = NULL; if(!scache) - return FALSE; + return NULL; result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config, &peer); if(result) - return FALSE; + return NULL; - if(peer) - *sobj = peer->sobj; + sobj = peer ? peer->sobj : NULL; CURL_TRC_SSLS(data, "%s cached session for '%s'", - *sobj ? "Found" : "No", ssl_peer_key); - return !!*sobj; + sobj ? "Found" : "No", ssl_peer_key); + return sobj; } void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf, @@ -1062,7 +1084,7 @@ out: CURLcode Curl_ssl_session_import(struct Curl_easy *data, const char *ssl_peer_key, const unsigned char *shmac, size_t shmac_len, - const unsigned char *sdata, size_t sdata_len) + const void *sdata, size_t sdata_len) { struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); struct Curl_ssl_scache_peer *peer = NULL; @@ -1150,17 +1172,17 @@ CURLcode Curl_ssl_session_export(struct Curl_easy *data, Curl_ssl_scache_lock(data); - Curl_dyn_init(&hbuf, (CURL_SHA256_DIGEST_LENGTH * 2) + 1); - Curl_dyn_init(&sbuf, CURL_SSL_TICKET_MAX); + curlx_dyn_init(&hbuf, (CURL_SHA256_DIGEST_LENGTH * 2) + 1); + curlx_dyn_init(&sbuf, CURL_SSL_TICKET_MAX); for(i = 0; scache && i < scache->peer_count; i++) { peer = &scache->peers[i]; if(!peer->ssl_peer_key && !peer->hmac_set) continue; /* skip free entry */ - if(peer->clientcert || peer->srp_username || peer->srp_password) - continue; /* not exporting those */ + if(!peer->exportable) + continue; - Curl_dyn_reset(&hbuf); + curlx_dyn_reset(&hbuf); cf_scache_peer_remove_expired(peer, now); n = Curl_llist_head(&peer->sessions); if(n) @@ -1172,22 +1194,22 @@ CURLcode Curl_ssl_session_export(struct Curl_easy *data, if(r) goto out; } - if(!Curl_dyn_len(&hbuf)) { - r = Curl_dyn_addn(&hbuf, peer->key_salt, sizeof(peer->key_salt)); + if(!curlx_dyn_len(&hbuf)) { + r = curlx_dyn_addn(&hbuf, peer->key_salt, sizeof(peer->key_salt)); if(r) goto out; - r = Curl_dyn_addn(&hbuf, peer->key_hmac, sizeof(peer->key_hmac)); + r = curlx_dyn_addn(&hbuf, peer->key_hmac, sizeof(peer->key_hmac)); if(r) goto out; } - Curl_dyn_reset(&sbuf); + curlx_dyn_reset(&sbuf); r = Curl_ssl_session_pack(data, s, &sbuf); if(r) goto out; r = export_fn(data, userptr, peer->ssl_peer_key, - Curl_dyn_uptr(&hbuf), Curl_dyn_len(&hbuf), - Curl_dyn_uptr(&sbuf), Curl_dyn_len(&sbuf), + curlx_dyn_uptr(&hbuf), curlx_dyn_len(&hbuf), + curlx_dyn_uptr(&sbuf), curlx_dyn_len(&sbuf), s->valid_until, s->ietf_tls_id, s->alpn, s->earlydata_max); if(r) @@ -1203,8 +1225,8 @@ CURLcode Curl_ssl_session_export(struct Curl_easy *data, out: Curl_ssl_scache_unlock(data); - Curl_dyn_free(&hbuf); - Curl_dyn_free(&sbuf); + curlx_dyn_free(&hbuf); + curlx_dyn_free(&sbuf); return r; } diff --git a/Utilities/cmcurl/lib/vtls/vtls_scache.h b/Utilities/cmcurl/lib/vtls/vtls_scache.h index b42873f787..eef50805e4 100644 --- a/Utilities/cmcurl/lib/vtls/vtls_scache.h +++ b/Utilities/cmcurl/lib/vtls/vtls_scache.h @@ -23,9 +23,9 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" -#include "cfilters.h" -#include "urldata.h" +#include "../curl_setup.h" +#include "../cfilters.h" +#include "../urldata.h" #ifdef USE_SSL @@ -85,12 +85,11 @@ void Curl_ssl_scache_unlock(struct Curl_easy *data); * @param cf the connection filter wanting to use it * @param data the transfer involved * @param ssl_peer_key the key for lookup - * @param sobj on return, the object for the peer key or NULL + * @retval sobj the object for the peer key or NULL */ -bool Curl_ssl_scache_get_obj(struct Curl_cfilter *cf, - struct Curl_easy *data, - const char *ssl_peer_key, - void **sobj); +void *Curl_ssl_scache_get_obj(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *ssl_peer_key); typedef void Curl_ssl_scache_obj_dtor(void *sobj); @@ -114,9 +113,9 @@ CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf, void *sobj, Curl_ssl_scache_obj_dtor *sobj_dtor_cb); -/* All about a SSL session ticket */ +/* All about an SSL session ticket */ struct Curl_ssl_session { - const unsigned char *sdata; /* session ticket data, plain bytes */ + const void *sdata; /* session ticket data, plain bytes */ size_t sdata_len; /* number of bytes in sdata */ curl_off_t valid_until; /* seconds since EPOCH until ticket expires */ int ietf_tls_id; /* TLS protocol identifier negotiated */ @@ -138,7 +137,7 @@ struct Curl_ssl_session { * @param psession on return the scached session instance created */ CURLcode -Curl_ssl_session_create(unsigned char *sdata, size_t sdata_len, +Curl_ssl_session_create(void *sdata, size_t sdata_len, int ietf_tls_id, const char *alpn, curl_off_t valid_until, size_t earlydata_max, @@ -147,7 +146,7 @@ Curl_ssl_session_create(unsigned char *sdata, size_t sdata_len, /* Variation of session creation with quic transport parameter bytes, * Takes ownership of `quic_tp` regardless of return code. */ CURLcode -Curl_ssl_session_create2(unsigned char *sdata, size_t sdata_len, +Curl_ssl_session_create2(void *sdata, size_t sdata_len, int ietf_tls_id, const char *alpn, curl_off_t valid_until, size_t earlydata_max, @@ -200,7 +199,7 @@ void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf, CURLcode Curl_ssl_session_import(struct Curl_easy *data, const char *ssl_peer_key, const unsigned char *shmac, size_t shmac_len, - const unsigned char *sdata, size_t sdata_len); + const void *sdata, size_t sdata_len); CURLcode Curl_ssl_session_export(struct Curl_easy *data, curl_ssls_export_cb *export_fn, diff --git a/Utilities/cmcurl/lib/vtls/vtls_spack.c b/Utilities/cmcurl/lib/vtls/vtls_spack.c index 6dec8e567b..152fad7eb3 100644 --- a/Utilities/cmcurl/lib/vtls/vtls_spack.c +++ b/Utilities/cmcurl/lib/vtls/vtls_spack.c @@ -22,19 +22,19 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_SSLS_EXPORT -#include "urldata.h" -#include "curl_trc.h" +#include "../urldata.h" +#include "../curl_trc.h" #include "vtls_scache.h" #include "vtls_spack.h" -#include "strdup.h" +#include "../strdup.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" #ifdef _MSC_VER #if _MSC_VER >= 1600 @@ -64,7 +64,7 @@ typedef unsigned __int64 uint64_t; static CURLcode spack_enc8(struct dynbuf *buf, uint8_t b) { - return Curl_dyn_addn(buf, &b, 1); + return curlx_dyn_addn(buf, &b, 1); } static CURLcode @@ -82,7 +82,7 @@ static CURLcode spack_enc16(struct dynbuf *buf, uint16_t val) uint8_t nval[2]; nval[0] = (uint8_t)(val >> 8); nval[1] = (uint8_t)val; - return Curl_dyn_addn(buf, nval, sizeof(nval)); + return curlx_dyn_addn(buf, nval, sizeof(nval)); } static CURLcode @@ -102,7 +102,7 @@ static CURLcode spack_enc32(struct dynbuf *buf, uint32_t val) nval[1] = (uint8_t)(val >> 16); nval[2] = (uint8_t)(val >> 8); nval[3] = (uint8_t)val; - return Curl_dyn_addn(buf, nval, sizeof(nval)); + return curlx_dyn_addn(buf, nval, sizeof(nval)); } static CURLcode @@ -127,7 +127,7 @@ static CURLcode spack_enc64(struct dynbuf *buf, uint64_t val) nval[5] = (uint8_t)(val >> 16); nval[6] = (uint8_t)(val >> 8); nval[7] = (uint8_t)val; - return Curl_dyn_addn(buf, nval, sizeof(nval)); + return curlx_dyn_addn(buf, nval, sizeof(nval)); } static CURLcode @@ -151,7 +151,7 @@ static CURLcode spack_encstr16(struct dynbuf *buf, const char *s) return CURLE_BAD_FUNCTION_ARGUMENT; r = spack_enc16(buf, (uint16_t)slen); if(!r) { - r = Curl_dyn_addn(buf, s, slen); + r = curlx_dyn_addn(buf, s, slen); } return r; } @@ -181,7 +181,7 @@ static CURLcode spack_encdata16(struct dynbuf *buf, return CURLE_BAD_FUNCTION_ARGUMENT; r = spack_enc16(buf, (uint16_t)data_len); if(!r) { - r = Curl_dyn_addn(buf, data, data_len); + r = curlx_dyn_addn(buf, data, data_len); } return r; } @@ -254,10 +254,11 @@ CURLcode Curl_ssl_session_pack(struct Curl_easy *data, } CURLcode Curl_ssl_session_unpack(struct Curl_easy *data, - const unsigned char *buf, size_t buflen, + const void *bufv, size_t buflen, struct Curl_ssl_session **ps) { struct Curl_ssl_session *s = NULL; + const unsigned char *buf = (const unsigned char *)bufv; const unsigned char *end = buf + buflen; uint8_t val8, *pval8; uint16_t val16; diff --git a/Utilities/cmcurl/lib/vtls/vtls_spack.h b/Utilities/cmcurl/lib/vtls/vtls_spack.h index 8905d7febf..4cdabae30e 100644 --- a/Utilities/cmcurl/lib/vtls/vtls_spack.h +++ b/Utilities/cmcurl/lib/vtls/vtls_spack.h @@ -23,7 +23,7 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_SSLS_EXPORT @@ -35,7 +35,7 @@ CURLcode Curl_ssl_session_pack(struct Curl_easy *data, struct dynbuf *buf); CURLcode Curl_ssl_session_unpack(struct Curl_easy *data, - const unsigned char *buf, size_t buflen, + const void *bufv, size_t buflen, struct Curl_ssl_session **ps); #endif /* USE_SSLS_EXPORT */ diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.c b/Utilities/cmcurl/lib/vtls/wolfssl.c index 546ba034af..9c6f518260 100644 --- a/Utilities/cmcurl/lib/vtls/wolfssl.c +++ b/Utilities/cmcurl/lib/vtls/wolfssl.c @@ -28,7 +28,7 @@ * */ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_WOLFSSL @@ -36,6 +36,7 @@ #include #include + #if LIBWOLFSSL_VERSION_HEX < 0x03004006 /* wolfSSL 3.4.6 (2015) */ #error "wolfSSL version should be at least 3.4.6" #endif @@ -55,28 +56,30 @@ #include -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" +#include "../urldata.h" +#include "../sendf.h" +#include "../curlx/inet_pton.h" #include "vtls.h" #include "vtls_int.h" #include "vtls_scache.h" #include "keylog.h" -#include "parsedate.h" -#include "connect.h" /* for the connect timeout */ -#include "select.h" -#include "strcase.h" +#include "../parsedate.h" +#include "../connect.h" /* for the connect timeout */ +#include "../progress.h" +#include "../select.h" +#include "../strcase.h" +#include "../strdup.h" #include "x509asn1.h" -#include "curl_printf.h" -#include "multiif.h" +#include "../curl_printf.h" +#include "../multiif.h" #include #include #include "wolfssl.h" /* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_memory.h" +#include "../memdebug.h" #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG #define USE_ECH_WOLFSSL @@ -93,11 +96,11 @@ #endif #endif -#ifdef HAVE_WOLFSSL_BIO +#ifdef HAVE_WOLFSSL_BIO_NEW #define USE_BIO_CHAIN -#ifdef HAVE_WOLFSSL_FULL_BIO +#ifdef HAVE_WOLFSSL_BIO_SET_SHUTDOWN #define USE_FULL_BIO -#else /* HAVE_WOLFSSL_FULL_BIO */ +#else /* HAVE_WOLFSSL_BIO_SET_SHUTDOWN */ #undef USE_FULL_BIO #endif /* wolfSSL 5.7.4 and older do not have these symbols, but only the @@ -111,10 +114,14 @@ #define wolfSSL_BIO_set_retry_read BIO_set_retry_read #endif /* !WOLFSSL_BIO_CTRL_GET_CLOSE */ -#else /* HAVE_WOLFSSL_BIO */ +#else /* HAVE_WOLFSSL_BIO_NEW */ #undef USE_BIO_CHAIN #endif +static CURLcode wssl_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done); + #ifdef OPENSSL_EXTRA /* * Availability note: @@ -122,13 +129,13 @@ * 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 + * wolfSSL_get_client_random and wolfSSL_get_keys require OPENSSL_EXTRA * (--enable-opensslextra or --enable-all). */ #if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) static int -wolfssl_tls13_secret_callback(SSL *ssl, int id, const unsigned char *secret, - int secretSz, void *ctx) +wssl_tls13_secret_callback(SSL *ssl, int id, const unsigned char *secret, + int secretSz, void *ctx) { const char *label; unsigned char client_random[SSL3_RANDOM_SIZE]; @@ -164,7 +171,7 @@ wolfssl_tls13_secret_callback(SSL *ssl, int id, const unsigned char *secret, return 0; } - if(SSL_get_client_random(ssl, client_random, SSL3_RANDOM_SIZE) == 0) { + if(wolfSSL_get_client_random(ssl, client_random, SSL3_RANDOM_SIZE) == 0) { /* Should never happen as wolfSSL_KeepArrays() was called before. */ return 0; } @@ -174,8 +181,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(WOLFSSL *ssl) +static void wssl_log_tls12_secret(WOLFSSL *ssl) { unsigned char *ms, *sr, *cr; unsigned int msLen, srLen, crLen, i, x = 0; @@ -217,7 +223,7 @@ wolfssl_log_tls12_secret(WOLFSSL *ssl) } #endif /* OPENSSL_EXTRA */ -static int wolfssl_do_file_type(const char *type) +static int wssl_do_file_type(const char *type) { if(!type || !type[0]) return WOLFSSL_FILETYPE_PEM; @@ -235,19 +241,19 @@ struct group_name_map { }; static const struct group_name_map gnm[] = { - { WOLFSSL_KYBER_LEVEL1, "KYBER_LEVEL1" }, - { WOLFSSL_KYBER_LEVEL3, "KYBER_LEVEL3" }, - { WOLFSSL_KYBER_LEVEL5, "KYBER_LEVEL5" }, - { WOLFSSL_P256_KYBER_LEVEL1, "P256_KYBER_LEVEL1" }, - { WOLFSSL_P384_KYBER_LEVEL3, "P384_KYBER_LEVEL3" }, - { WOLFSSL_P521_KYBER_LEVEL5, "P521_KYBER_LEVEL5" }, + { WOLFSSL_ML_KEM_512, "ML_KEM_512" }, + { WOLFSSL_ML_KEM_768, "ML_KEM_768" }, + { WOLFSSL_ML_KEM_1024, "ML_KEM_1024" }, + { WOLFSSL_P256_ML_KEM_512, "P256_ML_KEM_512" }, + { WOLFSSL_P384_ML_KEM_768, "P384_ML_KEM_768" }, + { WOLFSSL_P521_ML_KEM_1024, "P521_ML_KEM_1024" }, { 0, NULL } }; #endif #ifdef USE_BIO_CHAIN -static int wolfssl_bio_cf_create(WOLFSSL_BIO *bio) +static int wssl_bio_cf_create(WOLFSSL_BIO *bio) { #ifdef USE_FULL_BIO wolfSSL_BIO_set_shutdown(bio, 1); @@ -256,14 +262,14 @@ static int wolfssl_bio_cf_create(WOLFSSL_BIO *bio) return 1; } -static int wolfssl_bio_cf_destroy(WOLFSSL_BIO *bio) +static int wssl_bio_cf_destroy(WOLFSSL_BIO *bio) { if(!bio) return 0; return 1; } -static long wolfssl_bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) +static long wssl_bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) { struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio); long ret = 1; @@ -293,9 +299,11 @@ static long wolfssl_bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) ret = 1; break; #ifdef WOLFSSL_BIO_CTRL_EOF - case WOLFSSL_BIO_CTRL_EOF: + case WOLFSSL_BIO_CTRL_EOF: { /* EOF has been reached on input? */ - return !cf->next || !cf->next->connected; + struct ssl_connect_data *connssl = cf->ctx; + return connssl->peer_closed; + } #endif default: ret = 0; @@ -304,30 +312,29 @@ static long wolfssl_bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) return ret; } -static int wolfssl_bio_cf_out_write(WOLFSSL_BIO *bio, - const char *buf, int blen) +static int wssl_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_ctx *backend = - (struct wolfssl_ctx *)connssl->backend; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nwritten, skiplen = 0; CURLcode result = CURLE_OK; DEBUGASSERT(data); - if(backend->shutting_down && backend->io_send_blocked_len && - (backend->io_send_blocked_len < blen)) { + if(wssl->shutting_down && wssl->io_send_blocked_len && + (wssl->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; + " to %d bytes", blen, wssl->io_send_blocked_len); + skiplen = (ssize_t)(blen - wssl->io_send_blocked_len); + blen = wssl->io_send_blocked_len; } nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, FALSE, &result); - backend->io_result = result; + wssl->io_result = result; CURL_TRC_CF(data, cf, "bio_write(len=%d) -> %zd, %d", blen, nwritten, result); #ifdef USE_FULL_BIO @@ -335,20 +342,19 @@ static int wolfssl_bio_cf_out_write(WOLFSSL_BIO *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; + if(wssl->shutting_down && !wssl->io_send_blocked_len) + wssl->io_send_blocked_len = blen; } else if(!result && skiplen) nwritten += skiplen; return (int)nwritten; } -static int wolfssl_bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen) +static int wssl_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_ctx *backend = - (struct wolfssl_ctx *)connssl->backend; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result = CURLE_OK; @@ -358,8 +364,20 @@ static int wolfssl_bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen) if(!buf) return 0; + if((connssl->connecting_state == ssl_connect_2) && + !wssl->x509_store_setup) { + /* During handshake, init the x509 store before receiving the + * server response. This allows sending of ClientHello without delay. */ + result = Curl_wssl_setup_x509_store(cf, data, wssl); + if(result) { + CURL_TRC_CF(data, cf, "Curl_wssl_setup_x509_store() -> %d", result); + wssl->io_result = result; + return -1; + } + } + nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); - backend->io_result = result; + wssl->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); @@ -371,28 +389,28 @@ static int wolfssl_bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen) return (int)nread; } -static WOLFSSL_BIO_METHOD *wolfssl_bio_cf_method = NULL; +static WOLFSSL_BIO_METHOD *wssl_bio_cf_method = NULL; -static void wolfssl_bio_cf_init_methods(void) +static void wssl_bio_cf_init_methods(void) { - wolfssl_bio_cf_method = wolfSSL_BIO_meth_new(WOLFSSL_BIO_MEMORY, + wssl_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); + wolfSSL_BIO_meth_set_write(wssl_bio_cf_method, &wssl_bio_cf_out_write); + wolfSSL_BIO_meth_set_read(wssl_bio_cf_method, &wssl_bio_cf_in_read); + wolfSSL_BIO_meth_set_ctrl(wssl_bio_cf_method, &wssl_bio_cf_ctrl); + wolfSSL_BIO_meth_set_create(wssl_bio_cf_method, &wssl_bio_cf_create); + wolfSSL_BIO_meth_set_destroy(wssl_bio_cf_method, &wssl_bio_cf_destroy); } -static void wolfssl_bio_cf_free_methods(void) +static void wssl_bio_cf_free_methods(void) { - wolfSSL_BIO_meth_free(wolfssl_bio_cf_method); + wolfSSL_BIO_meth_free(wssl_bio_cf_method); } #else /* USE_BIO_CHAIN */ -#define wolfssl_bio_cf_init_methods() Curl_nop_stmt -#define wolfssl_bio_cf_free_methods() Curl_nop_stmt +#define wssl_bio_cf_init_methods() Curl_nop_stmt +#define wssl_bio_cf_free_methods() Curl_nop_stmt #endif /* !USE_BIO_CHAIN */ @@ -401,12 +419,15 @@ CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf, const char *ssl_peer_key, WOLFSSL_SESSION *session, int ietf_tls_id, - const char *alpn) + const char *alpn, + unsigned char *quic_tp, + size_t quic_tp_len) { CURLcode result = CURLE_OK; struct Curl_ssl_session *sc_session = NULL; - unsigned char *sdata = NULL; + unsigned char *sdata = NULL, *qtp_clone = NULL; unsigned int sdata_len; + unsigned int earlydata_max = 0; if(!session) goto out; @@ -429,12 +450,23 @@ CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf, result = CURLE_FAILED_INIT; goto out; } + if(quic_tp && quic_tp_len) { + qtp_clone = Curl_memdup0((char *)quic_tp, quic_tp_len); + if(!qtp_clone) { + free(sdata); + return CURLE_OUT_OF_MEMORY; + } + } +#ifdef WOLFSSL_EARLY_DATA + earlydata_max = wolfSSL_SESSION_get_max_early_data(session); +#endif - result = Curl_ssl_session_create(sdata, sdata_len, - ietf_tls_id, alpn, - (curl_off_t)time(NULL) + - wolfSSL_SESSION_get_timeout(session), 0, - &sc_session); + result = Curl_ssl_session_create2(sdata, sdata_len, + ietf_tls_id, alpn, + (curl_off_t)time(NULL) + + wolfSSL_SESSION_get_timeout(session), + earlydata_max, qtp_clone, quic_tp_len, + &sc_session); sdata = NULL; /* took ownership of sdata */ if(!result) { result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session); @@ -460,51 +492,120 @@ static int wssl_vtls_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) if(connssl && data) { (void)Curl_wssl_cache_session(cf, data, connssl->peer.scache_key, session, wolfSSL_version(ssl), - connssl->negotiated.alpn); + connssl->negotiated.alpn, NULL, 0); } } return 0; } -CURLcode Curl_wssl_setup_session(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct wolfssl_ctx *wss, - const char *ssl_peer_key) +static CURLcode wssl_on_session_reuse(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + struct Curl_ssl_session *scs, + bool *do_early_data) { - struct Curl_ssl_session *sc_session = NULL; + struct ssl_connect_data *connssl = cf->ctx; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; + CURLcode result = CURLE_OK; + + *do_early_data = FALSE; +#ifdef WOLFSSL_EARLY_DATA + connssl->earlydata_max = wolfSSL_SESSION_get_max_early_data( + wolfSSL_get_session(wssl->ssl)); +#else + (void)wssl; + connssl->earlydata_max = 0; +#endif + + if(!connssl->earlydata_max) { + /* Seems to be GnuTLS way to signal no EarlyData in session */ + CURL_TRC_CF(data, cf, "SSL session does not allow earlydata"); + } + else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) { + CURL_TRC_CF(data, cf, "SSL session has different ALPN, no early data"); + } + else { + infof(data, "SSL session allows %zu bytes of early data, " + "reusing ALPN '%s'", connssl->earlydata_max, scs->alpn); + connssl->earlydata_state = ssl_earlydata_await; + connssl->state = ssl_connection_deferred; + result = Curl_alpn_set_negotiated(cf, data, connssl, + (const unsigned char *)scs->alpn, + scs->alpn ? strlen(scs->alpn) : 0); + *do_early_data = !result; + } + return result; +} + +static CURLcode +wssl_setup_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wssl_ctx *wss, + struct alpn_spec *alpns, + const char *ssl_peer_key, + Curl_wssl_init_session_reuse_cb *sess_reuse_cb) +{ + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct Curl_ssl_session *scs = NULL; CURLcode result; - result = Curl_ssl_scache_take(cf, data, ssl_peer_key, &sc_session); - if(!result && sc_session && sc_session->sdata && sc_session->sdata_len) { + result = Curl_ssl_scache_take(cf, data, ssl_peer_key, &scs); + if(!result && scs && scs->sdata && scs->sdata_len && + (!scs->alpn || Curl_alpn_contains_proto(alpns, scs->alpn))) { WOLFSSL_SESSION *session; /* wolfSSL changes the passed pointer for whatever reasons, yikes */ - const unsigned char *sdata = sc_session->sdata; - session = wolfSSL_d2i_SSL_SESSION(NULL, &sdata, - (long)sc_session->sdata_len); + const unsigned char *sdata = scs->sdata; + session = wolfSSL_d2i_SSL_SESSION(NULL, &sdata, (long)scs->sdata_len); if(session) { - int ret = wolfSSL_set_session(wss->handle, session); + int ret = wolfSSL_set_session(wss->ssl, session); if(ret != WOLFSSL_SUCCESS) { - Curl_ssl_session_destroy(sc_session); - sc_session = NULL; + Curl_ssl_session_destroy(scs); + scs = NULL; infof(data, "cached session not accepted (%d), " "removing from cache", ret); } - else - infof(data, "SSL reusing session ID"); + else { + infof(data, "SSL reusing session with ALPN '%s'", + scs->alpn ? scs->alpn : "-"); + if(ssl_config->earlydata && + !cf->conn->connect_only && + !strcmp("TLSv1.3", wolfSSL_get_version(wss->ssl))) { + bool do_early_data = FALSE; + if(sess_reuse_cb) { + result = sess_reuse_cb(cf, data, alpns, scs, &do_early_data); + if(result) + goto out; + } +#ifdef WOLFSSL_EARLY_DATA + if(do_early_data) { + unsigned int edmax = (scs->earlydata_max < UINT_MAX) ? + (unsigned int)scs->earlydata_max : UINT_MAX; + /* We only try the ALPN protocol the session used before, + * otherwise we might send early data for the wrong protocol */ + Curl_alpn_restrict_to(alpns, scs->alpn); + wolfSSL_set_max_early_data(wss->ssl, edmax); + } +#else + /* Should never enable when not supported */ + DEBUGASSERT(!do_early_data); +#endif + } + } wolfSSL_SESSION_free(session); } else { failf(data, "could not decode previous session"); } } - Curl_ssl_scache_return(cf, data, ssl_peer_key, sc_session); +out: + Curl_ssl_scache_return(cf, data, ssl_peer_key, scs); return result; } static CURLcode wssl_populate_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, WOLFSSL_X509_STORE *store, - struct wolfssl_ctx *wssl) + struct wssl_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; @@ -514,38 +615,41 @@ static CURLcode wssl_populate_x509_store(struct Curl_cfilter *cf, 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; + bool imported_ca_info_blob = FALSE; -#if !defined(NO_FILESYSTEM) && defined(WOLFSSL_SYS_CA_CERTS) + /* We do not want to do this again, no matter the outcome */ + wssl->x509_store_setup = TRUE; + +#ifndef NO_FILESYSTEM /* load native CA certificates */ if(ssl_config->native_ca_store) { - if(wolfSSL_CTX_load_system_CA_certs(wssl->ctx) != WOLFSSL_SUCCESS) { +#ifdef WOLFSSL_SYS_CA_CERTS + if(wolfSSL_CTX_load_system_CA_certs(wssl->ssl_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; } +#else + infof(data, "ignoring native CA option because wolfSSL was built without " + "native CA support"); +#endif } #endif /* !NO_FILESYSTEM */ /* load certificate blob */ if(ca_info_blob) { - if(wolfSSL_CTX_load_verify_buffer(wssl->ctx, ca_info_blob->data, + if(wolfSSL_CTX_load_verify_buffer(wssl->ssl_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; - } + failf(data, "error importing CA certificate blob"); + return CURLE_SSL_CACERT_BADFILE; } else { + imported_ca_info_blob = TRUE; infof(data, "successfully imported CA certificate blob"); - wssl->x509_store_setup = TRUE; } } @@ -557,14 +661,15 @@ static CURLcode wssl_populate_x509_store(struct Curl_cfilter *cf, if(!store) return CURLE_OUT_OF_MEMORY; - if((ssl_cafile || ssl_capath) && (!wssl->x509_store_setup)) { + if(ssl_cafile || ssl_capath) { int rc = - wolfSSL_CTX_load_verify_locations_ex(wssl->ctx, + wolfSSL_CTX_load_verify_locations_ex(wssl->ssl_ctx, ssl_cafile, ssl_capath, WOLFSSL_LOAD_FLAG_IGNORE_ERR); if(WOLFSSL_SUCCESS != rc) { - if(conn_config->verifypeer) { + if(conn_config->verifypeer && + !imported_native_ca && !imported_ca_info_blob) { /* Fail if we insist on successfully verifying the server. */ failf(data, "error setting certificate verify locations:" " CAfile: %s CApath: %s", @@ -588,7 +693,6 @@ static CURLcode wssl_populate_x509_store(struct Curl_cfilter *cf, } #endif (void)store; - wssl->x509_store_setup = TRUE; return CURLE_OK; } @@ -620,8 +724,8 @@ wssl_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); + struct curltime now = curlx_now(); + timediff_t elapsed_ms = curlx_timediff(now, mb->time); timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; if(timeout_ms < 0) @@ -650,7 +754,7 @@ static WOLFSSL_X509_STORE *wssl_get_cached_x509_store(struct Curl_cfilter *cf, DEBUGASSERT(multi); share = multi ? Curl_hash_pick(&multi->proto_hash, - (void *)MPROTO_WSSL_X509_KEY, + CURL_UNCONST(MPROTO_WSSL_X509_KEY), sizeof(MPROTO_WSSL_X509_KEY)-1) : NULL; if(share && share->store && !wssl_cached_x509_store_expired(data, share) && @@ -673,7 +777,7 @@ static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, if(!multi) return; share = Curl_hash_pick(&multi->proto_hash, - (void *)MPROTO_WSSL_X509_KEY, + CURL_UNCONST(MPROTO_WSSL_X509_KEY), sizeof(MPROTO_WSSL_X509_KEY)-1); if(!share) { @@ -681,7 +785,7 @@ static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, if(!share) return; if(!Curl_hash_add2(&multi->proto_hash, - (void *)MPROTO_WSSL_X509_KEY, + CURL_UNCONST(MPROTO_WSSL_X509_KEY), sizeof(MPROTO_WSSL_X509_KEY)-1, share, wssl_x509_share_free)) { free(share); @@ -705,7 +809,7 @@ static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, free(share->CAfile); } - share->time = Curl_now(); + share->time = curlx_now(); share->store = store; share->CAfile = CAfile; } @@ -713,7 +817,7 @@ static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, - struct wolfssl_ctx *wssl) + struct wssl_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); @@ -733,11 +837,12 @@ CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, cached_store = cache_criteria_met ? wssl_get_cached_x509_store(cf, data) : NULL; - if(cached_store && wolfSSL_CTX_get_cert_store(wssl->ctx) == cached_store) { + if(cached_store && + wolfSSL_CTX_get_cert_store(wssl->ssl_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); + wolfSSL_CTX_set_cert_store(wssl->ssl_ctx, cached_store); } else if(cache_criteria_met) { /* wolfSSL's initial store in CTX is not shareable by default. @@ -747,7 +852,7 @@ CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, failf(data, "SSL: could not create a X509 store"); return CURLE_OUT_OF_MEMORY; } - wolfSSL_CTX_set_cert_store(wssl->ctx, store); + wolfSSL_CTX_set_cert_store(wssl->ssl_ctx, store); result = wssl_populate_x509_store(cf, data, store, wssl); if(!result) { @@ -756,7 +861,7 @@ CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, } else { /* We never share the CTX's store, use it. */ - WOLFSSL_X509_STORE *store = wolfSSL_CTX_get_cert_store(wssl->ctx); + WOLFSSL_X509_STORE *store = wolfSSL_CTX_get_cert_store(wssl->ssl_ctx); result = wssl_populate_x509_store(cf, data, store, wssl); } @@ -776,14 +881,14 @@ wssl_add_default_ciphers(bool tls13, struct dynbuf *buf) 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(curlx_dyn_len(buf)) { + CURLcode result = curlx_dyn_addn(buf, ":", 1); if(result) return result; } n = strlen(str); - if(Curl_dyn_addn(buf, str, n)) + if(curlx_dyn_addn(buf, str, n)) return CURLE_OUT_OF_MEMORY; } @@ -831,30 +936,41 @@ wssl_legacy_CTX_set_max_proto_version(WOLFSSL_CTX* ctx, int 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. - */ -static CURLcode -wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +#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" + +CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const struct alpn_spec *alpns_requested, + Curl_wssl_ctx_setup_cb *cb_setup, + void *cb_user_data, + void *ssl_user_data, + Curl_wssl_init_session_reuse_cb *sess_reuse_cb) { + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_primary_config *conn_config; + WOLFSSL_METHOD* req_method = NULL; + struct alpn_spec alpns; int res; char *curves; - struct ssl_connect_data *connssl = cf->ctx; - struct wolfssl_ctx *backend = - (struct wolfssl_ctx *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - const struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - WOLFSSL_METHOD* req_method = NULL; #ifdef WOLFSSL_HAVE_KYBER word16 pqkem = 0; size_t idx = 0; #endif + CURLcode result = CURLE_FAILED_INIT; - DEBUGASSERT(backend); - - if(connssl->state == ssl_connection_complete) - return CURLE_OK; + DEBUGASSERT(!wctx->ssl_ctx); + DEBUGASSERT(!wctx->ssl); + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) { + result = CURLE_FAILED_INIT; + goto out; + } + Curl_alpn_copy(&alpns, alpns_requested); #if LIBWOLFSSL_VERSION_HEX < 0x04002000 /* 4.2.0 (2019) */ req_method = wolfSSLv23_client_method(); @@ -863,58 +979,62 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) #endif if(!req_method) { failf(data, "wolfSSL: could not create a client method"); - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto out; } - if(backend->ctx) - wolfSSL_CTX_free(backend->ctx); + if(wctx->ssl_ctx) + wolfSSL_CTX_free(wctx->ssl_ctx); - backend->ctx = wolfSSL_CTX_new(req_method); - if(!backend->ctx) { + wctx->ssl_ctx = wolfSSL_CTX_new(req_method); + if(!wctx->ssl_ctx) { failf(data, "wolfSSL: could not create a context"); - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto out; } switch(conn_config->version) { case CURL_SSLVERSION_DEFAULT: case CURL_SSLVERSION_TLSv1: case CURL_SSLVERSION_TLSv1_0: - res = wolfSSL_CTX_set_min_proto_version(backend->ctx, TLS1_VERSION); + res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_VERSION); break; case CURL_SSLVERSION_TLSv1_1: - res = wolfSSL_CTX_set_min_proto_version(backend->ctx, TLS1_1_VERSION); + res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_1_VERSION); break; case CURL_SSLVERSION_TLSv1_2: - res = wolfSSL_CTX_set_min_proto_version(backend->ctx, TLS1_2_VERSION); + res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_2_VERSION); break; #ifdef WOLFSSL_TLS13 case CURL_SSLVERSION_TLSv1_3: - res = wolfSSL_CTX_set_min_proto_version(backend->ctx, TLS1_3_VERSION); + res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_3_VERSION); break; #endif default: failf(data, "wolfSSL: unsupported minimum TLS version value"); - return CURLE_SSL_CONNECT_ERROR; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } if(res != WOLFSSL_SUCCESS) { failf(data, "wolfSSL: failed set the minimum TLS version"); - return CURLE_SSL_CONNECT_ERROR; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } switch(conn_config->version_max) { #ifdef WOLFSSL_TLS13 case CURL_SSLVERSION_MAX_TLSv1_3: - res = wolfSSL_CTX_set_max_proto_version(backend->ctx, TLS1_3_VERSION); + res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_3_VERSION); break; #endif case CURL_SSLVERSION_MAX_TLSv1_2: - res = wolfSSL_CTX_set_max_proto_version(backend->ctx, TLS1_2_VERSION); + res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_2_VERSION); break; case CURL_SSLVERSION_MAX_TLSv1_1: - res = wolfSSL_CTX_set_max_proto_version(backend->ctx, TLS1_1_VERSION); + res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_1_VERSION); break; case CURL_SSLVERSION_MAX_TLSv1_0: - res = wolfSSL_CTX_set_max_proto_version(backend->ctx, TLS1_VERSION); + res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_VERSION); break; case CURL_SSLVERSION_MAX_DEFAULT: case CURL_SSLVERSION_MAX_NONE: @@ -922,20 +1042,23 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) break; default: failf(data, "wolfSSL: unsupported maximum TLS version value"); - return CURLE_SSL_CONNECT_ERROR; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } if(res != WOLFSSL_SUCCESS) { failf(data, "wolfSSL: failed set the maximum TLS version"); - return CURLE_SSL_CONNECT_ERROR; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } #ifndef WOLFSSL_TLS13 { char *ciphers = conn_config->cipher_list; if(ciphers) { - if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) { + if(!SSL_CTX_set_cipher_list(wctx->ssl_ctx, ciphers)) { failf(data, "failed setting cipher list: %s", ciphers); - return CURLE_SSL_CIPHER; + result = CURLE_SSL_CIPHER; + goto out; } infof(data, "Cipher selection: %s", ciphers); } @@ -946,40 +1069,42 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) 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); + curlx_dyn_init(&c, MAX_CIPHER_LEN); if(ciphers13) - result = Curl_dyn_add(&c, ciphers13); + result = curlx_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(curlx_dyn_len(&c)) + result = curlx_dyn_addn(&c, ":", 1); if(!result) - result = Curl_dyn_add(&c, ciphers12); + result = curlx_dyn_add(&c, ciphers12); } else result = wssl_add_default_ciphers(FALSE, &c); } if(result) - return result; + goto out; - 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; + if(!wolfSSL_CTX_set_cipher_list(wctx->ssl_ctx, curlx_dyn_ptr(&c))) { + failf(data, "failed setting cipher list: %s", curlx_dyn_ptr(&c)); + curlx_dyn_free(&c); + result = CURLE_SSL_CIPHER; + goto out; } - infof(data, "Cipher selection: %s", Curl_dyn_ptr(&c)); - Curl_dyn_free(&c); + infof(data, "Cipher selection: %s", curlx_dyn_ptr(&c)); + curlx_dyn_free(&c); } #endif curves = conn_config->curves; - if(curves) { + if(!curves && cf->conn->transport == TRNSPRT_QUIC) + curves = (char *)CURL_UNCONST(QUIC_GROUPS); + if(curves) { #ifdef WOLFSSL_HAVE_KYBER for(idx = 0; gnm[idx].name != NULL; idx++) { if(strncmp(curves, gnm[idx].name, strlen(gnm[idx].name)) == 0) { @@ -991,9 +1116,10 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) if(pqkem == 0) #endif { - if(!wolfSSL_CTX_set1_curves_list(backend->ctx, curves)) { + if(!wolfSSL_CTX_set1_curves_list(wctx->ssl_ctx, curves)) { failf(data, "failed setting curves list: '%s'", curves); - return CURLE_SSL_CIPHER; + result = CURLE_SSL_CIPHER; + goto out; } } } @@ -1005,30 +1131,32 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) 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 file_type = wssl_do_file_type(ssl_config->cert_type); int rc; switch(file_type) { case WOLFSSL_FILETYPE_PEM: rc = cert_blob ? - wolfSSL_CTX_use_certificate_chain_buffer(backend->ctx, + wolfSSL_CTX_use_certificate_chain_buffer(wctx->ssl_ctx, cert_blob->data, (long)cert_blob->len) : - wolfSSL_CTX_use_certificate_chain_file(backend->ctx, cert_file); + wolfSSL_CTX_use_certificate_chain_file(wctx->ssl_ctx, cert_file); break; case WOLFSSL_FILETYPE_ASN1: rc = cert_blob ? - wolfSSL_CTX_use_certificate_buffer(backend->ctx, cert_blob->data, + wolfSSL_CTX_use_certificate_buffer(wctx->ssl_ctx, cert_blob->data, (long)cert_blob->len, file_type) : - wolfSSL_CTX_use_certificate_file(backend->ctx, cert_file, file_type); + wolfSSL_CTX_use_certificate_file(wctx->ssl_ctx, cert_file, file_type); break; default: failf(data, "unknown cert type"); - return CURLE_BAD_FUNCTION_ARGUMENT; + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } if(rc != 1) { failf(data, "unable to use client certificate"); - return CURLE_SSL_CONNECT_ERROR; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } if(!key_blob && !key_file) { @@ -1036,53 +1164,57 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) key_file = cert_file; } else - file_type = wolfssl_do_file_type(ssl_config->key_type); + file_type = wssl_do_file_type(ssl_config->key_type); rc = key_blob ? - wolfSSL_CTX_use_PrivateKey_buffer(backend->ctx, key_blob->data, + wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, (long)key_blob->len, file_type) : - wolfSSL_CTX_use_PrivateKey_file(backend->ctx, key_file, file_type); + wolfSSL_CTX_use_PrivateKey_file(wctx->ssl_ctx, key_file, file_type); if(rc != 1) { failf(data, "unable to set private key"); - return CURLE_SSL_CONNECT_ERROR; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } } #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 file_type = wssl_do_file_type(ssl_config->cert_type); int rc; switch(file_type) { case WOLFSSL_FILETYPE_PEM: - rc = wolfSSL_CTX_use_certificate_chain_buffer(backend->ctx, + rc = wolfSSL_CTX_use_certificate_chain_buffer(wctx->ssl_ctx, cert_blob->data, (long)cert_blob->len); break; case WOLFSSL_FILETYPE_ASN1: - rc = wolfSSL_CTX_use_certificate_buffer(backend->ctx, cert_blob->data, + rc = wolfSSL_CTX_use_certificate_buffer(wctx->ssl_ctx, cert_blob->data, (long)cert_blob->len, file_type); break; default: failf(data, "unknown cert type"); - return CURLE_BAD_FUNCTION_ARGUMENT; + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } if(rc != 1) { failf(data, "unable to use client certificate"); - return CURLE_SSL_CONNECT_ERROR; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } if(!key_blob) key_blob = cert_blob; else - file_type = wolfssl_do_file_type(ssl_config->key_type); + file_type = wssl_do_file_type(ssl_config->key_type); - if(wolfSSL_CTX_use_PrivateKey_buffer(backend->ctx, key_blob->data, + if(wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, (long)key_blob->len, file_type) != 1) { failf(data, "unable to set private key"); - return CURLE_SSL_CONNECT_ERROR; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } } #endif /* !NO_FILESYSTEM */ @@ -1091,37 +1223,49 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) * 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. */ - wolfSSL_CTX_set_verify(backend->ctx, + wolfSSL_CTX_set_verify(wctx->ssl_ctx, conn_config->verifypeer ? WOLFSSL_VERIFY_PEER : WOLFSSL_VERIFY_NONE, NULL); #ifdef HAVE_SNI - if(connssl->peer.sni) { - size_t sni_len = strlen(connssl->peer.sni); + if(peer->sni) { + size_t sni_len = strlen(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) { + if(wolfSSL_CTX_UseSNI(wctx->ssl_ctx, WOLFSSL_SNI_HOST_NAME, + peer->sni, (unsigned short)sni_len) != 1) { failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } + CURL_TRC_CF(data, cf, "set SNI '%s'", peer->sni); } } #endif + if(ssl_config->primary.cache_session && + cf->conn->transport != TRNSPRT_QUIC) { + /* Register to get notified when a new session is received */ + wolfSSL_CTX_sess_set_new_cb(wctx->ssl_ctx, wssl_vtls_new_session_cb); + } + + if(cb_setup) { + result = cb_setup(cf, data, cb_user_data); + if(result) + goto out; + } + /* give application a chance to interfere with SSL set up. */ if(data->set.ssl.fsslctx) { - CURLcode result; - if(!backend->x509_store_setup) { - result = Curl_wssl_setup_x509_store(cf, data, backend); + if(!wctx->x509_store_setup) { + result = Curl_wssl_setup_x509_store(cf, data, wctx); if(result) - return result; + goto out; } - result = (*data->set.ssl.fsslctx)(data, backend->ctx, + result = (*data->set.ssl.fsslctx)(data, wctx->ssl_ctx, data->set.ssl.fsslctxp); if(result) { failf(data, "error signaled by ssl ctx callback"); - return result; + goto out; } } #ifdef NO_FILESYSTEM @@ -1130,82 +1274,87 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) " 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."); - return CURLE_SSL_CONNECT_ERROR; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } #endif /* Let's make an SSL structure */ - if(backend->handle) - wolfSSL_free(backend->handle); - backend->handle = wolfSSL_new(backend->ctx); - if(!backend->handle) { + wctx->ssl = wolfSSL_new(wctx->ssl_ctx); + if(!wctx->ssl) { failf(data, "SSL: could not create a handle"); - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto out; } + wolfSSL_set_app_data(wctx->ssl, ssl_user_data); +#ifdef WOLFSSL_QUIC + if(cf->conn->transport == TRNSPRT_QUIC) + wolfSSL_set_quic_use_legacy_codepoint(wctx->ssl, 0); +#endif + #ifdef WOLFSSL_HAVE_KYBER if(pqkem) { - if(wolfSSL_UseKeyShare(backend->handle, pqkem) != WOLFSSL_SUCCESS) { + if(wolfSSL_UseKeyShare(wctx->ssl, pqkem) != WOLFSSL_SUCCESS) { failf(data, "unable to use PQ KEM"); } } #endif -#ifdef HAVE_ALPN - if(connssl->alpn) { - struct alpn_proto_buf proto; - CURLcode result; + /* 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, wctx, &alpns, + peer->scache_key, sess_reuse_cb); + } - result = Curl_alpn_to_proto_str(&proto, connssl->alpn); - if(result || - wolfSSL_UseALPN(backend->handle, - (char *)proto.data, (unsigned int)proto.len, +#ifdef HAVE_ALPN + if(alpns.count) { + struct alpn_proto_buf proto; + memset(&proto, 0, sizeof(proto)); + Curl_alpn_to_proto_str(&proto, &alpns); + + if(wolfSSL_UseALPN(wctx->ssl, (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; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + CURL_TRC_CF(data, cf, "set ALPN: %s", proto.data); } #endif /* HAVE_ALPN */ #ifdef OPENSSL_EXTRA if(Curl_tls_keylog_enabled()) { /* Ensure the Client Random is preserved. */ - wolfSSL_KeepArrays(backend->handle); + wolfSSL_KeepArrays(wctx->ssl); #if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) - wolfSSL_set_tls13_secret_cb(backend->handle, - wolfssl_tls13_secret_callback, NULL); + wolfSSL_set_tls13_secret_cb(wctx->ssl, + wssl_tls13_secret_callback, NULL); #endif } #endif /* OPENSSL_EXTRA */ #ifdef HAVE_SECURE_RENEGOTIATION - if(wolfSSL_UseSecureRenegotiation(backend->handle) != SSL_SUCCESS) { + if(wolfSSL_UseSecureRenegotiation(wctx->ssl) != SSL_SUCCESS) { failf(data, "SSL: failed setting secure renegotiation"); - return CURLE_SSL_CONNECT_ERROR; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } #endif /* HAVE_SECURE_RENEGOTIATION */ - /* 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)Curl_wssl_setup_session(cf, data, backend, connssl->peer.scache_key); - /* 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_WOLFSSL if(ECH_ENABLED(data)) { int trying_ech_now = 0; if(data->set.str[STRING_ECH_PUBLIC]) { infof(data, "ECH: outername not (yet) supported with wolfSSL"); - return CURLE_SSL_CONNECT_ERROR; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } if(data->set.tls_ech == CURLECH_GREASE) { - infof(data, "ECH: GREASE'd ECH not yet supported for wolfSSL"); - return CURLE_SSL_CONNECT_ERROR; + infof(data, "ECH: GREASE is done by default by wolfSSL: no need to ask"); } if(data->set.tls_ech & CURLECH_CLA_CFG && data->set.str[STRING_ECH_CONFIG]) { @@ -1214,10 +1363,12 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) b64len = (word32) strlen(b64val); if(b64len - && wolfSSL_SetEchConfigsBase64(backend->handle, b64val, b64len) + && wolfSSL_SetEchConfigsBase64(wctx->ssl, b64val, b64len) != WOLFSSL_SUCCESS) { - if(data->set.tls_ech & CURLECH_HARD) - return CURLE_SSL_CONNECT_ERROR; + if(data->set.tls_ech & CURLECH_HARD) { + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } } else { trying_ech_now = 1; @@ -1225,13 +1376,17 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } } else { + struct ssl_connect_data *connssl = cf->ctx; struct Curl_dns_entry *dns = NULL; - dns = Curl_fetch_addr(data, connssl->peer.hostname, connssl->peer.port); + dns = Curl_dnscache_get(data, connssl->peer.hostname, connssl->peer.port, + cf->conn->ip_version); if(!dns) { infof(data, "ECH: requested but no DNS info available"); - if(data->set.tls_ech & CURLECH_HARD) - return CURLE_SSL_CONNECT_ERROR; + if(data->set.tls_ech & CURLECH_HARD) { + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } } else { struct Curl_https_rrinfo *rinfo = NULL; @@ -1242,11 +1397,13 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) size_t elen = rinfo->echconfiglist_len; infof(data, "ECH: ECHConfig from DoH HTTPS RR"); - if(wolfSSL_SetEchConfigs(backend->handle, ecl, (word32) elen) != + if(wolfSSL_SetEchConfigs(wctx->ssl, ecl, (word32) elen) != WOLFSSL_SUCCESS) { infof(data, "ECH: wolfSSL_SetEchConfigs failed"); - if(data->set.tls_ech & CURLECH_HARD) - return CURLE_SSL_CONNECT_ERROR; + if(data->set.tls_ech & CURLECH_HARD) { + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } } else { trying_ech_now = 1; @@ -1255,49 +1412,107 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } else { infof(data, "ECH: requested but no ECHConfig available"); - if(data->set.tls_ech & CURLECH_HARD) - return CURLE_SSL_CONNECT_ERROR; + if(data->set.tls_ech & CURLECH_HARD) { + result = CURLE_SSL_CONNECT_ERROR; + goto out; + } } Curl_resolv_unlink(data, &dns); } } - if(trying_ech_now && wolfSSL_set_min_proto_version(backend->handle, + if(trying_ech_now && wolfSSL_set_min_proto_version(wctx->ssl, TLS1_3_VERSION) != 1) { infof(data, "ECH: cannot force TLSv1.3 [ERROR]"); - return CURLE_SSL_CONNECT_ERROR; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } } #endif /* USE_ECH_WOLFSSL */ + result = CURLE_OK; + +out: + if(result && wctx->ssl) { + wolfSSL_free(wctx->ssl); + wctx->ssl = NULL; + } + if(result && wctx->ssl_ctx) { + wolfSSL_CTX_free(wctx->ssl_ctx); + wctx->ssl_ctx = NULL; + } + return result; +} + +/* + * This function loads all the client/CA certificates and CRLs. Setup the TLS + * layer and do all necessary magic. + */ +static CURLcode +wssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + CURLcode result; + + DEBUGASSERT(wssl); + + if(connssl->state == ssl_connection_complete) + return CURLE_OK; + + result = Curl_wssl_ctx_init(wssl, cf, data, &connssl->peer, + connssl->alpn, NULL, NULL, cf, + wssl_on_session_reuse); + if(result) + return result; + +#ifdef HAVE_ALPN + if(connssl->alpn && (connssl->state != ssl_connection_deferred)) { + struct alpn_proto_buf proto; + memset(&proto, 0, sizeof(proto)); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } +#endif + + /* Enable RFC2818 checks */ + if(conn_config->verifyhost) { + char *snihost = connssl->peer.sni ? + connssl->peer.sni : connssl->peer.hostname; + if(wolfSSL_check_domain_name(wssl->ssl, snihost) != + WOLFSSL_SUCCESS) { + return CURLE_SSL_CONNECT_ERROR; + } + } + #ifdef USE_BIO_CHAIN { WOLFSSL_BIO *bio; - bio = wolfSSL_BIO_new(wolfssl_bio_cf_method); + bio = wolfSSL_BIO_new(wssl_bio_cf_method); if(!bio) return CURLE_OUT_OF_MEMORY; wolfSSL_BIO_set_data(bio, cf); - wolfSSL_set_bio(backend->handle, bio, bio); + wolfSSL_set_bio(wssl->ssl, bio, bio); } #else /* USE_BIO_CHAIN */ /* pass the raw socket into the SSL layer */ - if(!wolfSSL_set_fd(backend->handle, + if(!wolfSSL_set_fd(wssl->ssl, (int)Curl_conn_cf_get_socket(cf, data))) { - failf(data, "SSL: SSL_set_fd failed"); + failf(data, "SSL: wolfSSL_set_fd failed"); return CURLE_SSL_CONNECT_ERROR; } #endif /* !USE_BIO_CHAIN */ - connssl->connecting_state = ssl_connect_2; return CURLE_OK; } -static char *wolfssl_strerror(unsigned long error, char *buf, - unsigned long size) +static char *wssl_strerror(unsigned long error, char *buf, + unsigned long size) { DEBUGASSERT(size > 40); *buf = '\0'; @@ -1313,15 +1528,10 @@ static char *wolfssl_strerror(unsigned long error, char *buf, return buf; } - -static CURLcode -wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) +CURLcode Curl_wssl_verify_pinned(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wssl_ctx *wssl) { - int ret = -1; - struct ssl_connect_data *connssl = cf->ctx; - struct wolfssl_ctx *backend = - (struct wolfssl_ctx *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); #ifndef CURL_DISABLE_PROXY const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf) ? data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : @@ -1330,151 +1540,6 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) const char * const pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; #endif - DEBUGASSERT(backend); - - wolfSSL_ERR_clear_error(); - - /* Enable RFC2818 checks */ - if(conn_config->verifyhost) { - 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 - 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). - * - * During the handshake (ret==-1), wolfSSL_want_read() is true as it waits - * for the server response. At that point the master secret is not yet - * available, so we must not try to read it. - * To log the secret on completion with a handshake failure, detect - * completion via the observation that there is nothing to read or write. - * 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 == WOLFSSL_SUCCESS || - (!wolfSSL_want_read(backend->handle) && - !wolfSSL_want_write(backend->handle))) { - wolfssl_log_tls12_secret(backend->handle); - /* Client Random and master secrets are no longer needed, erase these. - * Ignored while the handshake is still in progress. */ - wolfSSL_FreeArrays(backend->handle); - } - } -#endif /* OPENSSL_EXTRA */ - - if(ret != 1) { - int detail = wolfSSL_get_error(backend->handle, ret); - - if(WOLFSSL_ERROR_WANT_READ == detail) { - connssl->io_need = CURL_SSL_IO_NEED_RECV; - return CURLE_OK; - } - 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. - * This will enable the override of both mismatching SubjectAltNames - * as also mismatching CN fields */ - else if(DOMAIN_NAME_MISMATCH == detail) { -#if 1 - failf(data, " subject alt name(s) or common name do not match \"%s\"", - connssl->peer.dispname); - return CURLE_PEER_FAILED_VERIFICATION; -#else - /* When the wolfssl_check_domain_name() is used and you desire to - * continue on a DOMAIN_NAME_MISMATCH, i.e. 'ssl_config.verifyhost - * == 0', CyaSSL version 2.4.0 will fail with an INCOMPLETE_DATA - * error. The only way to do this is currently to switch the - * Wolfssl_check_domain_name() in and out based on the - * 'ssl_config.verifyhost' value. */ - if(conn_config->verifyhost) { - failf(data, - " subject alt name(s) or common name do not match \"%s\"\n", - connssl->dispname); - return CURLE_PEER_FAILED_VERIFICATION; - } - else { - infof(data, - " subject alt name(s) and/or common name do not match \"%s\"", - connssl->dispname); - return CURLE_OK; - } -#endif - } - else if(ASN_NO_SIGNER_E == detail) { - if(conn_config->verifypeer) { - failf(data, " CA signer not available for verification"); - return CURLE_SSL_CACERT_BADFILE; - } - else { - /* Just continue with a warning if no strict certificate - verification is required. */ - infof(data, "CA signer not available for verification, " - "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_WOLFSSL - 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_strerror((unsigned long)detail, error_buffer, - sizeof(error_buffer))); - return CURLE_SSL_CONNECT_ERROR; - } - } - if(pinnedpubkey) { #ifdef KEEP_PEER_CERT WOLFSSL_X509 *x509; @@ -1484,7 +1549,7 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) struct Curl_asn1Element *pubkey; CURLcode result; - x509 = wolfSSL_get_peer_certificate(backend->handle); + x509 = wolfSSL_get_peer_certificate(wssl->ssl); if(!x509) { failf(data, "SSL: failed retrieving server certificate"); return CURLE_SSL_PINNEDPUBKEYNOTMATCH; @@ -1520,95 +1585,285 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_NOT_BUILT_IN; #endif } - -#ifdef HAVE_ALPN - if(connssl->alpn) { - int rc; - char *protocol = NULL; - unsigned short protocol_len = 0; - - rc = wolfSSL_ALPN_GetProtocol(backend->handle, &protocol, &protocol_len); - - if(rc == WOLFSSL_SUCCESS) { - Curl_alpn_set_negotiated(cf, data, connssl, - (const unsigned char *)protocol, protocol_len); - } - 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; - } - } -#endif /* HAVE_ALPN */ - - connssl->connecting_state = ssl_connect_3; -#if (LIBWOLFSSL_VERSION_HEX >= 0x03009010) - infof(data, "SSL connection using %s / %s", - wolfSSL_get_version(backend->handle), - wolfSSL_get_cipher_name(backend->handle)); -#else - infof(data, "SSL connected"); -#endif - return CURLE_OK; } -static ssize_t wolfssl_send(struct Curl_cfilter *cf, - struct Curl_easy *data, - const void *mem, - size_t len, - CURLcode *curlcode) +#ifdef WOLFSSL_EARLY_DATA +static CURLcode wssl_send_earlydata(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct wolfssl_ctx *backend = - (struct wolfssl_ctx *)connssl->backend; - int memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; - int rc; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; + CURLcode result = CURLE_OK; + const unsigned char *buf; + size_t blen; - DEBUGASSERT(backend); + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sending); + wssl->io_result = CURLE_OK; + while(Curl_bufq_peek(&connssl->earlydata, &buf, &blen)) { + int nwritten = 0, rc; + + wolfSSL_ERR_clear_error(); + rc = wolfSSL_write_early_data(wssl->ssl, buf, (int)blen, &nwritten); + CURL_TRC_CF(data, cf, "wolfSSL_write_early_data(len=%zu) -> %d, %d", + blen, rc, nwritten); + if(rc < 0) { + int err = wolfSSL_get_error(wssl->ssl, rc); + switch(err) { + case WOLFSSL_ERROR_NONE: /* just did not get anything */ + case WOLFSSL_ERROR_WANT_READ: + case WOLFSSL_ERROR_WANT_WRITE: + result = CURLE_AGAIN; + break; + default: { + char error_buffer[256]; + int detail = wolfSSL_get_error(wssl->ssl, err); + CURL_TRC_CF(data, cf, "SSL send early data, error: '%s'(%d)", + wssl_strerror((unsigned long)err, error_buffer, + sizeof(error_buffer)), + detail); + result = CURLE_SEND_ERROR; + break; + } + } + goto out; + } + + Curl_bufq_skip(&connssl->earlydata, (size_t)nwritten); + } + /* sent everything there was */ + connssl->earlydata_state = ssl_earlydata_sent; + if(!Curl_ssl_cf_is_proxy(cf)) + Curl_pgrsEarlyData(data, (curl_off_t)connssl->earlydata_skip); + infof(data, "SSL sending %zu bytes of early data", connssl->earlydata_skip); +out: + return result; +} +#endif /* WOLFSSL_EARLY_DATA */ + +static CURLcode wssl_handshake(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + int ret = -1, detail; + CURLcode result; + + DEBUGASSERT(wssl); + connssl->io_need = CURL_SSL_IO_NEED_NONE; + +#ifdef WOLFSSL_EARLY_DATA + if(connssl->earlydata_state == ssl_earlydata_sending) { + result = wssl_send_earlydata(cf, data); + if(result) + return result; + } + DEBUGASSERT((connssl->earlydata_state == ssl_earlydata_none) || + (connssl->earlydata_state == ssl_earlydata_sent)); +#else + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_none); +#endif /* WOLFSSL_EARLY_DATA */ + + wolfSSL_ERR_clear_error(); + ret = wolfSSL_connect(wssl->ssl); + + if(!wssl->x509_store_setup) { + /* After having send off the ClientHello, we prepare the x509 + * store to verify the coming certificate from the server */ + result = Curl_wssl_setup_x509_store(cf, data, wssl); + if(result) { + CURL_TRC_CF(data, cf, "Curl_wssl_setup_x509_store() -> %d", result); + return result; + } + } + +#ifdef OPENSSL_EXTRA + 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). + * + * During the handshake (ret==-1), wolfSSL_want_read() is true as it waits + * for the server response. At that point the master secret is not yet + * available, so we must not try to read it. + * To log the secret on completion with a handshake failure, detect + * completion via the observation that there is nothing to read or write. + * 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 == WOLFSSL_SUCCESS || + (!wolfSSL_want_read(wssl->ssl) && + !wolfSSL_want_write(wssl->ssl))) { + wssl_log_tls12_secret(wssl->ssl); + /* Client Random and master secrets are no longer needed, erase these. + * Ignored while the handshake is still in progress. */ + wolfSSL_FreeArrays(wssl->ssl); + } + } +#endif /* OPENSSL_EXTRA */ + + detail = wolfSSL_get_error(wssl->ssl, ret); + CURL_TRC_CF(data, cf, "wolfSSL_connect() -> %d, detail=%d", ret, detail); + + if(ret == WOLFSSL_SUCCESS) { + return CURLE_OK; + } + else { + if(WOLFSSL_ERROR_WANT_READ == detail) { + connssl->io_need = CURL_SSL_IO_NEED_RECV; + return CURLE_AGAIN; + } + else if(WOLFSSL_ERROR_WANT_WRITE == detail) { + connssl->io_need = CURL_SSL_IO_NEED_SEND; + return CURLE_AGAIN; + } + else if(DOMAIN_NAME_MISMATCH == detail) { + /* There is no easy way to override only the CN matching. + * This will enable the override of both mismatching SubjectAltNames + * as also mismatching CN fields */ + failf(data, " subject alt name(s) or common name do not match \"%s\"", + connssl->peer.dispname); + return CURLE_PEER_FAILED_VERIFICATION; + } + else if(ASN_NO_SIGNER_E == detail) { + if(conn_config->verifypeer) { + failf(data, " CA signer not available for verification"); + return CURLE_SSL_CACERT_BADFILE; + } + /* Just continue with a warning if no strict certificate + verification is required. */ + infof(data, "CA signer not available for verification, " + "continuing anyway"); + return CURLE_OK; + } + 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; + } + else if(wssl->io_result) { + switch(wssl->io_result) { + case CURLE_SEND_ERROR: + case CURLE_RECV_ERROR: + return CURLE_SSL_CONNECT_ERROR; + default: + return wssl->io_result; + } + } +#ifdef USE_ECH_WOLFSSL + 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(wssl->ssl, echConfigs, + &echConfigsLen); + if(rv != WOLFSSL_SUCCESS) { + infof(data, "Failed to get ECHConfigs"); + } + else { + char *b64str = NULL; + size_t blen = 0; + + result = curlx_base64_encode((const char *)echConfigs, echConfigsLen, + &b64str, &blen); + if(!result && b64str) + infof(data, "ECH: (not yet) retry_configs %s", b64str); + free(b64str); + } + return CURLE_SSL_CONNECT_ERROR; + } +#endif + else { + char error_buffer[256]; + failf(data, "SSL_connect failed with error %d: %s", detail, + wssl_strerror((unsigned long)detail, error_buffer, + sizeof(error_buffer))); + return CURLE_SSL_CONNECT_ERROR; + } + } +} + +static ssize_t wssl_send(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *buf, size_t blen, + CURLcode *curlcode) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; + size_t total_written = 0; + ssize_t nwritten = -1; + DEBUGASSERT(wssl); wolfSSL_ERR_clear_error(); - rc = wolfSSL_write(backend->handle, mem, memlen); - if(rc <= 0) { - int err = wolfSSL_get_error(backend->handle, rc); + if(blen) { + int memlen = (blen > (size_t)INT_MAX) ? INT_MAX : (int)blen; + int rc; - switch(err) { - 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; - default: - if(backend->io_result == CURLE_AGAIN) { - CURL_TRC_CF(data, cf, "wolfssl_send(len=%zu) -> AGAIN", len); + rc = wolfSSL_write(wssl->ssl, buf, memlen); + if(rc <= 0) { + int err = wolfSSL_get_error(wssl->ssl, rc); + + switch(err) { + case WOLFSSL_ERROR_WANT_READ: + case WOLFSSL_ERROR_WANT_WRITE: + /* there is data pending, re-invoke wolfSSL_write() */ + if(total_written) { + *curlcode = CURLE_OK; + nwritten = total_written; + goto out; + } *curlcode = CURLE_AGAIN; - return -1; + nwritten = -1; + goto out; + + default: + if(wssl->io_result == CURLE_AGAIN) { + if(total_written) { + *curlcode = CURLE_OK; + nwritten = total_written; + goto out; + } + *curlcode = CURLE_AGAIN; + nwritten = -1; + goto out; + } + { + char error_buffer[256]; + failf(data, "SSL write: %s, errno %d", + wssl_strerror((unsigned long)err, error_buffer, + sizeof(error_buffer)), + SOCKERRNO); + } + *curlcode = CURLE_SEND_ERROR; + nwritten = -1; + goto out; } - CURL_TRC_CF(data, cf, "wolfssl_send(len=%zu) -> %d, %d", len, rc, err); - { - 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; } + else + total_written += rc; } - CURL_TRC_CF(data, cf, "wolfssl_send(len=%zu) -> %d", len, rc); - return rc; + + *curlcode = CURLE_OK; + nwritten = total_written; +out: + CURL_TRC_CF(data, cf, "wssl_send(len=%zu) -> %" FMT_OFF_T ", %d", + blen, nwritten, *curlcode); + return nwritten; } -static CURLcode wolfssl_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool send_shutdown, bool *done) +static CURLcode wssl_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; + struct wssl_ctx *wctx = (struct wssl_ctx *)connssl->backend; CURLcode result = CURLE_OK; char buf[1024]; char error_buffer[256]; @@ -1617,7 +1872,7 @@ static CURLcode wolfssl_shutdown(struct Curl_cfilter *cf, int detail; DEBUGASSERT(wctx); - if(!wctx->handle || cf->shutdown) { + if(!wctx->ssl || cf->shutdown) { *done = TRUE; goto out; } @@ -1625,12 +1880,12 @@ static CURLcode wolfssl_shutdown(struct Curl_cfilter *cf, wctx->shutting_down = TRUE; connssl->io_need = CURL_SSL_IO_NEED_NONE; *done = FALSE; - if(!(wolfSSL_get_shutdown(wctx->handle) & WOLFSSL_SENT_SHUTDOWN)) { + if(!(wolfSSL_get_shutdown(wctx->ssl) & 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); + nread = wolfSSL_read(wctx->ssl, buf, (int)sizeof(buf)); + err = wolfSSL_get_error(wctx->ssl, nread); CURL_TRC_CF(data, cf, "wolfSSL_read, nread=%d, err=%d", nread, err); if(!nread && err == WOLFSSL_ERROR_ZERO_RETURN) { bool input_pending; @@ -1652,16 +1907,16 @@ static CURLcode wolfssl_shutdown(struct Curl_cfilter *cf, } } - /* SSL should now have started the shutdown from our side. Since it + /* wolfSSL 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) { + if(wolfSSL_shutdown(wctx->ssl) == 1) { CURL_TRC_CF(data, cf, "SSL shutdown finished"); *done = TRUE; goto out; } - if(WOLFSSL_ERROR_WANT_WRITE == wolfSSL_get_error(wctx->handle, nread)) { + if(WOLFSSL_ERROR_WANT_WRITE == wolfSSL_get_error(wctx->ssl, nread)) { CURL_TRC_CF(data, cf, "SSL shutdown still wants to send"); connssl->io_need = CURL_SSL_IO_NEED_SEND; goto out; @@ -1672,11 +1927,11 @@ static CURLcode wolfssl_shutdown(struct Curl_cfilter *cf, for(i = 0; i < 10; ++i) { wolfSSL_ERR_clear_error(); - nread = wolfSSL_read(wctx->handle, buf, (int)sizeof(buf)); + nread = wolfSSL_read(wctx->ssl, buf, (int)sizeof(buf)); if(nread <= 0) break; } - err = wolfSSL_get_error(wctx->handle, nread); + err = wolfSSL_get_error(wctx->ssl, nread); switch(err) { case WOLFSSL_ERROR_ZERO_RETURN: /* no more data */ CURL_TRC_CF(data, cf, "SSL shutdown received"); @@ -1684,7 +1939,7 @@ static CURLcode wolfssl_shutdown(struct Curl_cfilter *cf, 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 + /* wolfSSL 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; @@ -1694,10 +1949,10 @@ static CURLcode wolfssl_shutdown(struct Curl_cfilter *cf, connssl->io_need = CURL_SSL_IO_NEED_SEND; break; default: - detail = wolfSSL_get_error(wctx->handle, err); + detail = wolfSSL_get_error(wctx->ssl, err); CURL_TRC_CF(data, cf, "SSL shutdown, error: '%s'(%d)", - wolfssl_strerror((unsigned long)err, error_buffer, - sizeof(error_buffer)), + wssl_strerror((unsigned long)err, error_buffer, + sizeof(error_buffer)), detail); result = CURLE_RECV_ERROR; break; @@ -1708,91 +1963,89 @@ out: return result; } -static void wolfssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) +static void wssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct wolfssl_ctx *backend = - (struct wolfssl_ctx *)connssl->backend; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; (void) data; - DEBUGASSERT(backend); + DEBUGASSERT(wssl); - if(backend->handle) { - wolfSSL_free(backend->handle); - backend->handle = NULL; + if(wssl->ssl) { + wolfSSL_free(wssl->ssl); + wssl->ssl = NULL; } - if(backend->ctx) { - wolfSSL_CTX_free(backend->ctx); - backend->ctx = NULL; + if(wssl->ssl_ctx) { + wolfSSL_CTX_free(wssl->ssl_ctx); + wssl->ssl_ctx = NULL; } } -static ssize_t wolfssl_recv(struct Curl_cfilter *cf, - struct Curl_easy *data, - char *buf, size_t blen, - CURLcode *curlcode) +static ssize_t wssl_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, size_t blen, + CURLcode *curlcode) { struct ssl_connect_data *connssl = cf->ctx; - struct wolfssl_ctx *backend = - (struct wolfssl_ctx *)connssl->backend; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; int buffsize = (blen > (size_t)INT_MAX) ? INT_MAX : (int)blen; int nread; - DEBUGASSERT(backend); + DEBUGASSERT(wssl); wolfSSL_ERR_clear_error(); *curlcode = CURLE_OK; - nread = wolfSSL_read(backend->handle, buf, buffsize); + nread = wolfSSL_read(wssl->ssl, buf, buffsize); if(nread <= 0) { - int err = wolfSSL_get_error(backend->handle, nread); + int err = wolfSSL_get_error(wssl->ssl, nread); switch(err) { case WOLFSSL_ERROR_ZERO_RETURN: /* no more data */ - CURL_TRC_CF(data, cf, "wolfssl_recv(len=%zu) -> CLOSED", blen); + CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> CLOSED", blen); *curlcode = CURLE_OK; return 0; 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); + if(!wssl->io_result && connssl->peer_closed) { + CURL_TRC_CF(data, cf, "wssl_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); + CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> AGAIN", blen); *curlcode = CURLE_AGAIN; return -1; default: - if(backend->io_result == CURLE_AGAIN) { - CURL_TRC_CF(data, cf, "wolfssl_recv(len=%zu) -> AGAIN", blen); + if(wssl->io_result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> AGAIN", blen); *curlcode = CURLE_AGAIN; return -1; } - else if(!backend->io_result && connssl->peer_closed) { - CURL_TRC_CF(data, cf, "wolfssl_recv(len=%zu) -> CLOSED", blen); + else if(!wssl->io_result && connssl->peer_closed) { + CURL_TRC_CF(data, cf, "wssl_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)), + wssl_strerror((unsigned long)err, error_buffer, + sizeof(error_buffer)), SOCKERRNO); } *curlcode = CURLE_RECV_ERROR; return -1; } } - CURL_TRC_CF(data, cf, "wolfssl_recv(len=%zu) -> %d", blen, nread); + + CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> %d", blen, nread); return nread; } - size_t Curl_wssl_version(char *buffer, size_t size) { #if LIBWOLFSSL_VERSION_HEX >= 0x03006000 @@ -1803,7 +2056,7 @@ size_t Curl_wssl_version(char *buffer, size_t size) } -static int wolfssl_init(void) +static int wssl_init(void) { int ret; @@ -1811,14 +2064,14 @@ static int wolfssl_init(void) Curl_tls_keylog_open(); #endif ret = (wolfSSL_Init() == WOLFSSL_SUCCESS); - wolfssl_bio_cf_init_methods(); + wssl_bio_cf_init_methods(); return ret; } -static void wolfssl_cleanup(void) +static void wssl_cleanup(void) { - wolfssl_bio_cf_free_methods(); + wssl_bio_cf_free_methods(); wolfSSL_Cleanup(); #ifdef OPENSSL_EXTRA Curl_tls_keylog_close(); @@ -1826,31 +2079,29 @@ static void wolfssl_cleanup(void) } -static bool wolfssl_data_pending(struct Curl_cfilter *cf, - const struct Curl_easy *data) +static bool wssl_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) { struct ssl_connect_data *ctx = cf->ctx; - struct wolfssl_ctx *backend; + struct wssl_ctx *wssl; (void)data; DEBUGASSERT(ctx && ctx->backend); - backend = (struct wolfssl_ctx *)ctx->backend; - if(backend->handle) /* SSL is in use */ - return wolfSSL_pending(backend->handle); + wssl = (struct wssl_ctx *)ctx->backend; + if(wssl->ssl) /* wolfSSL is in use */ + return wolfSSL_pending(wssl->ssl); else return FALSE; } -static CURLcode -wolfssl_connect_common(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool nonblocking, - bool *done) +static CURLcode wssl_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { - CURLcode result; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; + CURLcode result = CURLE_OK; /* check if the connection has already been established */ if(ssl_connection_complete == connssl->state) { @@ -1858,116 +2109,108 @@ wolfssl_connect_common(struct Curl_cfilter *cf, return CURLE_OK; } + *done = FALSE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we are allowed */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - result = wolfssl_connect_step1(cf, data); + result = wssl_connect_step1(cf, data); if(result) return result; + connssl->connecting_state = ssl_connect_2; } - while(ssl_connect_2 == connssl->connecting_state) { - - /* check allowed time left */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; + if(ssl_connect_2 == connssl->connecting_state) { + if(connssl->earlydata_state == ssl_earlydata_await) { + /* We defer the handshake until request data arrives. */ + DEBUGASSERT(connssl->state == ssl_connection_deferred); + goto out; } - - /* 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); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if - * this connection is part of a multi handle and this loop would - * execute again. This permits the owner of a multi handle to - * abort a connection attempt before step2 has completed while - * ensuring that a client using select() or epoll() will always - * have a valid fdset to wait on. - */ - result = wolfssl_connect_step2(cf, data); - if(result || (nonblocking && (ssl_connect_2 == connssl->connecting_state))) - return result; - } /* repeat step2 until all transactions are done. */ + result = wssl_handshake(cf, data); + if(result == CURLE_AGAIN) + goto out; + wssl->hs_result = result; + connssl->connecting_state = ssl_connect_3; + } if(ssl_connect_3 == connssl->connecting_state) { - /* In other backends, this is where we verify the certificate, but - * wolfSSL already does that as part of the handshake. */ + /* Once the handshake has errored, it stays in that state and will + * error again on every call. */ + if(wssl->hs_result) { + result = wssl->hs_result; + goto out; + } + result = Curl_wssl_verify_pinned(cf, data, wssl); + if(result) { + wssl->hs_result = result; + goto out; + } + /* handhshake was done without errors */ +#ifdef HAVE_ALPN + if(connssl->alpn) { + int rc; + char *protocol = NULL; + unsigned short protocol_len = 0; + + rc = wolfSSL_ALPN_GetProtocol(wssl->ssl, &protocol, &protocol_len); + + if(rc == WOLFSSL_SUCCESS) { + Curl_alpn_set_negotiated(cf, data, connssl, + (const unsigned char *)protocol, + protocol_len); + } + 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); + wssl->hs_result = result = CURLE_SSL_CONNECT_ERROR; + goto out; + } + } +#endif /* HAVE_ALPN */ + +#if (LIBWOLFSSL_VERSION_HEX >= 0x03009010) + infof(data, "SSL connection using %s / %s", + wolfSSL_get_version(wssl->ssl), + wolfSSL_get_cipher_name(wssl->ssl)); +#else + infof(data, "SSL connected"); +#endif + connssl->connecting_state = ssl_connect_done; + connssl->state = ssl_connection_complete; + +#ifdef WOLFSSL_EARLY_DATA + if(connssl->earlydata_state > ssl_earlydata_none) { + /* We should be in this state by now */ + DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sent); + connssl->earlydata_state = + (wolfSSL_get_early_data_status(wssl->ssl) == + WOLFSSL_EARLY_DATA_REJECTED) ? + ssl_earlydata_rejected : ssl_earlydata_accepted; + } +#endif /* WOLFSSL_EARLY_DATA */ } - if(ssl_connect_done == connssl->connecting_state) { - connssl->state = ssl_connection_complete; + if((connssl->connecting_state == ssl_connect_done) || + (connssl->state == ssl_connection_deferred)) { *done = TRUE; } - else + +out: + if(result) { *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; + if(result == CURLE_AGAIN) + return CURLE_OK; + } + else if((connssl->connecting_state == ssl_connect_done) || + (connssl->state == ssl_connection_deferred)) { + *done = TRUE; + } + return result; } - -static CURLcode wolfssl_connect_nonblocking(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - return wolfssl_connect_common(cf, data, TRUE, done); -} - - -static CURLcode wolfssl_connect(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - CURLcode result; - bool done = FALSE; - - result = wolfssl_connect_common(cf, data, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -static CURLcode wolfssl_random(struct Curl_easy *data, - unsigned char *entropy, size_t length) +static CURLcode wssl_random(struct Curl_easy *data, + unsigned char *entropy, size_t length) { WC_RNG rng; (void)data; @@ -1982,10 +2225,10 @@ static CURLcode wolfssl_random(struct Curl_easy *data, return CURLE_OK; } -static CURLcode wolfssl_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum /* output */, - size_t unused) +static CURLcode wssl_sha256sum(const unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *sha256sum /* output */, + size_t unused) { wc_Sha256 SHA256pw; (void)unused; @@ -1996,14 +2239,13 @@ static CURLcode wolfssl_sha256sum(const unsigned char *tmp, /* input */ return CURLE_OK; } -static void *wolfssl_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) +static void *wssl_get_internals(struct ssl_connect_data *connssl, + CURLINFO info UNUSED_PARAM) { - struct wolfssl_ctx *backend = - (struct wolfssl_ctx *)connssl->backend; + struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; (void)info; - DEBUGASSERT(backend); - return backend->handle; + DEBUGASSERT(wssl); + return wssl->ssl; } const struct Curl_ssl Curl_ssl_wolfssl = { @@ -2027,28 +2269,27 @@ const struct Curl_ssl Curl_ssl_wolfssl = { SSLSUPP_CA_CACHE | SSLSUPP_CIPHER_LIST, - sizeof(struct wolfssl_ctx), + sizeof(struct wssl_ctx), - wolfssl_init, /* init */ - wolfssl_cleanup, /* cleanup */ + wssl_init, /* init */ + wssl_cleanup, /* cleanup */ Curl_wssl_version, /* version */ - wolfssl_shutdown, /* shutdown */ - wolfssl_data_pending, /* data_pending */ - wolfssl_random, /* random */ + wssl_shutdown, /* shutdown */ + wssl_data_pending, /* data_pending */ + wssl_random, /* random */ NULL, /* cert_status_request */ - wolfssl_connect, /* connect */ - wolfssl_connect_nonblocking, /* connect_nonblocking */ + wssl_connect, /* connect */ Curl_ssl_adjust_pollset, /* adjust_pollset */ - wolfssl_get_internals, /* get_internals */ - wolfssl_close, /* close_one */ + wssl_get_internals, /* get_internals */ + wssl_close, /* close_one */ NULL, /* close_all */ NULL, /* set_engine */ NULL, /* set_engine_default */ NULL, /* engines_list */ NULL, /* false_start */ - wolfssl_sha256sum, /* sha256sum */ - wolfssl_recv, /* recv decrypted data */ - wolfssl_send, /* send data to encrypt */ + wssl_sha256sum, /* sha256sum */ + wssl_recv, /* recv decrypted data */ + wssl_send, /* send data to encrypt */ NULL, /* get_channel_binding */ }; diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.h b/Utilities/cmcurl/lib/vtls/wolfssl.h index 57935b60c7..0ddbee9ed9 100644 --- a/Utilities/cmcurl/lib/vtls/wolfssl.h +++ b/Utilities/cmcurl/lib/vtls/wolfssl.h @@ -23,25 +23,27 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #ifdef USE_WOLFSSL -#include "urldata.h" +#include "../urldata.h" + +struct alpn_spec; +struct ssl_peer; +struct Curl_ssl_session; 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; +struct wssl_ctx { + struct WOLFSSL_CTX *ssl_ctx; + struct WOLFSSL *ssl; CURLcode io_result; /* result of last BIO cfilter operation */ + CURLcode hs_result; /* result of handshake */ 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 */ @@ -49,21 +51,42 @@ struct wolfssl_ctx { size_t Curl_wssl_version(char *buffer, size_t size); +typedef CURLcode Curl_wssl_ctx_setup_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data); + +typedef CURLcode Curl_wssl_init_session_reuse_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + struct Curl_ssl_session *scs, + bool *do_early_data); + +CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const struct alpn_spec *alpns, + Curl_wssl_ctx_setup_cb *cb_setup, + void *cb_user_data, + void *ssl_user_data, + Curl_wssl_init_session_reuse_cb *sess_reuse_cb); + CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, - struct wolfssl_ctx *wssl); - -CURLcode Curl_wssl_setup_session(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct wolfssl_ctx *wss, - const char *ssl_peer_key); + struct wssl_ctx *wssl); CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf, struct Curl_easy *data, const char *ssl_peer_key, - WOLFSSL_SESSION *session, + struct WOLFSSL_SESSION *session, int ietf_tls_id, - const char *alpn); + const char *alpn, + unsigned char *quic_tp, + size_t quic_tp_len); + +CURLcode Curl_wssl_verify_pinned(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wssl_ctx *wssl); #endif /* USE_WOLFSSL */ diff --git a/Utilities/cmcurl/lib/vtls/x509asn1.c b/Utilities/cmcurl/lib/vtls/x509asn1.c index 0bc0a75a4b..c6246cbd09 100644 --- a/Utilities/cmcurl/lib/vtls/x509asn1.c +++ b/Utilities/cmcurl/lib/vtls/x509asn1.c @@ -22,39 +22,39 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ - defined(USE_MBEDTLS) + defined(USE_MBEDTLS) || defined(USE_RUSTLS) -#if defined(USE_WOLFSSL) || defined(USE_SCHANNEL) +#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_MBEDTLS) || defined(USE_WOLFSSL) || defined(USE_RUSTLS) #define WANT_PARSEX509 /* uses Curl_parseX509() */ #endif #if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ - defined(USE_MBEDTLS) + defined(USE_MBEDTLS) || defined(USE_RUSTLS) #define WANT_EXTRACT_CERTINFO /* uses Curl_extract_certinfo() */ -#define WANT_PARSEX509 /* ... uses Curl_parseX509() */ #endif #include -#include "urldata.h" -#include "strcase.h" -#include "curl_ctype.h" +#include "../urldata.h" +#include "../strcase.h" +#include "../curl_ctype.h" #include "hostcheck.h" -#include "vtls/vtls.h" -#include "vtls/vtls_int.h" -#include "sendf.h" -#include "inet_pton.h" -#include "curl_base64.h" +#include "vtls.h" +#include "vtls_int.h" +#include "../sendf.h" +#include "../curlx/inet_pton.h" +#include "../curlx/base64.h" #include "x509asn1.h" -#include "dynbuf.h" +#include "../curlx/dynbuf.h" /* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" +#include "../curl_printf.h" +#include "../curl_memory.h" +#include "../memdebug.h" /* * Constants. @@ -64,10 +64,10 @@ #define CURL_ASN1_MAX ((size_t) 0x40000) /* 256K */ /* ASN.1 classes. */ -#define CURL_ASN1_UNIVERSAL 0 -#define CURL_ASN1_APPLICATION 1 -#define CURL_ASN1_CONTEXT_SPECIFIC 2 -#define CURL_ASN1_PRIVATE 3 +/* #define CURL_ASN1_UNIVERSAL 0 */ +/* #define CURL_ASN1_APPLICATION 1 */ +/* #define CURL_ASN1_CONTEXT_SPECIFIC 2 */ +/* #define CURL_ASN1_PRIVATE 3 */ /* ASN.1 types. */ #define CURL_ASN1_BOOLEAN 1 @@ -76,27 +76,27 @@ #define CURL_ASN1_OCTET_STRING 4 #define CURL_ASN1_NULL 5 #define CURL_ASN1_OBJECT_IDENTIFIER 6 -#define CURL_ASN1_OBJECT_DESCRIPTOR 7 -#define CURL_ASN1_INSTANCE_OF 8 -#define CURL_ASN1_REAL 9 +/* #define CURL_ASN1_OBJECT_DESCRIPTOR 7 */ +/* #define CURL_ASN1_INSTANCE_OF 8 */ +/* #define CURL_ASN1_REAL 9 */ #define CURL_ASN1_ENUMERATED 10 -#define CURL_ASN1_EMBEDDED 11 +/* #define CURL_ASN1_EMBEDDED 11 */ #define CURL_ASN1_UTF8_STRING 12 -#define CURL_ASN1_RELATIVE_OID 13 -#define CURL_ASN1_SEQUENCE 16 -#define CURL_ASN1_SET 17 +/* #define CURL_ASN1_RELATIVE_OID 13 */ +/* #define CURL_ASN1_SEQUENCE 16 */ +/* #define CURL_ASN1_SET 17 */ #define CURL_ASN1_NUMERIC_STRING 18 #define CURL_ASN1_PRINTABLE_STRING 19 #define CURL_ASN1_TELETEX_STRING 20 -#define CURL_ASN1_VIDEOTEX_STRING 21 +/* #define CURL_ASN1_VIDEOTEX_STRING 21 */ #define CURL_ASN1_IA5_STRING 22 #define CURL_ASN1_UTC_TIME 23 #define CURL_ASN1_GENERALIZED_TIME 24 -#define CURL_ASN1_GRAPHIC_STRING 25 +/* #define CURL_ASN1_GRAPHIC_STRING 25 */ #define CURL_ASN1_VISIBLE_STRING 26 -#define CURL_ASN1_GENERAL_STRING 27 +/* #define CURL_ASN1_GENERAL_STRING 27 */ #define CURL_ASN1_UNIVERSAL_STRING 28 -#define CURL_ASN1_CHARACTER_STRING 29 +/* #define CURL_ASN1_CHARACTER_STRING 29 */ #define CURL_ASN1_BMP_STRING 30 @@ -256,7 +256,7 @@ static const char *getASN1Element(struct Curl_asn1Element *elem, #ifdef WANT_EXTRACT_CERTINFO /* - * Search the null terminated OID or OID identifier in local table. + * Search the null-terminated OID or OID identifier in local table. * Return the table entry pointer or NULL if not found. */ static const struct Curl_OID *searchOID(const char *oid) @@ -291,7 +291,7 @@ static CURLcode bool2str(struct dynbuf *store, { if(end - beg != 1) return CURLE_BAD_FUNCTION_ARGUMENT; - return Curl_dyn_add(store, *beg ? "TRUE": "FALSE"); + return curlx_dyn_add(store, *beg ? "TRUE": "FALSE"); } /* @@ -305,7 +305,7 @@ static CURLcode octet2str(struct dynbuf *store, CURLcode result = CURLE_OK; while(!result && beg < end) - result = Curl_dyn_addf(store, "%02x:", (unsigned char) *beg++); + result = curlx_dyn_addf(store, "%02x:", (unsigned char) *beg++); return result; } @@ -344,7 +344,7 @@ static CURLcode int2str(struct dynbuf *store, do val = (val << 8) | *(const unsigned char *) beg++; while(beg < end); - return Curl_dyn_addf(store, "%s%x", val >= 10 ? "0x" : "", val); + return curlx_dyn_addf(store, "%s%x", val >= 10 ? "0x" : "", val); } /* @@ -387,7 +387,7 @@ utf8asn1str(struct dynbuf *to, int type, const char *from, const char *end) if(type == CURL_ASN1_UTF8_STRING) { /* Just copy. */ if(inlength) - result = Curl_dyn_addn(to, from, inlength); + result = curlx_dyn_addn(to, from, inlength); } else { while(!result && (from < end)) { @@ -426,7 +426,7 @@ utf8asn1str(struct dynbuf *to, int type, const char *from, const char *end) charsize++; } buf[0] = (char) wc; - result = Curl_dyn_addn(to, buf, charsize); + result = curlx_dyn_addn(to, buf, charsize); } } return result; @@ -449,7 +449,7 @@ static CURLcode encodeOID(struct dynbuf *store, x = y / 40; y -= x * 40; - result = Curl_dyn_addf(store, "%u.%u", x, y); + result = curlx_dyn_addf(store, "%u.%u", x, y); if(result) return result; @@ -462,7 +462,7 @@ static CURLcode encodeOID(struct dynbuf *store, y = *(const unsigned char *) beg++; x = (x << 7) | (y & 0x7F); } while(y & 0x80); - result = Curl_dyn_addf(store, ".%u", x); + result = curlx_dyn_addf(store, ".%u", x); } return result; } @@ -480,16 +480,16 @@ static CURLcode OID2str(struct dynbuf *store, if(beg < end) { if(symbolic) { struct dynbuf buf; - Curl_dyn_init(&buf, CURL_X509_STR_MAX); + curlx_dyn_init(&buf, CURL_X509_STR_MAX); result = encodeOID(&buf, beg, end); if(!result) { - const struct Curl_OID *op = searchOID(Curl_dyn_ptr(&buf)); + const struct Curl_OID *op = searchOID(curlx_dyn_ptr(&buf)); if(op) - result = Curl_dyn_add(store, op->textoid); + result = curlx_dyn_add(store, op->textoid); else - result = Curl_dyn_add(store, Curl_dyn_ptr(&buf)); - Curl_dyn_free(&buf); + result = curlx_dyn_add(store, curlx_dyn_ptr(&buf)); + curlx_dyn_free(&buf); } } else @@ -568,12 +568,12 @@ static CURLcode GTime2str(struct dynbuf *store, tzl = end - tzp; } - 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, - sep, (int)tzl, tzp); + return curlx_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, + sep, (int)tzl, tzp); } #ifdef UNITTESTS @@ -622,10 +622,10 @@ static CURLcode UTime2str(struct dynbuf *store, tzp++; tzl = end - tzp; - 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); + return curlx_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); } /* @@ -658,7 +658,7 @@ static CURLcode ASN1tostr(struct dynbuf *store, result = octet2str(store, elem->beg, elem->end); break; case CURL_ASN1_NULL: - result = Curl_dyn_addn(store, "", 1); + result = curlx_dyn_addn(store, "", 1); break; case CURL_ASN1_OBJECT_IDENTIFIER: result = OID2str(store, elem->beg, elem->end, TRUE); @@ -702,7 +702,7 @@ static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) CURLcode result = CURLE_OK; bool added = FALSE; struct dynbuf temp; - Curl_dyn_init(&temp, CURL_X509_STR_MAX); + curlx_dyn_init(&temp, CURL_X509_STR_MAX); for(p1 = dn->beg; p1 < dn->end;) { p1 = getASN1Element(&rdn, p1, dn->end); @@ -725,12 +725,12 @@ static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) result = CURLE_BAD_FUNCTION_ARGUMENT; goto error; } - Curl_dyn_reset(&temp); + curlx_dyn_reset(&temp); result = ASN1tostr(&temp, &oid, 0); if(result) goto error; - str = Curl_dyn_ptr(&temp); + str = curlx_dyn_ptr(&temp); if(!str) { result = CURLE_BAD_FUNCTION_ARGUMENT; @@ -743,20 +743,20 @@ static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) ; if(added) { if(p3 - str > 2) - result = Curl_dyn_addn(store, "/", 1); + result = curlx_dyn_addn(store, "/", 1); else - result = Curl_dyn_addn(store, ", ", 2); + result = curlx_dyn_addn(store, ", ", 2); if(result) goto error; } /* Encode attribute name. */ - result = Curl_dyn_add(store, str); + result = curlx_dyn_add(store, str); if(result) goto error; /* Generate equal sign. */ - result = Curl_dyn_addn(store, "=", 1); + result = curlx_dyn_addn(store, "=", 1); if(result) goto error; @@ -764,12 +764,12 @@ static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) result = ASN1tostr(store, &value, 0); if(result) goto error; - Curl_dyn_reset(&temp); + curlx_dyn_reset(&temp); added = TRUE; /* use separator for next */ } } error: - Curl_dyn_free(&temp); + curlx_dyn_free(&temp); return result; } @@ -947,8 +947,8 @@ static CURLcode ssl_push_certinfo_dyn(struct Curl_easy *data, const char *label, struct dynbuf *ptr) { - size_t valuelen = Curl_dyn_len(ptr); - char *value = Curl_dyn_ptr(ptr); + size_t valuelen = curlx_dyn_len(ptr); + char *value = curlx_dyn_ptr(ptr); CURLcode result = Curl_ssl_push_certinfo_len(data, certnum, label, value, valuelen); @@ -966,7 +966,7 @@ static CURLcode do_pubkey_field(struct Curl_easy *data, int certnum, CURLcode result; struct dynbuf out; - Curl_dyn_init(&out, CURL_X509_STR_MAX); + curlx_dyn_init(&out, CURL_X509_STR_MAX); /* Generate a certificate information record for the public key. */ @@ -974,7 +974,7 @@ static CURLcode do_pubkey_field(struct Curl_easy *data, int certnum, if(!result) { if(data->set.ssl.certinfo) result = ssl_push_certinfo_dyn(data, certnum, label, &out); - Curl_dyn_free(&out); + curlx_dyn_free(&out); } return result; } @@ -1026,7 +1026,7 @@ static int do_pubkey(struct Curl_easy *data, int certnum, len = ((elem.end - q) * 8); if(len) { unsigned int i; - for(i = *(unsigned char *) q; !(i & 0x80); i <<= 1) + for(i = *(const unsigned char *) q; !(i & 0x80); i <<= 1) len--; } if(len > 32) @@ -1110,7 +1110,7 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, if(certnum) return CURLE_OK; - Curl_dyn_init(&out, CURL_X509_STR_MAX); + curlx_dyn_init(&out, CURL_X509_STR_MAX); /* Prepare the certificate information for curl_easy_getinfo(). */ /* Extract the certificate ASN.1 elements. */ @@ -1126,7 +1126,7 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, if(result) goto done; } - Curl_dyn_reset(&out); + curlx_dyn_reset(&out); /* Issuer. */ result = DNtostr(&out, &cert.issuer); @@ -1137,20 +1137,20 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, if(result) goto done; } - Curl_dyn_reset(&out); + curlx_dyn_reset(&out); /* Version (always fits in less than 32 bits). */ version = 0; for(ptr = cert.version.beg; ptr < cert.version.end; ptr++) version = (version << 8) | *(const unsigned char *) ptr; if(data->set.ssl.certinfo) { - result = Curl_dyn_addf(&out, "%x", version); + result = curlx_dyn_addf(&out, "%x", version); if(result) goto done; result = ssl_push_certinfo_dyn(data, certnum, "Version", &out); if(result) goto done; - Curl_dyn_reset(&out); + curlx_dyn_reset(&out); } /* Serial number. */ @@ -1162,7 +1162,7 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, if(result) goto done; } - Curl_dyn_reset(&out); + curlx_dyn_reset(&out); /* Signature algorithm .*/ result = dumpAlgo(&out, ¶m, cert.signatureAlgorithm.beg, @@ -1175,7 +1175,7 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, if(result) goto done; } - Curl_dyn_reset(&out); + curlx_dyn_reset(&out); /* Start Date. */ result = ASN1tostr(&out, &cert.notBefore, 0); @@ -1186,7 +1186,7 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, if(result) goto done; } - Curl_dyn_reset(&out); + curlx_dyn_reset(&out); /* Expire Date. */ result = ASN1tostr(&out, &cert.notAfter, 0); @@ -1197,7 +1197,7 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, if(result) goto done; } - Curl_dyn_reset(&out); + curlx_dyn_reset(&out); /* Public Key Algorithm. */ result = dumpAlgo(&out, ¶m, cert.subjectPublicKeyAlgorithm.beg, @@ -1211,13 +1211,13 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, goto done; } - rc = do_pubkey(data, certnum, Curl_dyn_ptr(&out), + rc = do_pubkey(data, certnum, curlx_dyn_ptr(&out), ¶m, &cert.subjectPublicKey); if(rc) { result = CURLE_OUT_OF_MEMORY; /* the most likely error */ goto done; } - Curl_dyn_reset(&out); + curlx_dyn_reset(&out); /* Signature. */ result = ASN1tostr(&out, &cert.signature, 0); @@ -1228,12 +1228,12 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, if(result) goto done; } - Curl_dyn_reset(&out); + curlx_dyn_reset(&out); /* Generate PEM certificate. */ - result = Curl_base64_encode(cert.certificate.beg, - cert.certificate.end - cert.certificate.beg, - &certptr, &clen); + result = curlx_base64_encode(cert.certificate.beg, + cert.certificate.end - cert.certificate.beg, + &certptr, &clen); if(result) goto done; @@ -1246,22 +1246,22 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, -----END CERTIFICATE-----\n */ - Curl_dyn_reset(&out); + curlx_dyn_reset(&out); /* Build the certificate string. */ - result = Curl_dyn_add(&out, "-----BEGIN CERTIFICATE-----\n"); + result = curlx_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); + result = curlx_dyn_addn(&out, &certptr[j], chunksize); if(!result) - result = Curl_dyn_addn(&out, "\n", 1); + result = curlx_dyn_addn(&out, "\n", 1); j += chunksize; } if(!result) - result = Curl_dyn_add(&out, "-----END CERTIFICATE-----\n"); + result = curlx_dyn_add(&out, "-----END CERTIFICATE-----\n"); } free(certptr); if(!result) @@ -1271,10 +1271,11 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, done: if(result) failf(data, "Failed extracting certificate chain"); - Curl_dyn_free(&out); + curlx_dyn_free(&out); return result; } #endif /* WANT_EXTRACT_CERTINFO */ -#endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP */ +#endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP + or USE_MBEDTLS or USE_RUSTLS */ diff --git a/Utilities/cmcurl/lib/vtls/x509asn1.h b/Utilities/cmcurl/lib/vtls/x509asn1.h index 5de8f18e9c..1c9c35c3d4 100644 --- a/Utilities/cmcurl/lib/vtls/x509asn1.h +++ b/Utilities/cmcurl/lib/vtls/x509asn1.h @@ -25,14 +25,14 @@ * ***************************************************************************/ -#include "curl_setup.h" +#include "../curl_setup.h" #if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ - defined(USE_MBEDTLS) + defined(USE_MBEDTLS) || defined(USE_RUSTLS) -#include "cfilters.h" -#include "urldata.h" +#include "../cfilters.h" +#include "../urldata.h" /* * Types. @@ -45,7 +45,7 @@ struct Curl_asn1Element { const char *end; /* Pointer to 1st byte after element. */ unsigned char class; /* ASN.1 element class. */ unsigned char tag; /* ASN.1 element tag. */ - bool constructed; /* Element is constructed. */ + BIT(constructed); /* Element is constructed. */ }; /* X509 certificate: RFC 5280. */ @@ -80,7 +80,7 @@ CURLcode Curl_verifyhost(struct Curl_cfilter *cf, struct Curl_easy *data, #ifdef UNITTESTS #if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ - defined(USE_MBEDTLS) + defined(USE_MBEDTLS) || defined(USE_RUSTLS) /* used by unit1656.c */ CURLcode Curl_x509_GTime2str(struct dynbuf *store, @@ -91,5 +91,6 @@ CURLcode Curl_x509_getASN1Element(struct Curl_asn1Element *elem, #endif #endif -#endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP */ +#endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP + or USE_MBEDTLS or USE_RUSTLS */ #endif /* HEADER_CURL_X509ASN1_H */ diff --git a/Utilities/cmcurl/lib/ws.c b/Utilities/cmcurl/lib/ws.c index 25d19c6972..fc02025f1d 100644 --- a/Utilities/cmcurl/lib/ws.c +++ b/Utilities/cmcurl/lib/ws.c @@ -27,10 +27,11 @@ #if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) #include "urldata.h" +#include "url.h" #include "bufq.h" -#include "dynbuf.h" +#include "curlx/dynbuf.h" #include "rand.h" -#include "curl_base64.h" +#include "curlx/base64.h" #include "connect.h" #include "sendf.h" #include "multiif.h" @@ -38,7 +39,8 @@ #include "easyif.h" #include "transfer.h" #include "select.h" -#include "nonblock.h" +#include "curlx/nonblock.h" +#include "curlx/strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -46,16 +48,26 @@ #include "memdebug.h" -#define WSBIT_FIN 0x80 -#define WSBIT_RSV1 0x40 -#define WSBIT_RSV2 0x20 -#define WSBIT_RSV3 0x10 +/*** + RFC 6455 Section 5.2 + + 0 1 2 3 4 5 6 7 + +-+-+-+-+-------+ + |F|R|R|R| opcode| + |I|S|S|S| (4) | + |N|V|V|V| | + | |1|2|3| | +*/ +#define WSBIT_FIN (0x80) +#define WSBIT_RSV1 (0x40) +#define WSBIT_RSV2 (0x20) +#define WSBIT_RSV3 (0x10) #define WSBIT_RSV_MASK (WSBIT_RSV1 | WSBIT_RSV2 | WSBIT_RSV3) -#define WSBIT_OPCODE_CONT 0 -#define WSBIT_OPCODE_TEXT (1) -#define WSBIT_OPCODE_BIN (2) -#define WSBIT_OPCODE_CLOSE (8) -#define WSBIT_OPCODE_PING (9) +#define WSBIT_OPCODE_CONT (0x0) +#define WSBIT_OPCODE_TEXT (0x1) +#define WSBIT_OPCODE_BIN (0x2) +#define WSBIT_OPCODE_CLOSE (0x8) +#define WSBIT_OPCODE_PING (0x9) #define WSBIT_OPCODE_PONG (0xa) #define WSBIT_OPCODE_MASK (0xf) @@ -65,58 +77,206 @@ #define WS_CHUNK_SIZE 65535 #define WS_CHUNK_COUNT 2 -struct ws_frame_meta { - char proto_opcode; - int flags; - const char *name; + +/* a client-side WS frame decoder, parsing frame headers and + * payload, keeping track of current position and stats */ +enum ws_dec_state { + WS_DEC_INIT, + WS_DEC_HEAD, + WS_DEC_PAYLOAD }; -static struct ws_frame_meta WS_FRAMES[] = { - { WSBIT_OPCODE_CONT, CURLWS_CONT, "CONT" }, - { WSBIT_OPCODE_TEXT, CURLWS_TEXT, "TEXT" }, - { WSBIT_OPCODE_BIN, CURLWS_BINARY, "BIN" }, - { WSBIT_OPCODE_CLOSE, CURLWS_CLOSE, "CLOSE" }, - { WSBIT_OPCODE_PING, CURLWS_PING, "PING" }, - { WSBIT_OPCODE_PONG, CURLWS_PONG, "PONG" }, +struct ws_decoder { + int frame_age; /* zero */ + int frame_flags; /* See the CURLWS_* defines */ + curl_off_t payload_offset; /* the offset parsing is at */ + curl_off_t payload_len; + unsigned char head[10]; + int head_len, head_total; + enum ws_dec_state state; + int cont_flags; }; -static const char *ws_frame_name_of_op(unsigned char proto_opcode) +/* a client-side WS frame encoder, generating frame headers and + * converting payloads, tracking remaining data in current frame */ +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 firstbyte; /* first byte of frame we encode */ + BIT(contfragment); /* set TRUE if the previous fragment sent was not final */ +}; + +/* A websocket connection with en- and decoder that treat frames + * and keep track of boundaries. */ +struct websocket { + struct Curl_easy *data; /* used for write callback handling */ + struct ws_decoder dec; /* decode of we frames */ + struct ws_encoder enc; /* decode of we frames */ + struct bufq recvbuf; /* raw data from the server */ + struct bufq sendbuf; /* raw data to be sent to the server */ + struct curl_ws_frame frame; /* the current WS FRAME received */ + size_t sendbuf_payload; /* number of payload bytes in sendbuf */ +}; + + +static const char *ws_frame_name_of_op(unsigned char firstbyte) { - unsigned char opcode = proto_opcode & WSBIT_OPCODE_MASK; - size_t i; - for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) { - if(WS_FRAMES[i].proto_opcode == opcode) - return WS_FRAMES[i].name; + switch(firstbyte & WSBIT_OPCODE_MASK) { + case WSBIT_OPCODE_CONT: + return "CONT"; + case WSBIT_OPCODE_TEXT: + return "TEXT"; + case WSBIT_OPCODE_BIN: + return "BIN"; + case WSBIT_OPCODE_CLOSE: + return "CLOSE"; + case WSBIT_OPCODE_PING: + return "PING"; + case WSBIT_OPCODE_PONG: + return "PONG"; + default: + return "???"; } - return "???"; } -static int ws_frame_op2flags(unsigned char proto_opcode) +static int ws_frame_firstbyte2flags(struct Curl_easy *data, + unsigned char firstbyte, int cont_flags) { - unsigned char opcode = proto_opcode & WSBIT_OPCODE_MASK; - size_t i; - for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) { - if(WS_FRAMES[i].proto_opcode == opcode) - return WS_FRAMES[i].flags; + switch(firstbyte) { + /* 0x00 - intermediate TEXT/BINARY fragment */ + case WSBIT_OPCODE_CONT: + if(!(cont_flags & CURLWS_CONT)) { + failf(data, "[WS] no ongoing fragmented message to resume"); + return 0; + } + return cont_flags | CURLWS_CONT; + /* 0x80 - final TEXT/BIN fragment */ + case (WSBIT_OPCODE_CONT | WSBIT_FIN): + if(!(cont_flags & CURLWS_CONT)) { + failf(data, "[WS] no ongoing fragmented message to resume"); + return 0; + } + return cont_flags & ~CURLWS_CONT; + /* 0x01 - first TEXT fragment */ + case WSBIT_OPCODE_TEXT: + if(cont_flags & CURLWS_CONT) { + failf(data, "[WS] fragmented message interrupted by new TEXT msg"); + return 0; + } + return CURLWS_TEXT | CURLWS_CONT; + /* 0x81 - unfragmented TEXT msg */ + case (WSBIT_OPCODE_TEXT | WSBIT_FIN): + if(cont_flags & CURLWS_CONT) { + failf(data, "[WS] fragmented message interrupted by new TEXT msg"); + return 0; + } + return CURLWS_TEXT; + /* 0x02 - first BINARY fragment */ + case WSBIT_OPCODE_BIN: + if(cont_flags & CURLWS_CONT) { + failf(data, "[WS] fragmented message interrupted by new BINARY msg"); + return 0; + } + return CURLWS_BINARY | CURLWS_CONT; + /* 0x82 - unfragmented BINARY msg */ + case (WSBIT_OPCODE_BIN | WSBIT_FIN): + if(cont_flags & CURLWS_CONT) { + failf(data, "[WS] fragmented message interrupted by new BINARY msg"); + return 0; + } + return CURLWS_BINARY; + /* 0x08 - first CLOSE fragment */ + case WSBIT_OPCODE_CLOSE: + failf(data, "[WS] invalid fragmented CLOSE frame"); + return 0; + /* 0x88 - unfragmented CLOSE */ + case (WSBIT_OPCODE_CLOSE | WSBIT_FIN): + return CURLWS_CLOSE; + /* 0x09 - first PING fragment */ + case WSBIT_OPCODE_PING: + failf(data, "[WS] invalid fragmented PING frame"); + return 0; + /* 0x89 - unfragmented PING */ + case (WSBIT_OPCODE_PING | WSBIT_FIN): + return CURLWS_PING; + /* 0x0a - first PONG fragment */ + case WSBIT_OPCODE_PONG: + failf(data, "[WS] invalid fragmented PONG frame"); + return 0; + /* 0x8a - unfragmented PONG */ + case (WSBIT_OPCODE_PONG | WSBIT_FIN): + return CURLWS_PONG; + /* invalid first byte */ + default: + if(firstbyte & WSBIT_RSV_MASK) + /* any of the reserved bits 0x40/0x20/0x10 are set */ + failf(data, "[WS] invalid reserved bits: %02x", firstbyte); + else + /* any of the reserved opcodes 0x3-0x7 or 0xb-0xf is used */ + failf(data, "[WS] invalid opcode: %02x", firstbyte); + return 0; } - return 0; } -static unsigned char ws_frame_flags2op(int flags) +static unsigned char ws_frame_flags2firstbyte(struct Curl_easy *data, + unsigned int flags, + bool contfragment, + CURLcode *err) { - size_t i; - for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) { - if(WS_FRAMES[i].flags & flags) - return (unsigned char)WS_FRAMES[i].proto_opcode; + switch(flags & ~CURLWS_OFFSET) { + case 0: + if(contfragment) { + infof(data, "[WS] no flags given; interpreting as continuation " + "fragment for compatibility"); + return (WSBIT_OPCODE_CONT | WSBIT_FIN); + } + failf(data, "[WS] no flags given"); + *err = CURLE_BAD_FUNCTION_ARGUMENT; + return 0xff; + case CURLWS_CONT: + if(contfragment) { + infof(data, "[WS] setting CURLWS_CONT flag without message type is " + "supported for compatibility but highly discouraged"); + return WSBIT_OPCODE_CONT; + } + failf(data, "[WS] No ongoing fragmented message to continue"); + *err = CURLE_BAD_FUNCTION_ARGUMENT; + return 0xff; + case CURLWS_TEXT: + return contfragment ? (WSBIT_OPCODE_CONT | WSBIT_FIN) + : (WSBIT_OPCODE_TEXT | WSBIT_FIN); + case (CURLWS_TEXT | CURLWS_CONT): + return contfragment ? WSBIT_OPCODE_CONT : WSBIT_OPCODE_TEXT; + case CURLWS_BINARY: + return contfragment ? (WSBIT_OPCODE_CONT | WSBIT_FIN) + : (WSBIT_OPCODE_BIN | WSBIT_FIN); + case (CURLWS_BINARY | CURLWS_CONT): + return contfragment ? WSBIT_OPCODE_CONT : WSBIT_OPCODE_BIN; + case CURLWS_CLOSE: + return WSBIT_OPCODE_CLOSE | WSBIT_FIN; + case (CURLWS_CLOSE | CURLWS_CONT): + failf(data, "[WS] CLOSE frame must not be fragmented"); + *err = CURLE_BAD_FUNCTION_ARGUMENT; + return 0xff; + case CURLWS_PING: + return WSBIT_OPCODE_PING | WSBIT_FIN; + case (CURLWS_PING | CURLWS_CONT): + failf(data, "[WS] PING frame must not be fragmented"); + *err = CURLE_BAD_FUNCTION_ARGUMENT; + return 0xff; + case CURLWS_PONG: + return WSBIT_OPCODE_PONG | WSBIT_FIN; + case (CURLWS_PONG | CURLWS_CONT): + failf(data, "[WS] PONG frame must not be fragmented"); + *err = CURLE_BAD_FUNCTION_ARGUMENT; + return 0xff; + default: + failf(data, "[WS] unknown flags: %x", flags); + *err = CURLE_BAD_FUNCTION_ARGUMENT; + return 0xff; } - return 0; -} - -/* No extensions are supported. If any of the RSV bits are set, we must fail */ -static bool ws_frame_rsv_supported(int flags) -{ - unsigned char reserved_bits = flags & WSBIT_RSV_MASK; - return reserved_bits == 0; } static void ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data, @@ -126,23 +286,23 @@ static void ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data, case 0: break; case 1: - 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"); + CURL_TRC_WS(data, "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) { - 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); + CURL_TRC_WS(data, "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 { - 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); + CURL_TRC_WS(data, "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; } @@ -158,6 +318,16 @@ typedef ssize_t ws_write_payload(const unsigned char *buf, size_t buflen, void *userp, CURLcode *err); +static void ws_dec_next_frame(struct ws_decoder *dec) +{ + dec->frame_age = 0; + dec->frame_flags = 0; + dec->payload_offset = 0; + dec->payload_len = 0; + dec->head_len = dec->head_total = 0; + dec->state = WS_DEC_INIT; + /* dec->cont_flags must be carried over to next frame */ +} static void ws_dec_reset(struct ws_decoder *dec) { @@ -167,6 +337,7 @@ static void ws_dec_reset(struct ws_decoder *dec) dec->payload_len = 0; dec->head_len = dec->head_total = 0; dec->state = WS_DEC_INIT; + dec->cont_flags = 0; } static void ws_dec_init(struct ws_decoder *dec) @@ -186,20 +357,19 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, dec->head[0] = *inbuf; Curl_bufq_skip(inraw, 1); - if(!ws_frame_rsv_supported(dec->head[0])) { - failf(data, "WS: unknown reserved bit in frame header: %x", - dec->head[0] & WSBIT_RSV_MASK); + dec->frame_flags = ws_frame_firstbyte2flags(data, dec->head[0], + dec->cont_flags); + if(!dec->frame_flags) { ws_dec_reset(dec); return CURLE_RECV_ERROR; } - dec->frame_flags = ws_frame_op2flags(dec->head[0]); - if(!dec->frame_flags) { - failf(data, "WS: unknown opcode: %x", - dec->head[0] & WSBIT_OPCODE_MASK); - ws_dec_reset(dec); - return CURLE_RECV_ERROR; + /* fragmentation only applies to data frames (text/binary); + * control frames (close/ping/pong) do not affect the CONT status */ + if(dec->frame_flags & (CURLWS_TEXT | CURLWS_BINARY)) { + dec->cont_flags = dec->frame_flags; } + dec->head_len = 1; /* ws_dec_info(dec, data, "seeing opcode"); */ continue; @@ -211,10 +381,30 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, if(dec->head[1] & WSBIT_MASK) { /* A client MUST close a connection if it detects a masked frame. */ - failf(data, "WS: masked input frame"); + failf(data, "[WS] masked input frame"); ws_dec_reset(dec); return CURLE_RECV_ERROR; } + if(dec->frame_flags & CURLWS_PING && dec->head[1] > 125) { + /* The maximum valid size of PING frames is 125 bytes. + Accepting overlong pings would mean sending equivalent pongs! */ + failf(data, "[WS] received PING frame is too big"); + ws_dec_reset(dec); + return CURLE_RECV_ERROR; + } + if(dec->frame_flags & CURLWS_PONG && dec->head[1] > 125) { + /* The maximum valid size of PONG frames is 125 bytes. */ + failf(data, "[WS] received PONG frame is too big"); + ws_dec_reset(dec); + return CURLE_RECV_ERROR; + } + if(dec->frame_flags & CURLWS_CLOSE && dec->head[1] > 125) { + /* The maximum valid size of CLOSE frames is 125 bytes. */ + failf(data, "[WS] received CLOSE frame is too big"); + ws_dec_reset(dec); + return CURLE_RECV_ERROR; + } + /* How long is the frame head? */ if(dec->head[1] == 126) { dec->head_total = 4; @@ -249,7 +439,7 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, break; case 10: if(dec->head[2] > 127) { - failf(data, "WS: frame length longer than 64 signed not supported"); + 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) | @@ -264,7 +454,7 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, default: /* this should never happen */ DEBUGASSERT(0); - failf(data, "WS: unexpected frame header length"); + failf(data, "[WS] unexpected frame header length"); return CURLE_RECV_ERROR; } @@ -300,8 +490,8 @@ 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; - CURL_TRC_WRITE(data, "websocket, passed %zd bytes payload, %" - FMT_OFF_T " remain", nwritten, remain); + CURL_TRC_WS(data, "passed %zd bytes payload, %" + FMT_OFF_T " remain", nwritten, remain); } return remain ? CURLE_AGAIN : CURLE_OK; @@ -320,14 +510,14 @@ static CURLcode ws_dec_pass(struct ws_decoder *dec, switch(dec->state) { case WS_DEC_INIT: - ws_dec_reset(dec); + ws_dec_next_frame(dec); dec->state = WS_DEC_HEAD; FALLTHROUGH(); case WS_DEC_HEAD: result = ws_dec_read_head(dec, data, inraw); if(result) { if(result != CURLE_AGAIN) { - infof(data, "WS: decode error %d", (int)result); + infof(data, "[WS] decode error %d", (int)result); break; /* real error */ } /* incomplete ws frame head */ @@ -369,11 +559,13 @@ static void update_meta(struct websocket *ws, curl_off_t payload_len, size_t cur_len) { + curl_off_t bytesleft = (payload_len - payload_offset - cur_len); + ws->frame.age = frame_age; ws->frame.flags = frame_flags; ws->frame.offset = payload_offset; ws->frame.len = cur_len; - ws->frame.bytesleft = (payload_len - payload_offset - cur_len); + ws->frame.bytesleft = bytesleft; } /* WebSockets decoding client writer */ @@ -415,13 +607,15 @@ static ssize_t ws_cw_dec_next(const unsigned char *buf, size_t buflen, struct ws_cw_dec_ctx *ctx = user_data; struct Curl_easy *data = ctx->data; struct websocket *ws = ctx->ws; + bool auto_pong = !data->set.ws_no_auto_pong; curl_off_t remain = (payload_len - (payload_offset + buflen)); (void)frame_age; - if((frame_flags & CURLWS_PING) && !remain) { + + if(auto_pong && (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"); + 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) @@ -452,9 +646,9 @@ static CURLcode ws_cw_write(struct Curl_easy *data, if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode) return Curl_cwriter_write(data, writer->next, type, buf, nbytes); - ws = data->conn->proto.ws; + ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); if(!ws) { - failf(data, "WS: not a websocket transfer"); + failf(data, "[WS] not a websocket transfer"); return CURLE_FAILED_INIT; } @@ -463,7 +657,7 @@ static CURLcode ws_cw_write(struct Curl_easy *data, 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); + infof(data, "[WS] error adding data to buffer %d", result); return result; } } @@ -479,17 +673,17 @@ static CURLcode ws_cw_write(struct Curl_easy *data, 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"); + CURL_TRC_WS(data, "buffered incomplete frame head"); return CURLE_OK; } else if(result) { - infof(data, "WS: decode error %d", (int)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", + failf(data, "[WS] decode ending with %zd frame bytes remaining", Curl_bufq_len(&ctx->buf)); return CURLE_RECV_ERROR; } @@ -511,12 +705,11 @@ static const struct Curl_cwtype ws_cw_decode = { 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=%" 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->payload_len - enc->payload_remain, enc->payload_len); + CURL_TRC_WS(data, "WS-ENC: %s [%s%s payload=%" + FMT_OFF_T "/%" FMT_OFF_T "]", + msg, ws_frame_name_of_op(enc->firstbyte), + (enc->firstbyte & WSBIT_FIN) ? "" : " NON-FIN", + enc->payload_len - enc->payload_remain, enc->payload_len); } static void ws_enc_reset(struct ws_encoder *enc) @@ -562,13 +755,12 @@ static ssize_t ws_enc_write_head(struct Curl_easy *data, CURLcode *err) { unsigned char firstbyte = 0; - unsigned char opcode; unsigned char head[14]; size_t hlen; ssize_t n; if(payload_len < 0) { - failf(data, "WS: starting new frame with negative payload length %" + failf(data, "[WS] starting new frame with negative payload length %" FMT_OFF_T, payload_len); *err = CURLE_SEND_ERROR; return -1; @@ -576,38 +768,40 @@ static ssize_t ws_enc_write_head(struct Curl_easy *data, 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((int)flags & ~CURLWS_CONT); - if(!opcode) { - failf(data, "WS: provided flags not recognized '%x'", flags); - *err = CURLE_SEND_ERROR; + firstbyte = ws_frame_flags2firstbyte(data, flags, enc->contfragment, err); + if(*err) { return -1; } - if(!(flags & CURLWS_CONT)) { - if(!enc->contfragment) - /* not marked as continuing, this is the final fragment */ - firstbyte |= WSBIT_FIN | opcode; - else - /* marked as continuing, this is the final fragment; set CONT - opcode and FIN bit */ - firstbyte |= WSBIT_FIN | WSBIT_OPCODE_CONT; + /* fragmentation only applies to data frames (text/binary); + * control frames (close/ping/pong) do not affect the CONT status */ + if(flags & (CURLWS_TEXT | CURLWS_BINARY)) { + enc->contfragment = (flags & CURLWS_CONT) ? (bit)TRUE : (bit)FALSE; + } - enc->contfragment = FALSE; + if(flags & CURLWS_PING && payload_len > 125) { + /* The maximum valid size of PING frames is 125 bytes. */ + failf(data, "[WS] given PING frame is too big"); + *err = CURLE_TOO_LARGE; + return -1; } - else if(enc->contfragment) { - /* 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; + if(flags & CURLWS_PONG && payload_len > 125) { + /* The maximum valid size of PONG frames is 125 bytes. */ + failf(data, "[WS] given PONG frame is too big"); + *err = CURLE_TOO_LARGE; + return -1; } - else { - firstbyte = opcode; - enc->contfragment = TRUE; + if(flags & CURLWS_CLOSE && payload_len > 125) { + /* The maximum valid size of CLOSE frames is 125 bytes. */ + failf(data, "[WS] given CLOSE frame is too big"); + *err = CURLE_TOO_LARGE; + return -1; } head[0] = enc->firstbyte = firstbyte; @@ -708,18 +902,18 @@ CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) { /* The request MUST contain an |Upgrade| header field whose value MUST include the "websocket" keyword. */ - "Upgrade:", "websocket" + "Upgrade", "websocket" }, { /* The request MUST contain a |Connection| header field whose value MUST include the "Upgrade" token. */ - "Connection:", "Upgrade", + "Connection", "Upgrade", }, { /* The request MUST include a header field with the name |Sec-WebSocket-Version|. The value of this header field MUST be 13. */ - "Sec-WebSocket-Version:", "13", + "Sec-WebSocket-Version", "13", }, { /* The request MUST include a header field with the name @@ -727,7 +921,7 @@ CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) consisting of a randomly selected 16-byte value that has been base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be selected randomly for each connection. */ - "Sec-WebSocket-Key:", NULL, + "Sec-WebSocket-Key", NULL, } }; heads[3].val = &keyval[0]; @@ -736,7 +930,7 @@ CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) result = Curl_rand(data, (unsigned char *)rand, sizeof(rand)); if(result) return result; - result = Curl_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen); + result = curlx_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen); if(result) return result; DEBUGASSERT(randlen < sizeof(keyval)); @@ -746,16 +940,26 @@ CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) } strcpy(keyval, randstr); free(randstr); - for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) { - if(!Curl_checkheaders(data, STRCONST(heads[i].name))) { - result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name, - heads[i].val); + for(i = 0; !result && (i < CURL_ARRAYSIZE(heads)); i++) { + if(!Curl_checkheaders(data, heads[i].name, strlen(heads[i].name))) { + result = curlx_dyn_addf(req, "%s: %s\r\n", heads[i].name, + heads[i].val); } } k->upgr101 = UPGR101_WS; return result; } +static void ws_conn_dtor(void *key, size_t klen, void *entry) +{ + struct websocket *ws = entry; + (void)key; + (void)klen; + Curl_bufq_free(&ws->recvbuf); + Curl_bufq_free(&ws->sendbuf); + free(ws); +} + /* * 'nread' is number of bytes of websocket data already in the buffer at * 'mem'. @@ -769,21 +973,19 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, CURLcode result; DEBUGASSERT(data->conn); - ws = data->conn->proto.ws; + ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); 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; #ifdef DEBUGBUILD { - char *p = getenv("CURL_WS_CHUNK_SIZE"); + const char *p = getenv("CURL_WS_CHUNK_SIZE"); if(p) { - long l = strtol(p, NULL, 10); - if(l > 0 && l <= (1*1024*1024)) { + curl_off_t l; + if(!curlx_str_number(&p, &l, 1*1024*1024)) chunk_size = (size_t)l; - } } } #endif @@ -794,6 +996,10 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, BUFQ_OPT_SOFT_LIMIT); ws_dec_init(&ws->dec); ws_enc_init(&ws->enc); + result = Curl_conn_meta_set(data->conn, CURL_META_PROTO_WS_CONN, + ws, ws_conn_dtor); + if(result) + return result; } else { Curl_bufq_reset(&ws->recvbuf); @@ -825,7 +1031,14 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, sizeof(ws->enc.mask)); if(result) return result; - infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x", + +#ifdef DEBUGBUILD + if(getenv("CURL_WS_FORCE_ZERO_MASK")) + /* force the bit mask to 0x00000000, effectively disabling masking */ + memset(ws->enc.mask, 0, sizeof(ws->enc.mask)); +#endif + + infof(data, "[WS] 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 */ @@ -849,12 +1062,12 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, nread, &result); if(nwritten < 0) return result; - infof(data, "%zu bytes websocket payload", nread); + CURL_TRC_WS(data, "%zu bytes payload", nread); } else { /* !connect_only */ /* And pass any additional data to the writers */ if(nread) { - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)mem, nread); + result = Curl_client_write(data, CLIENTWRITE_BODY, mem, nread); } } k->upgr101 = UPGR101_RECEIVED; @@ -882,6 +1095,8 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen, CURLcode *err) { struct ws_collect *ctx = userp; + struct Curl_easy *data = ctx->data; + bool auto_pong = !data->set.ws_no_auto_pong; size_t nwritten; curl_off_t remain = (payload_len - (payload_offset + buflen)); @@ -893,10 +1108,10 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen, ctx->payload_len = payload_len; } - if((frame_flags & CURLWS_PING) && !remain) { + if(auto_pong && (frame_flags & CURLWS_PING) && !remain) { /* auto-respond to PINGs, only works for single-frame payloads atm */ size_t bytes; - infof(ctx->data, "WS: auto-respond to PING with a PONG"); + infof(ctx->data, "[WS] auto-respond to PING with a PONG"); /* send back the exact same content as a PONG */ *err = curl_ws_send(ctx->data, buf, buflen, &bytes, 0, CURLWS_PONG); if(*err) @@ -950,19 +1165,19 @@ CURL_EXTERN CURLcode curl_ws_recv(CURL *d, void *buffer, if(!conn) { /* Unhappy hack with lifetimes of transfers and connection */ if(!data->set.connect_only) { - failf(data, "CONNECT_ONLY is required"); + failf(data, "[WS] CONNECT_ONLY is required"); return CURLE_UNSUPPORTED_PROTOCOL; } Curl_getconnectinfo(data, &conn); if(!conn) { - failf(data, "connection not found"); + failf(data, "[WS] connection not found"); return CURLE_BAD_FUNCTION_ARGUMENT; } } - ws = conn->proto.ws; + ws = Curl_conn_meta_get(conn, CURL_META_PROTO_WS_CONN); if(!ws) { - failf(data, "connection is not setup for websocket"); + failf(data, "[WS] connection is not setup for websocket"); return CURLE_BAD_FUNCTION_ARGUMENT; } @@ -983,7 +1198,7 @@ CURL_EXTERN CURLcode curl_ws_recv(CURL *d, void *buffer, } else if(n == 0) { /* connection closed */ - infof(data, "connection expectedly closed?"); + infof(data, "[WS] connection expectedly closed?"); return CURLE_GOT_NOTHING; } CURL_TRC_WS(data, "curl_ws_recv, added %zu bytes from network", @@ -1032,12 +1247,11 @@ static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, /* Simulate a blocking send after this chunk has been sent */ bool eagain_next = FALSE; size_t chunk_egain = 0; - char *p = getenv("CURL_WS_CHUNK_EAGAIN"); + const char *p = getenv("CURL_WS_CHUNK_EAGAIN"); if(p) { - long l = strtol(p, NULL, 10); - if(l > 0 && l <= (1*1024*1024)) { + curl_off_t l; + if(!curlx_str_number(&p, &l, 1*1024*1024)) chunk_egain = (size_t)l; - } } #endif @@ -1051,7 +1265,7 @@ static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, } #endif if(blocking) { - result = ws_send_raw_blocking(data, ws, (char *)out, outlen); + result = ws_send_raw_blocking(data, ws, (const char *)out, outlen); n = result ? 0 : outlen; } else if(data->set.connect_only || Curl_is_in_callback(data)) @@ -1068,11 +1282,11 @@ static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, return result; } else if(result) { - failf(data, "WS: flush, write error %d", result); + failf(data, "[WS] flush, write error %d", result); return result; } else { - infof(data, "WS: flushed %zu bytes", n); + CURL_TRC_WS(data, "flushed %zu bytes", n); Curl_bufq_skip(&ws->sendbuf, n); } } @@ -1104,7 +1318,7 @@ static CURLcode ws_send_raw_blocking(CURL *d, struct websocket *ws, buflen); left_ms = Curl_timeleft(data, NULL, FALSE); if(left_ms < 0) { - failf(data, "Timeout waiting for socket becoming writable"); + failf(data, "[WS] Timeout waiting for socket becoming writable"); return CURLE_SEND_ERROR; } @@ -1114,7 +1328,7 @@ static CURLcode ws_send_raw_blocking(CURL *d, struct websocket *ws, 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"); + failf(data, "[WS] Error while waiting for socket becoming writable"); return CURLE_SEND_ERROR; } } @@ -1125,11 +1339,12 @@ static CURLcode ws_send_raw_blocking(CURL *d, struct websocket *ws, 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; + struct websocket *ws; CURLcode result; + ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); if(!ws) { - failf(data, "Not a websocket transfer"); + failf(data, "[WS] Not a websocket transfer"); return CURLE_SEND_ERROR; } if(!buflen) @@ -1179,16 +1394,16 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg, goto out; } if(!data->conn) { - failf(data, "No associated connection"); + failf(data, "[WS] No associated connection"); result = CURLE_SEND_ERROR; goto out; } - if(!data->conn->proto.ws) { - failf(data, "Not a websocket transfer"); + ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); + if(!ws) { + failf(data, "[WS] Not a websocket transfer"); result = CURLE_SEND_ERROR; goto out; } - ws = data->conn->proto.ws; if(data->set.ws_raw_mode) { /* In raw mode, we write directly to the connection */ @@ -1198,7 +1413,7 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg, goto out; if(fragsize || flags) { - failf(data, "ws_send, raw mode: fragsize and flags cannot be non-zero"); + failf(data, "[WS] fragsize and flags must be zero in raw mode"); return CURLE_BAD_FUNCTION_ARGUMENT; } result = ws_send_raw(data, buffer, buflen, sent); @@ -1213,7 +1428,7 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg, if(buflen < ws->sendbuf_payload) { /* We have been called with LESS buffer data than before. This * is not how it's supposed too work. */ - failf(data, "curl_ws_send() called with smaller 'buflen' than " + failf(data, "[WS] curl_ws_send() called with smaller 'buflen' than " "bytes already buffered in previous call, %zu vs %zu", buflen, ws->sendbuf_payload); result = CURLE_BAD_FUNCTION_ARGUMENT; @@ -1222,7 +1437,7 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg, if((curl_off_t)buflen > (ws->enc.payload_remain + (curl_off_t)ws->sendbuf_payload)) { /* too large buflen beyond payload length of frame */ - infof(data, "WS: unaligned frame size (sending %zu instead of %" + failf(data, "[WS] unaligned frame size (sending %zu instead of %" FMT_OFF_T ")", buflen, ws->enc.payload_remain + ws->sendbuf_payload); result = CURLE_BAD_FUNCTION_ARGUMENT; @@ -1255,11 +1470,15 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg, if(n < 0 && (result != CURLE_AGAIN)) goto out; ws->sendbuf_payload += Curl_bufq_len(&ws->sendbuf) - prev_len; + if(!ws->sendbuf_payload) { + result = CURLE_AGAIN; + goto out; + } } /* flush, blocking when in callback */ result = ws_flush(data, ws, Curl_is_in_callback(data)); - if(!result) { + if(!result && ws->sendbuf_payload > 0) { *sent += ws->sendbuf_payload; buffer += ws->sendbuf_payload; buflen -= ws->sendbuf_payload; @@ -1296,42 +1515,31 @@ out: return result; } -static void ws_free(struct connectdata *conn) -{ - if(conn && conn->proto.ws) { - Curl_bufq_free(&conn->proto.ws->recvbuf); - Curl_bufq_free(&conn->proto.ws->sendbuf); - Curl_safefree(conn->proto.ws); - } -} - 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; + data->state.http_neg.accept_09 = FALSE; + data->state.http_neg.only_10 = FALSE; + data->state.http_neg.wanted = CURL_HTTP_V1x; + data->state.http_neg.allowed = CURL_HTTP_V1x; return Curl_http_setup_conn(data, conn); } -static CURLcode ws_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection) -{ - (void)data; - (void)dead_connection; - ws_free(conn); - return CURLE_OK; -} - 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; + if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && + data->conn && !data->set.ws_raw_mode) { + struct websocket *ws; + ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); + if(ws) + return &ws->frame; + + } return NULL; } @@ -1348,7 +1556,7 @@ const struct Curl_handler Curl_handler_ws = { Curl_http_getsock_do, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ - ws_disconnect, /* disconnect */ + ZERO_NULL, /* disconnect */ Curl_http_write_resp, /* write_resp */ Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ @@ -1375,7 +1583,7 @@ const struct Curl_handler Curl_handler_wss = { Curl_http_getsock_do, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ - ws_disconnect, /* disconnect */ + ZERO_NULL, /* disconnect */ Curl_http_write_resp, /* write_resp */ Curl_http_write_resp_hd, /* write_resp_hd */ ZERO_NULL, /* connection_check */ diff --git a/Utilities/cmcurl/lib/ws.h b/Utilities/cmcurl/lib/ws.h index c96bccab9f..b7655abbce 100644 --- a/Utilities/cmcurl/lib/ws.h +++ b/Utilities/cmcurl/lib/ws.h @@ -27,46 +27,8 @@ #if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) -/* a client-side WS frame decoder, parsing frame headers and - * payload, keeping track of current position and stats */ -enum ws_dec_state { - WS_DEC_INIT, - WS_DEC_HEAD, - WS_DEC_PAYLOAD -}; - -struct ws_decoder { - int frame_age; /* zero */ - int frame_flags; /* See the CURLWS_* defines */ - curl_off_t payload_offset; /* the offset parsing is at */ - curl_off_t payload_len; - unsigned char head[10]; - int head_len, head_total; - enum ws_dec_state state; -}; - -/* a client-side WS frame encoder, generating frame headers and - * converting payloads, tracking remaining data in current frame */ -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 firstbyte; /* first byte of frame we encode */ - BIT(contfragment); /* set TRUE if the previous fragment sent was not final */ -}; - -/* A websocket connection with en- and decoder that treat frames - * and keep track of boundaries. */ -struct websocket { - struct Curl_easy *data; /* used for write callback handling */ - struct ws_decoder dec; /* decode of we frames */ - struct ws_encoder enc; /* decode of we frames */ - struct bufq recvbuf; /* raw data from the server */ - struct bufq sendbuf; /* raw data to be sent to the server */ - struct curl_ws_frame frame; /* the current WS FRAME received */ - size_t sendbuf_payload; /* number of payload bytes in sendbuf */ -}; +/* meta key for storing protocol meta at connection */ +#define CURL_META_PROTO_WS_CONN "meta:proto:ws:conn" CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req); CURLcode Curl_ws_accept(struct Curl_easy *data, const char *mem, size_t len);